Linux Gstreamer深度解析之gst_audio_decoder_get_parse_state调用流程与实战(四十九)

简介: CSDN博客专家、《Android系统多媒体进阶实战》作者

博主新书推荐:《Android系统多媒体进阶实战》🚀

Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中...... 】🚀

Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中...... 】🚀

专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀

专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀

专题四:Android15快速自定义与集成音效实战课 🚀

专题五:Android15音频策略实战课 🚀

专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀

人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
更多原创,欢迎关注:Android系统攻城狮

🍉🍉🍉文章目录🍉🍉🍉

🌻1.前言

本篇目的:理解gst_audio_decoder_get_parse_state()如何读取GstAudioDecoder当前的同步状态和EOS状态,并通过自定义解析器验证这两个值在普通输入、断点输入和排空阶段的变化。

GstAudioDecoder的子类可以实现parse虚函数,把输入适配器中的编码数据切分成完整音频帧。

解析器在处理数据时,经常需要知道当前输入是否连续,以及当前调用是否处于EOS排空阶段。gst_audio_decoder_get_parse_state()就是基类提供的状态查询接口。

它读取两个布尔值:

  • sync:当前输入是否处于连续同步状态。
  • eos:当前解析是否由排空流程触发。

这里的sync不是"是否已经找到编码帧同步字",而是基类根据输入Buffer的DISCONT状态维护的解析连续性标志。

这个函数只读取状态,不修改解析器、不消费GstAdapter中的数据,也不会触发回调或发送事件。
#mermaid-svg-reWjHoi4ydwrIXA8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-reWjHoi4ydwrIXA8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-reWjHoi4ydwrIXA8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-reWjHoi4ydwrIXA8 .error-icon{fill:#552222;}#mermaid-svg-reWjHoi4ydwrIXA8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-reWjHoi4ydwrIXA8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-reWjHoi4ydwrIXA8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-reWjHoi4ydwrIXA8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-reWjHoi4ydwrIXA8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-reWjHoi4ydwrIXA8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-reWjHoi4ydwrIXA8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-reWjHoi4ydwrIXA8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-reWjHoi4ydwrIXA8 .marker.cross{stroke:#333333;}#mermaid-svg-reWjHoi4ydwrIXA8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-reWjHoi4ydwrIXA8 p{margin:0;}#mermaid-svg-reWjHoi4ydwrIXA8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-reWjHoi4ydwrIXA8 .cluster-label text{fill:#333;}#mermaid-svg-reWjHoi4ydwrIXA8 .cluster-label span{color:#333;}#mermaid-svg-reWjHoi4ydwrIXA8 .cluster-label span p{background-color:transparent;}#mermaid-svg-reWjHoi4ydwrIXA8 .label text,#mermaid-svg-reWjHoi4ydwrIXA8 span{fill:#333;color:#333;}#mermaid-svg-reWjHoi4ydwrIXA8 .node rect,#mermaid-svg-reWjHoi4ydwrIXA8 .node circle,#mermaid-svg-reWjHoi4ydwrIXA8 .node ellipse,#mermaid-svg-reWjHoi4ydwrIXA8 .node polygon,#mermaid-svg-reWjHoi4ydwrIXA8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-reWjHoi4ydwrIXA8 .rough-node .label text,#mermaid-svg-reWjHoi4ydwrIXA8 .node .label text,#mermaid-svg-reWjHoi4ydwrIXA8 .image-shape .label,#mermaid-svg-reWjHoi4ydwrIXA8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-reWjHoi4ydwrIXA8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-reWjHoi4ydwrIXA8 .rough-node .label,#mermaid-svg-reWjHoi4ydwrIXA8 .node .label,#mermaid-svg-reWjHoi4ydwrIXA8 .image-shape .label,#mermaid-svg-reWjHoi4ydwrIXA8 .icon-shape .label{text-align:center;}#mermaid-svg-reWjHoi4ydwrIXA8 .node.clickable{cursor:pointer;}#mermaid-svg-reWjHoi4ydwrIXA8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-reWjHoi4ydwrIXA8 .arrowheadPath{fill:#333333;}#mermaid-svg-reWjHoi4ydwrIXA8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-reWjHoi4ydwrIXA8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-reWjHoi4ydwrIXA8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-reWjHoi4ydwrIXA8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-reWjHoi4ydwrIXA8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-reWjHoi4ydwrIXA8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-reWjHoi4ydwrIXA8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-reWjHoi4ydwrIXA8 .cluster text{fill:#333;}#mermaid-svg-reWjHoi4ydwrIXA8 .cluster span{color:#333;}#mermaid-svg-reWjHoi4ydwrIXA8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-reWjHoi4ydwrIXA8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-reWjHoi4ydwrIXA8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-reWjHoi4ydwrIXA8 .icon-shape,#mermaid-svg-reWjHoi4ydwrIXA8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-reWjHoi4ydwrIXA8 .icon-shape p,#mermaid-svg-reWjHoi4ydwrIXA8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-reWjHoi4ydwrIXA8 .icon-shape .label rect,#mermaid-svg-reWjHoi4ydwrIXA8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-reWjHoi4ydwrIXA8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-reWjHoi4ydwrIXA8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-reWjHoi4ydwrIXA8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入数据进入GstAudioDecoder
基类设置解析状态
gst_audio_decoder_get_parse_state
读取sync和eos
子类parse决定取帧策略

🌻2.应用场景和用法

2.1解析器读取同步和EOS状态

gst_audio_decoder_get_parse_state()主要服务于实现了parse虚函数的音频解码器子类。

常见场景如下:

场景 查询状态 解析器的处理方向
普通连续输入 sync=TRUE、eos=FALSE 按正常帧边界继续解析
首个输入Buffer sync=FALSE、eos=FALSE 允许重新寻找帧边界
上游发生断点 sync=FALSE 不把前后数据当作连续码流
EOS排空 eos=TRUE 处理尾部数据或判断残留数据是否完整
不完整尾帧 eos决定当前是否允许结束 普通输入等待更多数据,EOS阶段结束解析

在基类内部,sync由DISCONT状态反向得到:

c 复制代码
ctx->sync = !priv->discont;

因此,第一次收到数据时通常还没有建立连续输出状态,sync可能为FALSE。当解码器成功输出一帧并清除断点状态后,后续解析调用才可能看到sync=TRUE。

eos则来自gst_audio_decoder_push_buffers()的force参数。普通链路传入FALSE,排空流程传入TRUE。

一些解析器会把eos传递给同步扫描逻辑,用于区分"还可以等待更多数据"和"输入已经结束"。如果解析器忽略这两个状态,也不会影响基类本身的状态维护。

2.2函数原型与返回语义

函数原型如下:

c 复制代码
void
gst_audio_decoder_get_parse_state (GstAudioDecoder *dec,
                                   gboolean *sync,
                                   gboolean *eos);
参数 含义
dec 已创建的GstAudioDecoder对象
sync 可选输出参数,用于接收当前同步状态
eos 可选输出参数,用于接收当前EOS状态

该函数没有返回值。sync和eos可以传入NULL,只查询其中一个状态:

c 复制代码
gboolean sync;

gst_audio_decoder_get_parse_state (
    decoder,
    &sync,
    NULL);

也可以同时读取两个状态:

c 复制代码
gboolean sync;
gboolean eos;

gst_audio_decoder_get_parse_state (
    decoder,
    &sync,
    &eos);

函数内部不会创建对象、增加引用、释放资源或修改调用者传入的数据。它只把基类私有上下文中的两个字段复制到调用者提供的变量中。

如果dec不是有效的GstAudioDecoder对象,函数会触发g_return_if_fail()并直接返回。因为函数没有错误码,调用者应当在进入解析流程前保证对象有效。

该接口最适合在子类的parse回调中调用。它不是通用的跨线程同步接口,也不是用来等待状态变化的阻塞函数。

🌻3.调用流程剖析

3.1函数读取基类私有上下文

gst_audio_decoder_get_parse_state()的实现非常短:

c 复制代码
void
gst_audio_decoder_get_parse_state (GstAudioDecoder * dec,
    gboolean * sync, gboolean * eos)
{
  g_return_if_fail (GST_IS_AUDIO_DECODER (dec));

  if (sync)
    *sync = dec->priv->ctx.sync;

  if (eos)
    *eos = dec->priv->ctx.eos;
}

函数首先检查dec是否为有效的GstAudioDecoder对象。

随后分别判断两个输出指针是否为空:

  • sync不为空时,复制dec->priv->ctx.sync。
  • eos不为空时,复制dec->priv->ctx.eos。
  • 指针为空时,跳过对应字段。

ctx是GstAudioDecoderPrivate中的解析上下文,除了sync和eos之外,还保存输入Caps、输出音频信息、延迟和分配器等状态。
#mermaid-svg-w7IKK4DNxNPZt7UY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w7IKK4DNxNPZt7UY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w7IKK4DNxNPZt7UY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w7IKK4DNxNPZt7UY .error-icon{fill:#552222;}#mermaid-svg-w7IKK4DNxNPZt7UY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w7IKK4DNxNPZt7UY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w7IKK4DNxNPZt7UY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w7IKK4DNxNPZt7UY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w7IKK4DNxNPZt7UY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w7IKK4DNxNPZt7UY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w7IKK4DNxNPZt7UY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w7IKK4DNxNPZt7UY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w7IKK4DNxNPZt7UY .marker.cross{stroke:#333333;}#mermaid-svg-w7IKK4DNxNPZt7UY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w7IKK4DNxNPZt7UY p{margin:0;}#mermaid-svg-w7IKK4DNxNPZt7UY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-w7IKK4DNxNPZt7UY .cluster-label text{fill:#333;}#mermaid-svg-w7IKK4DNxNPZt7UY .cluster-label span{color:#333;}#mermaid-svg-w7IKK4DNxNPZt7UY .cluster-label span p{background-color:transparent;}#mermaid-svg-w7IKK4DNxNPZt7UY .label text,#mermaid-svg-w7IKK4DNxNPZt7UY span{fill:#333;color:#333;}#mermaid-svg-w7IKK4DNxNPZt7UY .node rect,#mermaid-svg-w7IKK4DNxNPZt7UY .node circle,#mermaid-svg-w7IKK4DNxNPZt7UY .node ellipse,#mermaid-svg-w7IKK4DNxNPZt7UY .node polygon,#mermaid-svg-w7IKK4DNxNPZt7UY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-w7IKK4DNxNPZt7UY .rough-node .label text,#mermaid-svg-w7IKK4DNxNPZt7UY .node .label text,#mermaid-svg-w7IKK4DNxNPZt7UY .image-shape .label,#mermaid-svg-w7IKK4DNxNPZt7UY .icon-shape .label{text-anchor:middle;}#mermaid-svg-w7IKK4DNxNPZt7UY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-w7IKK4DNxNPZt7UY .rough-node .label,#mermaid-svg-w7IKK4DNxNPZt7UY .node .label,#mermaid-svg-w7IKK4DNxNPZt7UY .image-shape .label,#mermaid-svg-w7IKK4DNxNPZt7UY .icon-shape .label{text-align:center;}#mermaid-svg-w7IKK4DNxNPZt7UY .node.clickable{cursor:pointer;}#mermaid-svg-w7IKK4DNxNPZt7UY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-w7IKK4DNxNPZt7UY .arrowheadPath{fill:#333333;}#mermaid-svg-w7IKK4DNxNPZt7UY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-w7IKK4DNxNPZt7UY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-w7IKK4DNxNPZt7UY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w7IKK4DNxNPZt7UY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-w7IKK4DNxNPZt7UY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w7IKK4DNxNPZt7UY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-w7IKK4DNxNPZt7UY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-w7IKK4DNxNPZt7UY .cluster text{fill:#333;}#mermaid-svg-w7IKK4DNxNPZt7UY .cluster span{color:#333;}#mermaid-svg-w7IKK4DNxNPZt7UY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-w7IKK4DNxNPZt7UY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-w7IKK4DNxNPZt7UY rect.text{fill:none;stroke-width:0;}#mermaid-svg-w7IKK4DNxNPZt7UY .icon-shape,#mermaid-svg-w7IKK4DNxNPZt7UY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w7IKK4DNxNPZt7UY .icon-shape p,#mermaid-svg-w7IKK4DNxNPZt7UY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-w7IKK4DNxNPZt7UY .icon-shape .label rect,#mermaid-svg-w7IKK4DNxNPZt7UY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w7IKK4DNxNPZt7UY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-w7IKK4DNxNPZt7UY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-w7IKK4DNxNPZt7UY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 传入GstAudioDecoder
检查对象类型
读取ctx.sync
读取ctx.eos
写入sync输出变量
写入eos输出变量

这个函数不会重新计算同步状态,也不会检查适配器中的数据,更不会扫描码流寻找同步字。

因此,调用时读到的是基类在当前解析阶段预先设置好的快照。

3.2基类设置状态后调用子类parse

真正的状态来源位于gst_audio_decoder_push_buffers()。

普通输入Buffer到达时,基类通过gst_audio_decoder_chain_forward()调用:

c 复制代码
gst_audio_decoder_push_buffers (dec, FALSE);

收到EOS或执行排空时,基类调用:

c 复制代码
gst_audio_decoder_push_buffers (dec, TRUE);

进入gst_audio_decoder_push_buffers()后,基类先把force写入ctx->eos:

c 复制代码
ctx->eos = force;

如果子类实现了parse虚函数,基类在调用它之前根据当前断点状态设置ctx->sync:

c 复制代码
ctx->sync = !priv->discont;

ret = klass->parse (
    dec,
    priv->adapter,
    &offset,
    &len);

因此,子类的parse回调中调用gst_audio_decoder_get_parse_state()时,就能读取本次解析对应的状态。

调用入口 force值 eos状态
普通Buffer链路 FALSE FALSE
EOS排空链路 TRUE TRUE
当前Buffer带有断点 不影响eos sync=FALSE
连续输出后的普通输入 FALSE sync=TRUE

如果子类没有实现parse虚函数,基类会把适配器中的全部可用数据作为一个输入Buffer交给handle_frame,此时通常不会进入子类解析回调,也就没有调用该查询接口的必要。
#mermaid-svg-Cl2TUGC2UO7mHHIQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .error-icon{fill:#552222;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .marker.cross{stroke:#333333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Cl2TUGC2UO7mHHIQ p{margin:0;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .cluster-label text{fill:#333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .cluster-label span{color:#333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .cluster-label span p{background-color:transparent;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .label text,#mermaid-svg-Cl2TUGC2UO7mHHIQ span{fill:#333;color:#333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .node rect,#mermaid-svg-Cl2TUGC2UO7mHHIQ .node circle,#mermaid-svg-Cl2TUGC2UO7mHHIQ .node ellipse,#mermaid-svg-Cl2TUGC2UO7mHHIQ .node polygon,#mermaid-svg-Cl2TUGC2UO7mHHIQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .rough-node .label text,#mermaid-svg-Cl2TUGC2UO7mHHIQ .node .label text,#mermaid-svg-Cl2TUGC2UO7mHHIQ .image-shape .label,#mermaid-svg-Cl2TUGC2UO7mHHIQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .rough-node .label,#mermaid-svg-Cl2TUGC2UO7mHHIQ .node .label,#mermaid-svg-Cl2TUGC2UO7mHHIQ .image-shape .label,#mermaid-svg-Cl2TUGC2UO7mHHIQ .icon-shape .label{text-align:center;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .node.clickable{cursor:pointer;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .arrowheadPath{fill:#333333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Cl2TUGC2UO7mHHIQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Cl2TUGC2UO7mHHIQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Cl2TUGC2UO7mHHIQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .cluster text{fill:#333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .cluster span{color:#333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Cl2TUGC2UO7mHHIQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .icon-shape,#mermaid-svg-Cl2TUGC2UO7mHHIQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .icon-shape p,#mermaid-svg-Cl2TUGC2UO7mHHIQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .icon-shape .label rect,#mermaid-svg-Cl2TUGC2UO7mHHIQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Cl2TUGC2UO7mHHIQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Cl2TUGC2UO7mHHIQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Cl2TUGC2UO7mHHIQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 普通Buffer或EOS事件
push_buffers设置eos
根据discont设置sync
调用子类parse
get_parse_state读取上下文
解析器决定offset和length

🌻4.实战案例

4.1准备一个两字节一帧的测试解析器

本案例定义一个最小的GstAudioDecoder子类。

解析器规定每个编码帧固定为2字节:

  • 只有1字节时,返回GST_FLOW_EOS,表示当前数据不足以组成完整帧。
  • 达到2字节时,返回完整帧范围。
  • handle_frame收到完整帧后,输出一个S16音频样本。

测试依次提交以下数据:

操作 适配器中的数据 预期状态
第一次提交1字节 1字节 sync=FALSE、eos=FALSE
第二次提交1字节 2字节 sync=FALSE、eos=FALSE
第三次提交1字节 1字节 sync=TRUE、eos=FALSE
发送EOS 仍有1字节 sync=TRUE、eos=TRUE

第二次提交形成完整帧后,解码器输出一帧音频,基类随后清除初始断点状态。因此第三次解析时,sync变为TRUE。

4.2在parse回调中读取状态

解析器的核心代码如下:

c 复制代码
static GstFlowReturn
parse_decoder_parse (GstAudioDecoder * decoder,
    GstAdapter * adapter,
    gint * offset,
    gint * length)
{
  ParseDecoder *self = PARSE_DECODER (decoder);
  gboolean sync;
  gboolean eos;
  guint available;

  /* 读取基类在本次parse调用前准备好的状态 */
  gst_audio_decoder_get_parse_state (
      decoder,
      &sync,
      &eos);

  /* 记录状态,供主函数最后验证 */
  if (self->parse_calls < 8) {
    self->observed_sync[self->parse_calls] = sync;
    self->observed_eos[self->parse_calls] = eos;
  }
  self->parse_calls++;

  available = gst_adapter_available (adapter);

  /* 不足两个字节时,等待更多输入或进入EOS排空 */
  if (available < 2)
    return GST_FLOW_EOS;

  *offset = 0;
  *length = 2;

  return GST_FLOW_OK;
}

这里的GST_FLOW_EOS表示当前解析器暂时没有完整帧,并不等价于立刻向下游发送EOS。基类会把这种结果转换为"暂时没有可提交帧",保留适配器中的残留数据。

只有在EOS排空阶段,解析器读取到eos=TRUE后,才能根据自身协议决定是否丢弃不完整尾帧或执行特殊收尾逻辑。

4.3完整代码与验证逻辑
c 复制代码
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <gst/check/gstharness.h>

typedef struct _ParseDecoder
{
  GstAudioDecoder parent;

  /* 保存每次parse回调读到的状态 */
  gboolean observed_sync[8];
  gboolean observed_eos[8];
  guint parse_calls;
} ParseDecoder;

typedef struct _ParseDecoderClass
{
  GstAudioDecoderClass parent_class;
} ParseDecoderClass;

G_DEFINE_TYPE (ParseDecoder, parse_decoder, GST_TYPE_AUDIO_DECODER);

/* 自定义解码器的输入和输出Pad模板 */
static GstStaticPadTemplate decoder_sink_template =
GST_STATIC_PAD_TEMPLATE (
    "sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-test-custom"));

static GstStaticPadTemplate decoder_src_template =
GST_STATIC_PAD_TEMPLATE (
    "src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw"));

static gboolean
parse_decoder_set_format (GstAudioDecoder * decoder, GstCaps * caps)
{
  GstAudioInfo info;

  /* 设置一个简单的S16单声道输出格式 */
  gst_audio_info_set_format (
      &info,
      GST_AUDIO_FORMAT_S16,
      44100,
      1,
      NULL);

  return gst_audio_decoder_set_output_format (
      decoder,
      &info);
}

static GstFlowReturn
parse_decoder_parse (GstAudioDecoder * decoder,
    GstAdapter * adapter,
    gint * offset,
    gint * length)
{
  ParseDecoder *self = PARSE_DECODER (decoder);
  gboolean sync;
  gboolean eos;
  guint available;

  /* 读取本次parse调用对应的同步和EOS状态 */
  gst_audio_decoder_get_parse_state (
      decoder,
      &sync,
      &eos);

  /* 保存状态,便于主函数按调用顺序检查 */
  if (self->parse_calls < 8) {
    self->observed_sync[self->parse_calls] = sync;
    self->observed_eos[self->parse_calls] = eos;
  }
  self->parse_calls++;

  available = gst_adapter_available (adapter);

  /* 两个字节组成一帧,不足时保留数据 */
  if (available < 2)
    return GST_FLOW_EOS;

  *offset = 0;
  *length = 2;

  return GST_FLOW_OK;
}

static GstFlowReturn
parse_decoder_handle_frame (GstAudioDecoder * decoder,
    GstBuffer * input)
{
  GstBuffer *output;

  if (input == NULL)
    return GST_FLOW_OK;

  /* 分配一个S16样本作为测试输出 */
  output = gst_audio_decoder_allocate_output_buffer (
      decoder,
      sizeof (gint16));

  if (output == NULL)
    return GST_FLOW_ERROR;

  /* 提交一帧输出,使基类更新连续状态 */
  return gst_audio_decoder_finish_frame (
      decoder,
      output,
      1);
}

static void
parse_decoder_class_init (ParseDecoderClass * 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,
      "Parse State Decoder",
      "Decoder/Audio",
      "Parse state test decoder",
      "GStreamer");

  decoder_class->set_format =
      parse_decoder_set_format;
  decoder_class->parse =
      parse_decoder_parse;
  decoder_class->handle_frame =
      parse_decoder_handle_frame;
}

static void
parse_decoder_init (ParseDecoder * decoder)
{
}

static GstStaticPadTemplate harness_src_template =
GST_STATIC_PAD_TEMPLATE (
    "harness-src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-test-custom"));

static GstStaticPadTemplate harness_sink_template =
GST_STATIC_PAD_TEMPLATE (
    "harness-sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw"));

int
main (int argc, char *argv[])
{
  GstElement *decoder_element;
  GstHarness *harness;
  ParseDecoder *decoder;
  GstBuffer *buffer;
  guint i;

  gst_init (&argc, &argv);

  /* 创建测试解码器和Harness */
  decoder_element =
      g_object_new (parse_decoder_get_type (), NULL);

  harness = gst_harness_new_full (
      decoder_element,
      &harness_src_template,
      "sink",
      &harness_sink_template,
      "src");

  gst_object_unref (decoder_element);

  decoder = PARSE_DECODER (harness->element);

  /* 设置输入Caps,触发AudioDecoder格式配置 */
  gst_harness_set_src_caps (
      harness,
      gst_caps_new_empty_simple (
          "audio/x-test-custom"));

  /* 第一次只提交一个字节,解析器暂时无法组成完整帧 */
  buffer = gst_buffer_new_allocate (NULL, 1, NULL);
  g_assert_cmpint (
      gst_harness_push (harness, buffer),
      ==,
      GST_FLOW_OK);

  /* 第二次提交一个字节,适配器中形成两个字节 */
  buffer = gst_buffer_new_allocate (NULL, 1, NULL);
  g_assert_cmpint (
      gst_harness_push (harness, buffer),
      ==,
      GST_FLOW_OK);

  /* 取出第二次提交产生的音频输出 */
  buffer = gst_harness_pull (harness);
  g_assert_nonnull (buffer);
  gst_buffer_unref (buffer);

  /* 第三次只提交一个字节,保留一个不完整帧 */
  buffer = gst_buffer_new_allocate (NULL, 1, NULL);
  g_assert_cmpint (
      gst_harness_push (harness, buffer),
      ==,
      GST_FLOW_OK);

  /* EOS会进入force排空路径,使parse看到eos=TRUE */
  g_assert_true (
      gst_harness_push_event (
          harness,
          gst_event_new_eos ()));

  /* 至少应观察到普通解析和EOS排空解析 */
  g_assert_cmpuint (decoder->parse_calls, >=, 4);

  /* 第一次解析发生在初始断点状态 */
  g_assert_false (decoder->observed_sync[0]);
  g_assert_false (decoder->observed_eos[0]);

  /* 第二次解析仍属于初始连续状态 */
  g_assert_false (decoder->observed_sync[1]);
  g_assert_false (decoder->observed_eos[1]);

  /* 输出一帧后,普通解析进入连续状态 */
  g_assert_true (decoder->observed_sync[2]);
  g_assert_false (decoder->observed_eos[2]);

  /* EOS排空阶段只改变eos,不改变当前sync */
  g_assert_true (decoder->observed_sync[3]);
  g_assert_true (decoder->observed_eos[3]);

  /* 打印每次parse读取到的状态 */
  for (i = 0; i < decoder->parse_calls && i < 8; i++) {
    g_print (
        "parse[%u]: sync=%s eos=%s\n",
        i,
        decoder->observed_sync[i] ? "TRUE" : "FALSE",
        decoder->observed_eos[i] ? "TRUE" : "FALSE");
  }

  gst_harness_teardown (harness);

  return 0;
}

编译时需要链接gstreamer-1.0、gstreamer-audio-1.0和gstreamer-check-1.0。验证重点不是输出Buffer的音频内容,而是parse回调读取到的状态顺序:

text 复制代码
普通输入阶段:eos=FALSE
初始断点阶段:sync=FALSE
输出一帧后的连续阶段:sync=TRUE
EOS排空阶段:eos=TRUE

🌻5.总结

gst_audio_decoder_get_parse_state()就是从GstAudioDecoder私有解析上下文读取当前sync和eos状态,供子类parse回调决定帧边界和EOS收尾策略。

相关推荐
~光~~1 小时前
【嵌入式Linux学习】GFP_NOIO / GFP_NOFS(Linux 内核 gfp 分配标志)
linux·运维·学习
坤坤子吖1 小时前
ELF加载、动态链接、GOT与PLT
linux·笔记·学习
勤劳X码农2 小时前
2026年TTS工具音频质量量化评测:MOS评分、延迟与格式兼容性实测
音视频
android阿杜2 小时前
Ubuntu 26.04 LTS 系统每次重启后屏幕亮度都会增加一点
linux·运维·ubuntu
懿路向前2 小时前
【唤醒实战笔记】2026-09-29 | 智能体提示词迭代史(v1 → v8)
linux·笔记·算法
AI天行健2 小时前
内容工作室 AI 视频量产选型:星宇智算无限画布能解决哪些生产卡点
人工智能·音视频
路溪非溪3 小时前
Linux下Shell脚本基础知识(二)
linux·运维·服务器
嵌入式分享3 小时前
驱动之路#44:硬件 I2C 和软件 I2C 谁更坑?
linux·嵌入式硬件
小猴子爱上树3 小时前
跨马翻译:批量图片翻译+视频字幕+智能抠图一站式工具
python·音视频