多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Linux Gstreamer深度解析之gst_audio_decoder_set_tolerance调用流程与实战(五十一)

Linux Gstreamer深度解析之gst_audio_decoder_set_tolerance调用流程与实战(五十一) 简介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设置函数更新内部容差字段3.2解码输出阶段使用容差判断是否重同步4.实战案例4.1准备1000Hz测试解码器4.2设置容差并推动时间戳校正4.3完整代码5.总结1.前言本篇目的理解gst_audio_decoder_set_tolerance()如何配置音频时间戳抖动容差并通过最小解码器验证容差范围内保持连续时间戳、超出容差后重新同步的实际行为。音频解码器接收到的输入Buffer通常带有上游时间戳但这些时间戳不一定完全连续。容器时间基转换、网络抖动、丢包恢复或上游切换都可能让当前Buffer的时间戳与解码器根据已输出采样数推算出的理想时间戳产生偏差。GstAudioDecoder会维护一个理想输出时间轴并使用tolerance判断当前输入时间戳是否仍然可以接受。偏差不超过容差丢弃当前输入时间戳继续使用连续的理想时间戳。偏差超过容差以当前输入时间戳为新的基准重新同步输出时间轴。容差为0关闭容差保护时间戳检查时直接按照上游时间戳重新同步。gst_audio_decoder_set_tolerance()只负责修改解码器内部的容差字段不直接处理Buffer不立即改变输出时间戳也不会触发下游回调。真正的判断发生在后续解码器处理输入Buffer并准备输出Buffer的过程中。输入Buffer时间戳gst_audio_decoder_set_tolerance解码器理想时间轴比较时间戳偏差保持连续时间戳或重新同步2.应用场景和用法2.1音频时间戳容差场景gst_audio_decoder_set_tolerance()适合用于解码器需要容忍小幅时间戳抖动的场景。使用场景设置方式实际效果本地文件时间戳存在微小误差设置几毫秒容差小偏差继续沿用理想时间轴网络音频存在轻微抖动设置大于网络抖动的容差避免输出时间戳频繁跳变发生明显跳播或丢帧设置合理容差超出阈值后重新同步需要严格跟随上游时间戳设置为0每次有效时间戳都可能触发重新同步运行时调整策略再次调用设置函数后续输入使用新的容差查询当前配置调用gst_audio_decoder_get_tolerance()读取当前容差值容差不是音频数据的延迟也不是输出Buffer的大小。它表示输入时间戳和解码器理想时间戳之间允许存在的最大偏差。例如解码器已经输出了1000个采样采样率为1000Hz基准时间戳为0那么理想时间戳为1秒。如果下一个输入Buffer的时间戳是1003毫秒设置5毫秒容差后解码器会继续使用理想时间轴。如果输入时间戳是1020毫秒则偏差超过容差解码器会把1020毫秒作为新的同步基准。2.2函数原型与返回语义函数原型如下voidgst_audio_decoder_set_tolerance(GstAudioDecoder*dec,GstClockTime tolerance);参数含义dec已经创建的GstAudioDecoder对象tolerance时间戳抖动容差单位为纳秒该函数没有返回值。调用成功后容差会写入dec-priv-tolerance。最小调用方式如下GstClockTime tolerance5*GST_MSECOND;gst_audio_decoder_set_tolerance(decoder,tolerance);调用前decoder必须是有效的GstAudioDecoder对象tolerance不能是GST_CLOCK_TIME_NONE。调用过程中函数会检查dec是否为有效的音频解码器。检查tolerance是否为有效的GStreamer时间值。获取对象锁。更新私有字段tolerance。释放对象锁。该函数不会创建对象不会修改调用者传入的Buffer不会调用子类虚函数也不会直接发送事件。读取当前容差可以使用GstClockTime tolerance;tolerancegst_audio_decoder_get_tolerance(decoder);相关接口的职责边界如下接口主要职责gst_audio_decoder_set_tolerance()写入时间戳抖动容差gst_audio_decoder_get_tolerance()读取当前时间戳抖动容差gst_audio_decoder_set_output_format()设置解码器输出音频格式gst_audio_decoder_handle_frame()由子类处理输入数据gst_audio_decoder_finish_frame()提交解码后的输出音频帧gst_audio_decoder_set_property()处理GObject属性写入GstAudioDecoder还提供名为tolerance的GObject属性但直接调用gst_audio_decoder_set_tolerance()可以明确表达当前代码正在修改时间戳容差。3.调用流程剖析3.1设置函数更新内部容差字段gst_audio_decoder_set_tolerance()的实现比较短核心代码如下voidgst_audio_decoder_set_tolerance(GstAudioDecoder*dec,GstClockTime tolerance){g_return_if_fail(GST_IS_AUDIO_DECODER(dec));g_return_if_fail(GST_CLOCK_TIME_IS_VALID(tolerance));GST_OBJECT_LOCK(dec);dec-priv-tolerancetolerance;GST_OBJECT_UNLOCK(dec);}它只修改一个字段但这个字段会影响后续所有输入Buffer的时间戳判断。GST_OBJECT_LOCK()和GST_OBJECT_UNLOCK()保护了私有状态使设置函数可以安全地与其他对象访问并发执行。设置动作本身是同步完成的函数返回时字段已经更新。传入GstAudioDecoder和容差检查对象和时间值获取对象锁写入priv tolerance释放对象锁如果对象检查失败函数通过GStreamer的g_return_if_fail()直接返回。如果时间值是GST_CLOCK_TIME_NONE第二个检查也会失败。容差设置成功后不会立即产生可观察的输出变化因为此时还没有新的输入时间戳参与比较。只有后续输入Buffer进入解码流程容差字段才会影响时间戳校正结果。3.2解码输出阶段使用容差判断是否重同步在解码器准备输出Buffer时基类会维护两个关键时间值base_ts当前输出时间轴的基准时间戳。next_ts根据已经输出的采样数计算出的理想时间戳。理想时间戳的计算方式为next_tsbase_tsgst_util_uint64_scale(priv-samples,GST_SECOND,ctx-info.rate);随后基类计算理想时间戳和输入Buffer时间戳之间的差值diffGST_CLOCK_DIFF(next_ts,ts);当tolerance大于0时只有以下情况会触发重新同步diff-tolerance||difftolerance||GST_BUFFER_FLAG_RESYNC已经设置如果差值在容差范围内解码器保留原来的base_ts和采样计数继续生成连续的输出时间戳。如果差值超出容差或者输入Buffer带有GST_BUFFER_FLAG_RESYNC解码器会执行priv-base_tsts;priv-samples0;这表示当前输入时间戳成为新的时间轴起点。当tolerance为0时基类不会进入容差范围判断而是直接把当前有效输入时间戳设置为新的基准时间戳。否是输入Buffer带有PTS计算理想next_ts计算时间戳差值diff偏差是否超过容差保持连续输出时间轴更新base_ts并重新计数因此gst_audio_decoder_set_tolerance()与输出Buffer时间戳之间不是直接调用关系。它先修改解码器私有状态之后由基类的输出流程读取这个状态再决定保持理想时间轴还是执行重新同步。4.实战案例4.1准备1000Hz测试解码器为了让时间戳计算容易观察案例定义一个最小GstAudioDecoder子类输入Caps使用audio/x-test-custom。输出格式为1000Hz、单声道、S16。每个输入Buffer产生一个输出音频帧。每个音频帧对应1毫秒的理想时间长度。设置时间戳容差为5毫秒。测试输入三个Buffer输入Buffer输入PTS理想PTS预期行为第一个0毫秒0毫秒建立初始时间轴第二个4毫秒1毫秒偏差3毫秒在容差内第三个20毫秒2毫秒偏差18毫秒触发重新同步第二个Buffer虽然携带4毫秒的上游时间戳但它与理想时间戳只相差3毫秒因此输出仍然沿用连续的1毫秒时间戳。第三个Buffer的输入时间戳与理想时间戳相差18毫秒超过5毫秒容差解码器会把20毫秒设置为新的基准时间戳。4.2设置容差并推动时间戳校正测试程序按以下顺序执行创建自定义解码器和GstHarness。调用gst_audio_decoder_set_tolerance()设置5毫秒容差。依次提交PTS为0毫秒、4毫秒和20毫秒的输入Buffer。拉取每个输出Buffer并读取其PTS。比较实际输出PTS与预期时间轴。验证重点不是设置函数的返回值因为该函数没有返回值而是观察设置动作对后续输出时间戳的影响。程序还调用gst_audio_decoder_get_tolerance()确认容差已经写入解码器对象。4.3完整代码#includegst/gst.h#includegst/audio/audio.h#includegst/check/gstharness.htypedefstruct_ToleranceDecoder{GstAudioDecoder parent;}ToleranceDecoder;typedefstruct_ToleranceDecoderClass{GstAudioDecoderClass parent_class;}ToleranceDecoderClass;G_DEFINE_TYPE(ToleranceDecoder,tolerance_decoder,GST_TYPE_AUDIO_DECODER);/* 自定义解码器的输入和输出Pad模板 */staticGstStaticPadTemplate decoder_sink_templateGST_STATIC_PAD_TEMPLATE(sink,GST_PAD_SINK,GST_PAD_ALWAYS,GST_STATIC_CAPS(audio/x-test-custom));staticGstStaticPadTemplate decoder_src_templateGST_STATIC_PAD_TEMPLATE(src,GST_PAD_SRC,GST_PAD_ALWAYS,GST_STATIC_CAPS(audio/x-raw));/* 设置1000Hz、单声道、S16输出格式 */staticgbooleantolerance_decoder_set_format(GstAudioDecoder*decoder,GstCaps*caps){GstAudioInfo info;gst_audio_info_set_format(info,GST_AUDIO_FORMAT_S16,1000,1,NULL);returngst_audio_decoder_set_output_format(decoder,info);}/* 每个输入Buffer产生一个音频输出帧 */staticGstFlowReturntolerance_decoder_handle_frame(GstAudioDecoder*decoder,GstBuffer*input){GstBuffer*output;if(inputNULL)returnGST_FLOW_OK;/* S16单声道每个Frame占2字节 */outputgst_audio_decoder_allocate_output_buffer(decoder,sizeof(gint16));if(outputNULL)returnGST_FLOW_ERROR;/* 提交一个解码后的音频Frame */returngst_audio_decoder_finish_frame(decoder,output,1);}staticvoidtolerance_decoder_class_init(ToleranceDecoderClass*klass){GstElementClass*element_classGST_ELEMENT_CLASS(klass);GstAudioDecoderClass*decoder_classGST_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,Tolerance Decoder,Decoder/Audio,Audio timestamp tolerance test decoder,GStreamer);decoder_class-set_formattolerance_decoder_set_format;decoder_class-handle_frametolerance_decoder_handle_frame;}staticvoidtolerance_decoder_init(ToleranceDecoder*decoder){}/* 创建带指定PTS的输入Buffer */staticGstBuffer*create_input_buffer(GstClockTime pts){GstBuffer*buffer;buffergst_buffer_new_allocate(NULL,1,NULL);GST_BUFFER_PTS(buffer)pts;GST_BUFFER_DURATION(buffer)GST_MSECOND;returnbuffer;}/* 推送一个输入Buffer并检查输出PTS */staticgbooleanpush_and_check_pts(GstHarness*harness,GstClockTime input_pts,GstClockTime expected_pts){GstBuffer*output;GstClockTime actual_pts;if(gst_harness_push(harness,create_input_buffer(input_pts))!GST_FLOW_OK){returnFALSE;}outputgst_harness_pull(harness);if(outputNULL)returnFALSE;actual_ptsGST_BUFFER_PTS(output);g_print(input PTS: %GST_TIME_FORMAT, output PTS: %GST_TIME_FORMAT\n,GST_TIME_ARGS(input_pts),GST_TIME_ARGS(actual_pts));gst_buffer_unref(output);returnactual_ptsexpected_pts;}intmain(intargc,char*argv[]){GstElement*decoder_element;GstHarness*harness;GstAudioDecoder*decoder;GstClockTime tolerance;gboolean ok;staticGstStaticPadTemplate harness_src_templateGST_STATIC_PAD_TEMPLATE(harness-src,GST_PAD_SRC,GST_PAD_ALWAYS,GST_STATIC_CAPS(audio/x-test-custom));staticGstStaticPadTemplate harness_sink_templateGST_STATIC_PAD_TEMPLATE(harness-sink,GST_PAD_SINK,GST_PAD_ALWAYS,GST_STATIC_CAPS(audio/x-raw));gst_init(argc,argv);/* 创建自定义AudioDecoder对象 */decoder_elementg_object_new(tolerance_decoder_get_type(),NULL);/* 用GstHarness连接输入Pad和输出Pad */harnessgst_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));decoderGST_AUDIO_DECODER(harness-element);/* 设置5毫秒时间戳容差 */gst_audio_decoder_set_tolerance(decoder,5*GST_MSECOND);/* 读取容差确认设置已经生效 */tolerancegst_audio_decoder_get_tolerance(decoder);oktolerance5*GST_MSECOND;/* * 第一个Buffer建立时间轴。 * 第二个Buffer的4毫秒PTS与理想1毫秒PTS相差3毫秒 * 在5毫秒容差内输出应继续保持连续时间轴。 */okokpush_and_check_pts(harness,0,0);okokpush_and_check_pts(harness,4*GST_MSECOND,GST_MSECOND);/* * 第三个Buffer的20毫秒PTS与理想2毫秒PTS相差18毫秒 * 超出容差后20毫秒会成为新的时间轴基准。 */okokpush_and_check_pts(harness,20*GST_MSECOND,20*GST_MSECOND);gst_harness_teardown(harness);returnok?0:1;}5.总结gst_audio_decoder_set_tolerance()就是为GstAudioDecoder设置输入时间戳抖动容差让后续输出流程在小幅偏差时保持连续时间轴、在偏差过大时重新同步。
返回列表