简介: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按sample位置定位数据段
- 3.2由音频源元素接收读取结果
- 🌻4.实战案例
- 4.1准备内存RingBuffer测试对象
- 4.2写入一段数据并读取
- 4.3完整代码
- 🌻5.总结
🌻1.前言
本篇目的:理解
gst_audio_ring_buffer_read()如何根据sample位置从环形缓冲区读取音频数据,分析分段等待、数据复制、静音补齐和时间戳返回,并通过内存后端示例验证读取结果。
GstAudioRingBuffer把设备数据组织成多个固定大小的段。gst_audio_ring_buffer_read()按照sample位置找到对应段,把数据复制到调用者提供的内存中。读取位置尚未就绪时,它可能等待;读取位置已经落后于设备时,它会用静音数据补齐。
这个函数负责RingBuffer中的分段读取,不负责创建输出GstBuffer,也不负责决定音频流的时间戳策略。上层音频源元素会调用它,再将读取结果包装成输出Buffer。
🌻2.应用场景和用法
2.1从环形缓冲区读取音频数据
gst_audio_ring_buffer_read()主要用于音频源元素从设备环形缓冲区中读取数据。读取位置以sample表示,读取长度也以sample计数;在音频RingBuffer中,一个sample对应一个时刻的全部声道数据,也就是一个音频Frame。
| 使用场景 | 读取行为 | 需要关注的结果 |
|---|---|---|
| 录音源输出数据 | 从指定sample位置读取若干sample | 返回实际读取数量 |
| 读取跨越多个段的数据 | 复制当前段后继续处理下一段 | 目标内存需要容纳完整长度 |
| 读取位置尚未就绪 | 等待设备处理新的段 | RingBuffer启动、暂停或停止状态 |
| 读取位置已被设备覆盖 | 从静音段补齐数据 | 读取数量可能仍达到请求长度 |
| 声道顺序需要调整 | 将设备声道排列转换为GStreamer排列 | 输出数据声道顺序 |
调用前,RingBuffer必须已经采集资源,buf->memory和data都必须有效。len是sample数量,不是字节数。目标内存至少需要len × bpf字节,其中bpf是每个音频Frame占用的字节数。
2.2函数原型与返回语义
函数原型如下:
guintgst_audio_ring_buffer_read(GstAudioRingBuffer*buf,guint64 sample,guint8*data,guint len,GstClockTime*timestamp);| 参数或返回值 | 含义 |
|---|---|
buf | 已采集的音频环形缓冲区 |
sample | 需要读取的起始sample位置 |
data | 接收音频数据的目标内存 |
len | 请求读取的sample数量 |
timestamp | 可选输出参数,接收读取段对应的时间戳 |
| 返回值 | 实际读取的sample数量;发生参数检查错误时返回无符号形式的-1 |
函数会按sample / samples_per_seg计算段号,并按sample % samples_per_seg计算段内偏移。每次从一个段复制数据后,会更新sample位置和目标地址,再继续处理剩余长度。
相邻接口的职责不同:
| 接口 | 职责 |
|---|---|
gst_audio_ring_buffer_read() | 从环形缓冲区读取sample数据 |
gst_audio_ring_buffer_prepare_read() | 返回当前段的内存地址,主要供子类使用 |
gst_audio_ring_buffer_set_timestamp() | 设置指定段的时间戳 |
gst_audio_ring_buffer_advance() | 通知RingBuffer又处理完成了多少段 |
gst_audio_base_src_create() | 分配输出Buffer并调用read接口填充数据 |
🌻3.调用流程剖析
3.1按sample位置定位数据段
函数先检查buf、RingBuffer内存和目标指针。之后读取段大小、段总数、声道数、BPF和每段sample数量等参数。
每轮循环都会根据当前sample位置重新计算段号和段内偏移。由于实际存储是环形的,确定段可读后,段号会再对segtotal取模,映射到实际内存中的段。
函数通过segdone - segbase得到当前已经处理到的相对段位置,再与待读段号比较:
diff > 0:待读段已经可读。diff <= 0:待读段尚未可读,函数进入等待逻辑。diff >= segtotal:设备已经覆盖了待读段,函数把静音数据复制到目标内存。
diff <= 0时,内部等待逻辑会检查RingBuffer是否处于运行状态。若允许启动,等待逻辑可以启动RingBuffer;若数据仍未到达,则等待段处理信号。停止或刷新状态会让等待结束,read接口返回当前已经读取的数量,因此返回值可能小于len。
3.2由音频源元素接收读取结果
GstAudioBaseSrc在创建输出Buffer时,先检查RingBuffer是否已经采集,再根据协商格式计算BPF和读取长度。它将Buffer映射为可写内存后,调用gst_audio_ring_buffer_read()填充数据。
如果read接口返回的数量小于请求值,音频源会检查RingBuffer状态。遇到错误状态时走错误处理;如果只是播放状态中断,则等待重新运行,再从未读完的sample位置继续读取。
读取完成后,GstAudioBaseSrc根据sample位置、采样率和RingBuffer返回的时间戳设置输出Buffer的时间信息,并根据实际情况标记DISCONT。因此,read接口提供的是数据段读取结果,上层音频源负责将其整理为GStreamer数据流。
🌻4.实战案例
4.1准备内存RingBuffer测试对象
下面用一个简单的GstAudioRingBuffer子类模拟音频设备。子类申请一块内存作为环形缓冲区,避免依赖声卡。规格使用S16LE、48kHz、双声道,每段1024字节,共4段。
每个sample包含左右两个声道,每声道16位,因此BPF为4字节。每段可容纳256个sample。
4.2写入一段数据并读取
测试程序按以下顺序执行:
- 初始化RingBuffer规格并打开模拟设备。
- 调用采集接口,让子类申请内存,再由基类初始化段状态。
- 设置段0时间戳,启动RingBuffer,并模拟设备完成一个段。
- 从sample 0读取4个sample到目标数组。
- 检查读取数量、首尾样本值和返回时间戳,再释放资源。
模拟设备通过gst_audio_ring_buffer_advance()报告段处理进度。该调用只用于测试中模拟设备行为;真实设备子类会根据实际硬件处理进度通知RingBuffer。
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_ring_buffer_open_device(GstAudioRingBuffer*buf){returnTRUE;}/* 测试后端为音频段分配内存 */staticgbooleantest_ring_buffer_acquire(GstAudioRingBuffer*buf,GstAudioRingBufferSpec*spec){buf->size=spec->segsize*spec->segtotal;buf->memory=g_malloc0(buf->size);returnTRUE;}/* 释放测试后端申请的内存 */staticgbooleantest_ring_buffer_release(GstAudioRingBuffer*buf){g_free(buf->memory);buf->memory=NULL;buf->size=0;returnTRUE;}/* 测试后端模拟设备启动和停止 */staticgbooleantest_ring_buffer_start(GstAudioRingBuffer*buf){returnTRUE;}staticgbooleantest_ring_buffer_stop(GstAudioRingBuffer*buf){returnTRUE;}/* 测试后端模拟设备关闭 */staticgbooleantest_ring_buffer_close_device(GstAudioRingBuffer*buf){returnTRUE;}staticvoidtest_ring_buffer_class_init(TestRingBufferClass*klass){GstAudioRingBufferClass*ring_buffer_class=GST_AUDIO_RING_BUFFER_CLASS(klass);ring_buffer_class->open_device=test_ring_buffer_open_device;ring_buffer_class->acquire=test_ring_buffer_acquire;ring_buffer_class->release=test_ring_buffer_release;ring_buffer_class->start=test_ring_buffer_start;ring_buffer_class->stop=test_ring_buffer_stop;ring_buffer_class->close_device=test_ring_buffer_close_device;}staticvoidtest_ring_buffer_init(TestRingBuffer*ring_buffer){}intmain(intargc,char*argv[]){TestRingBuffer*test_buffer;GstAudioRingBuffer*ring_buffer;GstAudioRingBufferSpec*spec;gint16 source_samples[]={100,200,300,400,500,600,700,800};gint16 output_samples[8]={0};GstClockTime timestamp=GST_CLOCK_TIME_NONE;guint read_samples;gboolean opened=FALSE;gboolean acquired=FALSE;gboolean started=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;/* 设置S16双声道格式和环形段大小 */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))gotodone;spec->type=GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW;spec->segsize=1024;spec->segtotal=4;spec->seglatency=-1;/* read前必须先打开并采集RingBuffer */if(!gst_audio_ring_buffer_open_device(ring_buffer))gotodone;opened=TRUE;if(!gst_audio_ring_buffer_acquire(ring_buffer,spec))gotodone;acquired=TRUE;/* 将测试样本写入第一个段 */memcpy(ring_buffer->memory,source_samples,sizeof(source_samples));/* 保存第一个段的时间戳 */gst_audio_ring_buffer_set_timestamp(ring_buffer,0,100*GST_MSECOND);/* 允许启动,模拟设备完成一个段 */gst_audio_ring_buffer_may_start(ring_buffer,TRUE);if(!gst_audio_ring_buffer_start(ring_buffer))gotodone;started=TRUE;gst_audio_ring_buffer_advance(ring_buffer,1);/* 从sample 0读取4个sample,即4个双声道Frame */read_samples=gst_audio_ring_buffer_read(ring_buffer,0,(guint8*)output_samples,4,×tamp);/* 检查读取长度、样本内容和所在段时间戳 */ok=read_samples==4&&output_samples[0]==100&&output_samples[7]==800&×tamp==100*GST_MSECOND;done:/* 依次停止、释放采集资源并关闭设备 */if(started)gst_audio_ring_buffer_stop(ring_buffer);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_read()根据sample位置定位环形段,复制可读音频或用静音补齐,并返回实际读取数量及对应段的时间戳。