zoukankan      html  css  js  c++  java
  • 音频播放封装(pcm格式,Windows平台 c++)

    介绍 pcm格式是音频非压缩格式。如果要对音频文件播放,需要先转换为pcm格式。

    windows提供了多套函数用于播放,本文介绍Waveform Audio Functions系列函数。

    原始的播放函数比较难用,因工作需要,我写了一个播放器,将播放相关函数封装了;非常好用,还不易出错。

     播放流程

     程序头文件 可以根据头文件窥探函数功能,下面再做简单介绍。

    class CPcmPlay
    {
    public:
        CPcmPlay();
        ~CPcmPlay();
    
        //是否打开了 播放设备
        BOOL IsOpen();
    
        //nSamplesPerSec 采样频率 8000
        //采样位数  :8,16 
        //声道个数: 1
        BOOL Open(int nSamplesPerSec, int wBitsPerSample, int nChannels);
    
        //设置声音大小 0到100
        BOOL SetVolume(int volume);
    
        //播放内存数据
        //异步播放,block指针数据可以立即删除
        MMRESULT Play(LPSTR block, DWORD size);
    
        void StopPlay();    //停止播放
        BOOL IsOnPlay();    //是否有数据在播放
    
        void Close();//关闭播放设备
    
        double GetCurPlaySpan(); //获取当前块已播放的时长
        double GetLeftPlaySpan(); //获取剩余播放播放的时长
    
        BOOL IsNoPlayBuffer();//打开音频还没播放过
    
    private:
        void OnOpen();
        void OnClose();
        void OnDone(WAVEHDR *header);
    
        void AddHeader(WAVEHDR *header);
        void DelHeader(WAVEHDR *header);
    
        //根据数据长度,计算播放长度 单位秒
        double GetPlayTimeSpan(int bufferLen);
    
        void static CALLBACK   MyWaveOutProc(HWAVEOUT  hwo, UINT uMsg, DWORD_PTR dwInstance,
            DWORD_PTR dwParam1, DWORD_PTR dwParam2);
    private:
        UINT64            m_totalPlayBuffer;
        WAVEFORMATEX    m_waveForm;
        HWAVEOUT        m_hWaveOut;
    
        std::list<WAVEHDR*> m_listWaveOutHead;
        CCritical m_listLock;
    };

    1)打开音频设备

    BOOL CPcmPlay::Open(int nSamplesPerSec,int wBitsPerSample,int nChannels)
    {
        if (IsOpen())
            return FALSE;
    
        {
            CCriticalLock  lock(m_listLock);
            m_listWaveOutHead.clear();
        }
        m_totalPlayBuffer = 0;
        m_waveForm.nSamplesPerSec = nSamplesPerSec; /* sample rate */
        m_waveForm.wBitsPerSample = wBitsPerSample; /* sample size */
        m_waveForm.nChannels = nChannels; /* channels*/
        m_waveForm.cbSize = 0; /* size of _extra_ info */
        m_waveForm.wFormatTag = WAVE_FORMAT_PCM;
        m_waveForm.nBlockAlign = (m_waveForm.wBitsPerSample * m_waveForm.nChannels) >> 3;
        m_waveForm.nAvgBytesPerSec = m_waveForm.nBlockAlign * m_waveForm.nSamplesPerSec;
    
        if (waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_waveForm, (DWORD_PTR)MyWaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
        {
            return FALSE;
        }
        return TRUE;
    }

    需要先设置pcm格式,pcm相关介绍请参考别的文章。

    打开音频传入的有个参数值为CALLBACK_FUNCTION,表示播放事件,通过函数回调方式通知。

    由于音频播放是异步的,当音频播放完毕、音频设备关闭等消息,需要一个通知机制。回调函数如下:

    void  CALLBACK   CPcmPlay::MyWaveOutProc(
        HWAVEOUT  hwo,
        UINT      uMsg,
        DWORD_PTR dwInstance,
        DWORD_PTR dwParam1,
        DWORD_PTR dwParam2
    )
    {
        CPcmPlay *play = (CPcmPlay*)dwInstance;
        if (uMsg == WOM_OPEN) //音频打开
        {
            play->OnOpen();
            return;
        }
        if (uMsg == WOM_CLOSE) //音频句柄关闭
        {
            play->OnClose();
            return;
        }
    
        if (uMsg == WOM_DONE)//音频缓冲播放完毕
        {
            WAVEHDR *header = (WAVEHDR*)dwParam1;
            play->OnDone(header);
        }
    }
    waveOutOpen 传入参数与回调函数的参数有一定关联。waveOutOpen传入参数(DWORD_PTR)this,就是回调函数的DWORD_PTR dwInstance;通过这种关联,就可以找到类变量(CPcmPlay *play = (CPcmPlay*)dwInstance;)。
    2)播放数据
    MMRESULT CPcmPlay::Play(LPSTR block, DWORD size)
    {
        if (m_hWaveOut == NULL)
            return MMSYSERR_INVALHANDLE;
    
        WAVEHDR *header = new WAVEHDR();
        ZeroMemory(header, sizeof(WAVEHDR));
    
        //对应回调函数 DWORD_PTR dwParam1,
        header->dwUser = (DWORD_PTR)header;
    
        //new新的数据,并将block数据复制。
        //这样函数返回,block的数据可以立即释放
        LPSTR blockNew = new char[size];
        memcpy(blockNew, block, size);
        header->dwBufferLength = size;
        header->lpData = blockNew;
    
        //准备数据
        MMRESULT result = waveOutPrepareHeader(m_hWaveOut, header, sizeof(WAVEHDR));
        if (result != MMSYSERR_NOERROR)
        {
            FreeWaveHeader(header);
            return result;
        }
    
        //播放数据加入缓冲队列
        //播放时异步的,播放完毕之前,缓冲的数据不能释放
        AddHeader(header);
        result = waveOutWrite(m_hWaveOut, header, sizeof(WAVEHDR));
        if (result != MMSYSERR_NOERROR)
        {
            DelHeader(header);
            return result;
        }
        m_totalPlayBuffer += size;
    
        return MMSYSERR_NOERROR;
    }

    有一点特别注意,播放函数是异步的,就是播放完毕之前,播放缓冲数据不能释放。为了方便调用,重新将输入参数block的数据又new一块内存存放,调用方不必关心内存块啥时释放。

    我们将播放缓冲加入一个list列表中,当播放完毕,我们需要释放该缓冲。怎么知道缓冲数据是否播放完毕?是通过回调机制。参加前文回调函数。

    
    
    if (uMsg == WOM_DONE)//音频缓冲播放完毕
        {
           //对应回调函数 DWORD_PTR dwParam1,
        //header->dwUser = (DWORD_PTR)header;
    
            WAVEHDR *header = (WAVEHDR*)dwParam1;
            play->OnDone(header);
        }
    回调参数dwParam1对应header->dwUser,我们将dwUser设置为缓冲指针,这样,通过回调函数的参数就找到了对应播放缓冲。
    播放完毕的缓冲,需要释放。
    void CPcmPlay::DelHeader(WAVEHDR *header)
    {
        {
            CCriticalLock  lock(m_listLock);
            m_listWaveOutHead.remove(header);
        }
        FreeWaveHeader(header);
    }
    
    void FreeWaveHeader(WAVEHDR *header)
    {
        delete[]header->lpData;
        delete header;
    }

    由于回调函数和播放函数属于不同的线程,所以对列表操作加了锁。

     3 关闭音频播放

    void CPcmPlay::Close()
    {
        if (m_hWaveOut == NULL)
            return;
        
        StopPlay();
        MMRESULT result = waveOutClose(m_hWaveOut);
        m_hWaveOut = NULL;
    
        //等待释放所有的播放缓冲
        int n = 0;
        while (IsOnPlay() && n < 5000)
        {
            n++;
            ::Sleep(1);
        }
    }
    关闭播放时,有一点需要注意,有可能播放还没完毕。调用waveOutClose后,回调函数给通知,即uMsg == WOM_DONE,在回调函数中将缓冲数据释放。
    当所有的数据释放完毕,才能安全退出。
    这就是播放的基本流程,其实不难。但是,因为播放是异步的,所以处理缓冲释放方面有点小技巧。

    当然本类对其他一些函数也做了封装,方便调用,代码如下:
    //根据数据长度,计算播放长度 单位秒
    double CPcmPlay::GetPlayTimeSpan(int bufferLen)
    {
        if (m_waveForm.nSamplesPerSec == 0
            || m_waveForm.nSamplesPerSec == 0)
            return 0;
    
        double n = m_waveForm.nSamplesPerSec*m_waveForm.wBitsPerSample /8;
        double result = ((double)bufferLen)/n;
        return result;
    }
    
    
    //设置音量大小 volume取值范围0--100
    BOOL CPcmPlay::SetVolume(int volume)
    {
        if (m_hWaveOut == NULL)
            return FALSE;
    
        UINT16 n = volume;
        if (volume <= 0)
            n = 0;
        if (volume >= 100)
            n = 100;
    
        n = n * 0xFFFF / 100;
        DWORD dwVolume = n;
        dwVolume = (dwVolume << 16);
        dwVolume += n;
    
        MMRESULT result = waveOutSetVolume(m_hWaveOut, dwVolume);
        return (result == MMSYSERR_NOERROR);
    }
    
    //获取已播放时长 单位秒
    double CPcmPlay::GetCurPlaySpan()
    {
        if (m_hWaveOut == NULL)
            return 0;
    
        MMTIME mm = { 0 };
        mm.wType = TIME_BYTES;
        MMRESULT result = waveOutGetPosition(m_hWaveOut, &mm, sizeof(mm));
        if (mm.wType != TIME_BYTES
            || result != MMSYSERR_NOERROR)
            return 0;
    
        double span = GetPlayTimeSpan(mm.u.cb);
        return span;
    }
    
    //获取剩余播放时长 单位秒
    double CPcmPlay::GetLeftPlaySpan()
    {
        if (m_hWaveOut == NULL)
            return 0;
    
        MMTIME mm = { 0 };
        mm.wType = TIME_BYTES;
        MMRESULT result = waveOutGetPosition(m_hWaveOut, &mm, sizeof(mm));
        if (mm.wType != TIME_BYTES
            || result != MMSYSERR_NOERROR)
            return 0;
    
        double span = GetPlayTimeSpan(m_totalPlayBuffer - mm.u.cb);
        return span;
    }
    
    

    封装类下载地址https://download.csdn.net/download/qq_29939347/10746435

     
    
    
  • 相关阅读:
    SpringBoot集成JWT
    MongoDB学习入门
    Docker入门笔记
    商品分类(递归子目录)接口开发笔记
    深入理解java虚拟机
    -XX:+HeapDumpOnOutOfMemoryError
    使用kettle报Invalid byte 1 of 1-byte UTF-8 sequence异常
    kettle学习笔记(四)——kettle输入步骤
    eclipse maven项目,如何导出使用的依赖jar包
    kettle学习笔记(二)——kettle基本使用
  • 原文地址:https://www.cnblogs.com/yuanchenhui/p/pcm_play.html
Copyright © 2011-2022 走看看