简介: CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中...... 】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中...... 】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
更多原创,欢迎关注:Android系统攻城狮

🍉🍉🍉文章目录🍉🍉🍉
-
-
- 🌻1.前言
- 🌻2.应用场景和用法
-
- [🌻2.1 应用场景](#🌻2.1 应用场景)
- [🌻2.2 函数用法](#🌻2.2 函数用法)
- 🌻3.调用流程剖析
-
- [🌻3.1 校验参数并建立转换器对象](#🌻3.1 校验参数并建立转换器对象)
- [🌻3.2 组装转换链并选择执行路径](#🌻3.2 组装转换链并选择执行路径)
- 🌻4.实战案例
-
- [🌻4.1 准备输入和输出格式](#🌻4.1 准备输入和输出格式)
- [🌻4.2 创建并使用转换器](#🌻4.2 创建并使用转换器)
- [🌻4.3 验证转换结果](#🌻4.3 验证转换结果)
- 🌻5.总结
-
🌻1.前言
本篇目的:理解
gst_audio_converter_new()如何根据输入、输出音频信息创建可复用的GstAudioConverter,以及它为什么只是建立转换方案,而不是立即处理音频数据。
音频处理过程中,输入和输出格式经常存在差异:
text
输入:S16、48000Hz、2声道、交错布局
输出:F32、48000Hz、2声道、交错布局
也可能同时发生采样率、声道数量或内存布局变化。如果每次处理Buffer时都重新判断这些差异,转换逻辑会分散在数据处理路径中,难以管理和优化。
gst_audio_converter_new()接收两个GstAudioInfo,分别描述输入和输出音频格式,然后创建一个不透明的GstAudioConverter对象。它会在创建阶段检查参数、保存配置,并预先组织后续需要使用的转换链。
需要注意:
text
gst_audio_converter_new()
≠
立即转换一块音频数据
它负责建立转换器和转换方案。真正的数据处理由后续的gst_audio_converter_samples()或gst_audio_converter_convert()完成。
#mermaid-svg-59WPOl13bR7eD9Qu{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-59WPOl13bR7eD9Qu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-59WPOl13bR7eD9Qu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-59WPOl13bR7eD9Qu .error-icon{fill:#552222;}#mermaid-svg-59WPOl13bR7eD9Qu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-59WPOl13bR7eD9Qu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-59WPOl13bR7eD9Qu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-59WPOl13bR7eD9Qu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-59WPOl13bR7eD9Qu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-59WPOl13bR7eD9Qu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-59WPOl13bR7eD9Qu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-59WPOl13bR7eD9Qu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-59WPOl13bR7eD9Qu .marker.cross{stroke:#333333;}#mermaid-svg-59WPOl13bR7eD9Qu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-59WPOl13bR7eD9Qu p{margin:0;}#mermaid-svg-59WPOl13bR7eD9Qu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-59WPOl13bR7eD9Qu .cluster-label text{fill:#333;}#mermaid-svg-59WPOl13bR7eD9Qu .cluster-label span{color:#333;}#mermaid-svg-59WPOl13bR7eD9Qu .cluster-label span p{background-color:transparent;}#mermaid-svg-59WPOl13bR7eD9Qu .label text,#mermaid-svg-59WPOl13bR7eD9Qu span{fill:#333;color:#333;}#mermaid-svg-59WPOl13bR7eD9Qu .node rect,#mermaid-svg-59WPOl13bR7eD9Qu .node circle,#mermaid-svg-59WPOl13bR7eD9Qu .node ellipse,#mermaid-svg-59WPOl13bR7eD9Qu .node polygon,#mermaid-svg-59WPOl13bR7eD9Qu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-59WPOl13bR7eD9Qu .rough-node .label text,#mermaid-svg-59WPOl13bR7eD9Qu .node .label text,#mermaid-svg-59WPOl13bR7eD9Qu .image-shape .label,#mermaid-svg-59WPOl13bR7eD9Qu .icon-shape .label{text-anchor:middle;}#mermaid-svg-59WPOl13bR7eD9Qu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-59WPOl13bR7eD9Qu .rough-node .label,#mermaid-svg-59WPOl13bR7eD9Qu .node .label,#mermaid-svg-59WPOl13bR7eD9Qu .image-shape .label,#mermaid-svg-59WPOl13bR7eD9Qu .icon-shape .label{text-align:center;}#mermaid-svg-59WPOl13bR7eD9Qu .node.clickable{cursor:pointer;}#mermaid-svg-59WPOl13bR7eD9Qu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-59WPOl13bR7eD9Qu .arrowheadPath{fill:#333333;}#mermaid-svg-59WPOl13bR7eD9Qu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-59WPOl13bR7eD9Qu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-59WPOl13bR7eD9Qu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-59WPOl13bR7eD9Qu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-59WPOl13bR7eD9Qu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-59WPOl13bR7eD9Qu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-59WPOl13bR7eD9Qu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-59WPOl13bR7eD9Qu .cluster text{fill:#333;}#mermaid-svg-59WPOl13bR7eD9Qu .cluster span{color:#333;}#mermaid-svg-59WPOl13bR7eD9Qu 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-59WPOl13bR7eD9Qu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-59WPOl13bR7eD9Qu rect.text{fill:none;stroke-width:0;}#mermaid-svg-59WPOl13bR7eD9Qu .icon-shape,#mermaid-svg-59WPOl13bR7eD9Qu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-59WPOl13bR7eD9Qu .icon-shape p,#mermaid-svg-59WPOl13bR7eD9Qu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-59WPOl13bR7eD9Qu .icon-shape .label rect,#mermaid-svg-59WPOl13bR7eD9Qu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-59WPOl13bR7eD9Qu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-59WPOl13bR7eD9Qu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-59WPOl13bR7eD9Qu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① 输入GstAudioInfo
② 输出GstAudioInfo
③ 创建转换器
④ 组装转换链
⑤ 处理音频数据
🌻2.应用场景和用法
🌻2.1 应用场景
GstAudioConverter可以理解成一份已经准备好的音频转换方案。
输入和输出的差异由两个GstAudioInfo描述:
text
in_info = 输入格式
out_info = 输出格式
gst_audio_converter_new()根据这两个对象决定是否需要:
text
格式转换
通道混音
采样率转换
交错与非交错布局转换
量化、抖动和噪声整形
常见应用场景主要有以下几类。
第一类是audioconvertElement根据协商后的输入Caps和输出Caps创建转换器。Caps确定后,Element把Caps解析成GstAudioInfo,再将转换器保存到Element内部,后续逐个处理音频Buffer。
第二类是audioresample等需要持续处理音频流的Element。转换器创建后可以被重复使用,避免每个Buffer都重新构造格式转换逻辑。
第三类是自定义音频处理模块需要组合多种转换操作。例如输入是整数格式,输出是浮点格式,同时需要调整采样率和声道布局,这些操作可以由同一个GstAudioConverter统一组织。
第四类是转换配置可能发生变化的场景。如果只是输入和输出采样率变化,并且创建时启用了GST_AUDIO_CONVERTER_FLAG_VARIABLE_RATE,可以使用gst_audio_converter_update_config()更新速率。格式、声道数或布局发生变化时,通常需要释放旧转换器并重新创建。
对象关系可以概括为:
#mermaid-svg-7ZYIbrsdonl39BXg{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-7ZYIbrsdonl39BXg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7ZYIbrsdonl39BXg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7ZYIbrsdonl39BXg .error-icon{fill:#552222;}#mermaid-svg-7ZYIbrsdonl39BXg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7ZYIbrsdonl39BXg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7ZYIbrsdonl39BXg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7ZYIbrsdonl39BXg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7ZYIbrsdonl39BXg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7ZYIbrsdonl39BXg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7ZYIbrsdonl39BXg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7ZYIbrsdonl39BXg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7ZYIbrsdonl39BXg .marker.cross{stroke:#333333;}#mermaid-svg-7ZYIbrsdonl39BXg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7ZYIbrsdonl39BXg p{margin:0;}#mermaid-svg-7ZYIbrsdonl39BXg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7ZYIbrsdonl39BXg .cluster-label text{fill:#333;}#mermaid-svg-7ZYIbrsdonl39BXg .cluster-label span{color:#333;}#mermaid-svg-7ZYIbrsdonl39BXg .cluster-label span p{background-color:transparent;}#mermaid-svg-7ZYIbrsdonl39BXg .label text,#mermaid-svg-7ZYIbrsdonl39BXg span{fill:#333;color:#333;}#mermaid-svg-7ZYIbrsdonl39BXg .node rect,#mermaid-svg-7ZYIbrsdonl39BXg .node circle,#mermaid-svg-7ZYIbrsdonl39BXg .node ellipse,#mermaid-svg-7ZYIbrsdonl39BXg .node polygon,#mermaid-svg-7ZYIbrsdonl39BXg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7ZYIbrsdonl39BXg .rough-node .label text,#mermaid-svg-7ZYIbrsdonl39BXg .node .label text,#mermaid-svg-7ZYIbrsdonl39BXg .image-shape .label,#mermaid-svg-7ZYIbrsdonl39BXg .icon-shape .label{text-anchor:middle;}#mermaid-svg-7ZYIbrsdonl39BXg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7ZYIbrsdonl39BXg .rough-node .label,#mermaid-svg-7ZYIbrsdonl39BXg .node .label,#mermaid-svg-7ZYIbrsdonl39BXg .image-shape .label,#mermaid-svg-7ZYIbrsdonl39BXg .icon-shape .label{text-align:center;}#mermaid-svg-7ZYIbrsdonl39BXg .node.clickable{cursor:pointer;}#mermaid-svg-7ZYIbrsdonl39BXg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7ZYIbrsdonl39BXg .arrowheadPath{fill:#333333;}#mermaid-svg-7ZYIbrsdonl39BXg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7ZYIbrsdonl39BXg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7ZYIbrsdonl39BXg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7ZYIbrsdonl39BXg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7ZYIbrsdonl39BXg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7ZYIbrsdonl39BXg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7ZYIbrsdonl39BXg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7ZYIbrsdonl39BXg .cluster text{fill:#333;}#mermaid-svg-7ZYIbrsdonl39BXg .cluster span{color:#333;}#mermaid-svg-7ZYIbrsdonl39BXg 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-7ZYIbrsdonl39BXg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7ZYIbrsdonl39BXg rect.text{fill:none;stroke-width:0;}#mermaid-svg-7ZYIbrsdonl39BXg .icon-shape,#mermaid-svg-7ZYIbrsdonl39BXg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7ZYIbrsdonl39BXg .icon-shape p,#mermaid-svg-7ZYIbrsdonl39BXg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7ZYIbrsdonl39BXg .icon-shape .label rect,#mermaid-svg-7ZYIbrsdonl39BXg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7ZYIbrsdonl39BXg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7ZYIbrsdonl39BXg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7ZYIbrsdonl39BXg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入Caps或格式
输入GstAudioInfo
gst_audio_converter_new
GstAudioConverter
samples/convert处理数据
gst_audio_converter_new()不是单独的声道重排接口,也不是单独的采样率转换接口:
text
只交换声道位置:
gst_audio_reorder_channels()
只执行采样率转换:
GstAudioResampler
组合格式、通道、采样率和布局转换:
GstAudioConverter
🌻2.2 函数用法
函数原型:
cpp
GstAudioConverter *gst_audio_converter_new(
GstAudioConverterFlags flags,
GstAudioInfo *in_info,
GstAudioInfo *out_info,
GstStructure *config);
参数:
| 参数 | 作用 |
|---|---|
flags |
控制输入可写性和采样率更新能力 |
in_info |
输入音频格式描述 |
out_info |
输出音频格式描述 |
config |
可选的转换配置,传入后所有权交给函数 |
常用Flag:
| Flag | 作用 |
|---|---|
GST_AUDIO_CONVERTER_FLAG_NONE |
使用默认行为 |
GST_AUDIO_CONVERTER_FLAG_IN_WRITABLE |
表示输入Sample数组可写,可以作为转换过程中的临时存储 |
GST_AUDIO_CONVERTER_FLAG_VARIABLE_RATE |
允许通过gst_audio_converter_update_config()更新输入和输出采样率 |
config可以设置重采样方法、抖动方法、噪声整形、量化参数和声道混音矩阵等选项。该参数标记为transfer full,如果传入非空GstStructure,调用者不应在函数返回后继续释放或使用它。
返回值:
text
成功:返回新创建的GstAudioConverter*
失败:返回NULL
失败通常表示:
text
输入或输出信息为空
混音矩阵尺寸不正确
输入输出声道数不同且缺少必要的定位信息
转换方案无法建立
最小调用方式:
cpp
GstAudioInfo in_info;
GstAudioInfo out_info;
GstAudioConverter *converter;
gst_audio_info_set_format(
&in_info,
GST_AUDIO_FORMAT_S16,
48000,
2,
NULL);
gst_audio_info_set_format(
&out_info,
GST_AUDIO_FORMAT_F32,
48000,
2,
NULL);
converter = gst_audio_converter_new(
GST_AUDIO_CONVERTER_FLAG_NONE,
&in_info,
&out_info,
NULL);
这里的in_info和out_info由调用者提供。构造函数会把它们复制到转换器内部,不会接管这两个对象的生命周期。
返回的GstAudioConverter是一个不透明的Boxed对象,不是GObject。使用完成后应调用:
cpp
gst_audio_converter_free(converter);
不能使用gst_object_unref()释放。
调用前:
text
只有输入和输出格式描述
没有可执行的转换链
调用后:
text
得到GstAudioConverter
保存输入和输出信息
保存转换配置
建立内部转换链
确定是否支持原地处理或旁路处理
🌻3.调用流程剖析
🌻3.1 校验参数并建立转换器对象
gst_audio_converter_new()首先检查输入和输出GstAudioInfo指针:
cpp
g_return_val_if_fail(in_info != NULL, FALSE);
g_return_val_if_fail(out_info != NULL, FALSE);
虽然源码使用的是g_return_val_if_fail(),但函数返回类型是指针,因此失败时实际表现为返回NULL。
如果config不为空,函数会先读取其中的混音矩阵配置,并检查矩阵尺寸是否匹配输入和输出声道数。
例如:
text
输入2声道,输出1声道
混音矩阵必须有1行、2列
如果输入输出声道数不同,并且任意一方是未定位声道,同时又没有提供混音矩阵,函数会拒绝创建转换器。
参数检查通过后,函数分配转换器对象:
cpp
convert = g_new0(GstAudioConverter, 1);
convert->flags = flags;
convert->in = *in_info;
convert->out = *out_info;
这一步有两个关键结果:
text
flags被复制到转换器内部
in_info和out_info的内容被复制到转换器内部
调用者仍然拥有原来的GstAudioInfo,但之后修改原对象不会自动改变已经创建的转换器。
函数随后创建默认配置:
cpp
convert->config =
gst_structure_new_empty("GstAudioConverter");
如果调用者传入了config,构造函数会通过gst_audio_converter_update_config()复制配置内容,并释放传入的GstStructure。
#mermaid-svg-kH4sK5ZZC12qiAVv{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-kH4sK5ZZC12qiAVv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kH4sK5ZZC12qiAVv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kH4sK5ZZC12qiAVv .error-icon{fill:#552222;}#mermaid-svg-kH4sK5ZZC12qiAVv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kH4sK5ZZC12qiAVv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kH4sK5ZZC12qiAVv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kH4sK5ZZC12qiAVv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kH4sK5ZZC12qiAVv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kH4sK5ZZC12qiAVv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kH4sK5ZZC12qiAVv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kH4sK5ZZC12qiAVv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kH4sK5ZZC12qiAVv .marker.cross{stroke:#333333;}#mermaid-svg-kH4sK5ZZC12qiAVv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kH4sK5ZZC12qiAVv p{margin:0;}#mermaid-svg-kH4sK5ZZC12qiAVv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kH4sK5ZZC12qiAVv .cluster-label text{fill:#333;}#mermaid-svg-kH4sK5ZZC12qiAVv .cluster-label span{color:#333;}#mermaid-svg-kH4sK5ZZC12qiAVv .cluster-label span p{background-color:transparent;}#mermaid-svg-kH4sK5ZZC12qiAVv .label text,#mermaid-svg-kH4sK5ZZC12qiAVv span{fill:#333;color:#333;}#mermaid-svg-kH4sK5ZZC12qiAVv .node rect,#mermaid-svg-kH4sK5ZZC12qiAVv .node circle,#mermaid-svg-kH4sK5ZZC12qiAVv .node ellipse,#mermaid-svg-kH4sK5ZZC12qiAVv .node polygon,#mermaid-svg-kH4sK5ZZC12qiAVv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kH4sK5ZZC12qiAVv .rough-node .label text,#mermaid-svg-kH4sK5ZZC12qiAVv .node .label text,#mermaid-svg-kH4sK5ZZC12qiAVv .image-shape .label,#mermaid-svg-kH4sK5ZZC12qiAVv .icon-shape .label{text-anchor:middle;}#mermaid-svg-kH4sK5ZZC12qiAVv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kH4sK5ZZC12qiAVv .rough-node .label,#mermaid-svg-kH4sK5ZZC12qiAVv .node .label,#mermaid-svg-kH4sK5ZZC12qiAVv .image-shape .label,#mermaid-svg-kH4sK5ZZC12qiAVv .icon-shape .label{text-align:center;}#mermaid-svg-kH4sK5ZZC12qiAVv .node.clickable{cursor:pointer;}#mermaid-svg-kH4sK5ZZC12qiAVv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kH4sK5ZZC12qiAVv .arrowheadPath{fill:#333333;}#mermaid-svg-kH4sK5ZZC12qiAVv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kH4sK5ZZC12qiAVv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kH4sK5ZZC12qiAVv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kH4sK5ZZC12qiAVv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kH4sK5ZZC12qiAVv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kH4sK5ZZC12qiAVv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kH4sK5ZZC12qiAVv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kH4sK5ZZC12qiAVv .cluster text{fill:#333;}#mermaid-svg-kH4sK5ZZC12qiAVv .cluster span{color:#333;}#mermaid-svg-kH4sK5ZZC12qiAVv 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-kH4sK5ZZC12qiAVv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kH4sK5ZZC12qiAVv rect.text{fill:none;stroke-width:0;}#mermaid-svg-kH4sK5ZZC12qiAVv .icon-shape,#mermaid-svg-kH4sK5ZZC12qiAVv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kH4sK5ZZC12qiAVv .icon-shape p,#mermaid-svg-kH4sK5ZZC12qiAVv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kH4sK5ZZC12qiAVv .icon-shape .label rect,#mermaid-svg-kH4sK5ZZC12qiAVv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kH4sK5ZZC12qiAVv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kH4sK5ZZC12qiAVv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kH4sK5ZZC12qiAVv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} in_info/out_info
检查指针和配置
检查混音矩阵与声道定位
分配GstAudioConverter
复制格式和配置
这部分仍然没有处理任何音频Sample,只是在为后续转换建立合法的输入条件和内部对象。
🌻3.2 组装转换链并选择执行路径
创建对象后,函数按照固定顺序调用内部chain_*()函数,组织音频转换阶段:
text
chain_unpack()
chain_convert_in()
chain_mix()
chain_resample()
chain_convert_out()
chain_quantize()
chain_change_layout()
chain_pack()
这些阶段并不一定全部执行实际转换。
例如:
- 输入输出格式相同且属于中间格式时,可能不需要真正解包。
- 输入输出都是整数格式时,不需要增加浮点转换阶段。
- 输入输出采样率相同时,不建立重采样器。
- 输入输出声道布局相同时,不需要改变布局。
- 输入输出格式、布局和采样率都相同时,可能直接进入旁路模式。
各阶段的职责如下:
| 阶段 | 作用 |
|---|---|
chain_unpack() |
将输入格式解包为内部处理格式 |
chain_convert_in() |
必要时把整数Sample转换为浮点内部格式 |
chain_mix() |
处理声道数量和混音矩阵 |
chain_resample() |
处理输入和输出采样率差异 |
chain_convert_out() |
将内部浮点格式转换为输出处理格式 |
chain_quantize() |
处理位深变化、量化和抖动 |
chain_change_layout() |
转换交错或非交错布局 |
chain_pack() |
将内部格式打包为输出格式 |
#mermaid-svg-NCbUS7ZuGBojh45m{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-NCbUS7ZuGBojh45m .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NCbUS7ZuGBojh45m .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NCbUS7ZuGBojh45m .error-icon{fill:#552222;}#mermaid-svg-NCbUS7ZuGBojh45m .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NCbUS7ZuGBojh45m .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NCbUS7ZuGBojh45m .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NCbUS7ZuGBojh45m .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NCbUS7ZuGBojh45m .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NCbUS7ZuGBojh45m .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NCbUS7ZuGBojh45m .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NCbUS7ZuGBojh45m .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NCbUS7ZuGBojh45m .marker.cross{stroke:#333333;}#mermaid-svg-NCbUS7ZuGBojh45m svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NCbUS7ZuGBojh45m p{margin:0;}#mermaid-svg-NCbUS7ZuGBojh45m .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NCbUS7ZuGBojh45m .cluster-label text{fill:#333;}#mermaid-svg-NCbUS7ZuGBojh45m .cluster-label span{color:#333;}#mermaid-svg-NCbUS7ZuGBojh45m .cluster-label span p{background-color:transparent;}#mermaid-svg-NCbUS7ZuGBojh45m .label text,#mermaid-svg-NCbUS7ZuGBojh45m span{fill:#333;color:#333;}#mermaid-svg-NCbUS7ZuGBojh45m .node rect,#mermaid-svg-NCbUS7ZuGBojh45m .node circle,#mermaid-svg-NCbUS7ZuGBojh45m .node ellipse,#mermaid-svg-NCbUS7ZuGBojh45m .node polygon,#mermaid-svg-NCbUS7ZuGBojh45m .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NCbUS7ZuGBojh45m .rough-node .label text,#mermaid-svg-NCbUS7ZuGBojh45m .node .label text,#mermaid-svg-NCbUS7ZuGBojh45m .image-shape .label,#mermaid-svg-NCbUS7ZuGBojh45m .icon-shape .label{text-anchor:middle;}#mermaid-svg-NCbUS7ZuGBojh45m .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NCbUS7ZuGBojh45m .rough-node .label,#mermaid-svg-NCbUS7ZuGBojh45m .node .label,#mermaid-svg-NCbUS7ZuGBojh45m .image-shape .label,#mermaid-svg-NCbUS7ZuGBojh45m .icon-shape .label{text-align:center;}#mermaid-svg-NCbUS7ZuGBojh45m .node.clickable{cursor:pointer;}#mermaid-svg-NCbUS7ZuGBojh45m .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NCbUS7ZuGBojh45m .arrowheadPath{fill:#333333;}#mermaid-svg-NCbUS7ZuGBojh45m .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NCbUS7ZuGBojh45m .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NCbUS7ZuGBojh45m .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NCbUS7ZuGBojh45m .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NCbUS7ZuGBojh45m .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NCbUS7ZuGBojh45m .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NCbUS7ZuGBojh45m .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NCbUS7ZuGBojh45m .cluster text{fill:#333;}#mermaid-svg-NCbUS7ZuGBojh45m .cluster span{color:#333;}#mermaid-svg-NCbUS7ZuGBojh45m 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-NCbUS7ZuGBojh45m .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NCbUS7ZuGBojh45m rect.text{fill:none;stroke-width:0;}#mermaid-svg-NCbUS7ZuGBojh45m .icon-shape,#mermaid-svg-NCbUS7ZuGBojh45m .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NCbUS7ZuGBojh45m .icon-shape p,#mermaid-svg-NCbUS7ZuGBojh45m .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NCbUS7ZuGBojh45m .icon-shape .label rect,#mermaid-svg-NCbUS7ZuGBojh45m .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NCbUS7ZuGBojh45m .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NCbUS7ZuGBojh45m .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NCbUS7ZuGBojh45m :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入Sample
解包和内部格式
通道与采样率处理
量化与布局处理
打包为输出Sample
转换链建立后,函数还会进行路径优化。
当输入输出格式相同、布局相同、没有重采样,并且声道混音器判断为旁路时,转换器会设置:
text
convert = converter_passthrough
in_place = TRUE
passthrough = TRUE
这表示后续处理不需要真正转换数据,并且可以在原内存中完成。
如果格式发生变化,或者需要重采样、混音和布局转换,转换器通常会使用通用转换路径,并根据中间阶段决定是否分配临时内存。
最后,setup_allocators()根据输出格式和各转换阶段的可写性配置内存分配策略。
整个构造过程是同步的:
text
不进入Pipeline
不发送协议消息
不触发异步回调
不处理音频Buffer
构造完成后,应用再通过:
cpp
gst_audio_converter_samples()
或:
cpp
gst_audio_converter_convert()
提交音频数据。
如果需要计算输出空间,可以先调用:
cpp
gst_audio_converter_get_out_frames()
获取预计输出Frame数量。
🌻4.实战案例
🌻4.1 准备输入和输出格式
下面构造一个最小格式转换案例:
text
输入:S16、48000Hz、2声道、交错布局
输出:F32、48000Hz、2声道、交错布局
输入和输出的采样率、声道数和布局相同,只有Sample格式不同。
因此该案例主要验证:
text
gst_audio_converter_new()
↓
创建格式转换器
↓
gst_audio_converter_convert()
↓
分配F32输出数据
🌻4.2 创建并使用转换器
创建转换器:
cpp
converter = gst_audio_converter_new(
GST_AUDIO_CONVERTER_FLAG_NONE,
&in_info,
&out_info,
NULL);
返回值必须先判断:
cpp
if (converter == NULL) {
g_printerr("Could not create audio converter\n");
return 1;
}
随后使用gst_audio_converter_convert()处理输入数据。该接口会根据输入Frame数量计算输出Frame数量,并分配输出内存。
输出内存由调用者负责释放:
cpp
g_free(output);
转换器本身则使用:
cpp
gst_audio_converter_free(converter);
🌻4.3 验证转换结果
完整测试代码:
cpp
#include <gst/gst.h>
#include <gst/audio/audio.h>
int
main(int argc, char *argv[])
{
GstAudioInfo in_info;
GstAudioInfo out_info;
GstAudioConverter *converter;
gint16 input[] = {
1000, -1000,
2000, -2000
};
gpointer output = NULL;
gsize output_size = 0;
gst_init(&argc, &argv);
gst_audio_info_set_format(
&in_info,
GST_AUDIO_FORMAT_S16,
48000,
2,
NULL);
gst_audio_info_set_format(
&out_info,
GST_AUDIO_FORMAT_F32,
48000,
2,
NULL);
converter = gst_audio_converter_new(
GST_AUDIO_CONVERTER_FLAG_NONE,
&in_info,
&out_info,
NULL);
if (converter == NULL) {
g_printerr("Could not create audio converter\n");
return 1;
}
g_print(
"supports-inplace = %s\n",
gst_audio_converter_supports_inplace(converter)
? "TRUE" : "FALSE");
g_print(
"passthrough = %s\n",
gst_audio_converter_is_passthrough(converter)
? "TRUE" : "FALSE");
if (!gst_audio_converter_convert(
converter,
GST_AUDIO_CONVERTER_FLAG_NONE,
input,
sizeof(input),
&output,
&output_size)) {
g_printerr("Audio conversion failed\n");
g_free(output);
gst_audio_converter_free(converter);
return 1;
}
g_print(
"input-size = %" G_GSIZE_FORMAT "\n",
sizeof(input));
g_print(
"output-size = %" G_GSIZE_FORMAT "\n",
output_size);
g_free(output);
gst_audio_converter_free(converter);
return 0;
}
编译:
bash
gcc audio_converter_new.c -o audio_converter_new \
$(pkg-config --cflags --libs \
gstreamer-audio-1.0)
运行:
bash
./audio_converter_new
关键输出应类似:
text
supports-inplace = FALSE
passthrough = FALSE
input-size = 8
output-size = 16
输入数组包含4个S16Sample:
text
4 × 2Byte = 8Byte
它表示2个双声道Audio Frame。
输出格式是F32,每个Sample占4Byte:
text
4个Sample × 4Byte = 16Byte
因此输出大小从8Byte变成16Byte,说明转换器没有原地复用输入内存,而是按照输出格式分配了新的结果区域。
这个案例验证了三个事实:
text
gst_audio_converter_new()创建可复用的转换器对象
gst_audio_converter_convert()才真正处理音频数据
格式变化时通常不能使用旁路和原地转换
🌻5.总结
gst_audio_converter_new()就是根据输入输出GstAudioInfo和配置创建一条可复用的音频转换链,后续由其他接口使用它完成格式、声道、采样率或布局转换。