ALSA Programming HOWTO
根据ALSA写一简单的PCM应用程序,我们首先需要为PCM设备打开一个句柄(Handle),然后指定PCM流的方向是播放或者是捕获(playback还是capture),我们也可以配置一些我们想要的参数,比如,buffer size, sample rate, pcm数据格式等.因此我们就有了一个大体的框架了,简单高效,如下:
然后我们分配一个硬件配置结构体
初始化配置硬件的结构体,alsa已经给出了相应的函数,我们搬用就可以了:
如果你的硬件buffer不支持2^n,你可以使用snd_pcm_hw_params_set_buffer_size_near.这个函数,它会分配相邻的buffer空间.
当所有参数都配置完成后我们要让这些参数作用于PCM设备:
要想同时进行采集和播放音频数据仅仅使用一个PCM设备句柄是不可能的,所以我们要声明两个句柄,这样才能实现同步.下面仅介绍不同点:
[1] : PCM数据流的方向不一样了
[3] : 捕获函数不一样啊:
As in the case of playback, we have to take care that the application calls the read function before the capture buffer of the soundcard is completely filled. Otherwise there will be a buffer overrun.
/* 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;PCM设备在ALSA的接口有"plughw"和"hw",接口,不过我们这里使用默认的接口"default",
char *pcm_name;//定义设备名称
pcm_name = strdup("default");
然后我们分配一个硬件配置结构体
/* Allocate the snd_pcm_hw_params_t structure on the stack. */ snd_pcm_hw_params_alloca(&hwparams);现在我们可以打开设备了,
/* 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); }接下来的部分才是最重要的,前面基本上都是流程化的东西,接下来要说的是对设备的一些配置参数,搞明白这里我觉得比较重要,要配置的参数有 : 访问类型,采样率, 采样格式, 通道数, period的数量以及period的大小,这些参数在上一节中都有详细的介绍,如还有疑问可以点击:http://blog.csdn.net/liuchen_csdn/article/details/52095813
初始化配置硬件的结构体,alsa已经给出了相应的函数,我们搬用就可以了:
/* 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); }下面举个例子:我们假设声卡要配置的格式为: 立体声 16Bit Little Endian data, sampled at 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) */上面介绍的配置参数中有访问类型这个参数,这个参数指定了多路数据在buffer中的存储方式,分为两种:INTERLEAVED 和 NONINTERLEAVED,对于前者,它的意思就是在每个周期(period)左右声道的数据交叉存放,而对于后者,它是在每个周期(period)先采集左声道的数据存放在buffer中,接着采样右声道的数据在buffer中,例子如下:
/* 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); }接着我们要设置buffer的大小,buffer的大小是由函数的设置决定,有时参数是字节数,有时需要指定的是frames的数量,一帧的数据大小包括的是所有通道的数据大小,比如,立体声16-Bit 数据,它的一帧就是4Bytes.
/* 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); }对于 (periodsize * periods)>>2 的解释,periodsize这个是每个period的字节数,而periods指定是这个buffer要能够存放的周期个数, >>2的意思就是除以4,为什么要除以四呢? 这是因为每个frame占用的字节数是4Bytes啊,所以这个(periodsize * periods)>>2 的结果就是要一共要分配period个周期要容纳的frames数量.
如果你的硬件buffer不支持2^n,你可以使用snd_pcm_hw_params_set_buffer_size_near.这个函数,它会分配相邻的buffer空间.
当所有参数都配置完成后我们要让这些参数作用于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设备配置之后,我们就可以播放数据了,但是这里还要注意的 是 对于interleaved write access,我们调用:
/* 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);对于noninterleaved access,我们调用:
/* 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设备句柄是不可能的,所以我们要声明两个句柄,这样才能实现同步.下面仅介绍不同点:
[1] : PCM数据流的方向不一样了
/* Capture stream */ snd_pcm_stream_t stream_capture = SND_PCM_STREAM_CAPTURE;[2] : 参数的配置和播放一样,此处省略......
[3] : 捕获函数不一样啊:
/* Read num_frames frames from the PCM device */ /* pointed to by pcm_handle to buffer capdata. */ /* Returns the number of frames actually read. */ snd_pcm_readn(pcm_capture_handle, capdata, num_frames);[4] :
As in the case of playback, we have to take care that the application calls the read function before the capture buffer of the soundcard is completely filled. Otherwise there will be a buffer overrun.
int pcmreturn; while ((pcmreturn = snd_pcm_readi(pcm_capture_handle, capdata, periodsize>>2)) < 0) { snd_pcm_prepare(pcm_capture_handle); fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Overrun >>>>>>>>>>>>>>>\n"); }
转自:https://blog.csdn.net/liuchen_csdn/article/details/52097088