Physical Address:
ChongQing,China.
WebSite:


在安卓系统中,其整体音频链路大致如下:
App->AudioRecord/AudioTrack->AudioFlinger->Audio HAL->TinyALSA->ALSA Driver->Codec/ DSP/Mic/Speaker
其中各个软件层次如下所示:
App位于Android系统Application应用层;
AudioRecord/AndioTrack位于Java Framework层;
AudioFlinger位于Native Framework层;
Audio HAL则位于HAL层;
ALSA Driver则位于Kernel层;
在Audio HAL中,为了实现用户态与内核态之间的数据交互,就需要通过tinyALSA,这与Camera HAL中通过V4L2获取摄像头数据是类似的。
在开始使用tinyALSA之前,我们需要学习一下音频设备的一些基础信息,这些基础信息在/dev/snd目录下可以看到:
franzkafka@franzkafka-virtual-machine:/dev/snd$ ls -lah
total 0
drwxr-xr-x 3 root root 200 6月 2 23:16 .
drwxr-xr-x 19 root root 4.5K 6月 2 23:16 ..
drwxr-xr-x 2 root root 60 6月 2 23:16 by-path
crw-rw----+ 1 root audio 116, 6 6月 2 23:16 controlC0
crw-rw----+ 1 root audio 116, 5 6月 2 23:16 midiC0D0
crw-rw----+ 1 root audio 116, 3 6月 2 23:16 pcmC0D0c
crw-rw----+ 1 root audio 116, 2 6月 2 23:16 pcmC0D0p
crw-rw----+ 1 root audio 116, 4 6月 2 23:16 pcmC0D1p
crw-rw----+ 1 root audio 116, 1 6月 2 23:15 seq
crw-rw----+ 1 root audio 116, 33 6月 2 23:15 timer
这里我们重点要看的是pcm*开头的字符块设备,这些设备就是PCM Capture /Playback的设备;
pcmC0D0c:C0代表Card0(声卡),D0代表Device0(设备),c代表Capture,即音频输入设备,通常就是麦克风;
pcmC0D0p: C0代表Card0(声卡),D0代表Device0(设备),p代表Playback,即音频播放设备,通常就是扬声器;
在使用tinyALSA库时,需要导入依赖libtinyALSA,并导入头文件tinyalsa/asoundlib.h,如#include <tinyalsa/asoundlib.h>,之后就是具体的执行步骤了。这里我们按照音频录制与音频播放两种场景做介绍。
第一步:打开PCM设备,这一步使用的API为pcm_open,该接口定义如下所示:
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, struct pcm_config *config);
在使用时我们需要指定声卡、设备以及使用正确的flag,同时还需要传入正确的pcm_config;
pcm_config为结构体类型,其类型定义如下:
struct pcm_config {
unsigned int channels; //声道数
unsigned int rate;//采样率,采用16KHz,32KHz,48KHz等,值越大每秒采样越多
unsigned int period_size;//采样帧数( Frame),采样时所有声道的数据,可采用默认值1024
unsigned int period_count;//可采用默认值4,总Buffer容量= period_size* period_count
enum pcm_format format;//采样格式:PCM_FORMAT_S16_LE、PCM_FORMAT_S32_LE
unsigned int start_threshold;
unsigned int stop_threshold;
unsigned int silence_threshold;
unsigned int silence_size;
int avail_min;
};
使用示例如下所示:
struct pcm_config config;
struct pcm *pcm;
char *buffer;
unsigned int size;
unsigned int bytes_read = 0;
unsigned int frames = 0;
memset(&config, 0, sizeof(config));
pcm = pcm_open(card, device, PCM_IN, &config);
if (!pcm || !pcm_is_ready(pcm)) {
fprintf(stderr, "Unable to open PCM device (%s)\n",
pcm_get_error(pcm));
return 0;
}
在这个步骤中,就会尝试去打开声卡中的Capture设备,我们可以通过pcm_is_ready接口去判断是否正常。
第二步:读取PCM数据,这一步使用的API为pcm_read,该接口定义如下所示:
int pcm_read(struct pcm *pcm, void *data, unsigned int count);
这里面的size计算需要借助pcm_frames_to_bytes接口,如下所示:
size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
buffer = malloc(size);
if (!buffer) {
fprintf(stderr, "Unable to allocate %u bytes\n", size);
free(buffer);
pcm_close(pcm);
return 0;
}
printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate,
pcm_format_to_bits(format));
整体实现可参考如下所示:
size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
buffer = malloc(size);
if (!buffer) {
fprintf(stderr, "Unable to allocate %u bytes\n", size);
free(buffer);
pcm_close(pcm);
return 0;
}
printf("Capturing sample: %u ch, %u hz, %u bit\n", channels, rate,
pcm_format_to_bits(format));
clock_gettime(CLOCK_MONOTONIC, &now);
end.tv_sec = now.tv_sec + cap_time;
end.tv_nsec = now.tv_nsec;
while (capturing && !pcm_read(pcm, buffer, size)) {
if (fwrite(buffer, 1, size, file) != size) {
fprintf(stderr,"Error capturing sample\n");
break;
}
bytes_read += size;
if (cap_time) {
clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec > end.tv_sec ||
(now.tv_sec == end.tv_sec && now.tv_nsec >= end.tv_nsec))
break;
}
}
第三步:停止获取PCM数据,同时释放资源。这一步使用的API接口为pcm_close,该接口的定义如下所示:
int pcm_close(struct pcm *pcm);
该接口较为简单,也没有更多内容可以展开。
以上是基于tinylasa接口实现PCM数据读取的过程,通常我们可以借助这样的方式实现读取麦克风数据。
音频播放的部分其实与音频录制非常类似,但是还是有一些不同。
第一步:打开PCM设备,这一步使用的API为pcm_open,与录制不同的地方在于此时传入的FLAG标志为PCM_OUT,如下所示:
pcm = pcm_open(card, device, PCM_OUT, &config);
if (!pcm || !pcm_is_ready(pcm)) {
fprintf(stderr, "Unable to open PCM device %u (%s)\n",
device, pcm_get_error(pcm));
return;
}
此时打开的设备即为具备播放能力的设备,也就是扬声器这类设备。
第二步:写入PCM数据,这一步使用的API为pcm_write,该API的定义如下:
int pcm_write(struct pcm *pcm, const void *data, unsigned int count);
该API的使用示例如下:
do {
read_sz = size < data_sz ? size : data_sz;
num_read = fread(buffer, 1, read_sz, file);
if (num_read > 0) {
if (pcm_write(pcm, buffer, num_read)) {
fprintf(stderr, "Error playing sample\n");
break;
}
data_sz -= num_read;
}
} while (!closing && num_read > 0 && data_sz > 0);
第三步:写入完成,同样需要释放资源,使用的API仍旧为pcm_close。
通过以上三步,也就完成了将PCM数据写入设备从而实现播放的效果。