作者: Matthias Nagorni
译者: Kevin Lei
转载请注明出处:http://kevinlei.cublog.cn
1. 介绍
这个HOWTO计划提供一个简短的介绍,使用ALSA 0.9.0写一个简单的音频应用程序.
Section2解释了PCM音频最基本的函数.如果你删除其中的解释文本,那么最后你会得到一个极小的PCM回放程序. Section3简短地讨论了一些PCM截获函数.
在Section4你将学习怎么为ALSA音序器写一个简单的客户端.这一章节基于seqdemo.c这个例子,一个可以接收MIDI事件并且表示大多数重要的事件类型的程序.Section5演示ALSAMIDI音序器怎样被用于"路由"MIDI事件,从一个输入端口到一些输出端口.这一段基于midiroute.c这个例子.
Section6使PCM回放和MIDI输入联结起来,并且解释了迷你合成器--miniFMsynth.c.这一章节引入基于回调的音频回放,就像PaulDavis在linux-audio-dev邮件列表里建议的那样.Section7对基于ALSA音序器队列的MIDI调度提供一个短小的介绍,基于miniArp.c这个例子.
推荐你也看看doxygen生成的ALSA库函数参考.
编译一个ALSA应用程序:
只要用-lasound参数并且确保你已包含了
#include <alsa/asoundlib.h>
2. 基本PCM音频
为ALSA 0.9.0写一个简单的PCM应用程序我们首先需要一个PCM设备的句柄.然后我们必须指定同时可供回放或截获的PCM流的方向.我们同样必须提供一些关于我们想要使用的设置选项的信息,比如缓冲区大小,采样率,PCM数据格式等.所以,首先我们声明:
/* Handle for the PCM device */
snd_pcm_t *pcm_handle;
/* Playback stream */
snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
/* This structure contains information about */
/* the hardware and can be used to specify the */
/* configuration to be used for the PCM stream. */
snd_pcm_hw_params_t *hwparams;
最重要的ALSA PCM设备接口是"plughw"和"hw"接口.如果你使用"plughw"接口,你不需要很在意声卡硬件.如果你的声卡不支持你指定的采样率或采样格式,你的数据将自动被转换.这同样适用于声道的访问类型和号码.对于"hw"接口,你必须检查你的硬件是否支持你想要使用的设置选项.
/* Name of the PCM device, like plughw:0,0 */
/* The first number is the number of the soundcard, */
/* the second number is the number of the device. */
char *pcm_name;
然后我们初始化变量并分配一个hwparams结构:
/* Init pcm_name. Of course, later you */
/* will make this configurable ;-) */
pcm_name = strdup("plughw:0,0");
/* Allocate the snd_pcm_hw_params_t structure on the stack. */
snd_pcm_hw_params_alloca(&hwparams);
现在我们可以打开PCM设备:
/* Open PCM. The last parameter of this function is the mode. */
/* If this is set to 0, the standard mode is used. Possible */
/* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC. */
/* If SND_PCM_NONBLOCK is used, read / write access to the */
/* PCM device will return immediately. If SND_PCM_ASYNC is */
/* specified, SIGIO will be emitted whenever a period has */
/* been completely processed by the soundcard. */
if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
fprintf(stderr, "Error opening PCM device %s\n", pcm_name);
return(-1);
}
在我们可以往声卡写PCM数据之前,我们必须指定访问类型,采样格式,采样率,声道号码,周期数目以及周期大小.首先,我们以声卡的全部设置选项空间来初始化hwparams结构.
/* Init hwparams with full configuration space */
if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
fprintf(stderr, "Can not configure this PCM device.\n");
return(-1);
}
Information about possible configurations can be obtained with a set of functions named
关于合适的设置选项的信息,能以函数命名的一个集合获得.
snd_pcm_hw_params_can_<capability>
snd_pcm_hw_params_is_<property>
snd_pcm_hw_params_get_<parameter>
绝大多数重要的参数的可用性,换句话说,访问类型,缓冲区大小,声道号码,采样格式,采样率,以及周期数目,可以以函数命名的一个集合来检验.
snd_pcm_hw_params_test_<parameter>
如果"hw"接口被使用,这些查询函数尤其重要.设置选项空间能以一个函数命名的集合限制在某一设置选项
snd_pcm_hw_params_set_<parameter>
例如,我们假设声卡可以被设置为16位,Little Endian数据的立体声回放,44100Hz采样率.相应地,我们限制设置选项空间匹配于这个设置选项:
int rate = 44100; /* Sample rate */
int exact_rate; /* Sample rate returned by */
/* snd_pcm_hw_params_set_rate_near */
int dir; /* exact_rate == rate --> dir = 0 */
/* exact_rate < rate --> dir = -1 */
/* exact_rate > rate --> dir = 1 */
int periods = 2; /* Number of periods */
snd_pcm_uframes_t periodsize = 8192; /* Periodsize (bytes) */
访问类型指定了哪一个多声道数据储存在缓冲区的方法.对于交错访问,缓冲区里的每一个帧为声道容纳连续的采样数据.对于16位立体声数据,这意味着缓冲区以字为单位为左右声道交错地容纳采样数据.对于非交错访问,每一个周期为第一个声道容纳所有采样数据接着是第二个声道的采样数据.
/* Set access type. This can be either */
/* SND_PCM_ACCESS_RW_INTERLEAVED or */
/* SND_PCM_ACCESS_RW_NONINTERLEAVED. */
/* There are also access types for MMAPed */
/* access, but this is beyond the scope */
/* of this introduction. */
if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
fprintf(stderr, "Error setting access.\n");
return(-1);
}
/* Set sample format */
if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
fprintf(stderr, "Error setting format.\n");
return(-1);
}
/* Set sample rate. If the exact rate is not supported */
/* by the hardware, use nearest possible rate. */
exact_rate = rate;
if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0) {
fprintf(stderr, "Error setting rate.\n");
return(-1);
}
if (rate != exact_rate) {
fprintf(stderr, "The rate %d Hz is not supported by your hardware.\n
==> Using %d Hz instead.\n", rate, exact_rate);
}
/* Set number of channels */
if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0) {
fprintf(stderr, "Error setting channels.\n");
return(-1);
}
/* Set number of periods. Periods used to be called fragments. */
if (snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0) < 0) {
fprintf(stderr, "Error setting periods.\n");
return(-1);
}
缓冲区尺寸的单元依赖于函数.一些时候是字节,一些时候是必须指定的帧的数目.一个帧是对所有声道的采样数据数组.对于16位立体声数据,一个帧的长度是4个字节.
/* Set buffer size (in frames). The resulting latency is given by */
/* latency = periodsize * periods / (rate * bytes_per_frame) */
if (snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, (periodsize * periods)>>2) < 0) {
fprintf(stderr, "Error setting buffersize.\n");
return(-1);
}
如果你的硬件不支持2的N次方的缓冲区大小,你可以使用snd_pcm_hw_params_set_buffer_size_near函数.这个函数工作起来与snd_pcm_hw_params_set_rate_near相似.现在我们为PCM设备申请由pcm_handle指向的设置选项.这同样也将准备好PCM设备.
/* Apply HW parameter settings to */
/* PCM device and prepare device */
if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) {
fprintf(stderr, "Error setting HW params.\n");
return(-1);
}
在PCM设备被设置以后,我们可以开始对其写PCM数据.第一个写访问将开始PCM回放.对于交错写访问,我们使用函数:
/* Write num_frames frames from buffer data to */
/* the PCM device pointed to by pcm_handle. */
/* Returns the number of frames actually written. */
snd_pcm_sframes_t snd_pcm_writei(pcm_handle, data, num_frames);
对于非交错访问,我们将必须使用函数:
/* Write num_frames frames from buffer data to */
/* the PCM device pointed to by pcm_handle. */
/* Returns the number of frames actually written. */
snd_pcm_sframes_t snd_pcm_writen(pcm_handle, data, num_frames);
在PCM回放开始之后,我们必须确保我们的应用程序发送足够的数据到声卡缓冲区.否则,将发生缓冲区欠载.当这样一个缓冲区欠载发生以后,snd_pcm_prepare将被调用.一个简单的立体声锯齿波能以这样的方式生成:
unsigned char *data;
int pcmreturn, l1, l2;
short s1, s2;
int frames;
data = (unsigned char *)malloc(periodsize);
frames = periodsize >> 2;
for(l1 = 0; l1 < 100; l1++) {
for(l2 = 0; l2 < num_frames; l2++) {
s1 = (l2 % 128) * 100 - 5000;
s2 = (l2 % 256) * 100 - 5000;
data[4*l2] = (unsigned char)s1;
data[4*l2+1] = s1 >> 8;
data[4*l2+2] = (unsigned char)s2;
data[4*l2+3] = s2 >> 8;
}
while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
snd_pcm_prepare(pcm_handle);
fprintf(stderr,"<<<<<<<<<<<<<<< BufferUnderrun>>>>>>>>>>>>>>>\n");
}
}
如果我们想中止回放,我们既可以使用snd_pcm_drop,也可以使用snd_pcm_drain.第一个函数将立即中止回放并丢弃未回放的帧.后一个函数将在回放完所有帧后中止回放.
/* Stop PCM device and drop pending frames */
snd_pcm_drop(pcm_handle);
/* Stop PCM device after pending frames have been played */
snd_pcm_drain(pcm_handle);
(全文完)
参考链接:
原文
http://www.suse.de/~mana/alsa090_howto.html
Linux音频编程指南
http://www-128.ibm.com/developerworks/cn/linux/l-audio/
A Tutorial on Using the ALSA Audio API
http://www.equalarea.com/paul/alsa-audio.html
The ALSA library API reference
http://www.alsa-project.org/alsa-doc/alsa-lib/