拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
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 tolerance=5*GST_MSECOND;gst_audio_decoder_set_tolerance(decoder,tolerance);

调用前,decoder必须是有效的GstAudioDecoder对象,tolerance不能是GST_CLOCK_TIME_NONE。

调用过程中,函数会:

  1. 检查dec是否为有效的音频解码器。
  2. 检查tolerance是否为有效的GStreamer时间值。
  3. 获取对象锁。
  4. 更新私有字段tolerance。
  5. 释放对象锁。

该函数不会创建对象,不会修改调用者传入的Buffer,不会调用子类虚函数,也不会直接发送事件。

读取当前容差可以使用:

GstClockTime tolerance;tolerance=gst_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->tolerance=tolerance;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_ts=base_ts+gst_util_uint64_scale(priv->samples,GST_SECOND,ctx->info.rate);

随后,基类计算理想时间戳和输入Buffer时间戳之间的差值:

diff=GST_CLOCK_DIFF(next_ts,ts);

当tolerance大于0时,只有以下情况会触发重新同步:

diff<-tolerance||diff>tolerance||GST_BUFFER_FLAG_RESYNC已经设置

如果差值在容差范围内,解码器保留原来的base_ts和采样计数,继续生成连续的输出时间戳。

如果差值超出容差,或者输入Buffer带有GST_BUFFER_FLAG_RESYNC,解码器会执行:

priv->base_ts=ts;priv->samples=0;

这表示当前输入时间戳成为新的时间轴起点。

当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设置容差并推动时间戳校正

测试程序按以下顺序执行:

  1. 创建自定义解码器和GstHarness。
  2. 调用gst_audio_decoder_set_tolerance()设置5毫秒容差。
  3. 依次提交PTS为0毫秒、4毫秒和20毫秒的输入Buffer。
  4. 拉取每个输出Buffer并读取其PTS。
  5. 比较实际输出PTS与预期时间轴。

验证重点不是设置函数的返回值,因为该函数没有返回值,而是观察设置动作对后续输出时间戳的影响。

程序还调用gst_audio_decoder_get_tolerance()确认容差已经写入解码器对象。

4.3完整代码
#include<gst/gst.h>#include<gst/audio/audio.h>#include<gst/check/gstharness.h>typedefstruct_ToleranceDecoder{GstAudioDecoder parent;}ToleranceDecoder;typedefstruct_ToleranceDecoderClass{GstAudioDecoderClass parent_class;}ToleranceDecoderClass;G_DEFINE_TYPE(ToleranceDecoder,tolerance_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"));/* 设置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(input==NULL)returnGST_FLOW_OK;/* S16单声道每个Frame占2字节 */output=gst_audio_decoder_allocate_output_buffer(decoder,sizeof(gint16));if(output==NULL)returnGST_FLOW_ERROR;/* 提交一个解码后的音频Frame */returngst_audio_decoder_finish_frame(decoder,output,1);}staticvoidtolerance_decoder_class_init(ToleranceDecoderClass*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,"Tolerance Decoder","Decoder/Audio","Audio timestamp tolerance test decoder","GStreamer");decoder_class->set_format=tolerance_decoder_set_format;decoder_class->handle_frame=tolerance_decoder_handle_frame;}staticvoidtolerance_decoder_init(ToleranceDecoder*decoder){}/* 创建带指定PTS的输入Buffer */staticGstBuffer*create_input_buffer(GstClockTime pts){GstBuffer*buffer;buffer=gst_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;}output=gst_harness_pull(harness);if(output==NULL)returnFALSE;actual_pts=GST_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_pts==expected_pts;}intmain(intargc,char*argv[]){GstElement*decoder_element;GstHarness*harness;GstAudioDecoder*decoder;GstClockTime tolerance;gboolean ok;staticGstStaticPadTemplate harness_src_template=GST_STATIC_PAD_TEMPLATE("harness-src",GST_PAD_SRC,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-test-custom"));staticGstStaticPadTemplate harness_sink_template=GST_STATIC_PAD_TEMPLATE("harness-sink",GST_PAD_SINK,GST_PAD_ALWAYS,GST_STATIC_CAPS("audio/x-raw"));gst_init(&argc,&argv);/* 创建自定义AudioDecoder对象 */decoder_element=g_object_new(tolerance_decoder_get_type(),NULL);/* 用GstHarness连接输入Pad和输出Pad */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));decoder=GST_AUDIO_DECODER(harness->element);/* 设置5毫秒时间戳容差 */gst_audio_decoder_set_tolerance(decoder,5*GST_MSECOND);/* 读取容差,确认设置已经生效 */tolerance=gst_audio_decoder_get_tolerance(decoder);ok=tolerance==5*GST_MSECOND;/* * 第一个Buffer建立时间轴。 * 第二个Buffer的4毫秒PTS与理想1毫秒PTS相差3毫秒, * 在5毫秒容差内,输出应继续保持连续时间轴。 */ok=ok&&push_and_check_pts(harness,0,0);ok=ok&&push_and_check_pts(harness,4*GST_MSECOND,GST_MSECOND);/* * 第三个Buffer的20毫秒PTS与理想2毫秒PTS相差18毫秒, * 超出容差后,20毫秒会成为新的时间轴基准。 */ok=ok&&push_and_check_pts(harness,20*GST_MSECOND,20*GST_MSECOND);gst_harness_teardown(harness);returnok?0:1;}

🌻5.总结

gst_audio_decoder_set_tolerance()就是为GstAudioDecoder设置输入时间戳抖动容差,让后续输出流程在小幅偏差时保持连续时间轴、在偏差过大时重新同步。

返回列表