多线程应用---使用WaveOut* API开发AMR音频播放器(含源码下载)
[源代码以及工程实例下载 ]
1、 语音播放API
1.1 waveOutOpen - 打开播放设备
- MMRESULT waveOutOpen(
- LPHWAVEOUT phwo, /* 一个指向接收波形音频输出设备的句柄 */
- UINT_PTR uDeviceID, /* 将要被打开的波形音频输出设备的ID */
- LPWAVEFORMATEX pwfx, /* 一个指向将被送到设备的音频数据格式的WAVEFORMATEX结构的指针 */
- DWORD_PTR dwCallback, /* 它指向一个特定的CALLBACK函数,事件柄,窗口柄... */
- DWORD_PTR dwCallbackInstance, /* 传递到CALLBACK进程的用户实例数据,如是窗口,该参数设为0 */
- DWORD fdwOpen /* 用来打开设备的标识(FLAGS) */
- );
MMRESULT waveOutOpen(
LPHWAVEOUT phwo, /* 一个指向接收波形音频输出设备的句柄 */
UINT_PTR uDeviceID, /* 将要被打开的波形音频输出设备的ID */
LPWAVEFORMATEX pwfx, /* 一个指向将被送到设备的音频数据格式的WAVEFORMATEX结构的指针 */
DWORD_PTR dwCallback, /* 它指向一个特定的CALLBACK函数,事件柄,窗口柄... */
DWORD_PTR dwCallbackInstance, /* 传递到CALLBACK进程的用户实例数据,如是窗口,该参数设为0 */
DWORD fdwOpen /* 用来打开设备的标识(FLAGS) */
);
1)phwo:一个指向接收波形音频输出设备的句柄。用句柄来区别(identify)别的波形输出设备。如果fdwOpen被设定为 WAVE_FORMAT_QUERY,那么这个参数可能为NULL 。
2)uDevideID:将要被打开的波形音频输出设备的ID ,它可以是一个设备ID,也可以是一个已经打开的波形音频输入设备句柄,你可以用以下的值来货替:
WAVE_MAPPER - 该函数选一个能够播放给定格式的波形音频输出设备
3)pwfx:一个指向将被送到设备的音频数据格式的WAVEFORMATEX结构的指针,当调用waveOutOpen 函数之后可立即释放该结构;
4)dwCallback:它指向一个特定的CALLBACK函数,事件柄,窗口柄,或一个线程ID(用于在音频回放时以便处理与回放进度相关的消息)。如果无须CALLBACK函数,可以将其设为0 。
5)dwCallbackInstance :传递到CALLBACK进程的用户实例数据。如果是窗口CALLBACK进程的话,该参数不用(设为0)
6)fwOpen:用来打开设备的标识(FLAGS),它们的定义如下:
值 | 含义 |
CALLBACK_EVENT | dwCallback 参数栏是事件句柄 |
CALLBACK_FUNCTION | dwCallback 参数栏是CALLBACK函数地址 |
CALLBACK_NULL | 默认的设置,即无CALLBACK进程 |
CALLBACK_THREAD | dwCallback 参数栏是线程ID |
CALLBACK_WINDOW | dwCallback 参数栏是窗口句柄 |
WAVE_ALLOWSYNC | 如果该项被设置,一个同步的波形音频设备能被打开。如果在打开一个同步驱动时没有用该项,设备打开将会失败。 |
WAVE_FORMAT_DIRECT | 如果设定该项,音频设备不会对输出的音频数据执行转换 |
WAVE_FORMAT_QUERY | 如果设定该项,waveOutOpen 用于询问设备是否支持给定的格式,但设备实际上并没有被打开。此时phwo参数可为NULL |
WAVE_MAPPED | 该项被设定后uDeviceID参数表示将通过波形映射器映射到一个波形格式音频设备。 |
7)返回值:成功后返回MMSYSERR_NOERROR ,否则返回以下值:
值 | 描述 |
MMSYSERR_ALLOCATED | 表示资源已存在 |
MMSYSERR_BADDEVICEID | 设备ID超出范围 |
MMSYSERR_NODRIVER | 没有驱动 |
MMSYSERR_NOMEM | 不能分配内存 |
WAVERR_BADFORMAT | 企图打开一个不被支持的格式 |
WAVERR_SYNC | 设备是可同步的,但waveOutOpen没用有WAVE_ALLOWSYNC设置。 |
注:
waveoutopen创建设备实例句柄用于,使用其他waveoutAPI时将之作为参数,用于区别不同的音频流。
可用waveOutGetNumDevs函数测定在当前系统中输出设备的数目。
如果uDeviceID参数项是一个ID,它将会表示从0 到总的数目,WAAVE_MAPPER常量也可以用作装置ID。
pwfc所指的结构能够扩展到包含某些数据格式的特殊信息,例如,对于PCM数据,一个额外的UNIT类型用来表示取样的位数目。在这个情况下用PCMWAVEFORAMT结构。对于其它的格式,用WAVEFORMATEX结构来表示额外的数据长度。
如果你选用的是一个窗口或线程来接收CALLBACK信息,如下的信息将会被送到窗口处理函数中,来表明波形音频输出进程:MM_WOM_OPEN,MM_WOM_CLOSE ,和MM_WOM_DONE,如果你选用的是一个函数来接收CALLBACK信息,如下的信息将会被传到函数中,来显示波形音频输出进程:WOM_OPEN ,WOM_CLOSE,WOM_DONE。
1.2 waveOutPrepareHeader - 准备数据块
- MMRESULT waveOutPrepareHeader(
- HWAVEOUT hwo, /* 波形音频输出设备的句柄 */
- LPWAVEHDR pwh, /* 一个WAVEHDR结构的指针,其基址必须与样本大小对齐 */
- UINT cbwh /* WAVEHDR结构的大小,单位:字节 */
- );
- 备注:
MMRESULT waveOutPrepareHeader(
HWAVEOUT hwo, /* 波形音频输出设备的句柄 */
LPWAVEHDR pwh, /* 一个WAVEHDR结构的指针,其基址必须与样本大小对齐 */
UINT cbwh /* WAVEHDR结构的大小,单位:字节 */
);
备注:
- 调用此函数之前必须设置WAVEHDR结构的lpData,dwBufferLength,dwFlags成员,dwFlags成员必须设置为0。
- 一旦准备完成,不可以修改lpData指针。
- 尝试准备一个处于准备状态的数据块,将无效果,返回0
- 不应该在同一时刻为不同的波形设备准备同一个数据,如果想从一个设备录制并在另一个设备播放数据,但不想拷贝缓存块,可以分配两块WAVEHDR并将lpData指向同一数据缓存地址,其中一个调用<STRONG>waveInPrepareHeader</STRONG>,另一个调用<STRONG>waveOutPrepareHeader</STRONG>。
调用此函数之前必须设置WAVEHDR结构的lpData,dwBufferLength,dwFlags成员,dwFlags成员必须设置为0。
一旦准备完成,不可以修改lpData指针。
尝试准备一个处于准备状态的数据块,将无效果,返回0
不应该在同一时刻为不同的波形设备准备同一个数据,如果想从一个设备录制并在另一个设备播放数据,但不想拷贝缓存块,可以分配两块WAVEHDR并将lpData指向同一数据缓存地址,其中一个调用<strong>waveInPrepareHeader</strong>,另一个调用<strong>waveOutPrepareHeader</strong>。
1.3 waveOutWrite - 将音频数据块送到指定的音频输出设备
- MMRESULT waveOutWrite(
- HWAVEOUT hwo, /* 音频输出设备句柄 */
- LPWAVEHDR pwh, /* 包含音频数据块信息的WAVEHDR结构指针 */
- UINT cbwh /* WAVEHDR结构大小,单位byte */
- );
MMRESULT waveOutWrite(
HWAVEOUT hwo, /* 音频输出设备句柄 */
LPWAVEHDR pwh, /* 包含音频数据块信息的WAVEHDR结构指针 */
UINT cbwh /* WAVEHDR结构大小,单位byte */
);
注意:
要播放的数据一般在声音文件里面获得,并填入这个结构。由于是直接播放数据。所以要播放多少数据可以通过修改这个结构来达到目的。
播放完数据后 WHDR_DONE 会设置到pwh指向的结构体中的dwFlags 成员
在调用本函数之前必须调用waveOutPrepareHeader函数
除非是恢复被waveOutPause函数暂停的设备,回调函数会在第一个数据块一送达设备的时候就开始运作.回调函数在waveOutOpen里面设置
请不要在回调函数里面调用任何的waveOut系列的函数,否则一定会造成死锁。哪怕是waveOutUnprepareHeader,waveOutClose
1.4 waveOutUnprepareHeader - 清除音频缓存数据
- MMRESULT waveOutUnprepareHeader(
- HWAVEOUT hwo,
- LPWAVEHDR pwh,
- UINT cbwh
- );
MMRESULT waveOutUnprepareHeader(
HWAVEOUT hwo,
LPWAVEHDR pwh,
UINT cbwh
);
注意:
调用时机,送到的音频输出设备的数据已经播放完成即:pwh的dwFlags中WHDR_DONE位有效
1.5 waveOutPause - 暂停音频播放
1.6 waveOutRestart - 继续播放已暂停的音频设备
1.7 waveOutReset - 重设音频输出设备,将已准备好的缓存块均设置为已播放完成
1.8 waveOutClose - 关闭音频输出设备
2、解码器开发
- class AMRFileDecoder
- {
- public:
- AMRFileDecoder(void);
- AMRFileDecoder(LPCTSTR lpszFile);
- virtual ~AMRFileDecoder(void);
- private: // 屏蔽拷贝构造函数和赋值运算
- AMRFileDecoder(const AMRFileDecoder& )
- {
- ATLASSERT(FALSE);
- }
- AMRFileDecoder& operator=(const AMRFileDecoder&)
- {
- ATLASSERT(FALSE);
- return *this;
- }
- public:
- /// 设置需解码文件路径
- virtual void SetFilePathName(LPCTSTR lpszFile);
- /// 获取总时间长度,单位ms
- virtual ULONGLONG GetTimeLength();
- /// 获取解码后的波形格式
- virtual WAVEFORMATEX GetWaveFromatX();
- /// 开始解码,初始化解码器
- virtual BOOL BeginDecode();
- /// 解码,每解码一帧,游标后移至下一帧,返回解码后的帧大小,输出解码后的波形数据
- virtual DWORD Decode(LPSTR& pData);
- /// 判断是否解码结束
- virtual bool IsEOF();
- /// 结束解码,销毁解码器
- virtual void EndDecode();
- /// 判断解码器是否正常
- virtual bool IsVaild();
- /// 获取解码后的波形数据大小,单位byte
- virtual DWORD GetDecodedMaxSize();
- /// 获取解码后的波形数据帧大小,单位byte
- virtual DWORD GetDecodedFrameMaxSize();
- private:
- DWORD GetFrameCount();
- private:
- LPSTR m_pBaseAddress; // 文件映射内存块首地址
- LONGLONG m_liFileSize; // 文件映射内存块大小
- ULONG m_dwFrameCount; // 帧总数
- LPSTR m_pCurAddress; // 解码游标,指示当前解码位置
- LPVOID m_pvDecoderState; // 解码器状态指针
- CString m_sFilePathName; // 解码文件路径
- };
class AMRFileDecoder
{
public:
AMRFileDecoder(void);
AMRFileDecoder(LPCTSTR lpszFile);
virtual ~AMRFileDecoder(void);
private: // 屏蔽拷贝构造函数和赋值运算
AMRFileDecoder(const AMRFileDecoder& )
{
ATLASSERT(FALSE);
}
AMRFileDecoder& operator=(const AMRFileDecoder&)
{
ATLASSERT(FALSE);
return *this;
}
public:
/// 设置需解码文件路径
virtual void SetFilePathName(LPCTSTR lpszFile);
/// 获取总时间长度,单位ms
virtual ULONGLONG GetTimeLength();
/// 获取解码后的波形格式
virtual WAVEFORMATEX GetWaveFromatX();
/// 开始解码,初始化解码器
virtual BOOL BeginDecode();
/// 解码,每解码一帧,游标后移至下一帧,返回解码后的帧大小,输出解码后的波形数据
virtual DWORD Decode(LPSTR& pData);
/// 判断是否解码结束
virtual bool IsEOF();
/// 结束解码,销毁解码器
virtual void EndDecode();
/// 判断解码器是否正常
virtual bool IsVaild();
/// 获取解码后的波形数据大小,单位byte
virtual DWORD GetDecodedMaxSize();
/// 获取解码后的波形数据帧大小,单位byte
virtual DWORD GetDecodedFrameMaxSize();
private:
DWORD GetFrameCount();
private:
LPSTR m_pBaseAddress; // 文件映射内存块首地址
LONGLONG m_liFileSize; // 文件映射内存块大小
ULONG m_dwFrameCount; // 帧总数
LPSTR m_pCurAddress; // 解码游标,指示当前解码位置
LPVOID m_pvDecoderState; // 解码器状态指针
CString m_sFilePathName; // 解码文件路径
};
3、解码线程
- if(m_pDecoder == NULL || !m_pDecoder->IsVaild())
- return 0;
- // 开始解码,初始化解码器
- if(!m_pDecoder->BeginDecode())
- {
- return 0;
- }
- // 申请临时内存块,存储解码后的波形数据
- DWORD dwFrameMaxSize = m_pDecoder->GetDecodedFrameMaxSize();
- LPSTR pBufferBase = (LPSTR)malloc(dwFrameMaxSize);
- ATLASSERT(pBufferBase);
- memset(pBufferBase, 0, dwFrameMaxSize);
- if(pBufferBase == NULL) return 0;
- register ThreadMsg tmsg = TMSG_ALIVE;
- DWORD dwSizeAmount = 0;
- while(!m_pDecoder->IsEOF() && tmsg)
- {
- // 解码,每帧
- DWORD dwSize = m_pDecoder->Decode(pBufferBase);
- dwSizeAmount += dwSize;
- // 将解码后数据写入缓存区,供播放线程使用
- EnterCriticalSection(&m_cs);
- memcpy(m_waveData.pData + m_waveData.dwSize, pBufferBase, dwSize);
- m_waveData.dwSize += dwSize;
- LeaveCriticalSection(&m_cs);
- // 当解码数据量操作了一个播放缓存时,发个信号,通知可以开始播放了
- if(dwSizeAmount > BLOCK_SIZE)
- {
- dwSizeAmount = 0;
- SetEvent(m_hEventDecode);
- }
- // 节省CPU时间,让CPU有时间去干别的事儿
- Sleep(1);
- // 检测线程是否将被强制退出
- EnterCriticalSection(&m_cs);
- tmsg = m_msgDecodeThread;
- LeaveCriticalSection(&m_cs);
- }
- // 如果数据量很小,根本不足一个播放缓存,也要发个信号
- if(dwSizeAmount > 0)
- {
- SetEvent(m_hEventDecode);
- }
- m_waveData.bDecodeFinished = true;
- // 解码结束
- m_pDecoder->EndDecode();
- free(pBufferBase);
- pBufferBase = NULL;
- return 0;
- }
if(m_pDecoder == NULL || !m_pDecoder->IsVaild())
return 0;
// 开始解码,初始化解码器
if(!m_pDecoder->BeginDecode())
{
return 0;
}
// 申请临时内存块,存储解码后的波形数据
DWORD dwFrameMaxSize = m_pDecoder->GetDecodedFrameMaxSize();
LPSTR pBufferBase = (LPSTR)malloc(dwFrameMaxSize);
ATLASSERT(pBufferBase);
memset(pBufferBase, 0, dwFrameMaxSize);
if(pBufferBase == NULL) return 0;
register ThreadMsg tmsg = TMSG_ALIVE;
DWORD dwSizeAmount = 0;
while(!m_pDecoder->IsEOF() && tmsg)
{
// 解码,每帧
DWORD dwSize = m_pDecoder->Decode(pBufferBase);
dwSizeAmount += dwSize;
// 将解码后数据写入缓存区,供播放线程使用
EnterCriticalSection(&m_cs);
memcpy(m_waveData.pData + m_waveData.dwSize, pBufferBase, dwSize);
m_waveData.dwSize += dwSize;
LeaveCriticalSection(&m_cs);
// 当解码数据量操作了一个播放缓存时,发个信号,通知可以开始播放了
if(dwSizeAmount > BLOCK_SIZE)
{
dwSizeAmount = 0;
SetEvent(m_hEventDecode);
}
// 节省CPU时间,让CPU有时间去干别的事儿
Sleep(1);
// 检测线程是否将被强制退出
EnterCriticalSection(&m_cs);
tmsg = m_msgDecodeThread;
LeaveCriticalSection(&m_cs);
}
// 如果数据量很小,根本不足一个播放缓存,也要发个信号
if(dwSizeAmount > 0)
{
SetEvent(m_hEventDecode);
}
m_waveData.bDecodeFinished = true;
// 解码结束
m_pDecoder->EndDecode();
free(pBufferBase);
pBufferBase = NULL;
return 0;
}
4、播放线程
- unsigned int WavePlayer::PlayThreadProcImpl()
- {
- /// 定义为寄存器变量,因为它将会被高频率的使用,用于编译器优化
- register ThreadMsg tmsg = TMSG_ALIVE;
- /// 线程循环
- while( tmsg )
- {
- // 每次循环后,交出CPU控制权,放在此处,因为下面有continue语句
- Sleep(10);
- /// 首先检查线程消息
- EnterCriticalSection( &m_cs );
- tmsg = m_msgPlayThread;
- LeaveCriticalSection( &m_cs );
- // 线程要结束,退出线程循环
- if(!tmsg) break;
- // 如果设备为空,表示还没有打开设备,需要打开设备
- if(m_hWaveoutDev == NULL)
- {
- EnterCriticalSection(&m_cs);
- MMRESULT mmres = waveOutOpen(&m_hWaveoutDev, WAVE_MAPPER, &m_waveData.wfmtx, (DWORD_PTR)WaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
- LeaveCriticalSection(&m_cs);
- if(mmres != MMSYSERR_NOERROR)
- {
- // failed, try again.
- continue;
- }
- }
- // 检查空闲缓存块
- EnterCriticalSection( &m_cs );
- int free = m_wBlock.wfreeblock;
- LeaveCriticalSection( &m_cs );
- // 如果没有空闲的缓存了,等待...
- if(free < BP_TURN)
- {
- continue;
- }
- /
- /
- /// < 播放主循环 > ///
- /
- /
- WAVEHDR *current = NULL;
- /// BP_TURN为每次写入播放队列的块数
- for( unsigned int m = 0; m < BP_TURN; m++ )
- {
- /// 当前空闲播放缓存块
- current = &m_wBlock.pWaveHdr[m_wBlock.wcurrblock];
- // 首先需要检查有没有被Unprepare掉
- if( current->dwFlags & WHDR_PREPARED )
- {
- waveOutUnprepareHeader( m_hWaveoutDev, current, sizeof(WAVEHDR) );
- }
- /// 计算剩余需要播放的数据
- EnterCriticalSection(&m_cs);
- unsigned long left = m_waveData.dwSize - m_wBlock.wpos;
- unsigned int bDecodeFinished = m_waveData.bDecodeFinished;
- LeaveCriticalSection(&m_cs);
- unsigned long chunk = 0;
- if( left >= BLOCK_SIZE )
- {
- chunk = BLOCK_SIZE;
- }
- else if(!bDecodeFinished)
- {
- // 如果解码还没有结束,现有的数据量有不足以填满一个缓存块,先不写入缓存
- break;
- }
- else if( left && left < BLOCK_SIZE)
- {
- chunk = left;
- }
- else
- {
- //
- /// < 播放完成> ///
- //
- /// 获取空闲缓存块数量
- EnterCriticalSection( &m_cs );
- int free = m_wBlock.wfreeblock;
- LeaveCriticalSection( &m_cs );
- /// 当所有的缓存块都播放完了,才意味着播放真正完成
- if( free == BLOCK_COUNT )
- {
- /// Unprepare缓存块
- for( int j = 0; j < m_wBlock.wfreeblock; j++)
- {
- if( m_wBlock.pWaveHdr[j].dwFlags & WHDR_PREPARED )
- {
- waveOutUnprepareHeader(m_hWaveoutDev, &m_wBlock.pWaveHdr[j], sizeof(WAVEHDR));
- }
- }
- // 此时,才算真正的播放完成,关闭线程
- tmsg = TMSG_CLOSE;
- // 处理播放完成事件
- OnPlayFinished();
- }
- // 此break仅跳出该循环,没有跳出线程循环
- break;
- }
- /// prepare current wave data block header
- EnterCriticalSection(&m_cs);
- memcpy( current->lpData, &m_waveData.pData[m_wBlock.wpos], chunk );
- LeaveCriticalSection(&m_cs);
- current->dwBufferLength = chunk; // sizeof block
- m_wBlock.wpos += chunk; // update position
- /// prepare for playback
- waveOutPrepareHeader( m_hWaveoutDev, current, sizeof(WAVEHDR) );
- /// push to the queue
- waveOutWrite(m_hWaveoutDev, current, sizeof(WAVEHDR));
- /// 减小空闲块计数
- EnterCriticalSection( &m_cs );
- m_wBlock.wfreeblock--;
- LeaveCriticalSection( &m_cs );
- /// 使当前空闲块指向下一个
- m_wBlock.wcurrblock++;
- m_wBlock.wcurrblock %= BLOCK_COUNT;
- }
- }/// thread
- ///
- ///
- ///
- /// < force to close device which are still playing >
- ///
- ///
- if(m_hWaveoutDev)
- {
- waveOutReset( m_hWaveoutDev );
- /// unprepare any blocks that are still prepared
- for( int j = 0; j < BLOCK_COUNT; j++)
- {
- if( m_wBlock.pWaveHdr[j].dwFlags & WHDR_PREPARED )
- {
- waveOutUnprepareHeader(m_hWaveoutDev, &m_wBlock.pWaveHdr[j], sizeof(WAVEHDR));
- }
- }
- waveOutClose(m_hWaveoutDev);
- m_hWaveoutDev = NULL;
- }
- return THREAD_EXIT;
- }
unsigned int WavePlayer::PlayThreadProcImpl()
{
/// 定义为寄存器变量,因为它将会被高频率的使用,用于编译器优化
register ThreadMsg tmsg = TMSG_ALIVE;
/// 线程循环
while( tmsg )
{
// 每次循环后,交出CPU控制权,放在此处,因为下面有continue语句
Sleep(10);
/// 首先检查线程消息
EnterCriticalSection( &m_cs );
tmsg = m_msgPlayThread;
LeaveCriticalSection( &m_cs );
// 线程要结束,退出线程循环
if(!tmsg) break;
// 如果设备为空,表示还没有打开设备,需要打开设备
if(m_hWaveoutDev == NULL)
{
EnterCriticalSection(&m_cs);
MMRESULT mmres = waveOutOpen(&m_hWaveoutDev, WAVE_MAPPER, &m_waveData.wfmtx, (DWORD_PTR)WaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
LeaveCriticalSection(&m_cs);
if(mmres != MMSYSERR_NOERROR)
{
// failed, try again.
continue;
}
}
// 检查空闲缓存块
EnterCriticalSection( &m_cs );
int free = m_wBlock.wfreeblock;
LeaveCriticalSection( &m_cs );
// 如果没有空闲的缓存了,等待...
if(free < BP_TURN)
{
continue;
}
/
/
/// < 播放主循环 > ///
/
/
WAVEHDR *current = NULL;
/// BP_TURN为每次写入播放队列的块数
for( unsigned int m = 0; m < BP_TURN; m++ )
{
/// 当前空闲播放缓存块
current = &m_wBlock.pWaveHdr[m_wBlock.wcurrblock];
// 首先需要检查有没有被Unprepare掉
if( current->dwFlags & WHDR_PREPARED )
{
waveOutUnprepareHeader( m_hWaveoutDev, current, sizeof(WAVEHDR) );
}
/// 计算剩余需要播放的数据
EnterCriticalSection(&m_cs);
unsigned long left = m_waveData.dwSize - m_wBlock.wpos;
unsigned int bDecodeFinished = m_waveData.bDecodeFinished;
LeaveCriticalSection(&m_cs);
unsigned long chunk = 0;
if( left >= BLOCK_SIZE )
{
chunk = BLOCK_SIZE;
}
else if(!bDecodeFinished)
{
// 如果解码还没有结束,现有的数据量有不足以填满一个缓存块,先不写入缓存
break;
}
else if( left && left < BLOCK_SIZE)
{
chunk = left;
}
else
{
//
/// < 播放完成> ///
//
/// 获取空闲缓存块数量
EnterCriticalSection( &m_cs );
int free = m_wBlock.wfreeblock;
LeaveCriticalSection( &m_cs );
/// 当所有的缓存块都播放完了,才意味着播放真正完成
if( free == BLOCK_COUNT )
{
/// Unprepare缓存块
for( int j = 0; j < m_wBlock.wfreeblock; j++)
{
if( m_wBlock.pWaveHdr[j].dwFlags & WHDR_PREPARED )
{
waveOutUnprepareHeader(m_hWaveoutDev, &m_wBlock.pWaveHdr[j], sizeof(WAVEHDR));
}
}
// 此时,才算真正的播放完成,关闭线程
tmsg = TMSG_CLOSE;
// 处理播放完成事件
OnPlayFinished();
}
// 此break仅跳出该循环,没有跳出线程循环
break;
}
/// prepare current wave data block header
EnterCriticalSection(&m_cs);
memcpy( current->lpData, &m_waveData.pData[m_wBlock.wpos], chunk );
LeaveCriticalSection(&m_cs);
current->dwBufferLength = chunk; // sizeof block
m_wBlock.wpos += chunk; // update position
/// prepare for playback
waveOutPrepareHeader( m_hWaveoutDev, current, sizeof(WAVEHDR) );
/// push to the queue
waveOutWrite(m_hWaveoutDev, current, sizeof(WAVEHDR));
/// 减小空闲块计数
EnterCriticalSection( &m_cs );
m_wBlock.wfreeblock--;
LeaveCriticalSection( &m_cs );
/// 使当前空闲块指向下一个
m_wBlock.wcurrblock++;
m_wBlock.wcurrblock %= BLOCK_COUNT;
}
}/// thread
///
///
///
/// < force to close device which are still playing >
///
///
if(m_hWaveoutDev)
{
waveOutReset( m_hWaveoutDev );
/// unprepare any blocks that are still prepared
for( int j = 0; j < BLOCK_COUNT; j++)
{
if( m_wBlock.pWaveHdr[j].dwFlags & WHDR_PREPARED )
{
waveOutUnprepareHeader(m_hWaveoutDev, &m_wBlock.pWaveHdr[j], sizeof(WAVEHDR));
}
}
waveOutClose(m_hWaveoutDev);
m_hWaveoutDev = NULL;
}
return THREAD_EXIT;
}
5、主播放线程
- // 如果已经有播放的了,先停止
- if(m_ePlayStat != Play_Stop)
- {
- Stop();
- }
- // 设置解码器
- if(m_pDecoder == NULL)
- {
- m_pDecoder = new AMRFileDecoder(lpszFile);
- }
- else
- {
- m_pDecoder->SetFilePathName(lpszFile);
- }
- // 取播放时间
- if(pLength)
- {
- *pLength = (DWORD)m_pDecoder->GetTimeLength();
- }
- // 申请解码后的数据堆内存块
- DWORD dwWaveMaxSize = m_pDecoder->GetDecodedMaxSize();
- EnterCriticalSection(&m_cs);
- m_waveData.wfmtx = m_pDecoder->GetWaveFromatX();
- m_waveData.pData = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwWaveMaxSize);
- LeaveCriticalSection(&m_cs);
- // 设置回调函数
- // 创建解码线程
- if(m_hThreadDecode == NULL)
- {
- m_msgDecodeThread = TMSG_ALIVE;
- m_hThreadDecode = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)DecodeThread, (LPVOID)this, CREATE_SUSPENDED, NULL);
- ATLASSERT(m_hThreadDecode);
- ResumeThread(m_hThreadDecode);
- }
- // 等待解码缓存信号
- WaitForSingleObject(m_hEventDecode, INFINITE);
- // 创建播放线程
- if(m_hThreadPlay == NULL)
- {
- m_msgPlayThread = TMSG_ALIVE;
- m_hThreadPlay = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)PlayThread, (LPVOID)this, CREATE_SUSPENDED, NULL );
- ATLASSERT(m_hThreadPlay);
- ResumeThread(m_hThreadPlay);
- }
- m_ePlayStat = Play_Playing;
多线程应用---使用WaveOut* API开发AMR音频播放器(含源码下载)相关推荐
- arcgis开发 多版本之间如何兼容_arcgis api 4.x for js 结合 react 入门开发系列初探篇(附源码下载)...
你还在使用 JQuery 或者 Dojo 框架开发 arcgis api 4.x for js 吗?想试试模块化开发吗?随着前端技术的发展,arcgis api 4.x for js 也有了结合 re ...
- ASP.NET 3.5 新特性开发向导实践(附项目源码下载)
ASP.NET 3.5 新特性开发向导实践(附项目源码下载) 本篇文章将演示ASP.NET 3.5 部分新功能.新特性,包括LINQ.ListView控件.LinqDataSource.DataPag ...
- html5游戏开发 网页版-捕鱼达人游戏源码下载
html5游戏开发 网页版-捕鱼达人游戏源码下载 来玩一把! 转载于:https://www.cnblogs.com/jsfoot/p/3215371.html
- 修复采集接口版云开发表情包微信小程序源码下载增加制作等功能
大家好,相信很多人对这个界面的表情包小程序肯定不陌生吧 不错之前该款小程序是属于独立后端的,不管今天所发的这款是云开发的哟 运营着这个表情包的用户应该发现了,最近很多表情包图片都失效了 所以呢,今天小 ...
- 小程序源码:全新独家云开发微群人脉小程序源码下载社群空间站
今天给大家带来一款云开发版本的微群人脉小程序源码 该版本属于采集版本(群二维码自动采集) 该版本属于云开发版本(免服务器和域名) 这是一款不怕封小程序版本 PS:支持用户自主发布那一款还是有点危险因为 ...
- 云开发表白墙微信小程序源码下载免服务器和域名支持流量主收益
这是一款云开发的表白墙微信小程序 特点是云开发,所以也就无需服务器和域名的支持了 安装特别的简单 首先呢小程序账号开通云开发权限 然后把源码上传到微信开发者工具里面 然后点击开发者工具软件上面的云开发 ...
- JAVA毕业设计vue开发一个简单音乐播放器计算机源码+lw文档+系统+调试部署+数据库
JAVA毕业设计vue开发一个简单音乐播放器计算机源码+lw文档+系统+调试部署+数据库 JAVA毕业设计vue开发一个简单音乐播放器计算机源码+lw文档+系统+调试部署+数据库 本源码技术栈: 项目 ...
- 音乐播放器开发实例(可毕设含源码)
一.开发基础 最近很多大学生都快毕业了在整理毕业论文以及毕业作品,其实在大三的时候就可以开始考虑了并且可以提前和自己的导师进行沟通,让自己的作品直接成为论文选题,在国产操作系统上开发一个简易音乐播放器 ...
- Springboot+vue开发的图书借阅管理系统项目源码下载-P0029
前言 图书借阅管理系统项目是基于SpringBoot+Vue技术开发而来,功能相对比较简单,分为两个角色即管理员和学生用户,核心业务功能就是图书的发布.借阅与归还,相比于一些复杂的系统,该项目具备简单 ...
最新文章
- KeystoneERROR 1045 (28000):Access denied for user 'keystone'@'controller'(using password YES)HTTP500
- 全球及中国造纸行业十四五产量调研与运营能力状况分析报告2022版
- Python中修改字符串的四种方法
- minincom cannot open /dev/modem
- 使用equals判断对象是否相等出现的错误
- windows游戏编程:球球大作战(吃鸡版)源码
- 百分百成功的ros安装教程,有手就能看懂!!!
- Ruby静态分析工具检视:metric_fu, Simian, Saikuro以及其他
- excel超链接报本机限制问题
- python封装举例_卷1:第14章 Python打包工具
- 一名大学生选择军哥的乾颐堂是如何顺利通过华为HCIE的,又如何应对HCIE面试呢?...
- stm32cubeide烧写程序_初学STM32CubeIDE
- vue-element-admin/template+tornado(pyrestful)前后端分离框架实践(1)——自定义菜单和仪表盘
- 【Linux】Linux学习(三)常用基本命令(一)
- 齿轮系统动力学模型matlab程序代码
- 微信小程序语音聊天智能对话(demo)
- oracle中带有in查询的子查询绑定变量方式
- android 心率计算器,运动状态最佳心率计算器 (Target-Heart-Rate Calculator)
- ParaView Visualizer 在ubuntu22.04环境下快速开发入门指南
- 葡萄牙生产一台计算机,外贸教程-国际贸易教程作业题如果美国生产一台计算机需要60小时的劳动,生产 爱问知识人...