[源代码以及工程实例下载 ]
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。
MMRESULT waveOutPrepareHeader( HWAVEOUT hwo, /* 波形音频输出设备的句柄 */ LPWAVEHDR pwh, /* 一个WAVEHDR结构的指针,其基址必须与样本大小对齐 */ UINT cbwh /* WAVEHDR结构的大小,单位:字节 */ ); 备注:
调用此函数之前必须设置WAVEHDR结构的lpData,dwBufferLength,dwFlags成员,dwFlags成员必须设置为0。 一旦准备完成,不可以修改lpData指针。 尝试准备一个处于准备状态的数据块,将无效果,返回0 不应该在同一时刻为不同的波形设备准备同一个数据,如果想从一个设备录制并在另一个设备播放数据,但不想拷贝缓存块,可以分配两块WAVEHDR并将lpData指向同一数据缓存地址,其中一个调用waveInPrepareHeader,另一个调用waveOutPrepareHeader。
MMRESULT waveOutWrite( HWAVEOUT hwo, /* 音频输出设备句柄 */ LPWAVEHDR pwh, /* 包含音频数据块信息的WAVEHDR结构指针 */ UINT cbwh /* WAVEHDR结构大小,单位byte */ );
注意:
要播放的数据一般在声音文件里面获得,并填入这个结构。由于是直接播放数据。所以要播放多少数据可以通过修改这个结构来达到目的。
播放完数据后 WHDR_DONE 会设置到pwh指向的结构体中的dwFlags 成员
在调用本函数之前必须调用waveOutPrepareHeader函数
除非是恢复被waveOutPause函数暂停的设备,回调函数会在第一个数据块一送达设备的时候就开始运作.回调函数在waveOutOpen里面设置
请不要在回调函数里面调用任何的waveOut系列的函数,否则一定会造成死锁。哪怕是waveOutUnprepareHeader,waveOutClose
MMRESULT waveOutUnprepareHeader( HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh );
注意:
调用时机,送到的音频输出设备的数据已经播放完成即:pwh的dwFlags中WHDR_DONE位有效
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; // 解码文件路径 };
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; }
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; }
// 如果已经有播放的了,先停止 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;