对音频设备的操作主要是初始化音频设备以及往音频设备发送 PCM(Pulse Code Modulation)数据。为了方便,本文使用 ALSA(Advanced Linux Sound Architecture)提供的库和驱动。在编译和运行本文中的 MP3 流媒体播放器的时候,必须先安装 ALSA 相关的文件。
 本文用到的主要对 PCM 设备操作的函数分为 PCM 设备初始化的函数以及 PCM 接口的一些操作函数。
PCM 硬件设备参数设置和初始化的函数有:

  1. int  snd_pcm_hw_params_malloc (snd_pcm_hw_params_t **ptr)
  2. int  snd_pcm_hw_params_any (snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
  3. void snd_pcm_hw_params_free (snd_pcm_hw_params_t *obj)
  4. int  snd_pcm_hw_params_set_access ( snd_pcm_t *pcm,
  5. snd_pcm_hw_params_t *params,
  6. snd_pcm_access_t _access)
  7. int  snd_pcm_hw_params_set_format ( snd_pcm_t *pcm,
  8. snd_pcm_hw_params_t *params,
  9. snd_pcm_format_t val)
  10. int  snd_pcm_hw_params_set_channels(snd_pcm_t *pcm,
  11. snd_pcm_hw_params_t *params,
  12. unsigned int val)
  13. int snd_pcm_hw_params_set_rate_near(snd_pcm_t *pcm,
  14. snd_pcm_hw_params_t *params,
  15. unsigned int *val, int *dir)

PCM 接口函数有:

  1. int   snd_pcm_hw_params (snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
  2. int   snd_pcm_prepare (snd_pcm_t *pcm)
  3. int   snd_pcm_open (snd_pcm_t **pcm, const char *name,
  4. snd_pcm_stream_t stream, int mode)
  5. int   snd_pcm_close (snd_pcm_t *pcm)
  6. snd_pcm_sframes_t   snd_pcm_writei (snd_pcm_t *pcm,
  7. const void *buffer, snd_pcm_uframes_t size)

这些函数用到了 snd_pcm_hw_params_t 结构,此结构包含用来播放 PCM 数据流的硬件信息配置。在往音频设备(声卡)写入音频数据之前,必须设置访问类型、采样格式、采样率、声道数等。

首先使用 snd_pcm_open () 打开 PCM 设备,在 ALSA 中,PCM 设备都有名字与之对应。比如我们可以定义 PCM 设备名字为 char *pcm_name = "plughw:0,0"。 最重要的 PCM 设备接口是“plughw”以及“hw”接口。 使用“plughw”接口,程序员不必过多关心硬件,而且如果设置的配置参数和实际硬件支持的参数不一致,ALSA 会自动转换数据。如果使用“hw”接口,我们就必须检测硬件是否支持设置的参数了。Plughw 后面的两个数字分别表示设备号和次设备(subdevice)号。

snd_pcm_hw_params_malloc( ) 在栈中分配 snd_pcm_hw_params_t 结构的空间,然后使用 snd_pcm_hw_params_any( ) 函数用声卡的全配置空间参数初始化已经分配的 snd_pcm_hw_params_t 结构。snd_pcm_hw_params_set_access ( ) 设置访问类型,常用访问类型的宏定义有:

  1. SND_PCM_ACCESS_RW_INTERLEAVED

交错访问。在缓冲区的每个 PCM 帧都包含所有设置的声道的连续的采样数据。比如声卡要播放采样长度是 16-bit 的 PCM 立体声数据,表示每个 PCM 帧中有 16-bit 的左声道数据,然后是 16-bit 右声道数据。

  1. SND_PCM_ACCESS_RW_NONINTERLEAVED

非交错访问。每个 PCM 帧只是一个声道需要的数据,如果使用多个声道,那么第一帧是第一个声道的数据,第二帧是第二个声道的数据,依此类推。

函数 snd_pcm_hw_params_set_format() 设置数据格式,主要控制输入的音频数据的类型、无符号还是有符号、是 little-endian 还是 bit-endian。比如对于 16-bit 长度的采样数据可以设置为:

  1. SND_PCM_FORMAT_S16_LE      有符号16 bit Little Endian
  2. SND_PCM_FORMAT_S16_BE      有符号16 bit Big Endian
  3. SND_PCM_FORMAT_U16_LE      无符号16 bit Little Endian
  4. SND_PCM_FORMAT_U16_BE      无符号 16 bit Big Endian
  5. 比如对于 32-bit 长度的采样数据可以设置为:
  6. SND_PCM_FORMAT_S32_LE      有符号32 bit Little Endian
  7. SND_PCM_FORMAT_S32_BE      有符号32 bit Big Endian
  8. SND_PCM_FORMAT_U32_LE      无符号32 bit Little Endian
  9. SND_PCM_FORMAT_U32_BE      无符号 32 bit Big Endian

函数 snd_pcm_hw_params_set_channels() 设置音频设备的声道,常见的就是单声道和立体声,如果是立体声,设置最后一个参数为2。snd_pcm_hw_params_set_rate_near () 函数设置音频数据的最接近目标的采样率。snd_pcm_hw_params( ) 从设备配置空间选择一个配置,让函数 snd_pcm_prepare() 准备好 PCM 设备,以便写入 PCM 数据。snd_pcm_writei() 用来把交错的音频数据写入到音频设备。

初始化 PCM 设备的例程如下:

初始化 PCM 设备的例程

  1. /* open a PCM device */
  2. int open_device(struct mad_header const *header)
  3. {
  4. int err;
  5. snd_pcm_hw_params_t *hw_params;
  6. char  *pcm_name = "plughw:0,0";
  7. int rate = header->samplerate;
  8. int channels = 2;
  9. if (header->mode == 0) {
  10. channels = 1;
  11. } else {
  12. channels = 2;
  13. }
  14. if ((err = snd_pcm_open (&playback_handle,
  15. pcm_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
  16. printf("cannot open audio device %s (%s)\n",
  17. pcm_name,
  18. snd_strerror (err));
  19. return -1;
  20. }
  21. if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
  22. printf("cannot allocate hardware parameter structure (%s)\n",
  23. snd_strerror (err));
  24. return -1;
  25. }
  26. if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {
  27. printf("cannot initialize hardware parameter structure (%s)\n",
  28. snd_strerror (err));
  29. return -1;
  30. }
  31. if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params,
  32. SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
  33. printf("cannot set access type (%s)\n",
  34. snd_strerror (err));
  35. return -1;
  36. }
  37. if ((err = snd_pcm_hw_params_set_format (playback_handle,
  38. hw_params, SND_PCM_FORMAT_S32_LE)) < 0) {
  39. printf("cannot set sample format (%s)\n",
  40. snd_strerror (err));
  41. return -1;
  42. }
  43. if ((err = snd_pcm_hw_params_set_rate_near (playback_handle,
  44. hw_params, &rate, 0)) < 0) {
  45. printf("cannot set sample rate (%s)\n",
  46. snd_strerror (err));
  47. return -1;
  48. }
  49. if ((err = snd_pcm_hw_params_set_channels (playback_handle,
  50. hw_params, channels)) < 0) {
  51. printf("cannot set channel count (%s)\n",
  52. snd_strerror (err));
  53. return -1;
  54. }
  55. if ((err = snd_pcm_hw_params (playback_handle,
  56. hw_params)) < 0) {
  57. printf("cannot set parameters (%s)\n",
  58. snd_strerror (err));
  59. return -1;
  60. }
  61. snd_pcm_hw_params_free (hw_params);
  62. if ((err = snd_pcm_prepare (playback_handle)) < 0) {
  63. printf("cannot prepare audio interface for use (%s)\n",
  64. snd_strerror (err));
  65. return -1;
  66. }
  67. return 0;
  68. }

这里配置的 PCM 格式是 SND_PCM_FORMAT_S32_LE,采样的格式是每个采样有 32-bit 的数据,数据按照 little-endian 存放。如果通过 mad_frame_decode() 函数得到 PCM 数据后,要求每个采样数据只占 16-bit,需要把数据进行MAD的定点类型到 signed short 类型进行转换。那么,PCM 数据如何写入声卡中呢?函数实现例程如下所示:

PCM 数据写入声卡函数实现例程

  1. while (nsamples--) {
  2. /* nsamples 是采样的数目 */
  3. signed int sample;
  4. sample = pcm->samples[0][j];
  5. *(OutputPtr++) = sample & 0xff;
  6. *(OutputPtr++) = (sample >> 8);
  7. *(OutputPtr++) = (sample >> 16);
  8. *(OutputPtr++) = (sample >> 24);
  9. if (nchannels == 2) {
  10. sample = pcm->samples[1][j];
  11. *(OutputPtr++) = sample  & 0xff;
  12. *(OutputPtr++) = sample >> 8;
  13. *(OutputPtr++) = (sample >> 16);
  14. *(OutputPtr++) = (sample >> 24);
  15. }
  16. j++;
  17. }
  18. if ((err = snd_pcm_writei (playback_handle, buf, samples)) < 0) {
  19. err = xrun_recovery(playback_handle, err);
  20. if (err < 0) {
  21. printf("Write error: %s\n", snd_strerror(err));
  22. return -1;
  23. }
  24. }

这里用到了 http://www.alsa-project.org/ 关于 ALSA 文档中的例子函数 xrun_recovery( )。详细例子请参见http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html。使用此函数的目的是避免出现由于网络原因,声卡不能及时得到音频数据而使得 snd_pcm_writei() 不能正常连续工作。实际上在xrun_recovery( ) 中,又调用 snd_pcm_prepare() 和 snd_pcm_resume() 以实现能“恢复错误”的功能。-EPIPE错误表示应用程序没有及时把 PCM 采样数据送入ASLA 库。xrun_recovery() 函数如下所示:

xrun_recovery() 函数

  1. int xrun_recovery(snd_pcm_t *handle, int err)
  2. {
  3. if (err == -EPIPE) {    /* under-run */
  4. err = snd_pcm_prepare(handle);
  5. if (err < 0)
  6. printf("Can't recovery from underrun, prepare failed: %s\n",
  7. snd_strerror(err));
  8. return 0;
  9. } else if (err == -ESTRPIPE) {
  10. while ((err = snd_pcm_resume(handle)) == -EAGAIN)
  11. sleep(1);       /* wait until the suspend flag is released */
  12. if (err < 0) {
  13. err = snd_pcm_prepare(handle);
  14. if (err < 0)
  15. printf("Can't recovery from suspend, prepare failed: %s\n",
  16. snd_strerror(err));
  17. }
  18. return 0;
  19. }
  20. return err;
  21. }

知道了具体的音频设备操作方法,就该使用 MAD 提供的函数具体实现解码了。函数 mp3_decode_buf( ) 提供了使用 libmad 解码的方法。首先调用 mad_stream_buffer() 函数把 MP3 流数据和 decode_stream 关联,然后开始循环解码数据。如果在解码数据过程中,有不完整 PCM 数据帧,那么 decode_stream.error 的值就是MAD_ERROR_BUFLEN,且 decode_stream.next_frame 不为 NULL。这时候,把剩余的未解码的数据再拷贝到数据解码缓冲区里。 mad_frame_decode( ) 函数从 decode_stream 中得到 PCM 数据。

mad_frame_decode( ) 函数从 decode_stream 中得到 PCM 数据

  1. int mp3_decode_buf(char *input_buf, int size)
  2. {
  3. int decode_over_flag = 0;
  4. int remain_bytes = 0;
  5. int ret_val = 0;
  6. mad_stream_buffer(&decode_stream, input_buf, size);
  7. decode_stream.error = MAD_ERROR_NONE;
  8. while (1)
  9. {
  10. if (decode_stream.error == MAD_ERROR_BUFLEN) {
  11. if (decode_stream.next_frame != NULL) {
  12. remain_bytes = decode_stream.bufend - decode_stream.next_frame;
  13. memcpy(input_buf, decode_stream.next_frame, remain_bytes);
  14. return remain_bytes;
  15. }
  16. }
  17. ret_val = mad_frame_decode(&decode_frame, &decode_stream);
  18. /* 省略部分代码 */
  19. ...
  20. if (ret_val == 0) {
  21. if (play_frame(&decode_frame) == -1) {
  22. return -1;
  23. }
  24. }
  25. /* 后面代码省略 */
  26. ...
  27. }
  28. return 0;
  29. }
 
recommend from :http://www.ibm.com/developerworks/cn/linux/l-cn-libmadmp3player/index.html

本文出自 “驿落黄昏” 博客,请务必保留此出处http://yiluohuanghun.blog.51cto.com/3407300/868048

PCM音频设备的操作(转)的更多相关文章

  1. 使用AudioTrack播放PCM音频数据(android)

    众所周知,Android的MediaPlayer包含了Audio和video的播放功能,在Android的界面上,Music和Video两个应用程序都是调用MediaPlayer实现的.MediaPl ...

  2. Linux ALSA声卡驱动之三:PCM设备的创建

    声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! 1. PCM是什么 模数转换 模拟信号经过pcm(脉冲编码调制)后为pcm数据: PCM是 ...

  3. Linux音频编程指南

    Linux音频编程指南 虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有着非常丰富的媒体功能,本文就是以多媒体应用中最基本的声音为对象,介绍如何在Linux平台下开发实际的音频应用程序 ...

  4. 与音频相关的技术知识点总结(Linux方向的开发)

    几个术语和概念: 1.       关于PCM的 PCM是Pulse code modulation的缩写,它是对波形最直接的编码方式.它在音频中的地位可能和BMP在图片中的地位有点类似吧. Samp ...

  5. Linux音频编程指南(转)

    转自: http://www.ibm.com/developerworks/cn/linux/l-audio/ Linux音频编程指南 虽然目前Linux的优势主要体现在网络服务方面,但事实上同样也有 ...

  6. WebRTC源码分析:音频模块结构分析

    一.概要介绍WebRTC的音频处理流程,见下图: webRTC将音频会话抽象为一个通道Channel,譬如A与B进行音频通话,则A需要建立一个Channel与B进行音频数据传输.上图中有三个Chann ...

  7. Tiny6410声卡驱动——录音与回放

    在Linux下,音频设备程序的实现与文件系统的操作密切相关.Linux将各种设备以文件的形式给出统一的接口,这样的设计使得对设备的编程与对文件的操作基本相同,对Linux内核的系统调用也基本一致,从而 ...

  8. ALSA声卡08_从零编写之框架_学习笔记

    1.整体框架 (1)图示((DAI(全称Digital Audio Interface)接口)) 在嵌入式系统里面,声卡驱动是ASOC,是在ALSA驱动上封装的一层,包括以下三大块 (2)程序框架 m ...

  9. WebRTC VoiceEngine综合应用示例(一)——基本结构分析(转)

    把自己这两天学习VoiceEngine的成果分享出来,供大家参考,有什么问题也欢迎大家指出,一起学习一起进步. 本文将对VoiceEngine的基本结构做一个分析,分析的方法是自底向上的:看一个音频编 ...

随机推荐

  1. [Chapter 3 Process]Practice 3.3 Discuss three major complications that concurrent processing adds to an operating system.

    3.3  Original version of Apple's mobile iOS operating system provied no means of concurrent processi ...

  2. Java实现批量下载《神秘的程序员》漫画

    上周看了西乔的博客“西乔的九卦”.<神秘的程序员们>系列漫画感觉很喜欢,很搞笑.这些漫画经常出现在CSDN“程序员”杂志末页的,以前也看过一些. 后来就想下载下来,但是一张一张的点击右键“ ...

  3. c# 验证码类

    using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; us ...

  4. jQuery学习总结(一)

    jQuery当中独有的对象:jQuery对象: jQuery对象的缩写形式“$”:所以在使用时,我们都是用$来代替jQuery. 所以我们在页面元素选择或执行功能函数的时候可以这么写:$(functi ...

  5. 模板短信接口调用java,pythoy版(一) 网易云信

    说明 短信服务平台有很多,我只是个人需求,首次使用,算是测试用的,故选个网易(大公司). 稳定性:我只测试了15条短信... 不过前3条短信5分钟左右的延时,后面就比较快.... 我只是需要发短信,等 ...

  6. 如何防止鼠标移出移入子元素触发mouseout和mouseover事件

    js代码: function isMouseLeaveOrEnter(e, handler) { var reltg=e.relatedTarget?e.relatedTarget:e.type==' ...

  7. 《Linux内核设计与实现》读书笔记(十八)- 内核调试

    内核调试的难点在于它不能像用户态程序调试那样打断点,随时暂停查看各个变量的状态. 也不能像用户态程序那样崩溃后迅速的重启,恢复初始状态. 用户态程序和内核交互,用户态程序的各种状态,错误等可以由内核来 ...

  8. LinkedHashMap和HashMap的比较使用

    由于现在项目中用到了LinkedHashMap,并不是太熟悉就到网上搜了一下. import java.util.HashMap; import java.util.Iterator; import ...

  9. 微软BI 之SSIS 系列 - MVP 们也不解的 Scrip Task 脚本任务中的一个 Bug

    开篇介绍 前些天自己在整理 SSIS 2012 资料的时候发现了一个功能设计上的疑似Bug,在 Script Task 中是可以给只读列表中的变量赋值.我记得以前在 2008 的版本中为了弄明白这个配 ...

  10. atitit.404错误的排查流程总结

    atitit.404错误的排查流程总结 #----------jsp  head  errorPage="" del zeu ok le. #------resin 服务器配置问题 ...