关闭

多线程应用---使用WaveOut* API开发AMR音频播放器(含源码下载)

3106人阅读 评论(1) 收藏 举报

[源代码以及工程实例下载 ]

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) */
    );

    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结构的大小,单位:字节 */
    );
备注:
   调用此函数之前必须设置WAVEHDR结构的lpData,dwBufferLength,dwFlags成员,dwFlags成员必须设置为0。
   一旦准备完成,不可以修改lpData指针。
   尝试准备一个处于准备状态的数据块,将无效果,返回0
   不应该在同一时刻为不同的波形设备准备同一个数据,如果想从一个设备录制并在另一个设备播放数据,但不想拷贝缓存块,可以分配两块WAVEHDR并将lpData指向同一数据缓存地址,其中一个调用waveInPrepareHeader,另一个调用waveOutPrepareHeader

1.3 waveOutWrite - 将音频数据块送到指定的音频输出设备

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 
);

注意:
    调用时机,送到的音频输出设备的数据已经播放完成即: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;        // 解码文件路径
};

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;
}

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;
}

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;
5
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:332235次
    • 积分:4886
    • 等级:
    • 排名:第6228名
    • 原创:118篇
    • 转载:18篇
    • 译文:6篇
    • 评论:76条
    博客专栏