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

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

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

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

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

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

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

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

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

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

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

🍉🍉🍉文章目录🍉🍉🍉

🌻1.前言

本篇目的:理解gst_audio_encoder_set_frame_max()如何设置GstAudioEncoder一次交给子类处理的最大音频帧数,并通过一个最小编码器验证它对handle_frame()输入数据的影响。

GstAudioEncoder基类会先收集输入PCM,再按照子类提供的帧参数组织数据,最后调用子类的handle_frame()。如果编码器要求每个编码帧必须包含固定数量的采样点,子类还可以设置一次最多处理多少个这样的帧。

gst_audio_encoder_set_frame_max()保存的就是这个上限。它只更新基类的帧配置,不会自行收集PCM、调用编码器或发送输出Buffer。
#mermaid-svg-zQBGg1pEqdf6yvEZ{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-zQBGg1pEqdf6yvEZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zQBGg1pEqdf6yvEZ .error-icon{fill:#552222;}#mermaid-svg-zQBGg1pEqdf6yvEZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zQBGg1pEqdf6yvEZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zQBGg1pEqdf6yvEZ .marker.cross{stroke:#333333;}#mermaid-svg-zQBGg1pEqdf6yvEZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zQBGg1pEqdf6yvEZ p{margin:0;}#mermaid-svg-zQBGg1pEqdf6yvEZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zQBGg1pEqdf6yvEZ .cluster-label text{fill:#333;}#mermaid-svg-zQBGg1pEqdf6yvEZ .cluster-label span{color:#333;}#mermaid-svg-zQBGg1pEqdf6yvEZ .cluster-label span p{background-color:transparent;}#mermaid-svg-zQBGg1pEqdf6yvEZ .label text,#mermaid-svg-zQBGg1pEqdf6yvEZ span{fill:#333;color:#333;}#mermaid-svg-zQBGg1pEqdf6yvEZ .node rect,#mermaid-svg-zQBGg1pEqdf6yvEZ .node circle,#mermaid-svg-zQBGg1pEqdf6yvEZ .node ellipse,#mermaid-svg-zQBGg1pEqdf6yvEZ .node polygon,#mermaid-svg-zQBGg1pEqdf6yvEZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zQBGg1pEqdf6yvEZ .rough-node .label text,#mermaid-svg-zQBGg1pEqdf6yvEZ .node .label text,#mermaid-svg-zQBGg1pEqdf6yvEZ .image-shape .label,#mermaid-svg-zQBGg1pEqdf6yvEZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-zQBGg1pEqdf6yvEZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zQBGg1pEqdf6yvEZ .rough-node .label,#mermaid-svg-zQBGg1pEqdf6yvEZ .node .label,#mermaid-svg-zQBGg1pEqdf6yvEZ .image-shape .label,#mermaid-svg-zQBGg1pEqdf6yvEZ .icon-shape .label{text-align:center;}#mermaid-svg-zQBGg1pEqdf6yvEZ .node.clickable{cursor:pointer;}#mermaid-svg-zQBGg1pEqdf6yvEZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zQBGg1pEqdf6yvEZ .arrowheadPath{fill:#333333;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zQBGg1pEqdf6yvEZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zQBGg1pEqdf6yvEZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zQBGg1pEqdf6yvEZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zQBGg1pEqdf6yvEZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zQBGg1pEqdf6yvEZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zQBGg1pEqdf6yvEZ .cluster text{fill:#333;}#mermaid-svg-zQBGg1pEqdf6yvEZ .cluster span{color:#333;}#mermaid-svg-zQBGg1pEqdf6yvEZ 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-zQBGg1pEqdf6yvEZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zQBGg1pEqdf6yvEZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-zQBGg1pEqdf6yvEZ .icon-shape,#mermaid-svg-zQBGg1pEqdf6yvEZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zQBGg1pEqdf6yvEZ .icon-shape p,#mermaid-svg-zQBGg1pEqdf6yvEZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zQBGg1pEqdf6yvEZ .icon-shape .label rect,#mermaid-svg-zQBGg1pEqdf6yvEZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zQBGg1pEqdf6yvEZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zQBGg1pEqdf6yvEZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zQBGg1pEqdf6yvEZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入PCM数据
GstAudioEncoder收集数据
读取固定采样数和frame_max
组织多个完整音频帧
调用子类handle_frame

🌻2.应用场景和用法

2.1一次组织多个固定大小的音频帧

有些编码器按照固定大小的PCM帧工作。例如编码器每帧需要1024个采样点,子类可以把最小采样数和最大采样数都设为1024,再设置frame_max。

假设设置为4,基类在PCM数据足够时,最多可以把4个完整帧组织在一次handle_frame()调用中。单声道S16LE每个采样点占2字节,4帧输入数据为:

text 复制代码
1024 × 4 × 2 = 8192字节

一次交付多个完整帧,可以让编码器子类按批次处理输入。实际批次还受当前已收集PCM数量影响,数据不足时,基类不会凭空补齐完整帧。

frame_max表示一次最多组织的帧数,不是输出Buffer数量,也不是编码器可以缓存的最大字节数。

2.2函数原型与配置关系

函数原型如下:

c 复制代码
void
gst_audio_encoder_set_frame_max (GstAudioEncoder * enc,
                                gint num);
参数 含义
enc 已创建的GstAudioEncoder对象
num 一次最多接收的音频帧数

函数没有返回值。传入的值由基类保存在内部上下文的frame_max字段中。该接口检查enc是否为有效的GstAudioEncoder对象,但不提供设置成功或失败的返回码。

要让frame_max按多个固定帧组织数据,frame_samples_min和frame_samples_max必须相等:

c 复制代码
gst_audio_encoder_set_frame_samples_min (enc, 1024);
gst_audio_encoder_set_frame_samples_max (enc, 1024);
gst_audio_encoder_set_frame_max (enc, 4);

frame_samples_min与frame_samples_max描述每个音频帧包含多少个采样点。frame_max描述一次最多组合多少个这样的帧。只设置frame_max,没有配置一致的固定帧大小,不能达到多帧批量组织的效果。

这些值应在子类的set_format()中设置。输入格式发生重新协商时,基类会在调用set_format()前将这些配置重置为0,因此在其他位置提前设置可能会被清除。

相邻接口的职责如下:

接口 职责
gst_audio_encoder_set_frame_samples_min() 设置子类每帧至少需要的采样点数
gst_audio_encoder_set_frame_samples_max() 设置子类每帧最多需要的采样点数
gst_audio_encoder_set_frame_max() 设置一次最多交给子类处理的帧数
gst_audio_encoder_get_frame_max() 查询当前配置的一次最大帧数
gst_audio_encoder_finish_frame() 提交编码输出,并告知已处理的输入采样点数

🌻3.调用流程剖析

3.1函数保存一次最大帧数

gst_audio_encoder_set_frame_max()本身逻辑很短,关键操作是将参数写入GstAudioEncoder私有上下文:

c 复制代码
void
gst_audio_encoder_set_frame_max (GstAudioEncoder * enc, gint num)
{
  g_return_if_fail (GST_IS_AUDIO_ENCODER (enc));

  enc->priv->ctx.frame_max = num;
  GST_LOG_OBJECT (enc, "set to %d", num);
}

函数不会在这里校验frame_samples_min和frame_samples_max是否相等,也不会立即重新组织输入数据。它只是保存配置,供基类后续收集PCM时读取。

每次调用子类的set_format()之前,基类会将frame_samples_min、frame_samples_max和frame_max重置为0。子类应在set_format()中重新声明当前输入格式对应的帧处理要求。
#mermaid-svg-7OFvfj8hfG0IgNRr{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-7OFvfj8hfG0IgNRr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7OFvfj8hfG0IgNRr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7OFvfj8hfG0IgNRr .error-icon{fill:#552222;}#mermaid-svg-7OFvfj8hfG0IgNRr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7OFvfj8hfG0IgNRr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7OFvfj8hfG0IgNRr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7OFvfj8hfG0IgNRr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7OFvfj8hfG0IgNRr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7OFvfj8hfG0IgNRr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7OFvfj8hfG0IgNRr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7OFvfj8hfG0IgNRr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7OFvfj8hfG0IgNRr .marker.cross{stroke:#333333;}#mermaid-svg-7OFvfj8hfG0IgNRr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7OFvfj8hfG0IgNRr p{margin:0;}#mermaid-svg-7OFvfj8hfG0IgNRr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7OFvfj8hfG0IgNRr .cluster-label text{fill:#333;}#mermaid-svg-7OFvfj8hfG0IgNRr .cluster-label span{color:#333;}#mermaid-svg-7OFvfj8hfG0IgNRr .cluster-label span p{background-color:transparent;}#mermaid-svg-7OFvfj8hfG0IgNRr .label text,#mermaid-svg-7OFvfj8hfG0IgNRr span{fill:#333;color:#333;}#mermaid-svg-7OFvfj8hfG0IgNRr .node rect,#mermaid-svg-7OFvfj8hfG0IgNRr .node circle,#mermaid-svg-7OFvfj8hfG0IgNRr .node ellipse,#mermaid-svg-7OFvfj8hfG0IgNRr .node polygon,#mermaid-svg-7OFvfj8hfG0IgNRr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7OFvfj8hfG0IgNRr .rough-node .label text,#mermaid-svg-7OFvfj8hfG0IgNRr .node .label text,#mermaid-svg-7OFvfj8hfG0IgNRr .image-shape .label,#mermaid-svg-7OFvfj8hfG0IgNRr .icon-shape .label{text-anchor:middle;}#mermaid-svg-7OFvfj8hfG0IgNRr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7OFvfj8hfG0IgNRr .rough-node .label,#mermaid-svg-7OFvfj8hfG0IgNRr .node .label,#mermaid-svg-7OFvfj8hfG0IgNRr .image-shape .label,#mermaid-svg-7OFvfj8hfG0IgNRr .icon-shape .label{text-align:center;}#mermaid-svg-7OFvfj8hfG0IgNRr .node.clickable{cursor:pointer;}#mermaid-svg-7OFvfj8hfG0IgNRr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7OFvfj8hfG0IgNRr .arrowheadPath{fill:#333333;}#mermaid-svg-7OFvfj8hfG0IgNRr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7OFvfj8hfG0IgNRr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7OFvfj8hfG0IgNRr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7OFvfj8hfG0IgNRr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7OFvfj8hfG0IgNRr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7OFvfj8hfG0IgNRr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7OFvfj8hfG0IgNRr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7OFvfj8hfG0IgNRr .cluster text{fill:#333;}#mermaid-svg-7OFvfj8hfG0IgNRr .cluster span{color:#333;}#mermaid-svg-7OFvfj8hfG0IgNRr 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-7OFvfj8hfG0IgNRr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7OFvfj8hfG0IgNRr rect.text{fill:none;stroke-width:0;}#mermaid-svg-7OFvfj8hfG0IgNRr .icon-shape,#mermaid-svg-7OFvfj8hfG0IgNRr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7OFvfj8hfG0IgNRr .icon-shape p,#mermaid-svg-7OFvfj8hfG0IgNRr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7OFvfj8hfG0IgNRr .icon-shape .label rect,#mermaid-svg-7OFvfj8hfG0IgNRr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7OFvfj8hfG0IgNRr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7OFvfj8hfG0IgNRr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7OFvfj8hfG0IgNRr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 子类进入set_format
配置每帧最小采样数
配置每帧最大采样数
调用set_frame_max保存上限
frame_max写入私有上下文

3.2基类按配置组织PCM数据

输入PCM到达后,基类把数据放入内部GstAdapter,再根据可用字节数和帧配置决定交给子类的数据大小。

当frame_samples_min和frame_samples_max相等时,基类可以把输入按固定大小的完整帧组织。如果frame_max大于1,实际交付的帧数不会超过frame_max,也不会超过当前已经收集到的完整帧数。

当frame_max为0时,基类会按照当前可用PCM组织完整帧;设置为大于1的值时,则会对一次组织的完整帧数进行限制。
#mermaid-svg-jYW1WD9vta55OGMQ{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-jYW1WD9vta55OGMQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jYW1WD9vta55OGMQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jYW1WD9vta55OGMQ .error-icon{fill:#552222;}#mermaid-svg-jYW1WD9vta55OGMQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jYW1WD9vta55OGMQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jYW1WD9vta55OGMQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jYW1WD9vta55OGMQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jYW1WD9vta55OGMQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jYW1WD9vta55OGMQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jYW1WD9vta55OGMQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jYW1WD9vta55OGMQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jYW1WD9vta55OGMQ .marker.cross{stroke:#333333;}#mermaid-svg-jYW1WD9vta55OGMQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jYW1WD9vta55OGMQ p{margin:0;}#mermaid-svg-jYW1WD9vta55OGMQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jYW1WD9vta55OGMQ .cluster-label text{fill:#333;}#mermaid-svg-jYW1WD9vta55OGMQ .cluster-label span{color:#333;}#mermaid-svg-jYW1WD9vta55OGMQ .cluster-label span p{background-color:transparent;}#mermaid-svg-jYW1WD9vta55OGMQ .label text,#mermaid-svg-jYW1WD9vta55OGMQ span{fill:#333;color:#333;}#mermaid-svg-jYW1WD9vta55OGMQ .node rect,#mermaid-svg-jYW1WD9vta55OGMQ .node circle,#mermaid-svg-jYW1WD9vta55OGMQ .node ellipse,#mermaid-svg-jYW1WD9vta55OGMQ .node polygon,#mermaid-svg-jYW1WD9vta55OGMQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jYW1WD9vta55OGMQ .rough-node .label text,#mermaid-svg-jYW1WD9vta55OGMQ .node .label text,#mermaid-svg-jYW1WD9vta55OGMQ .image-shape .label,#mermaid-svg-jYW1WD9vta55OGMQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-jYW1WD9vta55OGMQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jYW1WD9vta55OGMQ .rough-node .label,#mermaid-svg-jYW1WD9vta55OGMQ .node .label,#mermaid-svg-jYW1WD9vta55OGMQ .image-shape .label,#mermaid-svg-jYW1WD9vta55OGMQ .icon-shape .label{text-align:center;}#mermaid-svg-jYW1WD9vta55OGMQ .node.clickable{cursor:pointer;}#mermaid-svg-jYW1WD9vta55OGMQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jYW1WD9vta55OGMQ .arrowheadPath{fill:#333333;}#mermaid-svg-jYW1WD9vta55OGMQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jYW1WD9vta55OGMQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jYW1WD9vta55OGMQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jYW1WD9vta55OGMQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jYW1WD9vta55OGMQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jYW1WD9vta55OGMQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jYW1WD9vta55OGMQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jYW1WD9vta55OGMQ .cluster text{fill:#333;}#mermaid-svg-jYW1WD9vta55OGMQ .cluster span{color:#333;}#mermaid-svg-jYW1WD9vta55OGMQ 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-jYW1WD9vta55OGMQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jYW1WD9vta55OGMQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-jYW1WD9vta55OGMQ .icon-shape,#mermaid-svg-jYW1WD9vta55OGMQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jYW1WD9vta55OGMQ .icon-shape p,#mermaid-svg-jYW1WD9vta55OGMQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jYW1WD9vta55OGMQ .icon-shape .label rect,#mermaid-svg-jYW1WD9vta55OGMQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jYW1WD9vta55OGMQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jYW1WD9vta55OGMQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jYW1WD9vta55OGMQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
PCM进入内部GstAdapter
计算可用字节数
每帧最小值等于最大值
按普通帧配置处理
计算当前完整帧数
应用frame_max上限
构造输入Buffer并调用handle_frame

子类在handle_frame()中处理收到的PCM。编码完成后,子类调用gst_audio_encoder_finish_frame()提交输出,并用samples参数说明编码输出代表多少个输入采样点。frame_max控制输入批次,finish_frame()负责提交编码结果,两者作用不同。

🌻4.实战案例

4.1配置固定帧大小和批次数

下面的测试编码器使用单声道S16LE输入,每个固定帧包含256个采样点,并将一次最多处理帧数设为4:

c 复制代码
gst_audio_encoder_set_frame_samples_min (enc, 256);
gst_audio_encoder_set_frame_samples_max (enc, 256);
gst_audio_encoder_set_frame_max (enc, 4);

当基类已经收集到1024个采样点时,预计一次交给handle_frame()的数据大小为:

text 复制代码
256 × 4 × 2 = 2048字节

这里每个采样点占2字节,完整输入Buffer包含4个256采样点的固定帧。

4.2提交输入并观察批次

测试程序使用GstHarness创建自定义编码器,提交2048字节的输入Buffer,再在handle_frame()中记录收到的Buffer大小和调用次数。

验证重点是:一次输入数据包含4个固定帧,frame_max设为4时,基类将这4帧组织为一次handle_frame()输入。

4.3完整代码
c 复制代码
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <gst/check/gstharness.h>

#define SAMPLES_PER_FRAME 256
#define FRAME_MAX 4

typedef struct _BatchEncoder
{
  GstAudioEncoder parent;
} BatchEncoder;

typedef struct _BatchEncoderClass
{
  GstAudioEncoderClass parent_class;
} BatchEncoderClass;

G_DEFINE_TYPE (BatchEncoder, batch_encoder, GST_TYPE_AUDIO_ENCODER);

static guint handle_frame_calls;
static gsize last_input_size;

/* 编码器接收原始音频,输出测试用压缩格式 */
static GstStaticPadTemplate encoder_sink_template =
GST_STATIC_PAD_TEMPLATE (
    "sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw"));

static GstStaticPadTemplate encoder_src_template =
GST_STATIC_PAD_TEMPLATE (
    "src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-test-encoded"));

static gboolean
batch_encoder_set_format (GstAudioEncoder * enc, GstAudioInfo * info)
{
  GstCaps *output_caps;
  gboolean result;

  /* 固定每帧采样点数,基类据此按完整帧收集PCM */
  gst_audio_encoder_set_frame_samples_min (enc, SAMPLES_PER_FRAME);
  gst_audio_encoder_set_frame_samples_max (enc, SAMPLES_PER_FRAME);

  /* 一次最多将4个完整帧交给handle_frame */
  gst_audio_encoder_set_frame_max (enc, FRAME_MAX);

  output_caps = gst_caps_new_empty_simple ("audio/x-test-encoded");
  result = gst_audio_encoder_set_output_format (enc, output_caps);
  gst_caps_unref (output_caps);

  return result;
}

static GstFlowReturn
batch_encoder_handle_frame (GstAudioEncoder * enc, GstBuffer * input)
{
  GstBuffer *output;
  gsize input_size;

  if (input == NULL)
    return GST_FLOW_OK;

  /* 记录基类本次组织出的PCM字节数 */
  handle_frame_calls++;
  input_size = gst_buffer_get_size (input);
  last_input_size = input_size;

  /* 测试编码器生成一个非空输出Buffer */
  output = gst_buffer_new_allocate (NULL, 1, NULL);
  if (output == NULL)
    return GST_FLOW_ERROR;

  /* 单声道S16LE每个采样点占2字节,提交对应采样点数 */
  return gst_audio_encoder_finish_frame (
      enc,
      output,
      input_size / sizeof (gint16));
}

static void
batch_encoder_class_init (BatchEncoderClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
  GstAudioEncoderClass *encoder_class =
      GST_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,
      "Batch Encoder",
      "Codec/Encoder/Audio",
      "Audio encoder frame batching test",
      "GStreamer");

  encoder_class->set_format = batch_encoder_set_format;
  encoder_class->handle_frame = batch_encoder_handle_frame;
}

static void
batch_encoder_init (BatchEncoder * enc)
{
}

/* 为Harness两端定义匹配的Pad模板 */
static GstStaticPadTemplate harness_src_template =
GST_STATIC_PAD_TEMPLATE (
    "harness-src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("audio/x-raw"));

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

int
main (int argc, char *argv[])
{
  GstElement *element;
  GstHarness *harness;
  GstCaps *input_caps;
  GstBuffer *input;
  GstBuffer *output;
  gsize input_size;
  gboolean ok;

  gst_init (&argc, &argv);

  /* 创建编码器,并用Harness连接输入和输出Pad */
  element = g_object_new (batch_encoder_get_type (), NULL);
  harness = gst_harness_new_full (
      element,
      &harness_src_template,
      "sink",
      &harness_sink_template,
      "src");
  gst_object_unref (element);

  /* 设置单声道S16LE、48000Hz输入格式,触发set_format配置 */
  input_caps = gst_caps_from_string (
      "audio/x-raw,format=S16LE,layout=interleaved,"
      "rate=48000,channels=1");
  gst_harness_set_src_caps (harness, input_caps);

  /* 4个固定帧组成1024个采样点,每点2字节 */
  input_size = SAMPLES_PER_FRAME * FRAME_MAX * sizeof (gint16);
  input = gst_buffer_new_allocate (NULL, input_size, NULL);

  /* 推入PCM,触发基类收集并调用handle_frame */
  ok = gst_harness_push (harness, input) == GST_FLOW_OK;

  /* 取出编码器输出Buffer,确认输出路径已经完成 */
  output = ok ? gst_harness_pull (harness) : NULL;
  ok = ok && output != NULL;

  if (output)
    gst_buffer_unref (output);

  if (ok) {
    g_print ("frame_max: %d\n",
        gst_audio_encoder_get_frame_max (
            GST_AUDIO_ENCODER (harness->element)));
    g_print ("handle_frame calls: %u\n", handle_frame_calls);
    g_print ("handle_frame input bytes: %" G_GSIZE_FORMAT "\n",
        last_input_size);
  }

  gst_harness_teardown (harness);

  return ok ? 0 : 1;
}

按这组输入和帧配置,预期观察到:

text 复制代码
frame_max: 4
handle_frame calls: 1
handle_frame input bytes: 2048

🌻5.总结

gst_audio_encoder_set_frame_max()就是为GstAudioEncoder设置一次最多组织的固定大小音频帧数,供基类决定交给子类handle_frame()的输入批次。

相关推荐
H.莓飛1 小时前
【C++】命名空间、缺省参数、函数重载与引用
linux·开发语言·c++·visual studio
91刘仁德1 小时前
IP协议详解:从IP协议头到网段划分、路由与NAT
linux·服务器·网络·网络协议·tcp/ip
硅基手札2 小时前
【Linux内核专栏 14】网络协议栈
linux·运维·网络协议
沫璃染墨4 小时前
《Linux工程实践篇(一):认识设计模式——从日志系统看策略模式》
linux·c++·安全·设计模式·策略模式
传奇开心果编程4 小时前
【Compose Multiplatform 跨端开发学与练】第7课 平台适配与互操作
android·windows·学习·ui·ios·kotlin·composer
mmsx4 小时前
Android 地图数据链路:从 GDAL、KML 到多引擎适配
android·kotlin
工作10年+,存储芯片行业4 小时前
Linux NVMe 中断排查与性能优化:CPU 亲和性
linux·运维·服务器·windows·性能优化·ssd·pcie
2601_968900774 小时前
大模型版本回归评估实战:从能力指标到额度口径的完整链路
android·数据挖掘·回归
wuminyu4 小时前
ForkJoinPool内部WorkQueue的Lock-Free数组操作以及并发任务窃取原理剖析
java·linux·c语言·jvm·c++