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声卡
相关推荐
阿巴斯甜1 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95272 小时前
Andorid Google 登录接入文档
android
黄林晴4 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab16 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿19 小时前
Android MediaPlayer 笔记
android
Jony_19 小时前
Android 启动优化方案
android
阿巴斯甜19 小时前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇19 小时前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android