使用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.
问题根源
- 音频缓冲区长度不足:采样率为44100Hz,1秒需要44100个双声道采样点,但原代码仅生成1000个采样,搭配
dwLoops := $FFFF无限循环短缓冲区,导致播放内容异常,呈现为噪音。 - 错误使用
dwLoops字段:该字段并非用于waveOutWrite的常规循环播放,多数设备不支持此方式,应生成完整时长的音频数据而非循环短片段。 - 播放等待方式不可靠:
Sleep(1000)无法保证音频完全播放完毕,系统调度延迟可能导致提前进入清理流程,中断音频播放。 - 结构体未初始化:
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
相关产品推荐
相关产品推荐

