简介: 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介绍
-
基本概念
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。
-
用法实例
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 在语音唤醒定时器中调用周期更新
-
应用场景
低功耗DSP无DMA中断,使用32kHz定时器模拟周期前进。
-
用法实例
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等时传输完成中调用周期更新
-
应用场景
USB声卡微帧125us完成一次等时包,累计1024帧后通知ALSA。
-
用法实例
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声卡 |