简介: CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中...... 】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中...... 】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
更多原创,欢迎关注:Android系统攻城狮

🍉🍉🍉文章目录🍉🍉🍉
🌻1.前言
本篇目的:理解
gst_audio_ring_buffer_prepare_read()如何根据RingBuffer当前位置返回分段索引、数据指针和长度,并通过实战观察回调填充与分段推进。
GstAudioRingBuffer把音频数据组织成多个固定大小的分段。设备子类需要取得当前分段的地址和长度,再把数据交给设备读写。gst_audio_ring_buffer_prepare_read()负责定位分段并返回这组信息;它本身不搬运音频数据,也不推进分段计数。
#mermaid-svg-l4aPiotowsXukbA1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-l4aPiotowsXukbA1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-l4aPiotowsXukbA1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-l4aPiotowsXukbA1 .error-icon{fill:#552222;}#mermaid-svg-l4aPiotowsXukbA1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-l4aPiotowsXukbA1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-l4aPiotowsXukbA1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-l4aPiotowsXukbA1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-l4aPiotowsXukbA1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-l4aPiotowsXukbA1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-l4aPiotowsXukbA1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-l4aPiotowsXukbA1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-l4aPiotowsXukbA1 .marker.cross{stroke:#333333;}#mermaid-svg-l4aPiotowsXukbA1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-l4aPiotowsXukbA1 p{margin:0;}#mermaid-svg-l4aPiotowsXukbA1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-l4aPiotowsXukbA1 .cluster-label text{fill:#333;}#mermaid-svg-l4aPiotowsXukbA1 .cluster-label span{color:#333;}#mermaid-svg-l4aPiotowsXukbA1 .cluster-label span p{background-color:transparent;}#mermaid-svg-l4aPiotowsXukbA1 .label text,#mermaid-svg-l4aPiotowsXukbA1 span{fill:#333;color:#333;}#mermaid-svg-l4aPiotowsXukbA1 .node rect,#mermaid-svg-l4aPiotowsXukbA1 .node circle,#mermaid-svg-l4aPiotowsXukbA1 .node ellipse,#mermaid-svg-l4aPiotowsXukbA1 .node polygon,#mermaid-svg-l4aPiotowsXukbA1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-l4aPiotowsXukbA1 .rough-node .label text,#mermaid-svg-l4aPiotowsXukbA1 .node .label text,#mermaid-svg-l4aPiotowsXukbA1 .image-shape .label,#mermaid-svg-l4aPiotowsXukbA1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-l4aPiotowsXukbA1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-l4aPiotowsXukbA1 .rough-node .label,#mermaid-svg-l4aPiotowsXukbA1 .node .label,#mermaid-svg-l4aPiotowsXukbA1 .image-shape .label,#mermaid-svg-l4aPiotowsXukbA1 .icon-shape .label{text-align:center;}#mermaid-svg-l4aPiotowsXukbA1 .node.clickable{cursor:pointer;}#mermaid-svg-l4aPiotowsXukbA1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-l4aPiotowsXukbA1 .arrowheadPath{fill:#333333;}#mermaid-svg-l4aPiotowsXukbA1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-l4aPiotowsXukbA1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-l4aPiotowsXukbA1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l4aPiotowsXukbA1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-l4aPiotowsXukbA1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l4aPiotowsXukbA1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-l4aPiotowsXukbA1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-l4aPiotowsXukbA1 .cluster text{fill:#333;}#mermaid-svg-l4aPiotowsXukbA1 .cluster span{color:#333;}#mermaid-svg-l4aPiotowsXukbA1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-l4aPiotowsXukbA1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-l4aPiotowsXukbA1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-l4aPiotowsXukbA1 .icon-shape,#mermaid-svg-l4aPiotowsXukbA1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-l4aPiotowsXukbA1 .icon-shape p,#mermaid-svg-l4aPiotowsXukbA1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-l4aPiotowsXukbA1 .icon-shape .label rect,#mermaid-svg-l4aPiotowsXukbA1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-l4aPiotowsXukbA1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-l4aPiotowsXukbA1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-l4aPiotowsXukbA1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 已采集的RingBuffer
gst_audio_ring_buffer_prepare_read
定位当前分段
返回分段地址和长度
设备子类处理数据
🌻2.应用场景和用法
2.1音频设备如何取得当前分段
gst_audio_ring_buffer_prepare_read()主要供音频设备子类使用。播放端取得RingBuffer中的一段音频,再交给设备写入;录音端取得目标分段,再让设备把采集到的数据写入该区域。
调用者通常先确认RingBuffer已采集并拥有有效内存,再调用该接口取得当前分段。设备处理完一个分段后,调用gst_audio_ring_buffer_advance()推进完成计数。下一次调用会据此定位新的分段。
如果RingBuffer没有设置填充回调,函数要求状态已经是GST_AUDIO_RING_BUFFER_STATE_STARTED。设置了回调时,函数会调用回调填充本次返回的分段内存。
2.2参数、返回值与相邻接口
函数原型如下:
c
gboolean
gst_audio_ring_buffer_prepare_read (GstAudioRingBuffer *buf,
gint *segment,
guint8 **readptr,
gint *len);
| 参数 | 含义 |
|---|---|
buf |
已采集并包含有效分段内存的RingBuffer |
segment |
输出参数,返回当前分段编号 |
readptr |
输出参数,返回当前分段的内存地址 |
len |
输出参数,返回当前分段的字节数 |
返回TRUE表示分段信息已写入输出参数。返回FALSE通常表示没有填充回调且RingBuffer尚未启动。无论是否设置回调,buf、segment、readptr和len都必须有效,RingBuffer内存也必须存在。
readptr指向RingBuffer内部内存,调用者不能释放它。函数返回的len是字节数,不是Frame数。相邻接口的分工如下:
| 接口 | 职责 |
|---|---|
gst_audio_ring_buffer_set_callback() |
设置分段填充回调 |
gst_audio_ring_buffer_prepare_read() |
定位当前分段并返回内存地址和长度 |
gst_audio_ring_buffer_advance() |
记录设备已处理的分段数 |
gst_audio_ring_buffer_read() |
从RingBuffer读取指定样本位置的数据 |
🌻3.调用流程剖析
3.1根据分段计数计算地址
函数先检查buf是否为有效RingBuffer。如果没有设置callback,它还会检查状态是否为GST_AUDIO_RING_BUFFER_STATE_STARTED;未启动时直接返回FALSE。
通过检查后,函数读取原子变量segdone,用它和spec.segtotal计算当前分段:
c
*segment = segdone % buf->spec.segtotal;
*len = buf->spec.segsize;
*readptr = buf->memory + *segment * *len;
segdone表示已经完成处理的分段累计数。取模后得到环形缓冲区中的当前分段编号。分段地址则由RingBuffer起始地址、分段编号和每段字节数共同确定。
#mermaid-svg-Bj5roYXgfBwNWouG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bj5roYXgfBwNWouG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bj5roYXgfBwNWouG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bj5roYXgfBwNWouG .error-icon{fill:#552222;}#mermaid-svg-Bj5roYXgfBwNWouG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bj5roYXgfBwNWouG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bj5roYXgfBwNWouG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bj5roYXgfBwNWouG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bj5roYXgfBwNWouG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bj5roYXgfBwNWouG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bj5roYXgfBwNWouG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bj5roYXgfBwNWouG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bj5roYXgfBwNWouG .marker.cross{stroke:#333333;}#mermaid-svg-Bj5roYXgfBwNWouG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bj5roYXgfBwNWouG p{margin:0;}#mermaid-svg-Bj5roYXgfBwNWouG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bj5roYXgfBwNWouG .cluster-label text{fill:#333;}#mermaid-svg-Bj5roYXgfBwNWouG .cluster-label span{color:#333;}#mermaid-svg-Bj5roYXgfBwNWouG .cluster-label span p{background-color:transparent;}#mermaid-svg-Bj5roYXgfBwNWouG .label text,#mermaid-svg-Bj5roYXgfBwNWouG span{fill:#333;color:#333;}#mermaid-svg-Bj5roYXgfBwNWouG .node rect,#mermaid-svg-Bj5roYXgfBwNWouG .node circle,#mermaid-svg-Bj5roYXgfBwNWouG .node ellipse,#mermaid-svg-Bj5roYXgfBwNWouG .node polygon,#mermaid-svg-Bj5roYXgfBwNWouG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bj5roYXgfBwNWouG .rough-node .label text,#mermaid-svg-Bj5roYXgfBwNWouG .node .label text,#mermaid-svg-Bj5roYXgfBwNWouG .image-shape .label,#mermaid-svg-Bj5roYXgfBwNWouG .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bj5roYXgfBwNWouG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bj5roYXgfBwNWouG .rough-node .label,#mermaid-svg-Bj5roYXgfBwNWouG .node .label,#mermaid-svg-Bj5roYXgfBwNWouG .image-shape .label,#mermaid-svg-Bj5roYXgfBwNWouG .icon-shape .label{text-align:center;}#mermaid-svg-Bj5roYXgfBwNWouG .node.clickable{cursor:pointer;}#mermaid-svg-Bj5roYXgfBwNWouG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bj5roYXgfBwNWouG .arrowheadPath{fill:#333333;}#mermaid-svg-Bj5roYXgfBwNWouG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bj5roYXgfBwNWouG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bj5roYXgfBwNWouG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bj5roYXgfBwNWouG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bj5roYXgfBwNWouG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bj5roYXgfBwNWouG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bj5roYXgfBwNWouG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bj5roYXgfBwNWouG .cluster text{fill:#333;}#mermaid-svg-Bj5roYXgfBwNWouG .cluster span{color:#333;}#mermaid-svg-Bj5roYXgfBwNWouG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bj5roYXgfBwNWouG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bj5roYXgfBwNWouG rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bj5roYXgfBwNWouG .icon-shape,#mermaid-svg-Bj5roYXgfBwNWouG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bj5roYXgfBwNWouG .icon-shape p,#mermaid-svg-Bj5roYXgfBwNWouG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bj5roYXgfBwNWouG .icon-shape .label rect,#mermaid-svg-Bj5roYXgfBwNWouG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bj5roYXgfBwNWouG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bj5roYXgfBwNWouG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bj5roYXgfBwNWouG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 读取segdone
对segtotal取模
得到当前segment
计算readptr和len
3.2回调填充与分段推进
如果设置了callback,函数会在计算出readptr和len后,直接调用该回调:
c
if (buf->callback)
buf->callback (buf, *readptr, *len, buf->cb_data);
回调拿到的是当前分段地址和字节数,可以在这块内存中填入音频数据。该调用发生在gst_audio_ring_buffer_prepare_read()返回之前。
函数不会在内部递增segdone。设备子类完成一个分段的处理后,需调用gst_audio_ring_buffer_advance()更新计数。下一次调用prepare_read()再根据新的计数定位分段。
GstAudioSink和GstAudioSrc的处理循环都使用该接口取得分段内存。Sink从分段中读取数据并写入音频设备;Src把设备读出的数据写入分段。实际设备读写由各自的设备回调完成。
🌻4.实战案例
4.1准备测试用RingBuffer
下面创建一个内存后端RingBuffer子类。它不连接真实音频设备:打开操作直接成功,采集操作分配一块内存。RingBuffer包含4个分段,每段16字节,填充回调会把当前分段写成0x5A。
该示例重点观察prepare_read()返回的分段编号、地址、长度和回调效果。
4.2读取当前分段并推进位置
测试按以下步骤执行:
- 创建RingBuffer并设置4段、每段16字节的规格。
- 打开和采集RingBuffer资源,再设置分段填充回调。
- 调用
prepare_read(),检查首段编号为0、长度为16字节,且首字节被回调写为0x5A。 - 调用
gst_audio_ring_buffer_advance()推进一个分段,再次调用prepare_read(),检查分段编号变为1,地址移动16字节。
这组检查可以确认:prepare_read()根据segdone定位分段,并同步调用填充回调;分段计数由advance()单独推进。
4.3完整代码
c
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <string.h>
typedef struct _TestRingBuffer
{
GstAudioRingBuffer parent;
} TestRingBuffer;
typedef struct _TestRingBufferClass
{
GstAudioRingBufferClass parent_class;
} TestRingBufferClass;
G_DEFINE_TYPE (TestRingBuffer, test_ring_buffer,
GST_TYPE_AUDIO_RING_BUFFER);
/* 模拟设备打开,不连接真实硬件 */
static gboolean
test_open_device (GstAudioRingBuffer *buf)
{
return TRUE;
}
/* 为测试RingBuffer分配分段内存 */
static gboolean
test_acquire (GstAudioRingBuffer *buf,
GstAudioRingBufferSpec *spec)
{
buf->size = spec->segsize * spec->segtotal;
buf->memory = g_malloc0 (buf->size);
return TRUE;
}
/* 释放测试后端申请的内存 */
static gboolean
test_release (GstAudioRingBuffer *buf)
{
g_free (buf->memory);
buf->memory = NULL;
buf->size = 0;
return TRUE;
}
/* 模拟设备关闭 */
static gboolean
test_close_device (GstAudioRingBuffer *buf)
{
return TRUE;
}
/* 填充prepare_read返回的当前分段 */
static void
fill_segment (GstAudioRingBuffer *buf,
guint8 *data,
guint len,
gpointer user_data)
{
guint *callback_count = user_data;
(*callback_count)++;
memset (data, 0x5A, len);
}
static void
test_ring_buffer_class_init (TestRingBufferClass *klass)
{
GstAudioRingBufferClass *ring_buffer_class =
GST_AUDIO_RING_BUFFER_CLASS (klass);
ring_buffer_class->open_device = test_open_device;
ring_buffer_class->acquire = test_acquire;
ring_buffer_class->release = test_release;
ring_buffer_class->close_device = test_close_device;
}
static void
test_ring_buffer_init (TestRingBuffer *ring_buffer)
{
}
int
main (int argc, char *argv[])
{
TestRingBuffer *test_buffer;
GstAudioRingBuffer *ring_buffer;
GstAudioRingBufferSpec *spec;
gint segment = -1;
gint len = 0;
guint8 *readptr = NULL;
guint callback_count = 0;
gboolean opened = FALSE;
gboolean acquired = FALSE;
gboolean ok = FALSE;
gst_init (&argc, &argv);
test_buffer = g_object_new (
test_ring_buffer_get_type (),
NULL);
ring_buffer = GST_AUDIO_RING_BUFFER (test_buffer);
spec = &ring_buffer->spec;
/* 设置测试规格,分段大小为16字节,总计4段 */
memset (spec, 0, sizeof (*spec));
gst_audio_info_init (&spec->info);
if (!gst_audio_info_set_format (
&spec->info,
GST_AUDIO_FORMAT_S16,
48000,
2,
NULL))
goto done;
spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW;
spec->segsize = 16;
spec->segtotal = 4;
spec->seglatency = -1;
/* prepare_read需要RingBuffer已经打开并采集资源 */
if (!gst_audio_ring_buffer_open_device (ring_buffer))
goto done;
opened = TRUE;
if (!gst_audio_ring_buffer_acquire (ring_buffer, spec))
goto done;
acquired = TRUE;
/* 设置回调,验证每次prepare_read都会填充当前段 */
gst_audio_ring_buffer_set_callback (
ring_buffer,
fill_segment,
&callback_count);
if (!gst_audio_ring_buffer_prepare_read (
ring_buffer,
&segment,
&readptr,
&len))
goto done;
/* 首次读取应定位第0段,回调写入0x5A */
if (segment != 0
|| len != 16
|| readptr != ring_buffer->memory
|| readptr[0] != 0x5A
|| callback_count != 1)
goto done;
/* 模拟设备处理完一段,再检查环形位置向前移动 */
gst_audio_ring_buffer_advance (ring_buffer, 1);
if (!gst_audio_ring_buffer_prepare_read (
ring_buffer,
&segment,
&readptr,
&len))
goto done;
ok = segment == 1
&& len == 16
&& readptr == ring_buffer->memory + 16
&& readptr[0] == 0x5A
&& callback_count == 2;
done:
/* 先移除回调,再释放其使用的栈上计数器 */
if (ring_buffer)
gst_audio_ring_buffer_set_callback (ring_buffer, NULL, NULL);
if (acquired)
gst_audio_ring_buffer_release (ring_buffer);
if (opened)
gst_audio_ring_buffer_close_device (ring_buffer);
if (ring_buffer)
gst_object_unref (ring_buffer);
return ok ? 0 : 1;
}
🌻5.总结
gst_audio_ring_buffer_prepare_read()根据segdone返回当前分段的内存地址和长度,并在设置了填充回调时同步填充该分段,但不会自行推进分段计数。