简介: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接收Buffer并更新输出状态
- 3.2从解码子类进入下游输出
- 🌻4.实战案例
- 4.1准备最小音频解码器
- 4.2提交输出Buffer并验证结果
- 4.3完整代码
- 🌻5.总结
🌻1.前言
本篇目的:理解
gst_audio_decoder_finish_frame()如何接收解码后的音频Buffer,更新解码器输出状态,并把数据交给下游,同时通过最小解码器验证Buffer所有权、输出帧数和返回值之间的关系。
GstAudioDecoder基类负责处理输入缓存、格式协商、时间戳和下游输出。解码器子类只需要在handle_frame()中完成编码数据解析,然后把得到的原始音频Buffer提交给基类。
gst_audio_decoder_finish_frame()就是这个提交动作的核心接口。
它接收三个关键信息:
- 当前解码器对象。
- 已经包含原始音频数据的
GstBuffer。 - 该Buffer表示的解码后音频Frame数量。
函数调用后,基类会接管Buffer,并根据当前输出状态完成后续处理。它不负责执行具体的AAC、MP3或Opus解码,也不负责创建解码器子类。
🌻2.应用场景和用法
2.1提交解码结果和丢弃帧
gst_audio_decoder_finish_frame()主要用于以下场景。
| 使用场景 | buf参数 | frames参数 | 结果 |
|---|---|---|---|
| 正常输出解码数据 | 有效音频Buffer | 输出Buffer包含的音频Frame数 | 基类处理并推送音频数据 |
| 解码器产生静音或空结果 | NULL | 被消耗的Frame数 | 不推送Buffer,但推进输出时间 |
| 丢弃前导帧 | NULL | 需要跳过的Frame数 | 这些Frame被标记为已处理 |
| 多次输出 | 不同音频Buffer | 每次对应的输出Frame数 | 子类可以分多次提交结果 |
| 下游出现错误 | 有效音频Buffer | 正常Frame数 | 返回对应的GstFlowReturn |
这里的frames表示解码后音频数据包含的音频Frame数量,不是输入压缩Buffer的字节数,也不是编码数据包的数量。
例如,输出格式为S16立体声时,一个音频Frame包含左右两个采样值。若输出Buffer包含20个立体声Frame,那么frames应当传入20,而不是40个采样值或80个字节。
当buf为NULL时,函数不会向下游发送数据。这种用法适合处理解码器预热、前导静音或需要跳过的输出区间。
2.2函数原型与返回语义
函数原型如下:
GstFlowReturngst_audio_decoder_finish_frame(GstAudioDecoder*dec,GstBuffer*buf,gint frames);| 参数 | 含义 |
|---|---|
dec | 已经初始化并处于处理状态的GstAudioDecoder对象 |
buf | 解码后的原始音频Buffer,函数接管其所有权,也可以传入NULL |
frames | buf中包含的解码后音频Frame数量 |
函数返回GstFlowReturn,常见结果包括:
GST_FLOW_OK:输出已经被基类接受。GST_FLOW_FLUSHING:解码器正在刷新或停止处理。GST_FLOW_EOS:下游已经结束。- 其他负值:下游或内部处理发生错误。
最小调用方式如下:
GstBuffer*output;GstFlowReturn ret;output=gst_audio_decoder_allocate_output_buffer(decoder,output_size);/* output交给finish_frame后,调用者不能再次释放 */ret=gst_audio_decoder_finish_frame(decoder,output,output_frames);returnret;调用前必须满足以下条件:
decoder是有效的GstAudioDecoder对象。- 当
buf包含有效数据时,源Pad输出Caps已经设置。 frames与输出Buffer中的音频数据数量一致。buf没有被其他代码提前释放。
调用成功后,finish_frame()会接管buf的所有权。handle_frame()收到的输入Buffer也可能因为这次调用而失效,因此子类不能在调用后继续访问输入Buffer。
几个相邻接口的职责不同:
| 接口 | 主要职责 |
|---|---|
handle_frame() | 接收输入编码帧并执行具体解码 |
gst_audio_decoder_set_output_format() | 设置解码器输出音频格式 |
gst_audio_decoder_allocate_output_buffer() | 按输出格式分配音频Buffer |
gst_audio_decoder_finish_frame() | 提交解码后的音频Frame |
gst_audio_decoder_finish_subframe() | 在一个输入帧内提交多个输出子帧 |
gst_audio_decoder_push_event() | 向下游发送事件 |
🌻3.调用流程剖析
3.1接收Buffer并更新输出状态
gst_audio_decoder_finish_frame()的核心职责不是解码,而是把子类生成的结果交给基类输出流程。
当buf有效时,基类需要确认当前输出Caps已经设置,然后处理Buffer的时间戳、持续时间和输出位置,最后尝试把数据推送到下游。
当buf为NULL时,基类不生成下游Buffer,只根据frames推进已经消耗的输出Frame数量。
可以把函数内部动作概括为:
检查解码器和参数 ↓ 判断是否存在输出Buffer ↓ 处理输出Frame数量 ↓ 更新输出时间状态 ↓ 尝试向下游推送或丢弃 ↓ 返回GstFlowReturnframes是这条流程中的关键参数。它让基类知道当前调用代表了多少解码后音频数据,从而计算后续输出的时间位置。
需要特别区分两个对象:
handle_frame()中的输入Buffer是压缩或编码数据。finish_frame()中的输出Buffer是解码后的原始音频数据。
frames描述的是第二个Buffer中的音频Frame数量。
3.2从解码子类进入下游输出
完整链路通常从基类调用子类的handle_frame()开始。
子类完成编码数据解析后,通常按以下顺序处理:
- 根据输出音频格式申请Buffer。
- 将PCM采样写入Buffer。
- 调用
gst_audio_decoder_finish_frame()。 - 将返回的
GstFlowReturn继续返回给基类。
基类接管Buffer后,还可能执行以下工作:
- 根据上游输入时间戳计算输出时间戳。
- 根据输出Frame数量计算Buffer持续时间。
- 调用
pre_push虚函数进行推送前处理。 - 根据配置聚合音频Buffer。
- 调用源Pad的推送逻辑并返回下游结果。
这些工作由GstAudioDecoder基类负责,解码器子类不应该重复实现。
如果一个输入编码帧对应多个输出音频区间,子类可以多次调用gst_audio_decoder_finish_subframe(),也可以根据解码器设计使用多次gst_audio_decoder_finish_frame()。如果只是普通的一进一出解码流程,直接调用finish_frame()即可。
finish_frame()返回的不是“解码是否成功”的普通布尔值,而是完整的下游流控结果。子类应当把这个返回值继续交给调用者处理,不能只返回GST_FLOW_OK。
🌻4.实战案例
4.1准备最小音频解码器
本案例定义一个测试用GstAudioDecoder子类。
它接收audio/x-test-custom格式的输入,并输出44100Hz、单声道、S16格式的原始音频。handle_frame()不实现真实的AAC或MP3解析,只申请一个包含单个S16采样的输出Buffer,然后调用gst_audio_decoder_finish_frame()提交一个音频Frame。
测试重点观察三件事:
finish_frame()是否接管输出Buffer。frames=1是否能够推动一个输出音频Frame。- 返回值是否能够通过Harness传递到下游。
4.2提交输出Buffer并验证结果
测试步骤如下:
- 创建解码器子类和
GstHarness,设置输入Caps。 - 向Harness提交一个输入Buffer,子类在
handle_frame()中申请输出Buffer并调用finish_frame()。 - 从Harness拉取输出Buffer,检查输出大小和流控结果。
由于输出格式是S16单声道,一个音频Frame占用2字节。因此,测试代码申请2字节输出Buffer,并把frames设置为1。
如果finish_frame()执行成功,Harness可以拉取到一个下游输出Buffer。代码还会检查Buffer大小,确认提交的数据确实按照输出格式生成。
4.3完整代码
#include<gst/gst.h>#include<gst/audio/audio.h>#include<gst/check/gstharness.h>typedefstruct_TestAudioDecoder{GstAudioDecoder parent;}TestAudioDecoder;typedefstruct_TestAudioDecoderClass{GstAudioDecoderClass parent_class;}TestAudioDecoderClass;G_DEFINE_TYPE(TestAudioDecoder,test_audio_decoder,GST_TYPE_AUDIO_DECODER);/* 定义解码器的输入和输出Pad能力 */staticGstStaticPadTemplate decoder_sink_template=GST_STATIC_PAD_TEMPLATE("sink",GST_PAD_SINK,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-test-custom"));staticGstStaticPadTemplate decoder_src_template=GST_STATIC_PAD_TEMPLATE("src",GST_PAD_SRC,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-raw"));/* 设置解码器输出为44100Hz、单声道、S16 */staticgbooleantest_audio_decoder_set_format(GstAudioDecoder*decoder,GstCaps*caps){GstAudioInfo info;gst_audio_info_set_format(&info,GST_AUDIO_FORMAT_S16,44100,1,NULL);returngst_audio_decoder_set_output_format(decoder,&info);}/* 生成一个原始音频Frame并提交给AudioDecoder基类 */staticGstFlowReturntest_audio_decoder_handle_frame(GstAudioDecoder*decoder,GstBuffer*input){GstBuffer*output;GstMapInfo map;if(input==NULL)returnGST_FLOW_OK;/* 单声道S16每个Frame占用2字节 */output=gst_audio_decoder_allocate_output_buffer(decoder,sizeof(gint16));if(output==NULL)returnGST_FLOW_ERROR;/* 写入一个可观察的PCM采样值 */if(!gst_buffer_map(output,&map,GST_MAP_WRITE)){gst_buffer_unref(output);returnGST_FLOW_ERROR;}*(gint16*)map.data=0;gst_buffer_unmap(output,&map);/* * finish_frame会接管output的所有权。 * frames=1表示这个Buffer包含一个解码后的音频Frame。 */returngst_audio_decoder_finish_frame(decoder,output,1);}staticvoidtest_audio_decoder_class_init(TestAudioDecoderClass*klass){GstElementClass*element_class=GST_ELEMENT_CLASS(klass);GstAudioDecoderClass*decoder_class=GST_AUDIO_DECODER_CLASS(klass);gst_element_class_add_static_pad_template(element_class,&decoder_sink_template);gst_element_class_add_static_pad_template(element_class,&decoder_src_template);gst_element_class_set_metadata(element_class,"Test Audio Decoder","Decoder/Audio","finish_frame test decoder","GStreamer");decoder_class->set_format=test_audio_decoder_set_format;decoder_class->handle_frame=test_audio_decoder_handle_frame;}staticvoidtest_audio_decoder_init(TestAudioDecoder*decoder){}/* Harness输入端接收编码测试数据 */staticGstStaticPadTemplate harness_src_template=GST_STATIC_PAD_TEMPLATE("harness-src",GST_PAD_SRC,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-test-custom"));/* Harness输出端接收解码后的原始音频 */staticGstStaticPadTemplate harness_sink_template=GST_STATIC_PAD_TEMPLATE("harness-sink",GST_PAD_SINK,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-raw"));intmain(intargc,char*argv[]){GstElement*decoder_element;GstHarness*harness;GstBuffer*input;GstBuffer*output;GstFlowReturn ret;gboolean valid_output;gst_init(&argc,&argv);/* 创建测试解码器,并交给Harness管理 */decoder_element=g_object_new(test_audio_decoder_get_type(),NULL);harness=gst_harness_new_full(decoder_element,&harness_src_template,"sink",&harness_sink_template,"src");gst_object_unref(decoder_element);/* 设置输入Caps,触发解码器格式配置 */gst_harness_set_src_caps(harness,gst_caps_new_simple("audio/x-test-custom",NULL));/* 创建一个最小输入Buffer */input=gst_buffer_new_allocate(NULL,1,NULL);GST_BUFFER_PTS(input)=0;GST_BUFFER_DURATION(input)=GST_MSECOND;/* 推入输入数据,触发handle_frame */ret=gst_harness_push(harness,input);/* * finish_frame返回的Flow结果会沿着handle_frame返回。 * 这里先确认下游处理没有失败。 */if(ret!=GST_FLOW_OK){gst_harness_teardown(harness);return1;}/* 拉取finish_frame提交的原始音频Buffer */output=gst_harness_pull(harness);if(output==NULL){gst_harness_teardown(harness);return1;}/* S16单声道一个Frame应当占用2字节 */valid_output=gst_buffer_get_size(output)==sizeof(gint16);g_print("flow-result: %s\n",ret==GST_FLOW_OK?"GST_FLOW_OK":"ERROR");g_print("output-size: %"G_GSIZE_FORMAT"\n",gst_buffer_get_size(output));gst_buffer_unref(output);gst_harness_teardown(harness);returnvalid_output?0:1;}🌻5.总结
gst_audio_decoder_finish_frame()就是把解码器子类生成的原始音频Buffer及其Frame数量交给GstAudioDecoder基类,由基类完成输出状态处理、下游推送和流控结果返回。