Audio:Android-TinyAlsa架构 PCM API

        之前有接触过Linux的音频AlsaAudio和PluseAudio框架,Android音频系统是基于Linux的Alsa驱动封装的TinyAlsa音频接口框架,最近在做相关的音频处理项目,正好结合项目check一下Android的TinyAlsa架构。

1.TinyAlsa-录制binary tinycap

        TinyAlsa的录制工具tinycap main函数里面主要是通过shell命令解析相关的音频设备参数以及记录音频数据的文件,然后通过capture_sample接口进行pcm API的相关操作。

file path:idh.code/external/tinyalsa/tinycap.c

int main(int argc, char **argv)
{
    //tinycap使用说明
    if (argc < 2) {
        fprintf(stderr, "Usage: %s file.wav [-D card] [-d device]"
                " [-c channels] [-r rate] [-b bits] [-p period_size]"
                " [-n n_periods] [-T capture time]\n", argv[0]);
        return 1;
    }

    file = fopen(argv[1], "wb");
    if (!file) {
        fprintf(stderr, "Unable to create file '%s'\n", argv[1]);
        return 1;
    }

    //解析tinycap相关参数
    /* parse command line arguments */
    argv += 2;
    while (*argv) {
        if (strcmp(*argv, "-d") == 0) {
            argv++;
            if (*argv)
                device = atoi(*argv);
        } 

        ...

        if (*argv)
            argv++;
    }

    ...

    //采样精度
    switch (bits) {
    case 32:
        format = PCM_FORMAT_S32_LE;
        break;
    case 24:
        format = PCM_FORMAT_S24_LE;
        break;
    case 16:
        format = PCM_FORMAT_S16_LE;
        break;
    default:
        fprintf(stderr, "%u bits is not supported.\n", bits);
        fclose(file);
        return 1;
    }

    ...

    /* install signal handler and begin capturing */
    signal(SIGINT, sigint_handler);
    signal(SIGHUP, sigint_handler);
    signal(SIGTERM, sigint_handler);

    //调用pcm_open、pcm_mmap_read方式进行录音
    frames = capture_sample(file, card, device, header.num_channels,
                            header.sample_rate, format,
                            period_size, period_count, cap_time);
    printf("Captured %u frames\n", frames);

    if(card == 0 && device == 2 && rate == 64000){
        header.num_channels = 4;
        header.sample_rate = 16000;
    }

    //关闭输入的文件流
    fclose(file);

    return 0;
}

        capture_sample接口里面关键的几个节点:通过pcm_open打开相对应的硬件设备、pcm_read调取声卡进行录音操作、fwrite文件流写入操作将音频设备读取到的buffer数据写入wav/pcm文件中;


unsigned int capture_sample(FILE *file, unsigned int card, unsigned int device,
                            unsigned int channels, unsigned int rate,
                            enum pcm_format format, unsigned int period_size,
                            unsigned int period_count, unsigned int cap_time)
{
    ...

    //调用tinyalsa的pcm api-pcm_open接口进行音频设备的open动作
    pcm = pcm_open(card, device, PCM_IN|PCM_MMAP|PCM_NOIRQ|PCM_MONOTONIC, &config);
    if (!pcm || !pcm_is_ready(pcm)) {
        fprintf(stderr, "Unable to open PCM device (%s)\n",
                pcm_get_error(pcm));
        return 0;
    }

    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));
    LOG("Capturing sample card %d device %d Capturing sample: %u ch, %u hz, %u bit\n", card, device, channels, rate, pcm_format_to_bits(format));

    ...

    //调用tinyalsa的pcm api-pcm_mmap_read接口调用音频设备进行录音
    while (capturing && !pcm_mmap_read(pcm, buffer, size)) {
        //通过fwrite文件流操作将从音频设备获得的buffer数据写入文件中
        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;
        }
    }

    frames = pcm_bytes_to_frames(pcm, bytes_read);
    free(buffer);

    //调用tinyalsa的pcm api-pcm_close接口释放音频设备
    pcm_close(pcm);
    return frames;
}

以下是tinyplay.c里面play_sample接口部分代码:

pcm = pcm_open(card, device, PCM_OUT|PCM_MMAP |PCM_NOIRQ, &config);
    if (!pcm || !pcm_is_ready(pcm)) {
        fprintf(stderr, "Unable to open PCM device %u (%s)\n",
                device, pcm_get_error(pcm));
        return;
    }

    size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
    buffer = malloc(size);
    if (!buffer) {
        fprintf(stderr, "Unable to allocate %d bytes\n", size);
        free(buffer);
        pcm_close(pcm);
        return;
    }

    printf("Playing sample: %u ch, %u hz, %u bit %u bytes\n", channels, rate, bits, data_sz);

    /* catch ctrl-c to shutdown cleanly */
    signal(SIGINT, stream_close);

    do {
        read_sz = size < data_sz ? size : data_sz;
        num_read = fread(buffer, 1, read_sz, file);
        if (num_read > 0) {
            if (pcm_mmap_write(pcm, buffer, num_read)) {
                fprintf(stderr, "Error playing sample\n");
                break;
            }
            data_sz -= num_read;
        }
    } while (!close && num_read > 0 && data_sz > 0);

    free(buffer);
    pcm_close(pcm);

2.TinyAlsa-pcm API

        TinyAlsa通过pcm API接口与底层Alsa驱动进行连接,关键接口pcm_open、pcm_read、pcm_write、pcm_close;结合上面的tinycap.c的调用demo,可以发现俩点需要关注的:

1.pcm_mmap_read:有的平台record使用的是mmap方式,此时record调用pcm_read方式是会报pcm_read fail:-21的error;

2.pcm_write/pcm_read:tinycap-pcm_read之后调用fwrite文件流操作写入audio file中;tinyplay-fread从audio file中读出文件中数据通过pcm_write调用音频pcm设备播放;

struct pcm *pcm_open(unsigned int card, unsigned int device,
                     unsigned int flags, struct pcm_config *config)
{
    struct pcm *pcm;
    struct snd_pcm_info info;
    struct snd_pcm_hw_params params;
    struct snd_pcm_sw_params sparams;
    char fn[256];
    int rc;

    LOG("pcm_open card %d device %d channel %d period_size %d period_count %d format %d\n", card, device ,config->channels, config->period_size, config->period_count, config->format);

    //我们项目使用的是展锐平台T610芯片,record采用的是MMAP方式,pcm_opem需要相对应的进行改变
    if(PCM_IN == flags){
        flags = PCM_IN|PCM_MMAP|PCM_NOIRQ|PCM_MONOTONIC;
    }

    pcm = calloc(1, sizeof(struct pcm));
    if (!pcm)
        return &bad_pcm; /* TODO: could support default config here */

    pcm->config = *config;

    //相对应的声卡设备
    snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
             flags & PCM_IN ? 'c' : 'p');
    pcm->flags = flags;
    pcm->fd = open(fn, O_RDWR|O_NONBLOCK);
    if (pcm->fd < 0) {
        oops(pcm, errno, "cannot open device '%s'", fn);
        return pcm;
    }

    if (fcntl(pcm->fd, F_SETFL, fcntl(pcm->fd, F_GETFL) &
              ~O_NONBLOCK) < 0) {
        oops(pcm, errno, "failed to reset blocking mode '%s'", fn);
        goto fail_close;
    }

    if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
        oops(pcm, errno, "cannot get info");
        goto fail_close;
    }
    pcm->subdevice = info.subdevice;

    //音频数据参数:采样精度、采样频率、通道数
    param_init(¶ms);
    param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT,
                   pcm_format_to_alsa(config->format));
    param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT,
                   SNDRV_PCM_SUBFORMAT_STD);
    param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
    param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
                  pcm_format_to_bits(config->format));
    param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS,
                  pcm_format_to_bits(config->format) * config->channels);
    param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS,
                  config->channels);
    param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
    param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate);

    ...

    //MMAP方式所对应的修改与处理
    if (flags & PCM_MMAP)
        param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
                       SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
    else
        param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
                       SNDRV_PCM_ACCESS_RW_INTERLEAVED);

    ...

    if (flags & PCM_MMAP) {
        pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
                                PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
        if (pcm->mmap_buffer == MAP_FAILED) {
            oops(pcm, errno, "failed to mmap buffer %d bytes\n",
                 pcm_frames_to_bytes(pcm, pcm->buffer_size));
            goto fail_close;
        }
    }

    ...

}

3.设备麦克阵列查看

adb shell cat /proc/asound/cards 确认声卡号

Audio:Android-TinyAlsa架构 PCM API_第1张图片

 /dev/snd 目录下查看对应的声卡设备信息

Audio:Android-TinyAlsa架构 PCM API_第2张图片

tinypcminfo –D 声卡号 确认声卡录音支持的数据:包括通道数、采样率、位数 等

Audio:Android-TinyAlsa架构 PCM API_第3张图片

使用 tinycap 命令录音并分析录音文件

tinycap /sdcard/test.pcm –D 3 –d 0 –c 8 –r 16000 –b 16 –p 1024 –n 4

        * -D  card       声卡号

        * -d  device     声卡设备号

        * -c  channels   通道数

        * -r  rate       采样率

        * -b  bits       pcm位宽

        * -p  period_size   一次中断的帧数* -n  n_periods     周期数

小结:

        Android音频方面的TinyAlsa架构完全可以类比Linux的AlsaAudio、PluseAudio来进行相关方面的check,TinyAlsa所使用的tinyplay类比与Alsa/Pluse的arecord和parecord;tinyplay可类比Alsa/Pluse的aplay和paplay进行比较;

        TinyAlsa方面主要的俩个API除了pcm还有一个mixer,主要是用于配置音频设备的相关control通道的方式可在之后进行一下分析。

 

 

你可能感兴趣的:(Audio,android)