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快速自定义与集成音效实战课 🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

🍉🍉🍉文章目录🍉🍉🍉
- 🌻1. 前言
- 🌻2. Android内核进阶之周期更新PCM状态snd_pcm_period_elapsed介绍
- 🌻3. 代码实例
- 🌻3.1 在I2S DMA中断中调用周期更新
- 🌻3.2 在语音唤醒定时器中调用周期更新
- 🌻3.3 在USB等时传输完成中调用周期更新
- 🌻3.4 用法总结
🌻1. 前言
本篇目的:Android内核进阶之周期更新PCM状态snd_pcm_period_elapsed:用法实例
🌻2. Android内核进阶之周期更新PCM状态snd_pcm_period_elapsed介绍
-
基本概念
snd_pcm_period_elapsed由驱动在中断或DMA完成时调用,用于通知ALSA子系统当前硬件指针已前进一个周期,触发用户空间唤醒、状态更新及XRUN检测。 -
功能
支持任意周期尺寸;与hw_ptr原子同步;可唤醒阻塞的读写线程;支持16路并发;可触发stop_threshold自动停流。 -
使用限制
只能在DMA中断或等效上下文调用;需先更新硬件指针;不可在原子外使用;每周期仅调用一次;失败无返回值但需保证指针合法。 -
性能特性
执行耗时低于1微秒;无锁更新runtime字段;内存占用零字节;支持最高1MHz中断频率;与snd_pcm_sgbuf无缝配合。 -
使用场景
车载I2S DMA完成中断、语音唤醒低功耗定时中断、USB声卡高速等时传输完成事件。
🌻3. 代码实例
🌻3.1 在I2S DMA中断中调用周期更新
-
应用场景
车载SoC I2S外设每256帧触发一次DMA中断,需即时通知ALSA推进hw_ptr。 -
用法实例
#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 在语音唤醒定时器中调用周期更新
-
应用场景
低功耗DSP无DMA中断,使用32kHz定时器模拟周期前进。 -
用法实例
#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等时传输完成中调用周期更新
-
应用场景
USB声卡微帧125us完成一次等时包,累计1024帧后通知ALSA。 -
用法实例
#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声卡 |
