简介:CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课🚀
专题四:Android15快速自定义与集成音效实战课🚀
专题五:Android15音频策略实战课🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例)🚀
人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
🍉🍉🍉文章目录🍉🍉🍉
- 🌻1.前言
- 🌻2.应用场景和用法
- 2.1向环形缓冲区提交样本
- 2.2函数原型与返回语义
- 🌻3.调用流程剖析
- 3.1从公开接口分派到默认实现
- 3.2按分段位置写入并更新进度
- 🌻4.实战案例
- 4.1准备一个内存RingBuffer
- 4.2提交4个Frame并检查写入位置
- 4.3完整代码
- 🌻5.总结
🌻1.前言
本篇目的:理解
gst_audio_ring_buffer_commit()如何把输入样本写入GstAudioRingBuffer,分析它如何处理环形分段、采样位置和速率换算,并通过内存后端观察提交结果。
GstAudioRingBuffer保存供音频设备读取或写入的分段缓冲区。gst_audio_ring_buffer_commit()把调用者提供的样本写入指定位置,并更新样本位置和换算累加器。它不会打开设备,也不会直接把数据写入声卡。
🌻2.应用场景和用法
2.1向环形缓冲区提交样本
gst_audio_ring_buffer_commit()主要用于把上游已经准备好的音频样本提交到环形缓冲区。
| 场景 | 调用时提供的数据 | 作用 |
|---|---|---|
| 常规播放 | 输入样本数与相同数量的输出样本数 | 按样本位置复制到RingBuffer |
| 速率换算 | 输入样本数与不同的输出样本数 | 在RingBuffer写入过程中完成样本增减 |
| 反向播放 | 正数输入样本数与负数输出样本数 | 按反向顺序写入目标位置 |
| 分段写入 | 跨越一个或多个缓冲段的样本 | 自动处理段边界,必要时等待可写段 |
| 中断后续写 | 上次返回的样本位置和accum | 从尚未处理的数据继续提交 |
这里的“样本数”指音频Frame数量,不是字节数。对于立体声PCM,一个样本Frame包含左右声道在同一时刻的值。输入数据的内存布局必须与RingBuffer音频格式一致。
2.2函数原型与返回语义
函数原型如下:
guintgst_audio_ring_buffer_commit(GstAudioRingBuffer*buf,guint64*sample,guint8*data,gint in_samples,gint out_samples,gint*accum);| 参数 | 含义 |
|---|---|
buf | 已采集并包含可写内存的GstAudioRingBuffer |
sample | 输入输出参数,表示写入位置;提交后更新为新的样本位置 |
data | 待提交的输入样本数据 |
in_samples | 输入数据中的样本数 |
out_samples | 目标写入样本数;负数表示反向提交 |
accum | 速率换算的累加状态,首次调用前应设为0 |
返回值为已处理的输入样本数。返回值可能小于本次输入样本数,例如提交期间RingBuffer被停止或刷新。guint接口用(guint)-1表示错误。
当in_samples或out_samples为0时,函数直接返回in_samples,不会调用类的commit虚函数。正常提交时,基类会分派到GstAudioRingBufferClass.commit;默认实现负责分段写入、通道重排和按输入输出样本数进行速率换算。子类也可以提供自己的实现。
相邻接口的职责不同:
| 接口 | 职责 |
|---|---|
gst_audio_ring_buffer_acquire() | 申请并初始化RingBuffer资源 |
gst_audio_ring_buffer_commit() | 把样本写入RingBuffer内存 |
gst_audio_ring_buffer_read() | 从RingBuffer读取样本 |
gst_audio_ring_buffer_advance() | 通知RingBuffer设备侧已处理的分段 |
gst_audio_ring_buffer_release() | 停止并释放RingBuffer资源 |
🌻3.调用流程剖析
3.1从公开接口分派到默认实现
公开接口先检查buf是否为有效的GstAudioRingBuffer。当输入样本数或输出样本数为0时,它直接返回输入样本数。
其余情况根据对象的实际类型取得GstAudioRingBufferClass,再调用commit虚函数。基类默认实现是default_commit()。该实现要求RingBuffer内存和输入数据指针都有效,然后读取格式信息、每Frame字节数、每段大小和总段数。
默认实现根据sample计算首个写入段和段内偏移。写入长度超过当前段剩余空间时,会继续写到下一段。可写段尚未到达时调用等待逻辑;目标位置已经落后于设备处理位置时,则跳过落后的写入区域。
3.2按分段位置写入并更新进度
默认实现使用sample / samples_per_seg确定写入段,使用sample % samples_per_seg确定段内偏移。每写入一段可用空间,就增加样本位置;跨段后从下一段开头继续处理。
当in_samples和out_samples相同时,默认实现逐Frame复制。两者不同时,它依据输入输出数量和accum执行样本速率换算。out_samples为负数时,按反向播放路径读取输入数据并写入。
GstAudioBaseSink会在渲染音频时调用该接口。若返回值小于本次输入样本数,Sink会等待Preroll或恢复条件,再根据返回值调整数据偏移和剩余样本数。该循环让提交过程能够处理停止、刷新等中断情况。
默认实现只写入RingBuffer管理的内存。设备侧如何消费这些分段,由对应的RingBuffer子类负责。gst_audio_ring_buffer_commit()本身不会直接调用声卡驱动。
🌻4.实战案例
4.1准备一个内存RingBuffer
案例创建一个测试用GstAudioRingBuffer子类,以一段普通内存模拟设备缓冲区。子类只负责打开测试后端、按规格分配内存和释放内存,commit虚函数沿用基类默认实现。
音频格式为48kHz、S16、双声道。每个Frame占4字节。测试提交4个Frame,因此输入数据长度为16字节。
4.2提交4个Frame并检查写入位置
程序先打开并采集RingBuffer,再把sample和accum初始化为0,提交4个输入Frame和4个目标Frame。
成功后检查三项结果:
- 返回值为4,表示4个输入Frame均已处理。
sample更新为4,表示写入位置推进了4个Frame。- RingBuffer内存的前16字节与输入数据一致。
4.3完整代码
#include<gst/gst.h>#include<gst/audio/audio.h>#include<string.h>typedefstruct_TestRingBuffer{GstAudioRingBuffer parent;}TestRingBuffer;typedefstruct_TestRingBufferClass{GstAudioRingBufferClass parent_class;}TestRingBufferClass;G_DEFINE_TYPE(TestRingBuffer,test_ring_buffer,GST_TYPE_AUDIO_RING_BUFFER);/* 打开测试后端,不访问真实音频设备 */staticgbooleantest_open_device(GstAudioRingBuffer*buf){returnTRUE;}/* 按分段规格申请测试用RingBuffer内存 */staticgbooleantest_acquire(GstAudioRingBuffer*buf,GstAudioRingBufferSpec*spec){buf->size=spec->segsize*spec->segtotal;buf->memory=g_malloc0(buf->size);returnTRUE;}/* 释放测试后端分配的内存 */staticgbooleantest_release(GstAudioRingBuffer*buf){g_free(buf->memory);buf->memory=NULL;buf->size=0;returnTRUE;}/* 关闭测试后端 */staticgbooleantest_close_device(GstAudioRingBuffer*buf){returnTRUE;}staticvoidtest_ring_buffer_class_init(TestRingBufferClass*klass){GstAudioRingBufferClass*ring_class=GST_AUDIO_RING_BUFFER_CLASS(klass);ring_class->open_device=test_open_device;ring_class->acquire=test_acquire;ring_class->release=test_release;ring_class->close_device=test_close_device;}staticvoidtest_ring_buffer_init(TestRingBuffer*ring_buffer){}intmain(intargc,char*argv[]){TestRingBuffer*test_buffer;GstAudioRingBuffer*ring_buffer;GstAudioRingBufferSpec*spec;gint16 pcm[]={100,-100,200,-200,300,-300,400,-400};guint64 sample=0;gint accum=0;guint written;gboolean opened=FALSE;gboolean acquired=FALSE;gboolean ok=FALSE;gst_init(&argc,&argv);/* 创建测试用RingBuffer对象 */test_buffer=g_object_new(test_ring_buffer_get_type(),NULL);ring_buffer=GST_AUDIO_RING_BUFFER(test_buffer);spec=&ring_buffer->spec;/* 配置48kHz、S16、双声道的原始音频 */gst_audio_info_init(&spec->info);gst_audio_info_set_format(&spec->info,GST_AUDIO_FORMAT_S16,48000,2,NULL);spec->type=GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW;/* 每段1024字节,共4段 */spec->segsize=1024;spec->segtotal=4;spec->seglatency=-1;/* 采集前先打开设备 */if(!gst_audio_ring_buffer_open_device(ring_buffer))gotodone;opened=TRUE;/* 申请内存,并由基类初始化分段状态 */if(!gst_audio_ring_buffer_acquire(ring_buffer,spec))gotodone;acquired=TRUE;/* 首次提交时,样本位置和速率换算累加器都从0开始 */written=gst_audio_ring_buffer_commit(ring_buffer,&sample,(guint8*)pcm,4,4,&accum);/* 检查输入消费数量、写入位置和RingBuffer内存内容 */ok=written==4&&sample==4&&memcmp(ring_buffer->memory,pcm,sizeof(pcm))==0;done:/* 按资源创建顺序的逆序释放RingBuffer资源 */if(acquired)gst_audio_ring_buffer_release(ring_buffer);if(opened)gst_audio_ring_buffer_close_device(ring_buffer);gst_object_unref(ring_buffer);returnok?0:1;}🌻5.总结
gst_audio_ring_buffer_commit()根据样本位置和分段规格将输入数据提交到RingBuffer内存,并更新写入位置与速率换算状态。