一.声音参数基本概念:
声音是连续模拟量,计算机将它离散化之后用数字表示,就有了以下几个名词术语。
样本长度 (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