调研linux库alsa

一.声音参数基本概念:

声音是连续模拟量,计算机将它离散化之后用数字表示,就有了以下几个名词术语。

样本长度 (sample) 样本是记录音频数据最基本的单位,计算机对每个通道采样量化时数字比特位数,常见的有8位和16位。

通道数 (channel) 该参数为1表示单声道,2则是立体声。

(frame) 帧记录了一个声音单元,其长度为样本长度与通道数的乘积,一段音频数据就是由苦干帧组成的。

采样率 (rate) 每秒钟采样次数,该次数是针对帧而言,常用的采样率如8KHz的人声,44.1KHz的mp3音乐, 96Khz的蓝光音频。

周期 (period) 音频设备一次处理所需要的桢数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。

交错模式 (interleaved) 是一种音频数据的记录方式

在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。

而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。

不过多数情况下,我们只需要使用交错模式就可以了。

period( 周期 ): 硬件中中断间的间隔时间。它表示输入延时。

比特率( Bits

Per Second ): 比特率表示每秒的比特数,比特率=采样率×通道数×样本长度

二. ALSA 介绍

1.ALSA 简介

ALSA表示高级Linux声音体系结构(Advanced Linux Sound Architecture)。ALSA是一个完全 开放源代码 的音频驱动程序集,除了像OSS那样提供了一组内核驱动程序模块之外,ALSA还专门为简化应用程序的编写提供了相应的函数库,与OSS提供的基于 ioctl 的原始 编程接口 相比,ALSA函数库使用起来要更加方便一些。利用该函数库,开发人员可以方便快捷的开发出自己的应用程序,细节则留给函数库内部处理。当然ALSA也提供了类似于OSS的系统接口,不过ALSA的开发者建议应用程序开发者使用音频函数库而不是驱动程序的API。

2.ALSA 版本支持

Linux内核2.5在开发过程中,ALSA被合并到了官方的源码树中。在发布内核2.6后,ALSA已经内建在稳定的内核版本中并将广泛地使用。在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,即可以完成对底层音频硬件的控制。

3.ALSA 基础

ALSA由许多声卡的声卡驱动程序组成,同时它也提供一个称为libasound的API库。应用程序开发者应该使用libasound而不是内核中的ALSA接口。因为libasound提供最高级并且编程方便的编程接口。并且提供一个设备逻辑命名功能,这样开发者甚至不需要知道类似设备文件这样的低层接口。

用户空间的alsa-lib对应用程序提供统一的API接口,这样可以隐藏了驱动层的实现细节,简化了应用程序的实现难度。内核空间中,alsa-soc其实是对alsa-driver的进一步封装,他针对 嵌入式 设备提供了一些列增强的功能。

4.ALSA 体系结构:

ALSA API可以分解成以下几个主要的接口:

1控制接口:提供管理声卡注册和请求可用设备的通用功能

2 PCM接口:管理数字音频回放(playback)和录音(capture)的接口。它是开发数字音频程序最常用到的接口。

3 Raw MIDI接口:支持MIDI(Musical Instrument Digital Interface),标准的电子乐器。这些API提供对声卡上MIDI总线的访问。这个原始接口基于MIDI事件工作,由程序员负责管理协议以及时间处理。

4定时器(Timer)接口:为同步音频事件提供对声卡上时间处理硬件的访问。

5时序器(Sequencer)接口

6混音器(Mixer)接口设备命名API库使用逻辑设备名而不是设备文件。

5. 伪代码

一个典型的声音程序使用PCM的程序通常类似下面的伪代码:

1.打开回放或录音接口

2.设置硬件参数(访问模式,数据格式,信道数,采样率,等等)

2.while有数据要被处理:读PCM数据(录音)或写PCM数据(回放)

3.关闭接口

三. ALSA 编译安装

1.ALSA 相关库下载

官方主页 http://www.alsa-project.org/

主要跟编程相关是

·alsa-lib. ALSA应用库(最常用)

· ftp://ftp.alsa-project.org/pub/lib/alsa-lib-1.0.22.tar.bz2

·alsa-driver一些常见芯片的ALSA驱动代码,一般内核会集成.

· ftp://ftp.alsa-project.org/pub/driver/alsa-driver-1.0.22.1.tar.bz2

·alsa-firmware一些DSP或ASIC的专用的微码(运在芯片之上,启动时由LINUX装入到硬件中).

· ftp://ftp.alsa-project.org/pub/firmware/alsa-firmware-1.0.20.tar.bz2

·alsa-utils一般ALSA小的测试工具.如aplay/arecord播放和录音小程序.

· ftp://ftp.alsa-project.org/pub/utils/alsa-utils-1.0.22.tar.bz2

·alsa-oss用alsa接口模拟旧的oss接口.

· ftp://ftp.alsa-project.org/pub/oss-lib/alsa-oss-1.0.17.tar.bz2

其中alsa-driver,alsa-firwware是内核开发者所接触的东西,对于已经正常运行硬件,通常意味着这一部分已经整合到内核当中,无需修改.

而alsa-utils主要是测试一些小工具.

因此对于一个应用程序开发者,或者嵌入式应用开发者,接触到主要是alsa-lib编译出来的库 libasound .

2.ALSA 驱动测试

cat

/proc/asound/devices 驱动测试

ls -l/dev/snd 设备测试

aplay –h工具alsa-utils测试

3. 嵌入式 linuxALSA 移植

·ALSA driver移植

·ALSA lib移植.

解压tar xvjf alsa-lib-1.0.22.tar.bz2

cd alsa-lib-1.0.22

生成Makefile

./configure --host=arm-linux  --prefix=$PWD/../../output/arm-linux --enable-static --enable-shared   --disable-python  --with-configdir=/usr/local/share  --with-plugindir=/usr/local/lib/alsa_lib

在这里要注意--with--configdir的选项.它将影响include/config.h中的ALSA_CONFIG_DIR目录.

它默认是你的--prefix目录.这样在嵌入式交叉编译将是一个桌面机的路径,在libasoud.so运行.会提示,如果出来这个提示,一般都是ALSA_CONFIG_DIR路径错误造成的.

ALSA  lib pcm.c:2145:(snd_pcm_open_noupdate) Unknown PCM default

aplay:  main:546: audio open error: No such file or directory

--with-plugindir也是同样道理了.它是设为ALSA_PLUGIN_DIR宏.

编译make

安装make install

开发板发布注意:

在开发板上发布alsa库.除了libasound.so库以外,必须还要把alsa.conf发布到板上--with-configdir所指向目录下的alsa目录,否则还是会报"audio open error: No such file ordirectory".

这个文件可以在make install后在你安装目录下的share找到alsa目录,把这个目录整个拷贝到开发板即可.

·ALSA utils移植

解压:tar xvjf alsa-utils-1.0.22.tar.bz2

cdalsa-utils-1.0.22

生成Makefile

./configure --host=arm-linux  --prefix=$PWD/../../output/arm-linux --enable-static  --enable-shared    --with-configdir=/usr/local/share  --with-libiconv-prefix=$PWD/../../output/arm-linux  CFLAGS="-I$PWD/../../output/arm-linux/include"  LDFLAGS="-L$PWD/../../output/arm-linux/lib -lasound -liconv"   --disable-alsamixer --disable-xmlto

注意这里LDFLAGS是必须,否则会找不到libasound.另外alsamixer是一个ncurses程序,基本上在嵌入式终端上很难移植.所以这里取消掉.--disable-xmlto也是因为找不到库.

编译make

安装make install

四. ALSA 录音 demo

#include

#include

#include

main (int argc, char *argv[])

{

int i;

int err;

short buf[128];

snd_pcm_t *capture_handle; // PCM设备句柄

snd_pcm_hw_params_t *hw_params;//硬件信息和PCM流配置

//1.打开PCM,最后一个参数0为标准配置

if ((err = snd_pcm_open (&capture_handle,argv[1], SND_PCM_STREAM_CAPTURE, 0)) < 0) {

fprintf (stderr, "cannot open audiodevice %s (%s)\n",

argv[1],

snd_strerror (err));

exit (1);

}

//2.分配snd_pcm_hw_params_t结构体

if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {

fprintf (stderr, "cannot allocatehardware parameter structure (%s)\n",

snd_strerror (err));

exit (1);

}

//3.初始化hw_paraws

if ((err = snd_pcm_hw_params_any (capture_handle,hw_params)) < 0) {

fprintf (stderr, "cannot initializehardware parameter structure (%s)\n",

snd_strerror (err));

exit (1);

}

//4.初始化访问权限

if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {

fprintf (stderr, "cannot set accesstype (%s)\n",

snd_strerror (err));

exit (1);

}

//5.初始化采样格式SND_PCM_FORMAT_U8,16位

if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {

fprintf (stderr, "cannot set sampleformat (%s)\n",

snd_strerror (err));

exit (1);

}

1.//6.设置采样率,如果硬件不支持我们设置的采样率,将使用最接近的

2.//val = 44100,有些录音采样频率固定为8KHz

if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, 44100, 0)) < 0) {

fprintf (stderr, "cannot set samplerate (%s)\n",

snd_strerror (err));

exit (1);

}

//7.设置通道数量

if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, 2)) < 0) {

fprintf (stderr, "cannot set channelcount (%s)\n",

snd_strerror (err));

exit (1);

}

//8.设置hw_params

if ((err = snd_pcm_hw_params (capture_handle,hw_params)) < 0) {

fprintf (stderr, "cannot setparameters (%s)\n",

snd_strerror (err));

exit (1);

}

//释放硬件参数设置

snd_pcm_hw_params_free (hw_params);

//如果发生xrun故障,从该故障中恢复过来

if ((err = snd_pcm_prepare (capture_handle)) <0) {

fprintf (stderr, "cannot prepareaudio interface for use (%s)\n",

snd_strerror (err));

exit (1);

}

//从声卡中读取数据,写入到标准输出设备

for (i = 0; i < 10; ++i) {

if ((err = snd_pcm_readi (capture_handle,buf, 128)) != 128) {

fprintf (stderr, "read fromaudio interface failed (%s)\n",

snd_strerror (err));

exit (1);

}

}

//10.关闭PCM设备句柄

snd_pcm_close (capture_handle);

exit (0);

}

/*

This example reads from the default PCMdevice

and writes to standard output for 5 secondsof data.

*/

/* Use thenewer ALSA ALI */

#defineALSA_PCM_NEW_HW_PARAMS_API

#include

#include

#include

intmain()

{

long loops;

int rc;

int size;

snd_pcm_t *handle;

snd_pcm_hw_params_t *params;

unsigned int val;

int dir;

snd_pcm_uframes_t frames;

char *buffer;

rc = snd_pcm_open(&handle,"default", SND_PCM_STREAM_CAPTURE, 0);

if (rc < 0) {

fprintf(stderr,

"unable to open pcm device:%s\n",

snd_strerror(rc));

exit(1);

}

/* Allocate a hardware parameters object.*/

snd_pcm_hw_params_alloca(¶ms);

/* Fill it in with default values. */

snd_pcm_hw_params_any(handle, params);

/* Set the desired hardware parameters.*/

/* Interleaved mode */

snd_pcm_hw_params_set_access(handle,params,

SND_PCM_ACCESS_RW_INTERLEAVED);

/* Signed 16-bit little-endian format*/

snd_pcm_hw_params_set_format(handle,params,

SND_PCM_FORMAT_S16_LE);

/* Two channels (stereo) */

snd_pcm_hw_params_set_channels(handle,params, 1);

/* 44100 bits/second sampling rate (CDquality) */

val = 16000;

snd_pcm_hw_params_set_rate_near(handle,params,

&val,&dir);

/* Set period size to 32 frames. */

frames = 32;

snd_pcm_hw_params_set_period_size_near(handle,

params,&frames, &dir);

/* Write the parameters to the driver*/

rc = snd_pcm_hw_params(handle,params);

if (rc < 0) {

fprintf(stderr,

"unable to set hw parameters:%s\n",

snd_strerror(rc));

exit(1);

}

/* Use a buffer large enough to hold oneperiod */

snd_pcm_hw_params_get_period_size(params,

&frames, &dir);

size = frames*2; /* 2 bytes/sample, 2channels */

buffer = (char *) malloc(size);

/* We want to loop for 5 seconds */

snd_pcm_hw_params_get_period_time(params,

&val, &dir);

loops = 5000000 / val;

while (loops > 0) {

loops--;

rc = snd_pcm_readi(handle, buffer,frames);

if (rc == -EPIPE) {

/* EPIPE means overrun */

fprintf(stderr, "overrunoccurred\n");

snd_pcm_prepare(handle);//重新准备好接受,恢复正常接受数据

} else if (rc < 0) {

fprintf(stderr,

"error from read:%s\n",

snd_strerror(rc));

} else if (rc != (int)frames) {

fprintf(stderr, "short read,read %d frames\n", rc);

}

rc = write(1, buffer, size);

if (rc != size)

fprintf(stderr,

"short write: wrote %dbytes\n", rc);

}

snd_pcm_drain(handle);//把所有挂起没有传输完的声音样本传输完全

snd_pcm_close(handle);

free(buffer);

return 0;

}

打开PCM设备、设置硬件参数。我们使用由ALSA自己选择的周期大小,申请该大小的缓冲区来存储样本。然后我们找出周期时间,这样我们就能计算出本程序为了能够播放5秒钟,需要多少个周期。

在处理数据的循环中,我们从标准输入中读入数据,并往缓冲区中填充一个周期的样本。然后检查并处理错误,这些错误可能是由到达文件结尾,或读取的数据长度与我期望的数据长度不一致导致的。

我们调用snd_pcm_writei来发送数据。它操作起来很像内核的写系统调用,只是这里的大小参数是以帧来计算的。我们检查其返回代码值。返回值为EPIPE表明发生了underrun,使得PCM音频流进入到XRUN状态并停止处理数据。从该状态中恢复过来的标准方法是调用snd_pcm_prepare函数,把PCM流置于PREPARED状态,这样下次我们向该PCM流中数据时,它就能重新开始处理数据。如果我们得到的错误码不是EPIPE,我们把错误码打印出来,然后继续。最后,如果写入的帧数不是我们期望的,则打印出错误消息。这个程序一直循环,直到5秒钟的帧全部传输完,或者输入流读到文件结尾。然后我们调用snd_pcm_drain把所有挂起没有传输完的声音样本传输完全,最后关闭该音频流,释放之前动态分配的缓冲区,退出。

1.rc = snd_pcm_readi(handle, buffer, frames);

2.if (rc == -EPIPE) {

3./* EPIPE means overrun */

4.fprintf(stderr, "overrun occurred\n");

5.snd_pcm_prepare(handle);

6.} else if (rc< 0) {

7.fprintf(stderr,

8."error from read: %s\n",

9.snd_strerror(rc));

10.} else if (rc != (int)frames) {

11.fprintf(stderr, "short read, read %d frames\n", rc);

12.}

snd_pcm结构用于表征一个PCM类型的snd_device.

struct snd_pcm {

struct snd_card *card; /*指向所属的card设备*/

int device; /* device number */

struct snd_pcm_str streams[2]; /*播放和录制两个数据流*/

wait_queue_head_t open_wait; /*打开pcm设备时等待打开一个可获得的substream */

}

应用程序缓存区的大小可以通过ALSA库函数调用来控制。缓存区可以很大,一次传输操作可能会导致不可接受的延迟,我们把它称为延时(latency)。为了解决这个问题,ALSA将缓存区拆分成一系列周期(period)(OSS/Free中叫片断fragments).ALSA以period为单元来传送数据。

一个周期(period)存储一些帧(frames)。每一帧包含时间上一个点所抓取的样本。对于立体声设备,一个帧会包含两个信道上的样本。

Buffer_size计算逻辑:

Buffer_size = c->min = a->min * b-> min = period_size *

periods

底层获得的Periods = 4,是tinplay.c中赋值的,period_size是上面计算出来的值。

. 必须区分清 alsa 里的 buffer

一. alsa展现的三层结构:

(1) audio interface

audio interface就是声卡,它含有hardware

buffer, 注意,这个 hardware buffer 是在声卡里面,不是内存 很多 codec 芯片并不像声卡那么强大还有 buffer ,只是 ap 把数据通过 i2s codec ,其实我也不清楚 wm8994 这写 codec 芯片里面有多大 buffer ,只是以为数据给它了就立马播放了!

(2)computer:

这个指的是计算机的内核和驱动,alsa驱动专门管一块内存,这就是传说中的ring buffer吧,alsa驱动空间里有period,

frames的概念,当(1)的audio interface引发中断,内核会捕捉到,再把处理移交alsa。

(3)application:

这个就是你写的程序,你在用户空间开辟一个buffer,比如playback,就交给alsa来play。

在上面的框架下,流程如下:

(1)playback:

application 开辟一个 buffer ,填上数据,调用 alsa 接口, alsa buffer 数据复制到其驱动空间的那块内存,再把数据交给 hardware buffer

(2)record:

同playback,相似的。

Alsa编译安装: http://blog.csdn.net/liu_chunhai/article/details/6582090

http://blog.csdn.net/shui1025701856/article/details/7646197

http://www.cnblogs.com/cslunatic/p/3677729.html

http://blog.csdn.net/zd394071264/article/details/8300045

http://blog.csdn.net/ropenyuan/article/details/9344299

http://www.cnblogs.com/lifan3a/articles/5481775.html

http://blog.chinaunix.net/uid-27106528-id-3328766.html

http://blog.csdn.net/u012769691/article/details/46727543

slsa编译

http://blog.chinaunix.net/uid-23065002-id-3884658.html

https://www.oschina.net/news/72059/alsa-lib-1-1-1

http://www.360doc.com/content/11/0613/13/168576_126609790.shtml

alsa录音

http://blog.csdn.net/lijin6249/article/details/51955206

推荐阅读 更多精彩内容