ALSA声音编程介绍

ALSA是Advanced Linux Sound Architecture简称。它包含一组kernel 驱动,一个应用编程接口(API)库以及一组工具函数。本文中,我们会向读者展示ALSA项目和组成部件的概况。后面会重点介绍ALSA PCM接口的编程。

ALSA不仅仅是sound API。选择ALSA可以让你最大程度的控制和执行执行低级的audio函数,或者使用其它sound API不支持的特定功能。如果你已经写了一个audio应用程序,那么你可能希望为它增加ALSA sound驱动支持。如果你的主要兴趣不是audio,仅仅是想播放声音,那么你最好使用高级的sound toolkits,比如SDL,OpenAL或者桌面环境提供的其他开发包。如果你想使用ALSA,那么要确保你的Linux系统支持ALSA。

History of ALSA

ALSA项目的出现是因为Linux kernel sound driver OSS/Free drivers不能得到很好的维护,而导致驱动无法支持新的sound技术。Jaroslav Kysela最初为一个sound card写了一个驱动,启动了这个项目,随着时间的推移越来越多的开发者加入进来,重新定义了API以支持更多的声卡。

在Linux kernel 2.5的开发过程中,ALSA被merged到官方内核中。随着kernel2.6的发布,ALSA成为稳定版内核的一部分,并且得到了广泛使用。

Digital Audio Basics

声音由空气压力变化的波形组成,被转换器(如麦克风)转换为电信号。一个模数转换器(ADC)把模拟电压信号转换为离散值,称为采样,采样是按照固 定时间间隔进行的,称为采样率。发送这些采样到数模转换器(DAC),再输出到loudspeaker,原始的声音就被重现了。

采样的位数大小,是决定声音数字精度的一个因素,另外一个主要因素是采样率。奈奎斯特理论指出当信号带宽小于1/2采样率时,可通过采样信号还原出原始信号。

ALSA Basics

ALSA包括支持各种声卡的Kernel设备驱动,API库libasound。应用开发者应该使用API而不是kernel系统调用接口。API库函数提供了高层次,编程友好的接口,开发者使用逻辑设备名而不需要考虑低级细节(如设备文件)。

与ALSA相反,OSS/Free要求应用在kernel系统调用级进行编程,这就需要开发者指定设备文件名并且使用ioctl来完成功能。为了兼 容OSS,ALSA提供了内核模块来模拟OSS/Free声音驱动,所以大部分现存的audio应用都无须修。libaoss是模拟包装库,可以用来模拟 OSS/Free API而不需要内核模块的模拟。

ALSA还提供了plugins能力,允许扩展一个新设备,甚至包括完全用软件实现的虚拟设备。ALSA提供了一组命令行工具,包括mixer,声音文件播放,以及对特定声卡特定功能的控制。

ALSA Architecture

ALSA API可以分为以下几个主要部分:

  • Control接口:一个通用的功能,用来管理声卡的寄存器以及查询可用设备。
  • PCM 接口:管理数字audio capture和playback的接口,本文的其余部分将主要介绍着个接口,因为这是audio应用最常用的接口
  • Raw MIDI接口:支持MIDI(Musical Instrument DIgital Interface,电子音乐设备的标准)。这个API提供了对声卡MIDI bus的访问。Raw接口由MIDI events驱动,程序负责管理协议和计时
  • Timer 接口:提供对声卡上计时硬件的访问,用于同步声音事件。
  • Sequencer interface:一个MIPI编程和声音同步接口,比raw MIDI接口级别更高,它管理大部分MIDI协议和计时。
  • Mixer接口:控制声卡上的信号路由和音量调节的设备。它是建立在control接口之上的。

Device Naming

API操作的是逻辑设备名而不是设备文件,设备名可以是真正的硬件设备或者插件。硬件设备使用hw:i,j这种格式,i是卡号而j是在这个卡上的设 备。第一个声音设备是hw:0,0。第一个sound设备的别名为defalut,本文后面的例子都使用default。Plugins使用另外一种命名 模式:例如plughw:是一个插件除了提供对硬件设备的访问还提供某种功能,比如软件实现采样率转换,因为硬件不支持这种操作。dmix插件允许合成几 路数据 dshare插件允许把单路数据动态分配到不同的应用中。

Sound Buffers and Data Transfer

声卡有一个硬件buffer。在录音时(capture)存储录音的采样值,当buffer填满后声卡生成一个中断,kernel sound驱动使用DMA传输采样数据到内存buffer。类似的在playback时,应用buffer数据通过DMA传输给sound card的硬件buffer

这个硬件buffer是一个ring buffers,意味着当到达buffer末端后,就会重新回到起始端。一个指针用来维护硬件buffer和应用buffer的当前位置。在内核外部,仅 能访问application buffer,所以在这里我们主要讨论application buffer。

buffer的尺寸可以通过ALSA库函数调用指定。buffer可以非常的大,一次传输完整个buffer的数据可能导致无法接收的延迟,所以,ALSA把这个buffer分割为一系列periods,以period做为传输数据的单位。

Period由多个frames组成,每一个frames包含在一个时间点的采样值,对于立体声设备来说,一个frames包含两个 channels的采用,图1演示了一个buffer, period,sample之间的关系,在这里,左右声道的数据存储在一帧中,这种模式称为interleaved。对于non-interleaved 模式,所有的左声道数据存放在一起,然后所有的右声道保存在一起。


Over and Under Run

当一个声音设备被激活,数据持续的在硬件和应用buffer间传送。在录音情况下(capture),如果应用没有快速的从buffer中读取数 据,环状buffer将被新到的数据覆盖,导致数据丢失,我们称之为overrun。在playback情况下,如果应用无法快速传送数据到buffer 中,那么导致hardware无数据可播放,这种情况我们称之为underrun。ALSA文档有时把这两种情况统称为XRUN。正确设计的应用最小化 XRUN的发生并且在XRUN发生后能够恢复操作。

A typical Sound Application

通常可以用下面伪代码表示PCM接口编程模式:

  1. open interface for capture or playback    
  2. set hardware parameters(access mode, data format, channels, rate, etc.)    
  3. while there is data to be processed:    
  4.     read PCM data(capture) or write PCM data(playback)    
  5. close interface  

 

我们先看看一些示例代码。我推荐你在linux系统编译运行它们,观察输出然后尝试一些修改。这些测试代码的完整列表可以从以下地址下载:ftp.linuxjournal.com/pub/lj/listings/issue126/6735.tgz


Listing 1. Display Some PCM Types and Formats

  1. #include <alsa/asoundlib.h>    
  2.     
  3. int main() {    
  4.     int val;    
  5.     
  6.     printf("ALSA library version: %s\n", SND_LIB_VERSION_STR);    
  7.     
  8.     printf("PCM stream types:\n");    
  9.     for (val = 0; val <= SND_PCM_STREAM_LAST; val++) {    
  10.         printf(" %s\n", snd_pcm_stream_name((snd_pcm_stream_t)val));    
  11.     }    
  12.     
  13.     printf("PCM access types:\n");    
  14.     for (val = 0; val <= SND_PCM_ACCESS_LAST; val++) {    
  15.         printf(" %s\n", snd_pcm_access_name((snd_pcm_access_t)val));    
  16.     }    
  17.     
  18.     printf("PCM formats:\n");    
  19.     for (val = 0; val <= SND_PCM_STREAM_LAST; val++) {    
  20.         if (snd_pcm_format_name((snd_pcm_format_t)val) != NULL) {    
  21.             printf(" %s (%s)\n",     
  22.                   snd_pcm_format_name((snd_pcm_format_t)val),    
  23.                   snd_pcm_format_description((snd_pcm_format_t)val));    
  24.         }    
  25.    }    
  26.     
  27.     
  28.    printf("\nPCM subformats:\n;);    
  29.    for (val = 0; val <= SND_PCM_SUBFORMAT_LAST; val++) {    
  30.        printf(" %s (%s)\n", snd_pcm_subformat_name((snd_pcm_subformat_t)val),    
  31.            snd_pcm_subformat_description((snd_pcm_subformat_t)val));    
  32.    }    
  33.    return 0;    
  34. }  

gcc -o test test.c -lasound

编译为可执行文件,程序必须链接ALSA库,libasound。有些ALSA库函数需要使用dlopen功能和浮点运算,所以有时需要增加-ldl和-lmgcc -o listing1 listing1.c -lasound在我的机器上,运行结果如下

  1. ALSA library version: 1.0.22    
  2.     
  3. PCM stream types:    
  4.  PLAYBACK    
  5.  CAPTURE    
  6.     
  7. PCM access types:    
  8.  MMAP_INTERLEAVED    
  9.  MMAP_NONINTERLEAVED    
  10.  MMAP_COMPLEX    
  11.  RW_INTERLEAVED    
  12.  RW_NONINTERLEAVED    
  13.     
  14. PCM formats:    
  15.  S8 (Signed 8 bit)    
  16.  U8 (Unsigned 8 bit)    
  17.     
  18. PCM subformats:    
  19.  STD (Standard)  

listing1 展示了一些ALSA使用的PCM数据类型和参数。需要包含头文件alsa/asoundlib.h,ALSA库函数以及常用宏都在这个文件中定义。这个程序的其余部分重复的打印了PCM数据类型。

Listing 2. Opening PCM Device and Setting Parameters

  1. /*  
  2.     This example opens the default PCM device, sets some parameters,  
  3. and then displays the value of most of the hardware parameters. It does  
  4. not perform any sound playback or recording.  
  5. */  
  6. /* Use the newer ALSA API */    
  7. #define ALSA_PCM_NEW_HW_PARAMS_API    
  8.     
  9. /* All of the ALSA library API is defined in this header */    
  10.     
  11. #include <alsa/asoundlib.h>    
  12.     
  13. int main()    
  14. {    
  15.     int rc;    
  16.     snd_pcm_t *handle;    
  17.     snd_pcm_hw_params_t *params;    
  18.     unsigned int val, val2;    
  19.     int dir;    
  20.     snd_pcm_uframes_t frames;    
  21.     
  22.     /* Open PCM device for playback */    
  23.     rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);    
  24.     if (rc < 0) {    
  25.         printf("unable to open pcm device\n");    
  26.     }    
  27.     
  28.     /* Allocate a hardware parameters object */    
  29.     snd_pcm_hw_params_alloca(¶ms);    
  30.     
  31.     /* Fill it in with default values. */    
  32.     snd_pcm_hw_params_any(handle, params);    
  33.     
  34.     /*Set the desired hardware parameters. */    
  35.     
  36.     /* Interleaved mode */    
  37.     snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);    
  38.     
  39.     /* Signed 16-bit little-endian format */    
  40.     snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);    
  41.     
  42.     /* Two channels (stero) */    
  43.     snd_pcm_hw_params_set_channels(handle, params, 2);    
  44.     
  45.     /* 44100 bits/second sampling rate */    
  46.     val = 44100;    
  47.     snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);    
  48.     
  49.     /* Write the parameters to the dirver */    
  50.     rc = snd_pcm_hw_params(handle, params);    
  51.     if (rc < 0) {    
  52.         printf("unable to set hw parameters: %s\n", snd_strerror(rc));    
  53.         exit(1);    
  54.     }    
  55.     
  56.     /* Display information about the PCM interface */    
  57.     printf("PCM handle name = '%s'\n", snd_pcm_name(handle));    
  58.     
  59.     printf("PCM state = %s\n", snd_pcm_state_name(snd_pcm_state(handle)));    
  60.     
  61.     snd_pcm_hw_params_get_format(params, &val);    
  62.     printf("format = '%s'(%s)\n", snd_pcm_format_name((snd_pcm_format_t)val),     
  63.              snd_pcm_format_description((snd_pcm_format_t)val));    
  64.     
  65.     snd_pcm_hw_params_get_subformat(params, (snd_pcm_subformat_t *)&val);    
  66.     printf("subformat = '%s' (%s)\n", snd_pcm_subformat_name((snd_pcm_subformat_t)val),    
  67.              snd_pcm_subformat_description((snd_pcm_subformat_t)val));    
  68.     
  69.     snd_pcm_hw_params_get_channels(params, &val);    
  70.     printf("channels=%d\n", val);    
  71.     
  72.     snd_pcm_hw_params_get_rate(params, &val, &dir);    
  73.     printf("rate = %d bps\n", val);    
  74.     
  75.     snd_pcm_hw_params_get_period_time(params, &val, &dir);    
  76.     printf("period time = %d us\n", val);    
  77.     
  78.     snd_pcm_hw_params_get_period_size(params, &frames, &dir);    
  79.     printf("period size = %d frames\n", (int)frames);    
  80.     
  81.     snd_pcm_hw_params_get_buffer_time(params, &val, &dir);    
  82.     printf("buffer time = %d us\n", val);    
  83.     
  84.     snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &val);    
  85.     printf("buffer size = %d frames\n", val);    
  86.     
  87.     snd_pcm_hw_params_get_periods(params, &val, &dir);    
  88.     printf("periods per buffer = %d frames\n", val);    
  89.     
  90.     snd_pcm_hw_params_get_rate_numden(params, &val, &val2);    
  91.     printf("exact rate=%d/%d bps\n", val, val2);    
  92.     
  93.     val = snd_pcm_hw_params_get_sbits(params);    
  94.     printf("significant bits = %d\n", val);    
  95.     
  96.     snd_pcm_hw_params_get_tick_time(params, &val, &dir);    
  97.     printf("tick time = %d us\n", val);    
  98.     
  99.     val = snd_pcm_hw_params_is_batch(params);    
  100.     printf("is batch = %d\n", val);    
  101.     
  102.     val = snd_pcm_hw_params_is_block_transfer(params);    
  103.     printf("is block transfer = %d\n", val);    
  104.     
  105.     val = snd_pcm_hw_params_is_double(params);    
  106.     printf("is double = %d\n", val);    
  107.     
  108.     val = snd_pcm_hw_params_is_half_duplex(params);    
  109.     printf("is half duplex = %d\n", val);    
  110.   
  111.     val = snd_pcm_hw_params_is_joint_duplex(params);    
  112.     printf("is joint duplex = %d\n", val);    
  113.   
  114.     val = snd_pcm_hw_params_can_overrange(params);    
  115.     printf("can overrange = %d\n", val);    
  116.     
  117.     val = snd_pcm_hw_params_can_mmap_sample_resolution(params);    
  118.     printf("can mmap = %d\n", val);    
  119.     
  120.     val = snd_pcm_hw_params_can_pause(params);    
  121.     printf("can overrange = %d\n", val);    
  122.     
  123.     val = snd_pcm_hw_params_can_sync_start(params);    
  124.     printf("can sync start = %d\n", val);    
  125.     snd_pcm_close(handle);    
  126.   
  127.     return 0;    
  128. }  

执行结果为

  1. PCM handle name = 'default'    
  2. PCM state = PREPARED    
  3. format = 'S16_LE'(Signed 16 bit Little Endian)    
  4. subformat = 'STD' (Standard)    
  5. channels=2    
  6. rate = 44100 bps    
  7. period time = 23219 us    
  8. period size = 1024 frames    
  9. buffer time = 23219 us    
  10. buffer size = 1048576 frames    
  11. periods per buffer = 1024 frames    
  12. exact rate=44100/1 bps    
  13. significant bits = 16    
  14. tick time = 0 us    
  15. is batch = 0    
  16. is block transfer = 1    
  17. is double = 0    
  18. is half duplex = 0    
  19. is joint duplex = 0    
  20. can overrange = 0    
  21. can mmap = 0    
  22. can overrange = 1    
  23. can sync start = 0  


List2打开缺省的PCM设备,设置一些参数,然后显示大部分的硬件参数。这个测试程序不会执行playback和recording。调用 snd_pcm_open打开缺省的PCM设备,打开模式为PLAYBACK。这个函数通过第一个参数返回一个句柄,后续的操作使用这个句柄操作这个 PCM设备。像大部分ALSA库函数调用,snd_pcm_open返回一个整数表示调用状态,负数指明出错原因。为了使测试代码更简洁,我忽略了大部分 的函数返回值。在产品级应用中,开发者应该检查每一个API调用,以便提供相应的出错处理。

为了设置流的硬件参数,我们需要分配一个snd_pcm_hw_param_t变量,首先调用macro snd_pcm_hw_params_alloca,接下来使用snd_pcm_hw_params_any来初始化这个变量。接下来使用ALSA提供的 API设置PCM 流的硬件参数,这些API的参数形式为(PCM handle, snd_pcm_hw_param_t *, val)。我们设置流为interleaved mode,16 bit sample size,2 channels和44100Hz采样率。对于采样率,声卡硬件不一定支持指定的采样率。我们使用函数 snd_pcm_hw_params_set_rate_near请求设置指定值附近的采样率。在调用snd_pcm_hw_params函数之前,所有 设置的硬件参数并不会被激活。

这个程序的其他部分获得并显示PCM 流的参数,包括周期和buffer sizes。显示结果依赖于测试机器的硬件。

在你的机器上运行这个程序,并尝试做一些修改。比如把device名从defalut改为hw:0,0或者plughw:查看结果是否改变。修改硬件参数的值,查看显示部分的变化。


Listing 3. SImple Sound Playback

  1. /*  
  2.  * This example reads standard from input and writes to   
  3.  * the default PCM device for 5 seconds of data.  
  4.  */  
  5. /* Use the newer ALSA API */    
  6. #define ALSA_PCM_NEW_HW_PARAMS_API    
  7.     
  8. #include <alsa/asoundlib.h>    
  9. int main()    
  10. {    
  11.     long loops;    
  12.     int rc;    
  13.     int size;    
  14.     snd_pcm_t *handle;    
  15.     snd_pcm_hw_params_t *params;    
  16.     unsigned int val;    
  17.     int dir;    
  18.     snd_pcm_uframes_t frames;    
  19.     char *buffer;    
  20.     
  21.     /* Open PCM device for playback */    
  22.     rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);    
  23.     if (rc < 0) {    
  24.         printf("unable to open pcm device: %s\n", snd_strerror(rc));    
  25.         exit(1);    
  26.     }    
  27.     
  28.     snd_pcm_hw_params_alloca(¶ms);    
  29.     
  30.     snd_pcm_hw_params_any(handle, params);    
  31.     
  32.     snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);    
  33.     
  34.     snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);    
  35.     
  36.     snd_pcm_hw_params_set_channels(handle, params, 2);    
  37.     
  38.     val = 44100;    
  39.     snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);    
  40.     
  41.     frames = 32;    
  42.     snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);    
  43.     
  44.     rc = snd_pcm_hw_params(handle, params);    
  45.     if (rc < 0) {    
  46.         printf("unable to set hw parameters: %s\n", snd_strerror(rc));    
  47.         exit(1);    
  48.     }    
  49.     
  50.     snd_pcm_hw_params_get_period_size(params, &frames, &dir);    
  51.     
  52.     size = frames * 4;    
  53.     buffer = (char *)malloc(size);    
  54.     
  55.     snd_pcm_hw_params_get_period_time(params, &val, &dir);    
  56.     loops = 5000000 / val;    
  57.     
  58.     while (loops > 0) {    
  59.         loops--;    
  60.         rc = read(0, buffer, size);    
  61.     
  62.         rc = snd_pcm_writei(handle, buffer, frames);    
  63.         if (rc == -EPIPE) {    
  64.             printf("underrun occured\n");    
  65.         }    
  66.         else if (rc < 0) {    
  67.             printf("error from writei: %s\n", snd_strerror(rc));    
  68.         }    
  69.     }    
  70.     
  71.     snd_pcm_drain(handle);    
  72.     snd_pcm_close(handle);    
  73.     free(buffer);    
  74.     
  75.     return 0;    
  76. }  



Listing 3扩展了前面的例子,写了一些随机的采样数据到声卡的playback。在这个例子中,我们从标准输入获取数据,当获取的数据达到一个period时,就把采样数据写入声卡。

这个程序的开始部分和前面的例子一样:打开PCM设备并设置硬件参数。我们使用ALSA选择的period size作为储存采样数据buffer的大小。通过这个period time我们就可以计算出5秒钟大概需要多少个periods。

在循环中我们从标准输入读取数据填充一个period的采样数据到buffer。我们检查并处理文件结束以及读取字节数和期望数不一致的情况。

使用snd_pcm_write函数发送数据到PCM设备。这个操作很像内核的write系统调用,除了size的单位是frames。检查返回的 错误码,错误码EPIPE表示underrun错误发生,这会导致PCM 流进入了XRUN状态,停止处理数据。从这种状态恢复的标准方法是调用snd_pcm_prepare,使得流进入PREPARED状态,这样我们就可以 再次向流中写入数据了,如果接收的是其他的错误码,那么我们显示错误码,并且继续执行。

这个程序循环执行5秒或者到达输入文件的结束符。我们调用snd_pcm_drain使得所有pending的采样数据被传输,然后关闭这个流。释放分配的buffer并退出。

执行这个程序,并使用/dev/urandom作为输入

  1. ./example < /dev/urandom  


Listing 4. Simple Sound Recording

  1. /*  
  2.   This example reads from the default PCM device  
  3.   and writes to standard output for 5 seconds of data.  
  4.  */    
  5.     
  6. /* Use the newer ALSA ALI */    
  7. #define ALSA_PCM_NEW_HW_PARAMS_API    
  8.     
  9. #include <alsa/asoundlib.h>    
  10.     
  11. int main()     
  12. {    
  13.     long loops;    
  14.     int rc;    
  15.     int size;    
  16.     snd_pcm_t *handle;    
  17.     snd_pcm_hw_params_t *params;    
  18.     unsigned int val;    
  19.     int dir;    
  20.     snd_pcm_uframes_t frames;    
  21.     char *buffer;    
  22.     
  23.     rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);    
  24.     <pre name="code" class="programlisting">    if (rc < 0) {    
  25.         fprintf(stderr,    
  26.             "unable to open pcm device: %s\n",    
  27.             snd_strerror(rc));    
  28.         exit(1);    
  29.     }    
  30.     
  31.     /* Allocate a hardware parameters object. */    
  32.     snd_pcm_hw_params_alloca(¶ms);    
  33.     
  34.     /* Fill it in with default values. */    
  35.     snd_pcm_hw_params_any(handle, params);    
  36.     
  37.     /* Set the desired hardware parameters. */    
  38.     
  39.     /* Interleaved mode */    
  40.     snd_pcm_hw_params_set_access(handle, params,    
  41.                       SND_PCM_ACCESS_RW_INTERLEAVED);    
  42.     
  43.     /* Signed 16-bit little-endian format */    
  44.     snd_pcm_hw_params_set_format(handle, params,    
  45.                               SND_PCM_FORMAT_S16_LE);    
  46.     
  47.     /* Two channels (stereo) */    
  48.     snd_pcm_hw_params_set_channels(handle, params, 2);    
  49.     
  50.     /* 44100 bits/second sampling rate (CD quality) */    
  51.     val = 44100;    
  52.     snd_pcm_hw_params_set_rate_near(handle, params,    
  53.                                   &val, &dir);    
  54.     
  55.     /* Set period size to 32 frames. */    
  56.     frames = 32;    
  57.     snd_pcm_hw_params_set_period_size_near(handle,    
  58.                               params, &frames, &dir);    
  59.     
  60.     /* Write the parameters to the driver */    
  61.     rc = snd_pcm_hw_params(handle, params);    
  62.     if (rc < 0) {    
  63.         fprintf(stderr,    
  64.             "unable to set hw parameters: %s\n",    
  65.             snd_strerror(rc));    
  66.         exit(1);    
  67.     }    
  68.     
  69.     /* Use a buffer large enough to hold one period */    
  70.     snd_pcm_hw_params_get_period_size(params,    
  71.                                       &frames, &dir);    
  72.     size = frames * 4; /* 2 bytes/sample, 2 channels */    
  73.     buffer = (char *) malloc(size);    
  74.     
  75.     /* We want to loop for 5 seconds */    
  76.     snd_pcm_hw_params_get_period_time(params,    
  77.                                          &val, &dir);    
  78.     loops = 5000000 / val;    
  79.     
  80.     while (loops > 0) {    
  81.         loops--;    
  82.         rc = snd_pcm_readi(handle, buffer, frames);    
  83.         if (rc == -EPIPE) {    
  84.             /* EPIPE means overrun */    
  85.             fprintf(stderr, "overrun occurred\n");    
  86.             snd_pcm_prepare(handle);    
  87.         } else if (rc < 0) {    
  88.         fprintf(stderr,    
  89.               "error from read: %s\n",    
  90.               snd_strerror(rc));    
  91.         } else if (rc != (int)frames) {    
  92.             fprintf(stderr, "short read, read %d frames\n", rc);    
  93.         }    
  94.         rc = write(1, buffer, size);    
  95.         if (rc != size)    
  96.             fprintf(stderr,    
  97.               "short write: wrote %d bytes\n", rc);    
  98.     }    
  99.     
  100.     snd_pcm_drain(handle);    
  101.     snd_pcm_close(handle);    
  102.     free(buffer);    
  103.     
  104.     return 0;    
  105. }  

Listing 4和Listing3很相像,除了执行了PCM capture。在我们打开PCM流时,我们指定了操作模式为SND_PCM_STREAM_CAPTURE。主循环中,使用snd_pcm_readi 从本地声音硬件读取samples,然后把采样输出到标准输出。如果你有一个microphone连接到声卡,使用mixer程序设置录音源和级别。相应 的,你可以运行一个CD player程序然后设置录音源为CD。如果你把listing4的输出重定向到一个文件,那么你可以用listing3来播放listing4的录音数 据:

  1. ./listing4 > sound.raw    
  2. ./listing3 < sound.raw  


如果你的声卡支持全双工,那么你可以通过管道把这个执行命令连接起来,可以播放录音数据

  1. ./listing4 | ./listing3  

通过修改PCM参数,你可以体验采样率和格式变化的效果。


Advanced Features

在前面的例子中,PCM流工作在阻塞模式,也就是说,在数据传输结束完之前不会函数不会返回。在一个交互式为主导的应用中,这种情况将使得应用长时 间无响应。ALSA允许非阻塞模式操纵流,这种方式下read/write操作立刻返回,如果数据传输无法立刻进行,那么read/write立刻返回一 个EBUSY错误码。
有些图形界面应用使用事件callback机制。ALSA支持异步PCM stream,当一个period的采样数据完成以后,注册的callback被调用。
snd_pcm_readi和snd_pcm_writei调用类似于linux的read/write系统调用。字母i表示帧是 interleaved;相对的是non-interleaved模式。Linux下的一些设备也支持mmap系统调用,ALSA支持mmap模式打开 PCM channel,应用层不需要数据copy就可以有效访问声音数据


Conclusion

我希望这篇文章能够成为你使用ALSA的动力。当2.6 kernel被大部分发行版使用后,ALSA的使用更加广泛,它的高级features应该能帮助linux audio应用开发者。
感谢Jaroslav Kysela和Takashi Iwai reviewing本文的初稿并提供了有用的反馈

posted @ 2015-11-05 15:41  苍月代表我  阅读(838)  评论(0编辑  收藏  举报