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(&params);
    param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
                   pcm_format_to_alsa(config->format));
    param_set_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
                   SNDRV_PCM_SUBFORMAT_STD);
    param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
                  pcm_format_to_bits(config->format));
    param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
                  pcm_format_to_bits(config->format) * config->channels);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
                  config->channels);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
    param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);

    ...

    //MMAP方式所对应的修改与处理
    if (flags & PCM_MMAP)
        param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
                       SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
    else
        param_set_mask(&params, 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 确认声卡号

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

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

使用 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通道的方式可在之后进行一下分析。

<think>嗯,用户的问题是关于在Android Studio中通过HAL层使用TinyALSA进行录音的实现。我需要先理解各个组件的角色和他们之间的关系。首先,Android的音频架构中,HAL层是硬件抽象层,负责与底层硬件驱动通信。而TinyALSA是一个轻量级的ALSA库,常用于嵌入式系统,可能被用作替代Android默认的音频驱动。 接下来,用户需要具体的实现步骤。我应该从配置HAL层开始,考虑如何创建HAL模块,定义必要的结构体如audio_hw_device和audio_stream_in。然后,需要调用TinyALSAAPI来打开PCM设备,配置参数,比如采样率、通道数和格式。这里可能需要使用pcm_open和pcm_params_set函数。 接着,要考虑如何读取音频数据,可能需要在一个循环中使用pcm_readi函数,并将数据传递给上层。同时,需要考虑错误处理和资源释放,比如在停止录音时关闭PCM设备。 还需要处理权限问题,确保应用有录音权限,并且在HAL层的实现中正确配置权限。此外,编译和集成HAL模块到Android系统可能需要修改makefile或使用Soong构建系统,生成共享库并放置到正确的目录下。 在示例代码部分,应该提供一个简化的HAL模块实现,展示如何初始化设备、打开输入流、读取数据等关键步骤。同时,要指出这只是基本实现,实际中需要考虑更多细节和优化。 相关问题部分,需要涵盖TinyALSA的其他功能、调试方法、权限问题以及替代方案,这样用户可以根据需要进一步探索。需要确保引用的来源正确,比如提到的AudioQueue.h引用可能来自Android的音频框架文档,但要注意TinyALSA的具体文档可能不同。 最后,检查回答是否符合用户的要求,确保使用正确的中文术语,结构清晰,步骤明确,并覆盖用户可能遇到的问题点,比如配置参数、权限和编译集成。</think>在Android Studio中通过HAL层实现TinyALSA录音需要理解Android音频架构的三个关键层级:应用框架层、HAL层和内核驱动层。以下是具体实现步骤: ### 1. 配置HAL层 在`hardware/libhardware/modules/audio`目录下创建HAL模块: ```c // audio_hw.c struct audio_device { struct audio_hw_device hw_device; struct pcm *pcm_in; }; static int adev_open_input_stream(struct audio_hw_device *dev, audio_io_handle_t handle, struct audio_config *config, struct audio_stream_in **stream_in) { struct audio_device *adev = (struct audio_device *)dev; struct pcm_config pcm_config = { .channels = 2, .rate = 48000, .format = PCM_FORMAT_S16_LE, .period_size = 1024, .period_count = 4 }; adev->pcm_in = pcm_open(0, 0, PCM_IN, &pcm_config); // 打开PCM输入设备 *stream_in = (struct audio_stream_in *)adev; return 0; } ``` ### 2. TinyALSA接口调用 使用`tinyalsa/pcm.h`中的核心函数: ```c // 启动录音线程 static void *record_thread(void *arg) { struct audio_device *adev = (struct audio_device *)arg; char buffer[4096]; while (recording_active) { int ret = pcm_readi(adev->pcm_in, buffer, pcm_get_buffer_size(adev->pcm_in)); // 将buffer传递给上层应用 } return NULL; } ``` ### 3. 权限配置 在`AndroidManifest.xml`中添加: ```xml <uses-permission android:name="android.permission.RECORD_AUDIO"/> ``` 在`sepolicy/audio_hal.te`中添加HAL层权限: ``` allow hal_audio_device devpts:chr_file { read write }; ``` ### 4. 编译集成 在`Android.bp`中添加编译配置: ```go cc_library_shared { name: "audio.primary.tinyalsa", srcs: ["audio_hw.c"], shared_libs: [ "liblog", "libtinyalsa" ], cflags: ["-Wall", "-Werror"], } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值