waveOutReset的N种死法, 及其解决方案

我遭遇到了调用waveOutReset死锁的问题,在GOOGLE上一搜,遇到同样问题的人还真不少,但没有人很明确地找到造成DEADLOCK的原因,都是糊里糊涂就把问题解决了,然后把运行OK的代码一贴完事。我花了四五个小时才彻底摸清楚规律,把这经验拿出来共享

原则:
(1) waveOutReset不是立即返回的函数, 而需要等待驻留在WAVEDEV里的音频BUFFER全部标记为WOM_DONE,经过callback proc处理完毕后, waveOutReset才会返回.
(2) 在waveOutReset调用到返回之间的这段时间内, 禁止任何线程对处于reset中的HWAVEOUT调用任何WAVE API, 否则立刻造成死锁.

把握住这两个原则就可以根治waveOutReset死锁问题了. 不过为了把事情描述得更具体些, 我把网上多数人造成死锁的情况列一下, 并给出解决方法

第一种死法:
在回调处理函数WaveCallbackFunc中,对标记为WOM_DONE的BUFFER进行waveOutUnprepareHeader

错误分析:
调用waveOutReset后, 系统内所有未播放的buffer全部被标记为WOM_DONE返回给WaveCallbackFunc, 这属于waveOurReset处理过程的一部分, 此时waveOurReset还未返回. 所以这时如果调用到waveOutUnprepareHeader, 就会立刻造成死锁. 我自己也是犯的这个错误。

解决方法:
在调用waveOurReset之前设置一个FLAG, 然后在WaveCallbackFunc里要调用waveOutUnprepareHeader之前判断一下这个FLAG, 如果处于reset过程中就跳过waveOutUnprepareHeader. 范例代码:

static CRITICAL_SECTION g_CS;
static BOOL g_bResetting = FALSE;

void Reset()
{
        EnterCriticalSection(&g_CS);  //之前InitializeCriticalSection一下
        g_bResetting = TRUE;
        LeaveCriticalSection(&g_CS);
       
        waveOutReset();

        g_bResetting = FALSE;  //RESET后就不会调WaveCallbackFunc了,所以不需要锁
}

VOID CALLBACK WaveCallbackFunc(HWAVEOUT hwo, UINT32 uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
       EnterCriticalSection(&g_CS);   //防止g_bResetting在if判断之后, waveOutUnprepareHeader之前被设成1了, callback和reset处于不同线程
       
        if( (uMsg == WOM_DONE) && !g_bResetting )
        {
                waveOutUnprepareHeader(hWaveOut, pWaveHdr, sizeof(WAVEHDR));
        }
       
        LeaveCriticalSection(&g_CS);
}
       
另外, 如果使用了每个BUFFER定长的BUFFER-CHAIN来做生产者-消费者模型, 那么在BUFFER-CHAIN初始化的时候顺便把所有WAVEHDR都PREPARE好, 而只在BUFFER-CHAIN全部释放的时候才做UNPREPARE, 那么在每次waveOutWrite和WOM_DONE的循环里就不需要PREPARE/UNPREPARE了,这也能避开上述情况造成的死锁


第二种死法: 某些人喜欢在在回调处理函数WaveCallbackFunc中,调用waveOutWrite往系统里填充新BUFFER.
错误分析: 同情况一, 在waveOutReset过程中,如果WaveCallbackFunc调用了waveOutWrite, 同样会造成死锁.
解决方法: 同情况一, 设置FLAG和临界区配合来防止. 不过比较建议在另外的线程中调waveOutWrite写入BUFFER, 这才符合生产-消费模型.

第三种死法: 在另外一个线程中调用waveOurWrite, 但是waveOutReset之前, 没有停下那个线程
错误分析: RESET过程中,还有waveOurWrite调用
解决方法: 把waveOurWrite那个线程suspend或退出, 或者在waveOutWrite前面用FLAG+临界区判断

第四种死法: 也是最匪夷所思的死法, 从UI的某个按钮一路调用到waveOutReset(处于同线程中),  而在WaveCallbackFunc中,同线程调用SetWindowText之类UI函数
错误分析:  事实是:
(1) 如果是在WindowProc的某个CASE里调用SetWindowText, 调用者和SetWindowText处于同线程中,那么运行正常, 多数UI也都会这么写;
(2) 如果处于Thread1的UI调用了waveOutReset, 导致处于Thread2中的WaveCallbackFunc运行并调用SetWindowText, 试图设置处于Thread1的UIcallbackProc, 那么SetWindowText会死锁, 导致WaveCallbackFunc死掉, 最后表面上看起来就是waveOutReset死掉.其实我也不明白为什么这样也能死,要问就去问盖茨大叔吧.
解决方法:
在WaveCallbackFunc中,用RESET FLAG + 临界区的方法保护UI调用函数, 使其在waveOutReset过程中不被调用, 或者用PostMessage这样的异步函数,实在不得不同步影响UI的话就忍一忍等waveOurReset函数返回后再做吧.

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/renjwjx/archive/2009/03/03/3954012.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Windows 平台上,可以使用 Windows API 中的 waveOutOpen、waveOutPrepareHeader、waveOutWrite 等函数来实现播放 PCM 音频的功能。具体步骤如下: 1. 打开音频设备 使用 waveOutOpen 函数打开音频设备,获取一个 HWO(音频输出设备句柄),可以指定音频格式、回调函数等参数。 2. 准备音频数据 使用 waveOutPrepareHeader 函数对音频数据进行预处理,为音频数据分配缓冲区,并将音频数据拷贝到缓冲区中。每个缓冲区都有一个 WAVEHDR 结构体表示,其中包含缓冲区地址、大小、状态等信息。 3. 开始播放音频 使用 waveOutWrite 函数将已经准备好的音频数据缓冲区加入到音频设备的播放队列中。当音频设备播放完一个缓冲区时,会调用回调函数,应用程序可以在回调函数中继续填充数据。 4. 停止播放音频 使用 waveOutReset 函数停止音频设备的播放,并将所有缓冲区从播放队列中移除。使用 waveOutUnprepareHeader 函数释放缓冲区及其相关资源。 以下是一个简单的示例代码,用于播放一个 PCM 音频文件: ```c++ #include <windows.h> #include <mmsystem.h> #include <stdio.h> #pragma comment(lib,"winmm.lib") int main() { HWAVEOUT hwo; WAVEFORMATEX wfx; MMRESULT result; // 打开音频设备 ZeroMemory(&wfx, sizeof(wfx)); wfx.wFormatTag = WAVE_FORMAT_PCM; // PCM 音频格式 wfx.nChannels = 2; // 双声道 wfx.nSamplesPerSec = 44100; // 采样率 wfx.wBitsPerSample = 16; // 量化位数 wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; result = waveOutOpen(&hwo, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL); if (result != MMSYSERR_NOERROR) { printf("Failed to open audio device. Error code: %d\n", result); return 1; } // 打开 PCM 音频文件 FILE* file = fopen("audio.pcm", "rb"); if (file == NULL) { printf("Failed to open audio file.\n"); return 1; } // 循环读取 PCM 数据,并播放 const int BUFFER_SIZE = 4096; char buffer[BUFFER_SIZE]; while (true) { int readSize = fread(buffer, 1, BUFFER_SIZE, file); if (readSize == 0) { break; } WAVEHDR hdr; ZeroMemory(&hdr, sizeof(hdr)); hdr.lpData = buffer; hdr.dwBufferLength = readSize; result = waveOutPrepareHeader(hwo, &hdr, sizeof(hdr)); if (result != MMSYSERR_NOERROR) { printf("Failed to prepare audio header. Error code: %d\n", result); return 1; } result = waveOutWrite(hwo, &hdr, sizeof(hdr)); if (result != MMSYSERR_NOERROR) { printf("Failed to play audio data. Error code: %d\n", result); return 1; } while (hdr.dwFlags & WHDR_PREPARED) { Sleep(10); } } // 关闭音频设备 waveOutReset(hwo); waveOutClose(hwo); fclose(file); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值