简介CSDN博客专家、《Android系统多媒体进阶实战》作者博主新书推荐《Android系统多媒体进阶实战》Android Audio工程师专栏地址Audio工程师进阶系列【原创干货持续更新中……】Android多媒体专栏地址多媒体系统工程师系列【原创干货持续更新中……】专题一 二AAOS车载系统AOSP14系统攻城狮入门视频实战课专题三Android14 Binder之HIDL与AIDL通信实战课专题四Android15快速自定义与集成音效实战课专题五Android15音频策略实战课专题六Android15音频性能实战课(无声/杂音/断音/爆音实战案例)人生格言人生从来没有捷径只有行动才是治疗恐惧和懒惰的唯一良药.更多原创,欢迎关注Android系统攻城狮文章目录1.前言2.应用场景和用法2.1提交编码结果并维护输出时间线2.2函数原型与所有权语义3.调用流程剖析3.1校验状态、协商Caps并准备事件3.2更新样本进度、时间戳并推送Buffer4.实战案例4.1准备最小音频编码器4.2提交编码结果并验证输出4.3完整代码5.总结1.前言本篇目的理解gst_audio_encoder_finish_frame()如何接收编码器输出、更新音频时间信息并把编码数据推送到下游同时通过一个最小编码器验证输入样本数、输出Buffer和返回值之间的关系。GstAudioEncoder基类负责收集上游原始音频并按照子类配置的Frame大小调用handle_frame()。真正完成编码后子类需要把编码结果交给gst_audio_encoder_finish_frame()。这个函数会继续处理Caps协商、待发送事件、时间戳、Buffer元数据和下游推送。gst_audio_encoder_finish_frame()的核心职责是接收编码后的GstBuffer。根据samples记录本次编码结果代表的输入样本数。为输出Buffer补充PTS、DTS、Duration和Offset。调用pre_push()进行最后处理。通过源Pad推送Buffer。将下游返回的GstFlowReturn返回给编码器子类。它不负责执行具体编码算法也不负责创建编码器内部的Codec对象。handle_frame产生编码Buffergst_audio_encoder_finish_frame更新样本进度和Buffer元数据执行pre_push并推送源Pad返回GstFlowReturn2.应用场景和用法2.1提交编码结果并维护输出时间线音频编码器子类通常在handle_frame()中完成以下工作获取基类提供的原始音频Buffer。调用具体Codec库进行编码。创建或分配编码输出Buffer。调用gst_audio_encoder_finish_frame()提交编码结果。典型使用场景如下使用场景调用方式基类完成的工作正常输出编码数据传入非空Buffer和实际样本数设置时间信息并推送Buffer编码器需要更多输入暂时不提交输出Buffer保留输入状态等待后续数据丢弃输入样本传入NULL和需要丢弃的样本数推进样本位置并标记不连续编码器无法精确统计样本数samples传入负数根据当前已提供的数据估算样本数输出Caps发生变化先设置新的输出Capsfinish_frame()中重新协商下游拒绝数据检查返回的GstFlowReturn将错误继续返回给上层处理samples表示编码Buffer所代表的每个声道样本数不是编码Buffer的字节数。例如输入为单声道8000Hz音频本次编码消耗4个样本编码器生成2字节压缩数据那么调用参数应当是gst_audio_encoder_finish_frame(encoder,encoded_buffer,4);其中encoded_buffer的大小是2字节而samples是4。两者描述的是不同维度Buffer大小描述编码结果占用多少字节。samples描述编码结果对应多少个输入音频样本。2.2函数原型与所有权语义函数原型如下GstFlowReturngst_audio_encoder_finish_frame(GstAudioEncoder*enc,GstBuffer*buffer,gint samples);参数含义enc已经创建并完成输入格式配置的GstAudioEncoderbuffer编码后的输出Buffer可以传入NULLsamples编码结果代表的每声道样本数返回值是GstFlowReturn通常直接从子类的handle_frame()返回GstFlowReturn ret;retgst_audio_encoder_finish_frame(encoder,output,samples);returnret;buffer采用转移所有权语义。子类把Buffer传入函数后不应继续对它执行gst_buffer_unref()。基类会负责后续推送或释放。如果buffer不是NULL它必须包含有效数据空Buffer会触发参数检查失败。如果samples小于0基类会使用当前已经提供给子类的数据量进行估算。在当前实现中估算值来自samplesenc-priv-offset/ctx-info.bpf;如果buffer为NULL表示这些样本没有产生编码输出。基类会推进样本位置并把后续输出标记为不连续。调用前必须满足以下条件输入格式已经通过set_format()配置。输出Caps已经通过gst_audio_encoder_set_output_format()设置。编码Buffer已经完成写入并且大小有效。samples能够描述本次编码结果对应的输入样本数。几个相邻接口的职责不同接口主要职责set_format()接收输入音频格式并配置编码器gst_audio_encoder_set_output_format()设置源Pad输出Capsgst_audio_encoder_allocate_output_buffer()分配编码输出Buffergst_audio_encoder_finish_frame()补充元数据并推送编码Bufferpre_push()在源Pad推送前进行最后一次子类处理gst_pad_push()把Buffer交给下游Pad3.调用流程剖析3.1校验状态、协商Caps并准备事件gst_audio_encoder_finish_frame()进入后首先取得编码器的类对象、私有状态和上下文。函数先检查两个前置条件g_return_val_if_fail(bufNULL||gst_buffer_get_size(buf)0,GST_FLOW_ERROR);if(!ctx-caps)gotono_caps;这意味着非空输出Buffer不能是零字节。没有设置输出Caps时函数不能继续处理有效编码数据。随后函数获取GstAudioEncoder流锁检查源Pad是否需要重新协商needs_reconfiguregst_pad_check_reconfigure(enc-srcpad);if(ctx-output_caps_changed||needs_reconfigure){if(!gst_audio_encoder_negotiate_unlocked(enc)){...}}如果协商失败函数会返回GST_FLOW_NOT_NEGOTIATED。如果源Pad正在Flush则可能返回GST_FLOW_FLUSHING。协商成功后基类会先推送早期事件和待发送标签再处理当前编码Buffer。这样可以保证Segment、Tag等事件先于对应的编码数据到达下游。编码子类传入Buffer检查Buffer和输出Caps获取流锁并检查重协商推送待发送事件和标签进入Buffer元数据处理调用finish_frame()后handle_frame()收到的输入Buffer可能已经失效。因此子类不能在调用完成后继续访问原始输入Buffer。3.2更新样本进度、时间戳并推送Buffer当buffer非空时基类会先把它转换为可写Buffer然后根据当前编码器状态填写输出元数据。核心时间信息包括GST_BUFFER_PTS输出Buffer的显示时间。GST_BUFFER_DTS输出Buffer的解码时间。GST_BUFFER_DURATION当前编码结果覆盖的时间长度。GST_BUFFER_OFFSET输出字节偏移或Granule相关位置。GST_BUFFER_OFFSET_END输出结束位置。PTS通常由基础时间戳、已经处理的样本数、Lookahead和输入采样率计算得到GST_BUFFER_PTS(buf)priv-base_tsgst_util_uint64_scale(priv-samples-ctx-lookahead,GST_SECOND,ctx-info.rate);当samples大于0时基类会推进priv-samples并根据新的样本位置计算Duration。随后如果编码器子类实现了pre_push()基类会把Buffer地址交给该回调。子类可以在这里修改或替换Buffer也可以返回错误或返回NULL阻止推送。最后基类调用retgst_pad_push(enc-srcpad,buf);返回值可能是GST_FLOW_OK下游正常接收。GST_FLOW_FLUSHING下游正在Flush。GST_FLOW_NOT_NEGOTIATEDCaps协商失败。其他Flow错误下游停止、断开或发生错误。Buffer推送完成后基类还会处理在该Buffer进入Adapter之后到达的待发送事件。读取samples和编码Buffer计算PTS和Duration更新样本计数和Offset执行pre_push调用gst_pad_push并返回Flow状态当buffer为NULL时基类不执行源Pad推送但仍然会根据samples推进内部样本位置。这种用法适合编码器丢弃尾部数据、处理不连续输入或执行Drain操作。4.实战案例4.1准备最小音频编码器本案例定义一个测试用GstAudioEncoder子类输入格式为S16LE、8000Hz、单声道。每次要求基类提供4个输入样本。输出Caps为audio/x-test-encoded。handle_frame()生成一个2字节的测试编码Buffer。调用gst_audio_encoder_finish_frame()时传入samples4。这个Demo不依赖外部Codec库只用于观察finish_frame()对输出Buffer和返回值的处理。4.2提交编码结果并验证输出测试过程分为三步创建自定义编码器和GstHarness通过Harness设置输入Caps。向编码器提交8字节的原始音频数据使基类收集4个S16样本并调用handle_frame()。在handle_frame()中创建2字节编码Buffer调用gst_audio_encoder_finish_frame()再从Harness取出输出Buffer。验证重点包括gst_harness_push()返回GST_FLOW_OK。输出Buffer能够被Harness取出。输出Buffer大小为2字节。finish_frame()返回的Flow状态被原样传回上层。samples4用于推进输入样本时间线而不是表示输出Buffer大小。4.3完整代码#includegst/gst.h#includegst/audio/audio.h#includegst/check/gstharness.htypedefstruct_TestAudioEncoder{GstAudioEncoder parent;}TestAudioEncoder;typedefstruct_TestAudioEncoderClass{GstAudioEncoderClass parent_class;}TestAudioEncoderClass;G_DEFINE_TYPE(TestAudioEncoder,test_audio_encoder,GST_TYPE_AUDIO_ENCODER);/* 编码器输入Pad接收原始S16音频 */staticGstStaticPadTemplate encoder_sink_templateGST_STATIC_PAD_TEMPLATE(sink,GST_PAD_SINK,GST_PAD_ALWAYS,GST_STATIC_CAPS(audio/x-raw, format(string)S16LE, rate(int)8000, channels(int)1, layout(string)interleaved));/* 编码器输出Pad发送测试编码格式 */staticGstStaticPadTemplate encoder_src_templateGST_STATIC_PAD_TEMPLATE(src,GST_PAD_SRC,GST_PAD_ALWAYS,GST_STATIC_CAPS(audio/x-test-encoded));/* 配置输入样本格式、每帧样本数和输出Caps */staticgbooleantest_audio_encoder_set_format(GstAudioEncoder*encoder,GstAudioInfo*info){GstCaps*caps;gboolean result;/* 要求基类每次提供4个输入样本 */gst_audio_encoder_set_frame_samples_min(encoder,4);gst_audio_encoder_set_frame_samples_max(encoder,4);/* finish_frame要求源Pad已经有输出Caps */capsgst_caps_new_empty_simple(audio/x-test-encoded);resultgst_audio_encoder_set_output_format(encoder,caps);gst_caps_unref(caps);returnresult;}/* 把4个输入样本转换为一个2字节测试输出 */staticGstFlowReturntest_audio_encoder_handle_frame(GstAudioEncoder*encoder,GstBuffer*input){GstBuffer*output;GstMapInfo map;/* NULL输入用于Drain场景本Demo没有额外编码数据 */if(inputNULL)returnGST_FLOW_OK;/* 分配2字节编码输出空间 */outputgst_audio_encoder_allocate_output_buffer(encoder,2);if(outputNULL)returnGST_FLOW_ERROR;/* 写入固定测试字节模拟Codec生成的压缩数据 */if(!gst_buffer_map(output,map,GST_MAP_WRITE)){gst_buffer_unref(output);returnGST_FLOW_ERROR;}map.data[0]0xA5;map.data[1]0x5A;gst_buffer_unmap(output,map);/* * buffer的所有权转移给finish_frame。 * samples表示编码结果对应的每声道输入样本数。 */returngst_audio_encoder_finish_frame(encoder,output,4);}staticvoidtest_audio_encoder_class_init(TestAudioEncoderClass*klass){GstElementClass*element_classGST_ELEMENT_CLASS(klass);GstAudioEncoderClass*encoder_classGST_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,Test Audio Encoder,Encoder/Audio,finish_frame flow test encoder,GStreamer);encoder_class-set_formattest_audio_encoder_set_format;encoder_class-handle_frametest_audio_encoder_handle_frame;}staticvoidtest_audio_encoder_init(TestAudioEncoder*encoder){}/* Harness输入Pad模板 */staticGstStaticPadTemplate harness_src_templateGST_STATIC_PAD_TEMPLATE(harness-src,GST_PAD_SRC,GST_PAD_ALWAYS,GST_STATIC_CAPS(audio/x-raw, format(string)S16LE, rate(int)8000, channels(int)1, layout(string)interleaved));/* Harness输出Pad模板 */staticGstStaticPadTemplate harness_sink_templateGST_STATIC_PAD_TEMPLATE(harness-sink,GST_PAD_SINK,GST_PAD_ALWAYS,GST_STATIC_CAPS(audio/x-test-encoded));intmain(intargc,char*argv[]){GstElement*encoder_element;GstHarness*harness;GstBuffer*input;GstBuffer*output;GstCaps*input_caps;GstFlowReturn flow;gboolean ok;gst_init(argc,argv);/* 创建自定义编码器并交给Harness管理 */encoder_elementg_object_new(test_audio_encoder_get_type(),NULL);harnessgst_harness_new_full(encoder_element,harness_src_template,sink,harness_sink_template,src);gst_object_unref(encoder_element);/* 设置输入音频Caps触发子类set_format */input_capsgst_caps_new_simple(audio/x-raw,format,G_TYPE_STRING,S16LE,rate,G_TYPE_INT,8000,channels,G_TYPE_INT,1,layout,G_TYPE_STRING,interleaved,NULL);gst_harness_set_src_caps(harness,input_caps);/* 4个S16单声道样本正好占8字节 */inputgst_buffer_new_allocate(NULL,8,NULL);/* 推送输入触发handle_frame和finish_frame */flowgst_harness_push(harness,input);/* 取出finish_frame最终推送的编码Buffer */outputgst_harness_pull(harness);okflowGST_FLOW_OKoutput!NULLgst_buffer_get_size(output)2;if(output)gst_buffer_unref(output);/* 释放Harness以及内部编码器资源 */gst_harness_teardown(harness);returnok?0:1;}5.总结gst_audio_encoder_finish_frame()就是接收编码器输出Buffer、按照输入样本数维护时间线并将编码数据推送到下游同时把下游Flow状态返回给编码器子类。