Android内核进阶之周期更新PCM状态snd_pcm_period_elapsed:用法实例(九十二)

简介: 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内核进阶之周期更新PCM状态snd_pcm_period_elapsed介绍](#🌻2. Android内核进阶之周期更新PCM状态snd_pcm_period_elapsed介绍)
      • [🌻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内核进阶之周期更新PCM状态snd_pcm_period_elapsed:用法实例

🌻2. Android内核进阶之周期更新PCM状态snd_pcm_period_elapsed介绍

  1. 基本概念

    snd_pcm_period_elapsed由驱动在中断或DMA完成时调用,用于通知ALSA子系统当前硬件指针已前进一个周期,触发用户空间唤醒、状态更新及XRUN检测。

  2. 功能

    支持任意周期尺寸;与hw_ptr原子同步;可唤醒阻塞的读写线程;支持16路并发;可触发stop_threshold自动停流。

  3. 使用限制

    只能在DMA中断或等效上下文调用;需先更新硬件指针;不可在原子外使用;每周期仅调用一次;失败无返回值但需保证指针合法。

  4. 性能特性

    执行耗时低于1微秒;无锁更新runtime字段;内存占用零字节;支持最高1MHz中断频率;与snd_pcm_sgbuf无缝配合。

  5. 使用场景

    车载I2S DMA完成中断、语音唤醒低功耗定时中断、USB声卡高速等时传输完成事件。

🌻3. 代码实例

🌻3.1 在I2S DMA中断中调用周期更新
  1. 应用场景

    车载SoC I2S外设每256帧触发一次DMA中断,需即时通知ALSA推进hw_ptr。

  2. 用法实例

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

static struct snd_pcm *pcm;

static irqreturn_t i2s_dma_irq(int irq, void *data)
{
    struct snd_pcm_substream *s = data;
    // 更新硬件指针
    s->runtime->hw_ptr += 256;
    // 通知ALSA周期完成
    snd_pcm_period_elapsed(s);
    return IRQ_HANDLED;
}

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 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 i2s_period_init(void)
{
    int err;
    struct snd_card *card;
    err = snd_card_new(NULL, -1, "I2SCard", THIS_MODULE, 0, &card);
    if (err < 0)
        return err;
    err = snd_pcm_new(card, "I2SPlay", 0, 1, 0, &pcm);
    if (err < 0)
        goto fail;
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &i2s_ops);
    strcpy(pcm->name, "I2S Period");
    err = snd_card_register(card);
    if (err < 0)
        goto fail;
    // 注册DMA中断
    request_irq(IRQ_I2S_DMA, i2s_dma_irq, 0, "i2s", pcm);
    return 0;
fail:
    snd_card_free(card);
    return err;
}

static void __exit i2s_period_exit(void)
{
    struct snd_card *card = snd_card_ref(-1);
    if (card) {
        free_irq(IRQ_I2S_DMA, card);
        snd_card_free(card);
    }
}
module_init(i2s_period_init);
module_exit(i2s_period_exit);
MODULE_LICENSE("GPL");

代码功能:DMA中断内更新hw_ptr后调用snd_pcm_period_elapsed,用户空间被唤醒,周期无XRUN。

🌻3.2 在语音唤醒定时器中调用周期更新
  1. 应用场景

    低功耗DSP无DMA中断,使用32kHz定时器模拟周期前进。

  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) {
        s->runtime->hw_ptr += 128;
        snd_pcm_period_elapsed(s);
    }
    mod_timer(&vw_timer, jiffies + HZ/250); // 128帧@16kHz
}

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_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 Period");
    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_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_init);
module_exit(vw_period_exit);
MODULE_LICENSE("GPL");

代码功能:定时器内每128帧调用snd_pcm_period_elapsed,低功耗保持指针更新,录音连续。

🌻3.3 在USB等时传输完成中调用周期更新
  1. 应用场景

    USB声卡微帧125us完成一次等时包,累计1024帧后通知ALSA。

  2. 用法实例

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

static struct snd_pcm *pcm;
static unsigned int usb_frame_cnt;

static void usb_iso_complete(struct urb *urb)
{
    struct snd_pcm_substream *s = urb->context;
    int i;
    for (i = 0; i < urb->number_of_packets; i++) {
        usb_frame_cnt += urb->iso_frame_desc[i].length / 4;
        if (usb_frame_cnt >= 1024) {
            s->runtime->hw_ptr += 1024;
            snd_pcm_period_elapsed(s);
            usb_frame_cnt = 0;
        }
    }
}

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 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_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 Period");
    err = snd_card_register(card);
    if (err < 0)
        goto fail;
    return 0;
fail:
    snd_card_free(card);
    return err;
}

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

代码功能:等时包完成回调中累计帧数,满1024帧调用snd_pcm_period_elapsed,USB高速无XRUN。

🌻3.4 用法总结

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