简介: CSDN博客专家、《Android系统多媒体进阶实战》作者

博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址: Audio工程师进阶系列原创干货持续更新中……】🚀
Android多媒体专栏地址: 多媒体系统工程师系列原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀

人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

更多原创,欢迎关注:Android系统攻城狮

欢迎关注Android系统攻城狮

🌻1. 前言

本篇目的:Android tinyalsa 深度解析之 pcm_set_avail_min 调用流程与实战。


🌻2. 用法与应用场景

pcm_set_avail_mintinyalsa 中用于动态调整**唤醒阈值(Wake-up Threshold)**的 API。它允许开发者在设备打开后,重新设定内核唤醒用户态线程所需的最小可用帧数。

  • 用法int pcm_set_avail_min(struct pcm *pcm, unsigned int avail_min);
  • 返回值:成功返回 0;失败返回负数。
  • 应用场景
  1. 动态延迟控制:根据系统负载或业务需求(如从高采样率切换到低功耗模式),动态调整 avail_min 以改变 CPU 的唤醒频率。
  2. 减少上下文切换:在录音或播放大数据块时,增大该值可以让线程一次性处理更多数据,从而降低 CPU 开销。
  3. 防止断流:在网络音频流不稳定的情况下,通过调整该阈值来优化缓冲区的填满策略。

🌻3. 调用流程剖析

3.1 核心步骤
  1. 参数更新:函数首先将传入的 avail_min 数值更新到用户态 struct pcm 的配置备份中。
  2. 构建软件参数结构体:填充内核所需的 snd_pcm_sw_params 结构体。在该结构体中,avail_min 是最重要的控制变量之一。
  3. 执行系统调用:发起 ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sw_params) 指令。
  4. 内核配置生效
  • 内核 ALSA 核心层:接收到指令后,更新该流(Substream)的 runtime->control->avail_min
  • 唤醒逻辑关联:内核的 pollwait 等待队列会自动关联此新阈值。当下一次硬件中断发生时,内核会检查当前可用空间是否满足新的 avail_min,若满足则唤醒用户态。

关键技术:软件参数(SW Params)的灵活性
硬件参数(HW Params,如采样率、声道)一旦在 pcm_prepare 后通常不可更改,但软件参数(SW Params)如 avail_min 是可以在流运行期间动态调整的。这为 Android 的 AudioTrackAudioRecord 在不同能效模式间切换提供了底层支持。

3.2 涉及核心时序图
Audio Hardware Kernel ALSA Core tinyalsa (pcm_set_avail_min) Audio HAL / App Audio Hardware Kernel ALSA Core tinyalsa (pcm_set_avail_min) Audio HAL / App 运行期效果 调用 pcm_set_avail_min(pcm, 2048) 1. 更新 pcm->>config.avail_min 2. 填充 snd_pcm_sw_params 3. ioctl(SNDRV_PCM_IOCTL_SW_PARAMS) 4. 更新 runtime 唤醒阈值 返回成功 返回 0 中断: 缓冲区空闲达到 2048 帧 唤醒被 poll/wait 阻塞的线程

🌻4. 实战应用案例

此案例演示了如何在播放过程中,将唤醒阈值从默认的 1 个周期动态调整为 2 个周期,以降低高负载下的中断频率。

#include <tinyalsa/asoundlib.h>
#include <stdio.h>

/**
 * 演示动态调整音频流的唤醒策略
 */
void optimize_wakeup_threshold(struct pcm *pcm, unsigned int new_avail_min) {
    if (!pcm || !pcm_is_ready(pcm)) return;

    printf("HAL: 当前 avail_min = %u\n", pcm_get_available_min(pcm));

    /* 核心调用:重新设置内核唤醒阈值 */
    printf("HAL: 正在尝试将阈值调整为: %u 帧...\n", new_avail_min);
    
    int ret = pcm_set_avail_min(pcm, new_avail_min);

    if (ret == 0) {
        printf("HAL: 阈值设置成功。新值已生效。\n");
        // 验证结果
        printf("HAL: 验证最新值: %u\n", pcm_get_available_min(pcm));
    } else {
        fprintf(stderr, "HAL: 设置失败: %s\n", pcm_get_error(pcm));
    }
}

int main() {
    struct pcm_config config = {
        .channels = 2,
        .rate = 48000,
        .period_size = 1024,
        .period_count = 4,
        .format = PCM_FORMAT_S16_LE,
        .avail_min = 1024, // 初始为 1 个周期
    };

    struct pcm *out = pcm_open(0, 0, PCM_OUT, &config);
    if (pcm_is_ready(out)) {
        pcm_prepare(out);
        
        // 模拟运行一段时间后,为了节能,降低唤醒频率(改为 2 个周期唤醒一次)
        optimize_wakeup_threshold(out, 2048);

        pcm_close(out);
    }
    return 0;
}


🌻5. 用法总结

特性 详情描述
底层指令 SW_PARAMS。通过软件参数 ioctl 实现,无需重新打开设备。
执行时机 动态可调。可以在流处于 PREPARED 或 RUNNING 状态时随时调用。
对性能影响 功耗平衡点。数值越大,中断次数越少,越省电,但相应地单次写入的数据量必须增大。
限制条件 范围约束。设置的值通常不应超过 buffer_size,否则线程可能永远不会被唤醒。
与 pcm_wait 关系 直接控制。该函数设置的值直接决定了 pcm_wait 被唤醒的时机。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐