简介: 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收尾策略。