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

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

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

 播放流程

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

  1. class CPcmPlay
  2. {
  3. public:
  4. CPcmPlay();
  5. ~CPcmPlay();
  6.  
  7. //是否打开了 播放设备
  8. BOOL IsOpen();
  9.  
  10. //nSamplesPerSec 采样频率 8000
  11. //采样位数 :8,16
  12. //声道个数: 1
  13. BOOL Open(int nSamplesPerSec, int wBitsPerSample, int nChannels);
  14.  
  15. //设置声音大小 0到100
  16. BOOL SetVolume(int volume);
  17.  
  18. //播放内存数据
  19. //异步播放,block指针数据可以立即删除
  20. MMRESULT Play(LPSTR block, DWORD size);
  21.  
  22. void StopPlay(); //停止播放
  23. BOOL IsOnPlay(); //是否有数据在播放
  24.  
  25. void Close();//关闭播放设备
  26.  
  27. double GetCurPlaySpan(); //获取当前块已播放的时长
  28. double GetLeftPlaySpan(); //获取剩余播放播放的时长
  29.  
  30. BOOL IsNoPlayBuffer();//打开音频还没播放过
  31.  
  32. private:
  33. void OnOpen();
  34. void OnClose();
  35. void OnDone(WAVEHDR *header);
  36.  
  37. void AddHeader(WAVEHDR *header);
  38. void DelHeader(WAVEHDR *header);
  39.  
  40. //根据数据长度,计算播放长度 单位秒
  41. double GetPlayTimeSpan(int bufferLen);
  42.  
  43. void static CALLBACK MyWaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
  44. DWORD_PTR dwParam1, DWORD_PTR dwParam2);
  45. private:
  46. UINT64 m_totalPlayBuffer;
  47. WAVEFORMATEX m_waveForm;
  48. HWAVEOUT m_hWaveOut;
  49.  
  50. std::list<WAVEHDR*> m_listWaveOutHead;
  51. CCritical m_listLock;
  52. };

1)打开音频设备

  1. BOOL CPcmPlay::Open(int nSamplesPerSec,int wBitsPerSample,int nChannels)
  2. {
  3. if (IsOpen())
  4. return FALSE;
  5.  
  6. {
  7. CCriticalLock lock(m_listLock);
  8. m_listWaveOutHead.clear();
  9. }
  10. m_totalPlayBuffer = ;
  11. m_waveForm.nSamplesPerSec = nSamplesPerSec; /* sample rate */
  12. m_waveForm.wBitsPerSample = wBitsPerSample; /* sample size */
  13. m_waveForm.nChannels = nChannels; /* channels*/
  14. m_waveForm.cbSize = ; /* size of _extra_ info */
  15. m_waveForm.wFormatTag = WAVE_FORMAT_PCM;
  16. m_waveForm.nBlockAlign = (m_waveForm.wBitsPerSample * m_waveForm.nChannels) >> ;
  17. m_waveForm.nAvgBytesPerSec = m_waveForm.nBlockAlign * m_waveForm.nSamplesPerSec;
  18.  
  19. if (waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_waveForm, (DWORD_PTR)MyWaveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
  20. {
  21. return FALSE;
  22. }
  23. return TRUE;
  24. }

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

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

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

  1. void CALLBACK CPcmPlay::MyWaveOutProc(
  2. HWAVEOUT hwo,
  3. UINT uMsg,
  4. DWORD_PTR dwInstance,
  5. DWORD_PTR dwParam1,
  6. DWORD_PTR dwParam2
  7. )
  8. {
  9. CPcmPlay *play = (CPcmPlay*)dwInstance;
  10. if (uMsg == WOM_OPEN) //音频打开
  11. {
  12. play->OnOpen();
  13. return;
  14. }
  15. if (uMsg == WOM_CLOSE) //音频句柄关闭
  16. {
  17. play->OnClose();
  18. return;
  19. }
  20.  
  21. if (uMsg == WOM_DONE)//音频缓冲播放完毕
  22. {
  23. WAVEHDR *header = (WAVEHDR*)dwParam1;
  24. play->OnDone(header);
  25. }
  26. }
  1. waveOutOpen 传入参数与回调函数的参数有一定关联。waveOutOpen传入参数(DWORD_PTR)this,就是回调函数的DWORD_PTR dwInstance;通过这种关联,就可以找到类变量(CPcmPlay *play = (CPcmPlay*)dwInstance;)。
    2)播放数据
  1. MMRESULT CPcmPlay::Play(LPSTR block, DWORD size)
  2. {
  3. if (m_hWaveOut == NULL)
  4. return MMSYSERR_INVALHANDLE;
  5.  
  6. WAVEHDR *header = new WAVEHDR();
  7. ZeroMemory(header, sizeof(WAVEHDR));
  8.  
  9. //对应回调函数 DWORD_PTR dwParam1,
  10. header->dwUser = (DWORD_PTR)header;
  11.  
  12. //new新的数据,并将block数据复制。
  13. //这样函数返回,block的数据可以立即释放
  14. LPSTR blockNew = new char[size];
  15. memcpy(blockNew, block, size);
  16. header->dwBufferLength = size;
  17. header->lpData = blockNew;
  18.  
  19. //准备数据
  20. MMRESULT result = waveOutPrepareHeader(m_hWaveOut, header, sizeof(WAVEHDR));
  21. if (result != MMSYSERR_NOERROR)
  22. {
  23. FreeWaveHeader(header);
  24. return result;
  25. }
  26.  
  27. //播放数据加入缓冲队列
  28. //播放时异步的,播放完毕之前,缓冲的数据不能释放
  29. AddHeader(header);
  30. result = waveOutWrite(m_hWaveOut, header, sizeof(WAVEHDR));
  31. if (result != MMSYSERR_NOERROR)
  32. {
  33. DelHeader(header);
  34. return result;
  35. }
  36. m_totalPlayBuffer += size;
  37.  
  38. return MMSYSERR_NOERROR;
  39. }

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

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

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

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

 3 关闭音频播放

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

    当然本类对其他一些函数也做了封装,方便调用,代码如下:
  1. //根据数据长度,计算播放长度 单位秒
  2. double CPcmPlay::GetPlayTimeSpan(int bufferLen)
  3. {
  4. if (m_waveForm.nSamplesPerSec ==
  5. || m_waveForm.nSamplesPerSec == )
  6. return ;
  7.  
  8. double n = m_waveForm.nSamplesPerSec*m_waveForm.wBitsPerSample /;
  9. double result = ((double)bufferLen)/n;
  10. return result;
  11. }
  12.  
  13. //设置音量大小 volume取值范围0--100
  14. BOOL CPcmPlay::SetVolume(int volume)
  15. {
  16. if (m_hWaveOut == NULL)
  17. return FALSE;
  18.  
  19. UINT16 n = volume;
  20. if (volume <= )
  21. n = ;
  22. if (volume >= )
  23. n = ;
  24.  
  25. n = n * 0xFFFF / ;
  26. DWORD dwVolume = n;
  27. dwVolume = (dwVolume << );
  28. dwVolume += n;
  29.  
  30. MMRESULT result = waveOutSetVolume(m_hWaveOut, dwVolume);
  31. return (result == MMSYSERR_NOERROR);
  32. }
  33.  
  34. //获取已播放时长 单位秒
  35. double CPcmPlay::GetCurPlaySpan()
  36. {
  37. if (m_hWaveOut == NULL)
  38. return ;
  39.  
  40. MMTIME mm = { };
  41. mm.wType = TIME_BYTES;
  42. MMRESULT result = waveOutGetPosition(m_hWaveOut, &mm, sizeof(mm));
  43. if (mm.wType != TIME_BYTES
  44. || result != MMSYSERR_NOERROR)
  45. return ;
  46.  
  47. double span = GetPlayTimeSpan(mm.u.cb);
  48. return span;
  49. }
  50.  
  51. //获取剩余播放时长 单位秒
  52. double CPcmPlay::GetLeftPlaySpan()
  53. {
  54. if (m_hWaveOut == NULL)
  55. return ;
  56.  
  57. MMTIME mm = { };
  58. mm.wType = TIME_BYTES;
  59. MMRESULT result = waveOutGetPosition(m_hWaveOut, &mm, sizeof(mm));
  60. if (mm.wType != TIME_BYTES
  61. || result != MMSYSERR_NOERROR)
  62. return ;
  63.  
  64. double span = GetPlayTimeSpan(m_totalPlayBuffer - mm.u.cb);
  65. return span;
  66. }
  1.  

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

  1.  
  1.  

音频播放封装(pcm格式,Windows平台 c++)的更多相关文章

  1. 如何实现Windows平台RTMP播放器/RTSP播放器播放窗口添加OSD文字叠加

    好多开发者在做Windows平台特别是单屏多画面显示时,希望像监控摄像机一样,可以在播放画面添加OSD台标,以实现字符叠加效果,大多开发者可很轻松的实现以上效果,针对此,本文以大牛直播SDK (Git ...

  2. Windows平台真实时毫秒级4K H264/H265直播技术方案

    背景 在刚提出4K视频的时候,大多数人都觉得没有必要,4K的出现,意味着更高的硬件规格和传输要求,1080P看的很爽.很清晰,完全满足了日常的需求.随着电视的尺寸越来越大,原本1080P成像已经无法满 ...

  3. Android 音视频开发(一):PCM 格式音频的播放与采集

    什么是 PCM 格式 声音从模拟信号转化为数字信号的技术,经过采样.量化.编码三个过程将模拟信号数字化. 采样 顾名思义,对模拟信号采集样本,该过程是从时间上对信号进行数字化,例如每秒采集 44100 ...

  4. 最简单的视音频播放示例9:SDL2播放PCM

    本文记录SDL播放音频的技术.在这里使用的版本是SDL2.实际上SDL本身并不提供视音频播放的功能,它只是封装了视音频播放的底层API.在Windows平台下,SDL封装了Direct3D这类的API ...

  5. 视频和音频播放的演示最简单的例子9:SDL2广播PCM

    ===================================================== 最简单的视频和音频播放的演示样品系列列表: 最简单的视音频播放演示样例1:总述 最简单的视音 ...

  6. 最简单的视音频播放示例8:DirectSound播放PCM

    本文记录DirectSound播放音频的技术.DirectSound是Windows下最常见的音频播放技术.目前大部分的音频播放应用都是通过DirectSound来播放的.本文记录一个使用Direct ...

  7. 最简单的视音频播放演示样例8:DirectSound播放PCM

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  8. Android音频: 怎样使用AudioTrack播放一个WAV格式文件?

    翻译 By Long Luo 原文链接:Android Audio: Play a WAV file on an AudioTrack 译者注: 1. 因为这是技术文章,所以有些词句使用原文,表达更准 ...

  9. Windows平台RTMP/RTSP播放器实现实时音量调节

    为什么要做实时音量调节 RTMP或RTSP直播播放音量调节,主要用于多实例(多窗口)播放场景下,比如同时播放4路RTMP或RTSP流,如果音频全部打开,几路audio同时打开,可能会影响用户体验,我们 ...

随机推荐

  1. uint8_t / uint16_t / uint32_t /uint64_t

    这些数据类型是 C99 中定义的,它就是一个结构的标注,可理解为type/typedef的缩写,表示通过typedef定义.它们只是使用typedef给类型起的别名 #ifndef _UINT8_T ...

  2. java基础-day11

    第11天 综合练习 今日内容介绍 u 综合练习 第1章   综合练习 1.1      综合练习一 A:键盘录入3个学生信息(学号,姓名,年龄,居住地)存入集合,要求学生信息的学号不能重复 B:遍历集 ...

  3. POJ2061 Subsequence 2017-05-25 19:49 83人阅读 评论(0) 收藏

    Subsequence Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 14709   Accepted: 6210 Desc ...

  4. hdu 4542 打表+含k个约数最小数

    http://acm.hdu.edu.cn/showproblem.php?pid=4542 给出一个数K和两个操作 如果操作是0,就求出一个最小的正整数X,满足X的约数个数为K. 如果操作是1,就求 ...

  5. python操作Hbase

    本地操作 启动thrift服务:./bin/hbase-daemon.sh start thrift hbase模块产生: 下载thrfit源码包:thrift-0.8.0.tar.gz 解压安装 . ...

  6. how can I make the login form transparent?

    This is how you can make the Login Form transparent: 1. Add this css to Server Module-> Custom cs ...

  7. Cordova - Windows 下创建第一个 Android App

    官方文档: Create your first Cordova app Android Platform Guide 安装 JDK 和 Android SDK 注意: 需要将 JK 和 Android ...

  8. Android--------------BroadcastReceiver的学习

    一.广播的注册方式 发送广播: Intent mIntent = new Intent("com.simware.BroadcastReceiverDemo"); mIntent. ...

  9. Akka(20): Stream:异步运算,压力缓冲-Async, batching backpressure and buffering

    akka-stream原则上是一种推式(push-model)的数据流.push-model和pull-model的区别在于它们解决问题倾向性:push模式面向高效的数据流下游(fast-downst ...

  10. underscore.js源码研究(3)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...