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

WaveOut API无声音输出及WOM_DONE未触发问题求助

问题描述
  • 所有函数返回成功,回调函数能响应MM_WOM_OPEN和MM_WOM_CLOSE事件,但声卡无声音输出
  • 在C++Builder6中运行程序时,Windows音量混合器可见Wave条目,但无声音播放
  • 调用DatasRefresh(...)本应修改载波波形,但未生效
  • WOM_DONE事件未触发,而MM_WOM_OPEN和MM_WOM_CLOSE可正常触发
代码示例
#include <windows.h>
#include <mmsystem.h>

typedef size_t herz_t;
typedef float  progress_t;

class audio32_t 
{ 
public:      
    audio32_t();
    virtual ~audio32_t();

    void DatasRefresh(const herz_t &_carrier, const progress_t &_level)
    { 
        audio32_t::_carrier(_carrier, _level, this->_header);
        ::waveOutWrite(this->_apiout, &this->_header, sizeof(WAVEHDR));
    }

private:
    HANDLE        _apiclock;
    HWAVEOUT      _apiout;
    WAVEHDR       _header;

    static WAVEFORMATEX  _w32style;

    static void _carrier(const herz_t &, const progress_t &, WAVEHDR &);
    static void _w32proc(HWAVEOUT, UINT, DWORD, DWORD, DWORD);
};

//---------------------------------------------------------------------------

WAVEFORMATEX audio32_t::_w32style;

audio32_t::audio32_t() : _apiclock(0),
                         _apiout(0),
                         _header()
{ 
    audio32_t::_w32style.wFormatTag      = WAVE_FORMAT_PCM;
    audio32_t::_w32style.nChannels       = 2;
    audio32_t::_w32style.nSamplesPerSec  = 44100;
    audio32_t::_w32style.wBitsPerSample  = audio32_t::_w32style.nChannels * 8;
    audio32_t::_w32style.cbSize          = 0;

    audio32_t::_w32style.nBlockAlign     = static_cast<BYTE>(   
        audio32_t::_w32style.nChannels * (audio32_t::_w32style.wBitsPerSample / 8)
    );

    audio32_t::_w32style.nAvgBytesPerSec = audio32_t::_w32style.nSamplesPerSec
                                         * audio32_t::_w32style.nBlockAlign;

    this->_header.dwBufferLength  = 0;
    this->_header.lpData          = 0;
    this->_header.dwBytesRecorded = 0;
    this->_header.dwUser          = 0;
    this->_header.dwFlags         = WHDR_BEGINLOOP | WHDR_ENDLOOP;
    this->_header.dwLoops         = 0xFFFF;
    this->_header.lpNext          = NULL;
    this->_header.reserved        = 0;

    this->_header.dwBufferLength  = audio32_t::_w32style.nAvgBytesPerSec;
    this->_header.lpData          = ::new char[this->_header.dwBufferLength * 2];

    ::waveOutOpen( &this->_apiout, WAVE_MAPPER,
                   &audio32_t::_w32style,
                   reinterpret_cast<DWORD>(&audio32_t::_w32proc),
                   reinterpret_cast<DWORD>(this),
                   CALLBACK_FUNCTION
                 );
}

audio32_t::~audio32_t() { ::waveOutClose(this->_apiout); }

void audio32_t::_carrier(const herz_t &_herz, const progress_t &_lvl, WAVEHDR &_header)
{ 
    const size_t _half  = (_header.dwBufferLength / 2);
    const double _w     = 2.0 * M_PI * double(_herz) / double(audio32_t::_w32style.nSamplesPerSec);

    for(size_t _x = 0; _x < _header.dwBufferLength; _x += 4)
    { 
        const double _byte = ::sin(double(_x / 4) * _w);
        const short _sample = static_cast<short>(_byte * 32767.0 * (_lvl / 100.0f));

        if(_x > _half)
        { 
            // 左声道静音,右声道发声
            reinterpret_cast<short*>(_header.lpData)[_x / 4 * 2 + 0] = 0;
            reinterpret_cast<short*>(_header.lpData)[_x / 4 * 2 + 1] = _sample;
        }
        else
        { 
            // 左声道发声,右声道静音
            reinterpret_cast<short*>(_header.lpData)[_x / 4 * 2 + 0] = _sample;
            reinterpret_cast<short*>(_header.lpData)[_x / 4 * 2 + 1] = 0;
        }
    }
}

void audio32_t::_w32proc(HWAVEOUT _out, UINT _request, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{ 
    audio32_t *_this = reinterpret_cast<audio32_t*>(dwInstance);

    switch(_request)
    { 
        case MM_WOM_OPEN: 
        { 
            ::waveOutPrepareHeader(_this->_apiout, &_this->_header, sizeof(WAVEHDR));
            ::waveOutWrite(_this->_apiout, &_this->_header, sizeof(WAVEHDR));
            break;
        }

        case WOM_DONE: 
        { 
            // 如果需要循环播放,这里重新调用waveOutWrite
            // ::waveOutWrite(_this->_apiout, &_this->_header, sizeof(WAVEHDR));
            break;
        }

        case MM_WOM_CLOSE: 
        { 
            ::waveOutReset(_this->_apiout);
            ::waveOutUnprepareHeader(_this->_apiout, &_this->_header, sizeof(WAVEHDR));

            if(_this->_header.lpData != 0)
            { 
                ::delete [] _this->_header.lpData;
                _this->_header.lpData = 0;
            }
            break;
        }

        default: 
            break;
    }
}

#include <conio.h>
#include <iostream.h>

void main()
{ 
    audio32_t _audio;

    _audio.DatasRefresh(0, 0);

    _audio.DatasRefresh(1200, 25);  ::Sleep(500);    cout << "+";
    _audio.DatasRefresh(1200, 50);  ::Sleep(500);    cout << "+";
    _audio.DatasRefresh(1200, 75);  ::Sleep(500);    cout << "+";
    _audio.DatasRefresh(1200, 100); ::Sleep(500);    cout << "=";
    _audio.DatasRefresh(1200, 75);  ::Sleep(500);    cout << "-";
    _audio.DatasRefresh(1200, 50);  ::Sleep(500);    cout << "-";
    _audio.DatasRefresh(1200, 25);  ::Sleep(500);    cout << "-";
    _audio.DatasRefresh(1200,  0);  ::Sleep(500);    cout << "0";

    ::cout << ::endl << "ok";
    ::getch();
}
问题分析与修复建议

1. 音频格式与数据生成不匹配

  • 问题:设置的是双声道16位PCM格式(nChannels=2,wBitsPerSample=16),但_carrier函数按单字节步长处理数据,生成的是8位单声道格式的无效数据,导致设备无法正确解码播放。
  • 修复:
    • 循环步长改为4(每个采样占4字节:2通道×2字节/通道)
    • 生成16位采样值(范围-32768到32767),而非8位的0-255偏移值
    • 用short类型直接操作采样数据,避免字节级错误

2. WOM_DONE事件未触发

  • 问题:给WAVEHDR设置了WHDR_BEGINLOOP | WHDR_ENDLOOP标志,且dwLoops=0xFFFF无限循环,设备会持续播放该缓冲区,不会触发WOM_DONE。
  • 修复:
    • 若需要触发WOM_DONE,移除循环标志,设置dwLoops=0,然后在WOM_DONE回调中重新调用waveOutWrite实现循环
    • 若保留循环,WOM_DONE会在循环结束或调用waveOutReset后触发

3. DatasRefresh修改载波无效

  • 问题:缓冲区处于循环播放状态时,设备正在使用该缓冲区,直接修改数据可能被设备缓存覆盖,且未停止播放就重新调用waveOutWrite不会生效。
  • 修复:
    • 修改数据前调用waveOutPause暂停播放,修改后调用waveOutRestart恢复
    • 或者使用双缓冲区机制:准备两个缓冲区,交替提交播放,在WOM_DONE回调中更新另一个缓冲区的数据

4. 构造函数的潜在错误

  • 问题:main函数中调用audio32_t _audio("mmsystem");,但类的构造函数是无参的,会导致编译错误;另外未检查waveOutOpen的返回值,无法确认设备是否成功打开。
  • 修复:
    • 将构造函数调用改为audio32_t _audio;
    • 添加waveOutOpen返回值检查:
      MMRESULT result = ::waveOutOpen(...);
      if (result != MMSYSERR_NOERROR) {
          // 处理错误,比如弹窗提示
      }
      

5. 其他细节修复

  • byte_t未定义,替换为Windows原生的BYTE类型
  • 回调函数中dwInstance参数是类实例指针,需用reinterpret_cast正确转换,避免类型错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 00:13:18