简介: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准备测试编码器
- 4.2观察子类收到的数据
- 4.3完整代码
- 🌻5.总结
🌻1.前言
本篇目的:理解
gst_audio_encoder_set_hard_min()如何控制GstAudioEncoder在强制处理尾部数据时,是否把不足最小样本数的数据交给编码器子类。
编码器子类可以通过gst_audio_encoder_set_frame_samples_min()声明每次处理至少需要多少个样本。正常编码时,如果累积数据还没达到这个下限,基类会继续等待。
到了EOS等需要排空数据的时机,剩余数据可能仍然不足最小样本数。gst_audio_encoder_set_hard_min()决定这时如何处理:启用后,基类会跳过子类的handle_frame(),丢弃这段尾部数据;未启用时,子类可能收到不足下限的数据。
🌻2.应用场景和用法
2.1处理不足最小样本数的尾部数据
gst_audio_encoder_set_hard_min()适用于每次编码必须满足固定样本数的编码器。例如,编码器每帧要求1024个样本,而流结束时只剩下300个样本。
| 场景 | hard_min设置 | EOS处理时的行为 |
|---|---|---|
| 编码器支持短帧 | FALSE | 子类可以收到不足最小值的尾部数据 |
| 编码器要求固定帧长 | TRUE | 基类跳过子类处理并丢弃尾部数据 |
| 输入尚未结束 | 任意值 | 未进入强制处理时,基类继续等待达到最小值 |
启用该选项会丢失不足最小样本数的尾部音频。只有编码格式或子类实现确实不能处理短帧时,才适合启用。
2.2函数原型与状态变化
函数原型如下:
voidgst_audio_encoder_set_hard_min(GstAudioEncoder*enc,gboolean enabled);| 参数 | 含义 |
|---|---|
enc | 已创建的GstAudioEncoder对象 |
enabled | 是否启用硬性最小样本数处理 |
该函数没有返回值。调用时会先检查enc是否为有效的GstAudioEncoder对象,再通过对象锁更新私有字段hard_min。初始值为FALSE。
最小调用方式如下:
/* 编码器不接受短帧时启用硬性最小样本数 */gst_audio_encoder_set_hard_min(GST_AUDIO_ENCODER(encoder),TRUE);它与相邻接口的职责不同:
| 接口 | 职责 |
|---|---|
gst_audio_encoder_set_frame_samples_min() | 设置子类每次至少需要的样本数 |
gst_audio_encoder_set_hard_min() | 决定强制处理时如何处理不足下限的剩余数据 |
gst_audio_encoder_get_hard_min() | 查询当前硬性最小值处理状态 |
gst_audio_encoder_finish_frame() | 提交子类产生的编码数据及其对应样本数 |
hard_min不会设置帧长,也不会改变最小样本数。它只影响基类进入强制处理路径后的分支。
🌻3.调用流程剖析
3.1设置硬性最小值状态
gst_audio_encoder_set_hard_min()本身只更新一个布尔字段,不会立即处理音频Buffer。
voidgst_audio_encoder_set_hard_min(GstAudioEncoder*enc,gboolean enabled){g_return_if_fail(GST_IS_AUDIO_ENCODER(enc));GST_OBJECT_LOCK(enc);enc->priv->hard_min=enabled;GST_OBJECT_UNLOCK(enc);}设置状态的过程是:
- 检查传入对象是否为
GstAudioEncoder。 - 获取对象锁,更新
enc->priv->hard_min。 - 释放对象锁并返回。
基类初始化时hard_min默认为FALSE。因此,如果编码器子类没有主动设置,尾部数据会沿默认处理策略进入后续流程。
3.2强制处理时选择丢弃或交给子类
正常输入过程中,基类会把输入数据放入Adapter,并根据frame_samples_min判断是否满足子类要求。如果可用样本不足且当前没有强制处理,基类会退出这轮处理,等待更多输入。
EOS等需要排空数据的路径会调用gst_audio_encoder_drain(),再通过gst_audio_encoder_push_buffers()强制处理剩余数据。此时,如果剩余样本仍然不足最小值,基类会设置内部force状态。
处理分支如下:
if(priv->force&&priv->hard_min&&buf){ret=gst_audio_encoder_finish_frame(enc,NULL,-1);}else{ret=klass->handle_frame(enc,buf);}当force和hard_min同时为真且存在剩余Buffer时,基类直接调用gst_audio_encoder_finish_frame()处理这段剩余数据,不再调用子类的handle_frame()。这就是硬性最小值模式下丢弃不足下限尾部数据的路径。
如果hard_min为FALSE,上述条件不成立,子类的handle_frame()会收到剩余Buffer。子类是否能正确编码短帧,取决于自身实现和编码格式要求。
🌻4.实战案例
4.1准备测试编码器
下面的测试编码器把最小样本数设为4个样本/声道。测试输入为S16立体声格式,每个音频Frame包含左右声道各一个样本。
测试时只提交3个Frame,也就是每声道3个样本。普通输入阶段还达不到4个样本,因此子类不会立即收到数据。随后发送EOS,基类开始强制排空。程序分别测试hard_min=TRUE和hard_min=FALSE两种情况。
4.2观察子类收到的数据
测试子类在handle_frame()中记录调用次数和输入Buffer大小:
hard_min=TRUE时,EOS排空阶段由基类丢弃不足下限的尾部,子类的处理次数保持为0。hard_min=FALSE时,子类收到12字节输入,也就是3个S16立体声Frame。
这里的Demo用于观察GstAudioEncoder的分支行为,不实现真实音频压缩算法。
4.3完整代码
#include<gst/gst.h>#include<gst/audio/audio.h>#include<gst/check/gstharness.h>typedefstruct_HardMinEncoder{GstAudioEncoder parent;guint handle_calls;gsize last_input_bytes;}HardMinEncoder;typedefstruct_HardMinEncoderClass{GstAudioEncoderClass parent_class;}HardMinEncoderClass;G_DEFINE_TYPE(HardMinEncoder,hard_min_encoder,GST_TYPE_AUDIO_ENCODER);staticGstStaticPadTemplate encoder_sink_template=GST_STATIC_PAD_TEMPLATE("sink",GST_PAD_SINK,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-raw"));staticGstStaticPadTemplate encoder_src_template=GST_STATIC_PAD_TEMPLATE("src",GST_PAD_SRC,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-test-custom"));staticgbooleanhard_min_encoder_set_format(GstAudioEncoder*enc,GstAudioInfo*info){GstCaps*caps;/* 子类需要每声道至少4个样本,再设置测试用输出Caps */gst_audio_encoder_set_frame_samples_min(enc,4);caps=gst_caps_new_empty_simple("audio/x-test-custom");gst_audio_encoder_set_output_format(enc,caps);gst_caps_unref(caps);returnTRUE;}staticGstFlowReturnhard_min_encoder_handle_frame(GstAudioEncoder*enc,GstBuffer*input){HardMinEncoder*self=(HardMinEncoder*)enc;GstBuffer*output;/* 记录基类是否把尾部输入交给了子类 */self->handle_calls++;if(input==NULL)returnGST_FLOW_OK;self->last_input_bytes=gst_buffer_get_size(input);/* 测试编码器只输出一个字节,不代表真实压缩算法 */output=gst_buffer_new_allocate(NULL,1,NULL);/* 输入包含3个立体声Frame,即每声道3个样本 */returngst_audio_encoder_finish_frame(enc,output,3);}staticvoidhard_min_encoder_class_init(HardMinEncoderClass*klass){GstElementClass*element_class=GST_ELEMENT_CLASS(klass);GstAudioEncoderClass*encoder_class=GST_AUDIO_ENCODER_CLASS(klass);gst_element_class_add_static_pad_template(element_class,&encoder_sink_template);gst_element_class_add_static_pad_template(element_class,&encoder_src_template);gst_element_class_set_metadata(element_class,"HardMinEncoder","Encoder/Audio","Test hard minimum handling","Example");encoder_class->set_format=hard_min_encoder_set_format;encoder_class->handle_frame=hard_min_encoder_handle_frame;}staticvoidhard_min_encoder_init(HardMinEncoder*self){}staticGstStaticPadTemplate harness_src_template=GST_STATIC_PAD_TEMPLATE("harness-src",GST_PAD_SRC,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-raw"));staticGstStaticPadTemplate harness_sink_template=GST_STATIC_PAD_TEMPLATE("harness-sink",GST_PAD_SINK,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-test-custom"));typedefstruct{guint handle_calls;gsize input_bytes;}TestResult;staticTestResultrun_case(gboolean hard_min){GstElement*element;GstHarness*harness;GstBuffer*input;HardMinEncoder*encoder;TestResult result={0,0};/* 创建编码器,并设置本次测试的硬性最小值开关 */element=g_object_new(hard_min_encoder_get_type(),NULL);gst_audio_encoder_set_hard_min(GST_AUDIO_ENCODER(element),hard_min);harness=gst_harness_new_full(element,&harness_src_template,"sink",&harness_sink_template,"src");gst_object_unref(element);/* 配置S16立体声输入,采样率为48000Hz */gst_harness_set_src_caps(harness,gst_caps_new_simple("audio/x-raw","format",G_TYPE_STRING,"S16LE","rate",G_TYPE_INT,48000,"channels",G_TYPE_INT,2,"layout",G_TYPE_STRING,"interleaved",NULL));/* 输入3个Frame,每个Frame为左右声道各一个S16样本 */input=gst_buffer_new_allocate(NULL,3*2*sizeof(gint16),NULL);g_assert_cmpint(gst_harness_push(harness,input),==,GST_FLOW_OK);/* EOS使基类强制处理Adapter中不足4个样本的尾部 */g_assert(gst_harness_push_event(harness,gst_event_new_eos()));encoder=(HardMinEncoder*)harness->element;result.handle_calls=encoder->handle_calls;result.input_bytes=encoder->last_input_bytes;gst_harness_teardown(harness);returnresult;}intmain(intargc,char*argv[]){TestResult hard_result;TestResult soft_result;gst_init(&argc,&argv);hard_result=run_case(TRUE);soft_result=run_case(FALSE);/* 启用hard_min后,尾部被基类丢弃,子类没有收到handle_frame调用 */g_assert_cmpuint(hard_result.handle_calls,==,0);/* 未启用hard_min时,子类收到3个立体声Frame,共12字节 */g_assert_cmpuint(soft_result.handle_calls,==,1);g_assert_cmpuint(soft_result.input_bytes,==,12);return0;}🌻5.总结
gst_audio_encoder_set_hard_min()用于控制GstAudioEncoder在强制排空时如何处理不足最小样本数的尾部数据,启用后基类会跳过子类并丢弃这段数据。