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声卡
相关推荐
Cola可洛3 小时前
修复Flyme移植BUG
android·bug
消失的旧时光-19433 小时前
Kotlinx.serialization 使用指南
android·kotlin·json
消失的旧时光-19434 小时前
Kotlinx.serialization 项目集成
android·kotlin·json
梦里不知身是客115 小时前
datax如何做增量导入
android
我是好小孩6 小时前
【Android】RecyclerView的高度问题、VH复用概念、多子项的实现;
android·java·网络
4Forsee6 小时前
【Android】模板化解决复杂场景的滑动冲突问题
android·java·rpc
彭同学学习日志6 小时前
解决 Android Navigation 组件导航栏配置崩溃:从错误到实现的完整指南
android·kotlin
法的空间6 小时前
让 Flutter 资源管理更智能
android·flutter·ios
江上清风山间明月7 小时前
Flutter中Column中使用ListView时溢出问题的解决方法
android·flutter·column·listview