首先先了解 ALSA 的框架
http://blog.csdn.net/martingang/article/details/8142173
声卡内部有一个硬件缓存区域。
当声卡接 收到外界声音信号时,通过计算机它将信号转化为可用的比特流并存储在用来发送数据给计算机的硬件缓存中。当缓存存储了足够多的数据时,声卡中断计算机告知 它数据已经准备好了。
将数据从计算机传送到外界时。此过程中声卡中断计算机并通知它缓存有空间,计算机将会往里面存储数据。声卡 接着将那些数据转化成所要求的任何格式并将其传送到外界,并且传输。
声卡循环使用这块缓存。当到了缓存的末端时,会 将开始的数据擦除用来存数据。
为了保证这个过程的正确执行,有一些变量需要设置。它们包括:
。当在计算机使用的比特流和外界使用的模拟信号之间
转化时声卡要用什么样的
格式?
。声卡的
采样率是多少?
。当
有多少数据时声卡才中断计算机?
。
硬件缓存要设置为多大?
头两个问题是控制音频数据质量的基础。后两个问题影响着音频信号的延迟。这个术语涉及到以下两个方面的延迟
1. 数据从外界到达声卡与转化成计算机中可用的数据之间的延迟(称为:内部延迟)
2. 数据被计算机传输,并传输到外界的延迟(称为:外部延迟)
虽然一些程序不需要关心这两方面的东西,但对很多音频相关软件它们都十分重要。
-
一个典型的音频应用程序
-
下面是一个典型的音频应用程序的大致结构:
-
open_the_device();
-
set_the_parameters_of_the_device();
-
while (!done) {
-
/* one or both of these */
-
receive_audio_data_from_the_device();
-
deliver_audio_data_to_the_device();
-
}
-
close the device
-
最简单的回放程序, 设置成双声道,16bit(采样位数),44.1KHZ,交叉存储,常规的读写(不用mmap)。然后传送一些随即的数据给声卡,退出。它也许反映了最简单的ALSA Audio API的用法,但它并不是一个真正可运行的程序。
-
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <alsa/asoundlib.h>
-
-
main (int argc, char *argv[])
-
{
-
int i;
-
int err;
-
short buf[128];
-
snd_pcm_t *playback_handle;
-
snd_pcm_hw_params_t *hw_params;
-
-
if ((err = snd_pcm_open (&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0))<0) {
-
fprintf (stderr, "open audio dev failed %s (%s)\n", argv[1],snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
-
fprintf (stderr, "cannot alloc hw param structure (%s)\n",snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {
-
fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
-
fprintf (stderr, "cannot set access type (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
-
fprintf (stderr, "cannot set sample format (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, 44100, 0)) < 0) {
-
fprintf (stderr, "cannot set sample rate (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, 2)) < 0) {
-
fprintf (stderr, "cannot set channel count (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0) {
-
fprintf (stderr, "cannot set parameters (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
snd_pcm_hw_params_free (hw_params);
-
-
if ((err = snd_pcm_prepare (playback_handle)) < 0) {
-
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
for (i = 0; i < 10; ++i) {
-
if ((err = snd_pcm_writei (playback_handle, buf, 128)) != 128) {
-
fprintf (stderr, "write to audio interface failed (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
}
-
-
snd_pcm_close (playback_handle);
-
exit (0);
-
}
-
最简单的捕获数据程序
-
这段程序打开声卡捕获数据,设置为双声道,16bit,44.1KHZ,交叉存储,常规的读写。然后读一些数据,退出。它并非实际运行程序。
-
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <alsa/asoundlib.h>
-
-
main (int argc, char *argv[])
-
{
-
int i;
-
int err;
-
short buf[128];
-
snd_pcm_t *capture_handle;
-
snd_pcm_hw_params_t *hw_params;
-
-
if ((err = snd_pcm_open (&capture_handle, argv[1], SND_PCM_STREAM_CAPTURE, 0)) < 0) {
-
fprintf (stderr, "cannot open audio device %s (%s)\n",
-
argv[1],
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
-
fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_any (capture_handle, hw_params)) < 0) {
-
fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_access (capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
-
fprintf (stderr, "cannot set access type (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_format (capture_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
-
fprintf (stderr, "cannot set sample format (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_rate_near (capture_handle, hw_params, 44100, 0)) < 0) {
-
fprintf (stderr, "cannot set sample rate (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_channels (capture_handle, hw_params, 2)) < 0) {
-
fprintf (stderr, "cannot set channel count (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params (capture_handle, hw_params)) < 0) {
-
fprintf (stderr, "cannot set parameters (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
snd_pcm_hw_params_free (hw_params);
-
-
if ((err = snd_pcm_prepare (capture_handle)) < 0) {
-
fprintf (stderr, "cannot prepare audio 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 from audio interface failed (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
}
-
-
snd_pcm_close (capture_handle);
-
exit (0);
-
}
-
最简单的驱动中断程序
-
这段程序打开声卡回放,配置成双声道,16bit,44.1KHZ,交叉存储,常规读写。它将等待声卡直到声卡准备好回放数据,同时传输一些数据给它。这种设计可以使你的程序很容易的通过一种回调驱动机制来和系统绑定
-
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <errno.h>
-
#include <poll.h>
-
#include <alsa/asoundlib.h>
-
-
snd_pcm_t *playback_handle;
-
short buf[4096];
-
-
int
-
playback_callback (snd_pcm_sframes_t nframes)
-
{
-
int err;
-
-
printf ("playback callback called with %u frames\n", nframes);
-
-
/* ... fill buf with data ... */
-
-
if ((err = snd_pcm_writei (playback_handle, buf, nframes)) < 0) {
-
fprintf (stderr, "write failed (%s)\n", snd_strerror (err));
-
}
-
-
return err;
-
}
-
-
main (int argc, char *argv[])
-
{
-
-
snd_pcm_hw_params_t *hw_params;
-
snd_pcm_sw_params_t *sw_params;
-
snd_pcm_sframes_t frames_to_deliver;
-
int nfds;
-
int err;
-
struct pollfd *pfds;
-
-
if ((err = snd_pcm_open (&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
-
fprintf (stderr, "cannot open audio device %s (%s)\n",
-
argv[1],
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
-
fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {
-
fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
-
fprintf (stderr, "cannot set access type (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
-
fprintf (stderr, "cannot set sample format (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, 44100, 0)) < 0) {
-
fprintf (stderr, "cannot set sample rate (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, 2)) < 0) {
-
fprintf (stderr, "cannot set channel count (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0) {
-
fprintf (stderr, "cannot set parameters (%s)\n",
-
snd_strerror (err));
-
exit (1);
-
}
-
-
snd_pcm_hw_params_free (hw_params);
-
-
/* tell ALSA to wake us up whenever 4096 or more frames
-
of playback data can be delivered. Also, tell
-
ALSA that we'll start the device ourselves.
*/
if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
fprintf (stderr, "cannot allocate software parameters structure (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params_current (playback_handle, sw_params)) < 0) {
fprintf (stderr, "cannot initialize software parameters structure (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096)) < 0) {
fprintf (stderr, "cannot set minimum available count (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U)) < 0) {
fprintf (stderr, "cannot set start mode (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params (playback_handle, sw_params)) < 0) {
fprintf (stderr, "cannot set software parameters (%s)\n",
snd_strerror (err));
exit (1);
}
/* the interface will interrupt the kernel every 4096 frames, and ALSA
will wake up this program very soon after that.
*/
if ((err = snd_pcm_prepare (playback_handle)) < 0) {
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
exit (1);
}
while (1) {
/* wait till the interface is ready for data, or 1 second
has elapsed.
*/
if ((err = snd_pcm_wait (playback_handle, 1000)) < 0) {
fprintf (stderr, "poll failed (%s)\n", strerror (errno));
break;
}
/* find out how much space is available for playback data */
if ((frames_to_deliver = snd_pcm_avail_update (playback_handle)) < 0) {
if (frames_to_deliver == -EPIPE) {
fprintf (stderr, "an xrun occured\n");
break;
} else {
fprintf (stderr, "unknown ALSA avail update return value (%d)\n",
frames_to_deliver);
break;
}
}
frames_to_deliver = frames_to_deliver > 4096 ? 4096 : frames_to_deliver;
/* deliver the data */
if (playback_callback (frames_to_deliver) != frames_to_deliver) {
fprintf (stderr, "playback callback failed\n");
break;
}
}
snd_pcm_close (playback_handle);
exit (0);
}
名词术语:
Capture
从外界获取音频数据(和“录音”不同,录音意味着将数据存储起来,它并不是ALSA库的API)。
Playback
将音频播放出来,使能被听见,这也许并不是必须的。
Duplex
捕获和回放在同一声卡同一时刻同时发生的情况
Xrun
一旦声卡开始运行,便一直持续直到被要求停止。它将收集数据给计算机使用并且(或者)发送计算机中的数据给外界。但有很多种情况你的程序并非可以控制声卡正常的进行。
在回放的时候,当声卡需要从计算机中收到数据时,而此时并没有,xrun便可以强迫它使用在硬件缓存中留有的旧数据。这种情况叫做“下溢”。
在捕获的时候,声卡也许要传送数据给计算机,但此时计算机没有空间来存储,因此,声卡会重写部分硬件缓存中没有及时送出的数据。这种情况叫做“上溢”。简单的说,我们用词“xrun”来概括其中的一个或几个。
Pcm
Pulse Code Modulation(脉冲编码调制)。这个词(还有缩写)描述了一种用数字化形式表示模拟信号的方法。这种方法几乎被所有的计算机音卡所使用,它在ALSA API中用“audio”来简称。
Channel
Frame
一个采样是描述某一时刻的单通道下一个信号点的声音信号的振幅的信号值。当我们谈及数字音频时,我们经常谈到的是代表所有通道的一个信号点的数据。它是单个通道采样的一个集合,叫做"帧"。当我们依据帧来表示一段时间时,它粗略的等于人们用一组采样来衡量,但前者更精确(???);更重要的是,当我们在讨论代表一个时刻所有通道的数据量时,帧是唯一能引起人们感官感受的标准。几乎所有的ALSA Audio API使用帧作为衡量音频质量的标准。
Interleaved
一种将
同一时刻所有通道音频数据依次存取的交叉存储形式。参看:不交叉存储。
Non-interleaved
一种将
每个通道的数据按顺序存储的存储形式,不同的通道的数据存储在另一个缓存中或者同一个缓存的其他地方。和交叉存储对比。
Sample clock
时 钟源,用来决定采样的传送,接收时刻。一些音卡允许你使用外部时钟,比如“全球时钟信号”(经常在一些工作室中使用),还有使用来自数字数据的时钟信号的 “自动同步信号”。所有的声卡内部都必须至少有一个采样时钟源,典型的是一种小的晶体时钟。一些声卡不允许改变时钟频率,一些则有不是很精准的时钟(比如,44.1khz)(???)。没有两个时钟能够以同一个时钟频率运行,如果你需要两个采样时钟进行同步,他们必须要和同一个采样时钟进行同步。
硬件相关参数
采样率。 (在声卡使用了外部时钟源的情况下, 无法改变)
采样格式。 从声卡传输采样格式, S16_LE
通道数: 单声道,多声道,根据声卡自定义
数据接入:直接读写(专用函数读写调用)、映射模式(开始和结束时注册)。
数据布局: 存储为是否交叉存储,
Interleaved、 Non-interleaved
中断间隔: 读或写完整个硬件缓存需要发生多少次中断。它能根据一些指定的时间段来设置,也可以根据一段时间的长短来设置。设置决定了在声卡向计算机发出中断之前积累的空间中或数据中的帧数。它控制着延迟
缓冲大小: 硬件缓存的大小。它可以用时间长短或帧数来指定。
样本长度(sample):样本是记录音频数据最基本的单位,常见的有8位和16位。
通道数(channel):该参数为1表示单声道,2则是立体声。
桢(frame):桢记录了一个声音单元,其长度为样本长度与通道数的乘积。
采样率(rate):每秒钟采样次数,该次数是针对桢而言。
周期(period):音频设备一次处理所需要的桢数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。
交错模式(interleaved):是一种音频数据的记录方式,在交错模式下,数据以连续桢的形式存放,即首先记录完桢1的左声道样本和右声道样本(假设为立体声格式),再开始桢2的记录。而在非交错模式下,首先记录的是一个周期内所有桢的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。不过多数情况下,我们只需要使用交错模式就可以了。
-
int bit_per_sample; //样本长度(bit)
int period_size; //周期长度(桢数)
int chunk_byte; //周期长度(字节数)
-
snd_pcm_hw_params_set_access ( pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); //设置为交错模式
-
snd_pcm_hw_params_set_format( pcm_handle, w_params, SND_FORMAT_S16_LE); //使用用16位样本
-
snd_pcm_hw_params_set_rate_near( pcm_handle, hw_params, 44100, 0); //设置采样率为44.1KHz
-
snd_pcm_hw_params_set_channels( pcm_handle, hw_params, 2); //设置为立体声
-
-
// 以下参数保存起来备用。
-
snd_pcm_hw_params_get_period_size( hw_params, &period_size); //获取周期长度
-
bit_per_sample = snd_pcm_hw_format_physical_width( hw_params.format ); //获取样本长度
-
chunk_byte = period_size * bit_per_sample * hw_params.channels / 8;
-
//计算周期长度(字节数(bytes) = 每周期的桢数 * 样本长度(bit) * 通道数 / 8 )
-
-
snd_pcm_hw_params( pcm_handle, hw_params); //设置参数
-
设置好参数后便可以开始录音了。录音过程实际上就是从音频设备中读取数据信息并保存。
-
-
char *wave_buf;
-
int wave_buf_len;
-
bool device_capture( int dtime /*录音长度(单位:秒)*/){
-
wave_buf_len = dtime * params.rate * bit_per_sample * params.channels / 8 ;
-
//计算音频数据长度(秒数 * 采样率 * 桢长度)
-
char *data = wave_buf = (char*)malloc( wave_buf_len ); //分配空间
-
-
int r = 0;
-
while ( data & wave_buf <= wave_buf_len & chunk_size ){
-
r = snd_pcm_readi( pcm_handle, data , chunk_size); // 读写为 chunk_size 单位
-
if ( r>0 ) data += r * chunk_byte;
-
else return false
-
}
-
return true;
-
}
软件相关参数:
用snd_pcm_start来显式打开设备,但如果是回放的话要求缓存中预先填充数据。如果你没有预填数据而想开始回放,函数则会返回-EPIPE,表明现在没有数据在向硬件缓存中
Xrun发生时:capture时上溢, playback时下溢, 可以设定回放或者静音或者停止声卡。
传输块大小: 这个决定了传入/出数据给硬件缓存时所用的帧数
/*########################################################*/
阅读(1268) | 评论(0) | 转发(0) |