You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用waveOut API无法播放1200Hz蜂鸣声问题求助

问题分析与解决方案

问题背景

需要通过waveOut API生成1200Hz、时长1秒的双声道蜂鸣声,左右声道以SmallInt(C++ short)编码,音量100%,但运行时仅能听到微弱噪音,原Delphi 6代码如下:

unit C4Audio;

interface

uses Windows;

type
  TAudioBits  = SHORT;
  TAudioDelay = WORD;
  TAudioHerz  = WORD;

  TAudioByte = record
                     Left, Right : TAudioBits;
               end;

  TAudioStereo = record
                       Left, Right : 0..100;
                 end;
implementation

uses Forms,
     mmsystem;

var
  AudioAPI    : hWaveOut;
  AudioBuffer : array of TAudioByte;
  AudioHeader : TWaveHdr;
  AudioStyle  : TWaveFormatEx;

var _alpha   : Real;
    _cursor  : Integer;
    _high    : Integer;

initialization
               with AudioStyle
                    do begin
                            wFormatTag      := WAVE_FORMAT_PCM;
                            nChannels       := 2;
                            nSamplesPerSec  := 44100;
                            wBitsPerSample  := SizeOf(TAudioBits) * 8;
                            nBlockAlign     := (wBitsPerSample div 8) * nChannels;
                            nAvgBytesPerSec := nBlockAlign * nSamplesPerSec;
                            cbSize          := 0;
                       end;

               if waveOutOpen(@AudioAPI, WAVE_MAPPER, @AudioStyle, 0, 0, CALLBACK_NULL) = MMSYSERR_NOERROR
                  then begin
                            SetLength(AudioBuffer, 1000);

                            with AudioHeader
                                 do begin
                                         dwBufferLength := Length(AudioBuffer) * SizeOf(TAudioByte);
                                         dwFlags        := 0;
                                         dwLoops        := $FFFF;
                                         lpData         := @AudioBuffer;
                                    end;

                            _alpha := 0;
                            _high  := High(TAudioBits);

                            for _cursor := Low(AudioBuffer) to High(AudioBuffer)
                                do begin
                                        AudioBuffer[_cursor].Left  := Round(_high * Sin(_alpha));
                                        AudioBuffer[_cursor].Right := Round(_high * Sin(_alpha));

                                        _alpha := _alpha  + 2 * PI * 1200 / AudioStyle.nSamplesPerSec;
                                   end;

                            if (waveOutPrepareHeader(AudioAPI, @AudioHeader, SizeOf(TWaveHdr)) = MMSYSERR_NOERROR)
                               and
                               (waveOutWrite(AudioAPI, @AudioHeader, sizeof(TWaveHdr)) = MMSYSERR_NOERROR)
                               then Sleep(1000);
                      end
                  else
                    AudioAPI := 0;

finalization
            if AudioAPI <> 0
              then begin
                        while waveOutUnprepareHeader(AudioAPI, @AudioHeader, SizeOf(TWaveHdr)) = WAVERR_STILLPLAYING
                              do Application.ProcessMessages;

                        if waveOutClose(AudioAPI) = MMSYSERR_NOERROR
                           then begin
                                end;
                   end;
end.

问题根源

  1. 音频缓冲区长度不足:采样率为44100Hz,1秒需要44100个双声道采样点,但原代码仅生成1000个采样,搭配dwLoops := $FFFF无限循环短缓冲区,导致播放内容异常,呈现为噪音。
  2. 错误使用dwLoops字段:该字段并非用于waveOutWrite的常规循环播放,多数设备不支持此方式,应生成完整时长的音频数据而非循环短片段。
  3. 播放等待方式不可靠:Sleep(1000)无法保证音频完全播放完毕,系统调度延迟可能导致提前进入清理流程,中断音频播放。
  4. 结构体未初始化:TWaveFormatEx和TWaveHdr未完整初始化,残留的内存垃圾值可能导致API调用异常。

修正后的代码

unit C4Audio;

interface

uses Windows;

type
  TAudioBits  = SHORT;
  TAudioDelay = WORD;
  TAudioHerz  = WORD;

  TAudioSample = record // 重命名避免与Byte类型混淆
    Left, Right : TAudioBits;
  end;

  TAudioStereo = record
    Left, Right : 0..100;
  end;

implementation

uses Forms, mmsystem, SysUtils;

var
  AudioAPI    : hWaveOut;
  AudioBuffer : array of TAudioSample;
  AudioHeader : TWaveHdr;
  AudioStyle  : TWaveFormatEx;
  PlayDoneEvent: THandle; // 用于监听播放完成的事件句柄

var
  _alpha   : Real;
  _cursor  : Integer;
  _high    : Integer;

// waveOut回调函数,播放完成时触发事件
procedure WaveOutCallback(hwo: hWaveOut; uMsg: UINT; dwInstance: DWORD; dwParam1: DWORD; dwParam2: DWORD); stdcall;
begin
  if uMsg = WOM_DONE then
    SetEvent(PlayDoneEvent);
end;

initialization
  // 初始化格式结构体,清除内存垃圾
  ZeroMemory(@AudioStyle, SizeOf(TWaveFormatEx));
  with AudioStyle do
  begin
    wFormatTag      := WAVE_FORMAT_PCM;
    nChannels       := 2;
    nSamplesPerSec  := 44100;
    wBitsPerSample  := SizeOf(TAudioBits) * 8;
    nBlockAlign     := (wBitsPerSample div 8) * nChannels;
    nAvgBytesPerSec := nBlockAlign * nSamplesPerSec;
    cbSize          := 0;
  end;

  // 创建播放完成事件
  PlayDoneEvent := CreateEvent(nil, True, False, nil);
  if PlayDoneEvent = 0 then Exit;

  // 打开音频设备,注册回调函数
  if waveOutOpen(@AudioAPI, WAVE_MAPPER, @AudioStyle, Cardinal(@WaveOutCallback), 0, CALLBACK_FUNCTION) = MMSYSERR_NOERROR then
  begin
    // 生成1秒完整采样数据:采样率 × 时长
    SetLength(AudioBuffer, AudioStyle.nSamplesPerSec * 1);

    // 初始化音频头部结构体
    ZeroMemory(@AudioHeader, SizeOf(TWaveHdr));
    with AudioHeader do
    begin
      dwBufferLength := Length(AudioBuffer) * SizeOf(TAudioSample);
      dwFlags        := 0;
      dwLoops        := 0; // 禁用循环,使用完整缓冲区
      lpData         := @AudioBuffer[0];
    end;

    _alpha := 0;
    _high  := High(TAudioBits); // SmallInt最大值32767,对应100%音量

    for _cursor := Low(AudioBuffer) to High(AudioBuffer) do
    begin
      // 生成左右声道正弦波数据
      AudioBuffer[_cursor].Left  := Round(_high * Sin(_alpha));
      AudioBuffer[_cursor].Right := Round(_high * Sin(_alpha));
      // 更新相位,确保1200Hz频率精准
      _alpha := _alpha + 2 * PI * 1200 / AudioStyle.nSamplesPerSec;
      // 相位溢出优化,避免数值过大降低计算精度
      while _alpha > 2 * PI do
        _alpha := _alpha - 2 * PI;
    end;

    // 准备并写入音频数据
    if (waveOutPrepareHeader(AudioAPI, @AudioHeader, SizeOf(TWaveHdr)) = MMSYSERR_NOERROR) and
       (waveOutWrite(AudioAPI, @AudioHeader, SizeOf(TWaveHdr)) = MMSYSERR_NOERROR) then
    begin
      // 等待播放完成事件触发,替代Sleep保证播放完整
      WaitForSingleObject(PlayDoneEvent, INFINITE);
    end;
  end
  else
    AudioAPI := 0;

finalization
  if AudioAPI <> 0 then
  begin
    // 重置音频设备,停止播放
    waveOutReset(AudioAPI);
    // 释放音频头部资源
    while waveOutUnprepareHeader(AudioAPI, @AudioHeader, SizeOf(TWaveHdr)) = WAVERR_STILLPLAYING do
      Application.ProcessMessages;
    // 关闭音频设备
    waveOutClose(AudioAPI);
  end;
  // 释放事件句柄
  if PlayDoneEvent <> 0 then
    CloseHandle(PlayDoneEvent);
end.

关键修改说明

  • 缓冲区长度修正:根据采样率计算1秒所需的44100个采样点,确保音频内容完整。
  • 事件回调机制:使用CALLBACK_FUNCTION注册回调,通过事件等待音频播放完成,替代不可靠的Sleep。
  • 结构体初始化:用ZeroMemory清除结构体内存垃圾,避免API调用异常。
  • 移除无效循环:将dwLoops设为0,生成完整1秒音频数据,避免循环短片段导致的噪音。
  • 相位优化:定期重置相位值,防止数值过大降低正弦波计算精度。

内容的提问来源于stack exchange,提问作者blueperfect

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:58:10