安卓系统中基于tinyALSA实现音频播放与获取

Views: 7
0 0
Read Time:3 Minute, 28 Second

在安卓系统中,其整体音频链路大致如下:

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数据写入设备从而实现播放的效果。

Happy
Happy
0 %
Sad
Sad
0 %
Excited
Excited
0 %
Sleepy
Sleepy
0 %
Angry
Angry
0 %
Surprise
Surprise
0 %
FranzKafka95
FranzKafka95

极客,文学爱好者。如果你也喜欢我,那你大可不必害羞。

Articles: 102

Leave a Reply

Your email address will not be published. Required fields are marked *

en_USEN