简介: 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内核进阶之snd_pcm_lib_period_bytes介绍](#🌻2. Android内核进阶之snd_pcm_lib_period_bytes介绍)
- [🌻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_lib_period_bytes:用法实例
🌻2. Android内核进阶之snd_pcm_lib_period_bytes介绍
-
基本概念
snd_pcm_lib_period_bytes以字节为单位返回当前子流的周期尺寸,用于DMA描述符填充、缓存边界计算、中断负载评估等场景。
-
功能
支持与任意格式/通道/采样率组合自动计算;与hw_params周期尺寸同步;散页与连续缓冲通用;可内联调用;返回值为unsigned int。
-
使用限制
只能在子流打开且hw_params完成后调用;结果以字节为单位;不可用于缓冲未分配场景;需要临时变量保存;不可修改返回值。
-
性能特性
单次乘法运算;耗时低于20 ns;无锁访问runtime字段;内存零占用;支持16路并发。
-
使用场景
车载I2S DMA描述符长度填充、语音唤醒散页中断边界、USB声卡微帧负载计算。
🌻3. 代码实例
🌻3.1 获取周期字节填充I2S DMA描述符
-
应用场景
车载SoC I2S需要知道每中断应搬运的字节数,用于DMA控制器配置。
-
用法实例
c
#include <sound/core.h>
#include <sound/pcm.h>
#include <linux/module.h>
static struct snd_pcm *pcm;
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 int i2s_trigger(struct snd_pcm_substream *s, int cmd)
{
if (cmd == SNDRV_PCM_TRIGGER_START) {
unsigned int period_bytes = snd_pcm_lib_period_bytes(s);
/* 填入DMA控制器长度寄存器 */
dma_set_length(period_bytes);
}
return 0;
}
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 car_period_bytes_init(void)
{
int err;
struct snd_card *card;
err = snd_card_new(NULL, -1, "CarCard", THIS_MODULE, 0, &card);
if (err < 0)
return err;
err = snd_pcm_new(card, "CarPlay", 0, 1, 0, &pcm);
if (err < 0)
goto fail;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &i2s_ops);
strcpy(pcm->name, "Car PeriodBytes");
err = snd_card_register(card);
if (err < 0)
goto fail;
return 0;
fail:
snd_card_free(card);
return err;
}
static void __exit car_period_bytes_exit(void)
{
struct snd_card *card = snd_card_ref(-1);
if (card)
snd_card_free(card);
}
module_init(car_period_bytes_init);
module_exit(car_period_bytes_exit);
MODULE_LICENSE("GPL");
代码功能:触发阶段通过snd_pcm_lib_period_bytes得到周期字节数,直接填入DMA长度寄存器,无需手动计算。
🌻3.2 获取周期字节用于语音唤醒散页边界
-
应用场景
DSP需要知道每中断应处理的散页字节上限,避免越界。
-
用法实例
c
#include <sound/core.h>
#include <sound/pcm.h>
#include <linux/module.h>
static struct snd_pcm *pcm;
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 void vw_timer_fn(struct timer_list *t)
{
struct snd_pcm_substream *s = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
if (s && s->runtime) {
unsigned int period_bytes = snd_pcm_lib_period_bytes(s);
/* DSP处理不超过周期字节 */
dsp_process_slice(period_bytes);
snd_pcm_period_elapsed(s);
}
mod_timer(t, jiffies + HZ/250);
}
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_bytes_init(void)
{
int err;
struct snd_card *card;
static struct timer_list vw_timer;
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 PeriodBytes");
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_bytes_exit(void)
{
struct snd_card *card = snd_card_ref(-1);
if (card)
snd_card_free(card);
}
module_init(vw_period_bytes_init);
module_exit(vw_period_bytes_exit);
MODULE_LICENSE("GPL");
代码功能:定时器内通过snd_pcm_lib_period_bytes得到周期字节,作为DSP处理上限,避免溢出。
🌻3.3 获取周期字节用于USB微帧负载计算
-
应用场景
USB等时传输需要把周期字节拆分为多个微帧,每125us发送一次。
-
用法实例
c
#include <sound/core.h>
#include <sound/pcm.h>
#include <linux/module.h>
#include <linux/usb.h>
static struct snd_pcm *pcm;
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 void usb_iso_fill(struct urb *urb)
{
struct snd_pcm_substream *s = urb->context;
unsigned int period_bytes = snd_pcm_lib_period_bytes(s);
unsigned int micro_frame_bytes = period_bytes / 8; // 8微帧/周期
int i;
for (i = 0; i < 8; i++) {
dma_addr_t addr = snd_pcm_sgbuf_get_addr(s, i * micro_frame_bytes);
urb->iso_frame_desc[i].offset = i * micro_frame_bytes;
urb->iso_frame_desc[i].length = micro_frame_bytes;
}
}
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_bytes_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 PeriodBytes");
err = snd_card_register(card);
if (err < 0)
goto fail;
return 0;
fail:
snd_card_free(card);
return err;
}
static void __exit usb_period_bytes_exit(void)
{
struct snd_card *card = snd_card_ref(-1);
if (card)
snd_card_free(card);
}
module_init(usb_period_bytes_init);
module_exit(usb_period_bytes_exit);
MODULE_LICENSE("GPL");
代码功能:通过snd_pcm_lib_period_bytes得到周期字节后均分8份,每份地址通过snd_pcm_sgbuf_get_addr填入URB,实现微帧级精确传输。
🌻3.4 用法总结
| 代码关键字 | 功能描述 | 典型应用 |
|---|---|---|
| snd_pcm_lib_period_bytes DMA | 周期字节长度 | 车载I2S |
| snd_pcm_lib_period_bytes DSP | 处理边界 | 语音唤醒 |
| snd_pcm_lib_period_bytes URB | 微帧拆分 | USB等时 |