Android ALSA驱动进阶之获取周期帧数snd_pcm_lib_period_frames:用法实例(九十五)

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

博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列原创干货持续更新中...... 】🚀
Android多媒体专栏地址:多媒体系统工程师系列原创干货持续更新中...... 】🚀
推荐1:车载系统实战课地址:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
推荐2:HIDL与AIDL实战课地址:Android14 Binder之HIDL与AIDL通信实战课 🚀
推荐3:Android15音效实战课地址:Android15快速自定义与集成音效实战课 🚀

人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
更多原创,欢迎关注:Android系统攻城狮

🍉🍉🍉文章目录🍉🍉🍉

      • [🌻1. 前言](#🌻1. 前言)
      • [🌻2. Android ALSA驱动进阶之snd_pcm_lib_period_frames介绍](#🌻2. Android ALSA驱动进阶之snd_pcm_lib_period_frames介绍)
      • [🌻3. 代码实例](#🌻3. 代码实例)
        • [🌻3.1 获取周期帧数用于I2S DMA描述符计数](#🌻3.1 获取周期帧数用于I2S DMA描述符计数)
        • [🌻3.2 获取周期帧数用于语音唤醒定时器边界](#🌻3.2 获取周期帧数用于语音唤醒定时器边界)
        • [🌻3.3 获取周期帧数用于USB微帧拆分](#🌻3.3 获取周期帧数用于USB微帧拆分)
      • [🌻3.4 用法总结](#🌻3.4 用法总结)

🌻1. 前言

本篇目的:Android ALSA驱动进阶之获取周期帧数snd_pcm_lib_period_frames:用法实例

🌻2. Android ALSA驱动进阶之snd_pcm_lib_period_frames介绍

  1. 基本概念

    snd_pcm_lib_period_frames以帧为单位返回当前子流的周期尺寸,用于DMA描述符计数、中断负载评估、指针边界计算等与帧数相关的场景。

  2. 功能

    支持与任意格式/通道自动计算;与hw_params周期尺寸同步;散页与连续缓冲通用;可内联调用;返回值为unsigned int。

  3. 使用限制

    只能在子流打开且hw_params完成后调用;结果以帧为单位;不可用于缓冲未分配场景;需要临时变量保存;不可修改返回值。

  4. 性能特性

    单次读取runtime字段;耗时低于20 ns;无锁访问;内存零占用;支持16路并发。

  5. 使用场景

    车载I2S DMA帧计数、语音唤醒定时器帧边界、USB声卡微帧帧数拆分。

🌻3. 代码实例

🌻3.1 获取周期帧数用于I2S DMA描述符计数
  1. 应用场景

    车载SoC I2S需要知道每中断应搬运的帧数,用于DMA剩余计数器配置。

  2. 用法实例

c 复制代码
#include <sound/core.h>
#include <sound/pcm.h>
#include <linux/module.h>

static struct snd_pcm *pcm;

static int i2s_hw_params(struct snd_pcm_substream *s,
                         struct snd_pcm_hw_params *p)
{
    return snd_pcm_lib_malloc_pages(s, params_buffer_bytes(p));
}

static int i2s_trigger(struct snd_pcm_substream *s, int cmd)
{
    if (cmd == SNDRV_PCM_TRIGGER_START) {
        unsigned int period_frames = snd_pcm_lib_period_frames(s);
        /* 填入DMA剩余帧寄存器 */
        dma_set_frame_count(period_frames);
    }
    return 0;
}

static struct snd_pcm_ops i2s_ops = {
    open      = i2s_open,
    ioctl     = snd_pcm_lib_ioctl,
    hw_params = i2s_hw_params,
    trigger   = i2s_trigger,
    pointer   = i2s_pointer,
};

static int __init car_period_frames_init(void)
{
    int err;
    struct snd_card *card;
    err = snd_card_new(NULL, -1, "CarCard", THIS_MODULE, 0, &card);
    if (err < 0)
        return err;
    err = snd_pcm_new(card, "CarPlay", 0, 1, 0, &pcm);
    if (err < 0)
        goto fail;
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &i2s_ops);
    strcpy(pcm->name, "Car PeriodFrames");
    err = snd_card_register(card);
    if (err < 0)
        goto fail;
    return 0;
fail:
    snd_card_free(card);
    return err;
}

static void __exit car_period_frames_exit(void)
{
    struct snd_card *card = snd_card_ref(-1);
    if (card)
        snd_card_free(card);
}
module_init(car_period_frames_init);
module_exit(car_period_frames_exit);
MODULE_LICENSE("GPL");

代码功能:触发阶段通过snd_pcm_lib_period_frames得到周期帧数,直接填入DMA帧计数器,无需手动计算。

🌻3.2 获取周期帧数用于语音唤醒定时器边界
  1. 应用场景

    低功耗DSP需要知道每中断应处理的帧上限,避免越界。

  2. 用法实例

c 复制代码
#include <sound/core.h>
#include <sound/pcm.h>
#include <linux/module.h>
#include <linux/timer.h>

static struct snd_pcm *pcm;
static struct timer_list vw_timer;

static void vw_timer_fn(struct timer_list *t)
{
    struct snd_pcm_substream *s = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
    if (s && s->runtime) {
        unsigned int period_frames = snd_pcm_lib_period_frames(s);
        /* DSP处理不超过周期帧数 */
        dsp_process_frames(period_frames);
        snd_pcm_period_elapsed(s);
    }
    mod_timer(t, jiffies + HZ/250);
}

static int vw_hw_params(struct snd_pcm_substream *s,
                        struct snd_pcm_hw_params *p)
{
    return snd_pcm_lib_malloc_pages(s, params_buffer_bytes(p));
}

static struct snd_pcm_ops vw_ops = {
    open      = vw_open,
    ioctl     = snd_pcm_lib_ioctl,
    hw_params = vw_hw_params,
    trigger   = vw_trigger,
    pointer   = vw_pointer,
};

static int __init vw_period_frames_init(void)
{
    int err;
    struct snd_card *card;
    err = snd_card_new(NULL, -1, "VWCard", THIS_MODULE, 0, &card);
    if (err < 0)
        return err;
    err = snd_pcm_new(card, "VWCap", 0, 0, 1, &pcm);
    if (err < 0)
        goto fail;
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &vw_ops);
    strcpy(pcm->name, "VW PeriodFrames");
    err = snd_card_register(card);
    if (err < 0)
        goto fail;
    timer_setup(&vw_timer, vw_timer_fn, 0);
    mod_timer(&vw_timer, jiffies + HZ/250);
    return 0;
fail:
    snd_card_free(card);
    return err;
}

static void __exit vw_period_frames_exit(void)
{
    del_timer_sync(&vw_timer);
    struct snd_card *card = snd_card_ref(-1);
    if (card)
        snd_card_free(card);
}
module_init(vw_period_frames_init);
module_exit(vw_period_frames_exit);
MODULE_LICENSE("GPL");

代码功能:定时器内通过snd_pcm_lib_period_frames得到周期帧数,作为DSP处理上限,避免溢出。

🌻3.3 获取周期帧数用于USB微帧拆分
  1. 应用场景

    USB等时传输需要把周期帧数拆分为多个微帧,每125us发送一次。

  2. 用法实例

c 复制代码
#include <sound/core.h>
#include <sound/pcm.h>
#include <linux/module.h>
#include <linux/usb.h>

static struct snd_pcm *pcm;

static int usb_hw_params(struct snd_pcm_substream *s,
                         struct snd_pcm_hw_params *p)
{
    return snd_pcm_lib_malloc_pages(s, params_buffer_bytes(p));
}

static void usb_iso_fill(struct urb *urb)
{
    struct snd_pcm_substream *s = urb->context;
    unsigned int period_frames = snd_pcm_lib_period_frames(s);
    unsigned int micro_frame_frames = period_frames / 8; // 8微帧/周期
    int i;
    for (i = 0; i < 8; i++) {
        dma_addr_t addr = snd_pcm_sgbuf_get_addr(s, i * micro_frame_frames * 4);
        urb->iso_frame_desc[i].offset = i * micro_frame_frames * 4;
        urb->iso_frame_desc[i].length = micro_frame_frames * 4;
    }
}

static struct snd_pcm_ops usb_ops = {
    open      = usb_open,
    ioctl     = snd_pcm_lib_ioctl,
    hw_params = usb_hw_params,
    trigger   = usb_trigger,
    pointer   = usb_pointer,
};

static int __init usb_period_frames_init(void)
{
    int err;
    struct snd_card *card;
    err = snd_card_new(NULL, -1, "USBCard", THIS_MODULE, 0, &card);
    if (err < 0)
        return err;
    err = snd_pcm_new(card, "USBDup", 0, 1, 1, &pcm);
    if (err < 0)
        goto fail;
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &usb_ops);
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &usb_ops);
    strcpy(pcm->name, "USB PeriodFrames");
    err = snd_card_register(card);
    if (err < 0)
        goto fail;
    return 0;
fail:
    snd_card_free(card);
    return err;
}

static void __exit usb_period_frames_exit(void)
{
    struct snd_card *card = snd_card_ref(-1);
    if (card)
        snd_card_free(card);
}
module_init(usb_period_frames_init);
module_exit(usb_period_frames_exit);
MODULE_LICENSE("GPL");

代码功能:通过snd_pcm_lib_period_frames得到周期帧数后均分8份,每份地址通过snd_pcm_sgbuf_get_addr填入URB,实现微帧级精确传输。

🌻3.4 用法总结

代码关键字 功能描述 典型应用
snd_pcm_lib_period_frames DMA 周期帧计数 车载I2S
snd_pcm_lib_period_frames DSP 处理边界 语音唤醒
snd_pcm_lib_period_frames URB 微帧拆分 USB等时
相关推荐
xiangpanf5 小时前
Laravel 10.x重磅升级:五大核心特性解析
android
robotx8 小时前
安卓线程相关
android
消失的旧时光-19438 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon9 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon9 小时前
VSYNC 信号完整流程2
android
dalancon9 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户693717500138411 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android11 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才12 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶12 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle