简介: 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 reorder_map驱动声道重排流程](#🌻3.2 reorder_map驱动声道重排流程)
- 🌻4.实战案例
-
- [🌻4.1 准备声道位置和PCM数据](#🌻4.1 准备声道位置和PCM数据)
- [🌻4.2 生成reorder_map并重排](#🌻4.2 生成reorder_map并重排)
- [🌻4.3 验证重排结果](#🌻4.3 验证重排结果)
- 🌻5.总结
-
🌻1.前言
本篇目的:理解
gst_audio_get_channel_reorder_map()如何根据输入、输出声道位置生成reorder_map,以及这个映射表如何指导后续音频声道重排。
多声道音频中,每个Sample除了有具体数值之外,还具有明确的声道含义。
例如双声道通常按照:
text
index 0 → FRONT_LEFT
index 1 → FRONT_RIGHT
保存。
但某些音频源、硬件接口或者外部音频格式可能采用:
text
index 0 → FRONT_RIGHT
index 1 → FRONT_LEFT
此时虽然仍然是2个声道,但是两个声道在Buffer中的排列顺序已经不同。
如果直接把这样的PCM数据交给要求:
text
FRONT_LEFT
FRONT_RIGHT
顺序的下游处理,就可能出现左右声道颠倒。
因此,GStreamer需要建立:
text
当前声道顺序
↓
目标声道顺序
之间的位置映射。
gst_audio_get_channel_reorder_map()就是完成这个工作的函数:它不直接修改PCM数据,而是根据from和to两个声道位置数组,计算出一个reorder_map。
#mermaid-svg-t4CaFu1cmHoSVx9r{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-t4CaFu1cmHoSVx9r .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-t4CaFu1cmHoSVx9r .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-t4CaFu1cmHoSVx9r .error-icon{fill:#552222;}#mermaid-svg-t4CaFu1cmHoSVx9r .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-t4CaFu1cmHoSVx9r .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-t4CaFu1cmHoSVx9r .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-t4CaFu1cmHoSVx9r .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-t4CaFu1cmHoSVx9r .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-t4CaFu1cmHoSVx9r .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-t4CaFu1cmHoSVx9r .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-t4CaFu1cmHoSVx9r .marker{fill:#333333;stroke:#333333;}#mermaid-svg-t4CaFu1cmHoSVx9r .marker.cross{stroke:#333333;}#mermaid-svg-t4CaFu1cmHoSVx9r svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-t4CaFu1cmHoSVx9r p{margin:0;}#mermaid-svg-t4CaFu1cmHoSVx9r .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-t4CaFu1cmHoSVx9r .cluster-label text{fill:#333;}#mermaid-svg-t4CaFu1cmHoSVx9r .cluster-label span{color:#333;}#mermaid-svg-t4CaFu1cmHoSVx9r .cluster-label span p{background-color:transparent;}#mermaid-svg-t4CaFu1cmHoSVx9r .label text,#mermaid-svg-t4CaFu1cmHoSVx9r span{fill:#333;color:#333;}#mermaid-svg-t4CaFu1cmHoSVx9r .node rect,#mermaid-svg-t4CaFu1cmHoSVx9r .node circle,#mermaid-svg-t4CaFu1cmHoSVx9r .node ellipse,#mermaid-svg-t4CaFu1cmHoSVx9r .node polygon,#mermaid-svg-t4CaFu1cmHoSVx9r .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-t4CaFu1cmHoSVx9r .rough-node .label text,#mermaid-svg-t4CaFu1cmHoSVx9r .node .label text,#mermaid-svg-t4CaFu1cmHoSVx9r .image-shape .label,#mermaid-svg-t4CaFu1cmHoSVx9r .icon-shape .label{text-anchor:middle;}#mermaid-svg-t4CaFu1cmHoSVx9r .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-t4CaFu1cmHoSVx9r .rough-node .label,#mermaid-svg-t4CaFu1cmHoSVx9r .node .label,#mermaid-svg-t4CaFu1cmHoSVx9r .image-shape .label,#mermaid-svg-t4CaFu1cmHoSVx9r .icon-shape .label{text-align:center;}#mermaid-svg-t4CaFu1cmHoSVx9r .node.clickable{cursor:pointer;}#mermaid-svg-t4CaFu1cmHoSVx9r .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-t4CaFu1cmHoSVx9r .arrowheadPath{fill:#333333;}#mermaid-svg-t4CaFu1cmHoSVx9r .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-t4CaFu1cmHoSVx9r .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-t4CaFu1cmHoSVx9r .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-t4CaFu1cmHoSVx9r .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-t4CaFu1cmHoSVx9r .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-t4CaFu1cmHoSVx9r .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-t4CaFu1cmHoSVx9r .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-t4CaFu1cmHoSVx9r .cluster text{fill:#333;}#mermaid-svg-t4CaFu1cmHoSVx9r .cluster span{color:#333;}#mermaid-svg-t4CaFu1cmHoSVx9r 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-t4CaFu1cmHoSVx9r .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-t4CaFu1cmHoSVx9r rect.text{fill:none;stroke-width:0;}#mermaid-svg-t4CaFu1cmHoSVx9r .icon-shape,#mermaid-svg-t4CaFu1cmHoSVx9r .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-t4CaFu1cmHoSVx9r .icon-shape p,#mermaid-svg-t4CaFu1cmHoSVx9r .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-t4CaFu1cmHoSVx9r .icon-shape .label rect,#mermaid-svg-t4CaFu1cmHoSVx9r .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-t4CaFu1cmHoSVx9r .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-t4CaFu1cmHoSVx9r .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-t4CaFu1cmHoSVx9r :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① 输入from顺序
② 输入to顺序
③ 生成reorder_map
④ 按映射搬移声道
⑤ 得到目标顺序
可以把它理解成一张"座位调整表"。
原来的座位:
text
0号位置 → Right
1号位置 → Left
目标座位:
text
0号位置 → Left
1号位置 → Right
函数并不会亲自搬动两个声道的数据,而是先算出:
text
reorder_map = {1, 0}
表示:
text
输入0号声道 → 输出1号位置
输入1号声道 → 输出0号位置
后续代码再按照这张表真正搬动PCM数据。
🌻2.应用场景和用法
🌻2.1 应用场景
gst_audio_get_channel_reorder_map()主要解决的问题不是:
text
有多少个声道?
而是:
text
这些声道按照什么顺序排列?
例如同样是一帧双声道PCM:
text
Buffer:
Sample0 Sample1
↓ ↓
R L
但下游要求:
text
Sample0 Sample1
↓ ↓
L R
两端都包含:
text
FRONT_LEFT
FRONT_RIGHT
只是排列顺序不同。
#mermaid-svg-stchdvRTbRb29X3e{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-stchdvRTbRb29X3e .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-stchdvRTbRb29X3e .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-stchdvRTbRb29X3e .error-icon{fill:#552222;}#mermaid-svg-stchdvRTbRb29X3e .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-stchdvRTbRb29X3e .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-stchdvRTbRb29X3e .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-stchdvRTbRb29X3e .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-stchdvRTbRb29X3e .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-stchdvRTbRb29X3e .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-stchdvRTbRb29X3e .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-stchdvRTbRb29X3e .marker{fill:#333333;stroke:#333333;}#mermaid-svg-stchdvRTbRb29X3e .marker.cross{stroke:#333333;}#mermaid-svg-stchdvRTbRb29X3e svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-stchdvRTbRb29X3e p{margin:0;}#mermaid-svg-stchdvRTbRb29X3e .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-stchdvRTbRb29X3e .cluster-label text{fill:#333;}#mermaid-svg-stchdvRTbRb29X3e .cluster-label span{color:#333;}#mermaid-svg-stchdvRTbRb29X3e .cluster-label span p{background-color:transparent;}#mermaid-svg-stchdvRTbRb29X3e .label text,#mermaid-svg-stchdvRTbRb29X3e span{fill:#333;color:#333;}#mermaid-svg-stchdvRTbRb29X3e .node rect,#mermaid-svg-stchdvRTbRb29X3e .node circle,#mermaid-svg-stchdvRTbRb29X3e .node ellipse,#mermaid-svg-stchdvRTbRb29X3e .node polygon,#mermaid-svg-stchdvRTbRb29X3e .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-stchdvRTbRb29X3e .rough-node .label text,#mermaid-svg-stchdvRTbRb29X3e .node .label text,#mermaid-svg-stchdvRTbRb29X3e .image-shape .label,#mermaid-svg-stchdvRTbRb29X3e .icon-shape .label{text-anchor:middle;}#mermaid-svg-stchdvRTbRb29X3e .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-stchdvRTbRb29X3e .rough-node .label,#mermaid-svg-stchdvRTbRb29X3e .node .label,#mermaid-svg-stchdvRTbRb29X3e .image-shape .label,#mermaid-svg-stchdvRTbRb29X3e .icon-shape .label{text-align:center;}#mermaid-svg-stchdvRTbRb29X3e .node.clickable{cursor:pointer;}#mermaid-svg-stchdvRTbRb29X3e .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-stchdvRTbRb29X3e .arrowheadPath{fill:#333333;}#mermaid-svg-stchdvRTbRb29X3e .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-stchdvRTbRb29X3e .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-stchdvRTbRb29X3e .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-stchdvRTbRb29X3e .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-stchdvRTbRb29X3e .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-stchdvRTbRb29X3e .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-stchdvRTbRb29X3e .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-stchdvRTbRb29X3e .cluster text{fill:#333;}#mermaid-svg-stchdvRTbRb29X3e .cluster span{color:#333;}#mermaid-svg-stchdvRTbRb29X3e 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-stchdvRTbRb29X3e .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-stchdvRTbRb29X3e rect.text{fill:none;stroke-width:0;}#mermaid-svg-stchdvRTbRb29X3e .icon-shape,#mermaid-svg-stchdvRTbRb29X3e .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-stchdvRTbRb29X3e .icon-shape p,#mermaid-svg-stchdvRTbRb29X3e .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-stchdvRTbRb29X3e .icon-shape .label rect,#mermaid-svg-stchdvRTbRb29X3e .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-stchdvRTbRb29X3e .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-stchdvRTbRb29X3e .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-stchdvRTbRb29X3e :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入
R L
from
FR FL
reorder_map
1 0
to
FL FR
输出
L R
它常用于:
- 不同多声道音频格式之间转换声道顺序。
- 外部音频库的声道顺序转换为GStreamer需要的顺序。
- 硬件输出声道顺序与GStreamer声道顺序不一致时建立映射。
- 自定义Element处理多声道PCM数据时预先生成声道映射。
- 多个Buffer使用相同声道布局时,只计算一次映射,后续反复使用。
这里需要注意:
text
gst_audio_get_channel_reorder_map()
只计算:
text
声道位置 → 声道位置
的映射关系。
它不会修改:
text
PCM Buffer
GstBuffer
GstAudioInfo
Caps
真正需要直接重排音频数据时,可以使用gst_audio_reorder_channels()等接口。
因此两者职责可以简单理解为:
text
gst_audio_get_channel_reorder_map()
↓
算出"怎么搬"
而:
text
gst_audio_reorder_channels()
↓
真正"搬数据"
🌻2.2 函数用法
函数原型:
cpp
gboolean
gst_audio_get_channel_reorder_map (
gint channels,
const GstAudioChannelPosition *from,
const GstAudioChannelPosition *to,
gint *reorder_map);
参数:
| 参数 | 作用 |
|---|---|
channels |
声道数量 |
from |
当前Buffer中的声道排列顺序 |
to |
希望转换到的目标声道排列顺序 |
reorder_map |
保存计算得到的声道位置映射 |
| 返回值 | 成功返回TRUE,无法建立映射返回FALSE |
最简单的双声道示例:
cpp
GstAudioChannelPosition from[2] = {
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT
};
GstAudioChannelPosition to[2] = {
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT
};
gint reorder_map[2];
gboolean ret =
gst_audio_get_channel_reorder_map(
2,
from,
to,
reorder_map);
调用前:
text
from:
index 0 → FRONT_RIGHT
index 1 → FRONT_LEFT
to:
index 0 → FRONT_LEFT
index 1 → FRONT_RIGHT
调用成功后:
text
reorder_map[0] = 1
reorder_map[1] = 0
最容易理解错误的地方,就是reorder_map的方向。
它不是:
text
输出i应该读取哪个输入
而是:
text
输入i应该放到哪个输出位置
即:
text
output[reorder_map[i]] = input[i]
对于:
text
reorder_map = {1, 0}
就是:
text
input[0] → output[1]
input[1] → output[0]
因此:
text
输入:
index 0 = Right
index 1 = Left
最终转换成:
text
输出:
index 0 = Left
index 1 = Right
from和to必须描述相同的一组声道位置,只允许排列顺序不同;如果某个输入声道在目标数组中找不到对应位置,函数会返回FALSE。
🌻3.调用流程剖析
🌻3.1 声道映射生成流程
gst_audio_get_channel_reorder_map()的核心工作非常简单:
text
遍历from中的每一个声道
↓
去to中寻找相同声道
↓
记录它在to中的下标
↓
保存到reorder_map
源码首先会检查几个基本条件:
text
reorder_map != NULL
channels > 0
channels <= 64
from != NULL
to != NULL
随后还会验证from和to中的声道位置是否合法。
核心流程可以简化为:
#mermaid-svg-9H23BVdDSPlU8ZVD{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-9H23BVdDSPlU8ZVD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9H23BVdDSPlU8ZVD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9H23BVdDSPlU8ZVD .error-icon{fill:#552222;}#mermaid-svg-9H23BVdDSPlU8ZVD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9H23BVdDSPlU8ZVD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9H23BVdDSPlU8ZVD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9H23BVdDSPlU8ZVD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9H23BVdDSPlU8ZVD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9H23BVdDSPlU8ZVD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9H23BVdDSPlU8ZVD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9H23BVdDSPlU8ZVD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9H23BVdDSPlU8ZVD .marker.cross{stroke:#333333;}#mermaid-svg-9H23BVdDSPlU8ZVD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9H23BVdDSPlU8ZVD p{margin:0;}#mermaid-svg-9H23BVdDSPlU8ZVD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9H23BVdDSPlU8ZVD .cluster-label text{fill:#333;}#mermaid-svg-9H23BVdDSPlU8ZVD .cluster-label span{color:#333;}#mermaid-svg-9H23BVdDSPlU8ZVD .cluster-label span p{background-color:transparent;}#mermaid-svg-9H23BVdDSPlU8ZVD .label text,#mermaid-svg-9H23BVdDSPlU8ZVD span{fill:#333;color:#333;}#mermaid-svg-9H23BVdDSPlU8ZVD .node rect,#mermaid-svg-9H23BVdDSPlU8ZVD .node circle,#mermaid-svg-9H23BVdDSPlU8ZVD .node ellipse,#mermaid-svg-9H23BVdDSPlU8ZVD .node polygon,#mermaid-svg-9H23BVdDSPlU8ZVD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9H23BVdDSPlU8ZVD .rough-node .label text,#mermaid-svg-9H23BVdDSPlU8ZVD .node .label text,#mermaid-svg-9H23BVdDSPlU8ZVD .image-shape .label,#mermaid-svg-9H23BVdDSPlU8ZVD .icon-shape .label{text-anchor:middle;}#mermaid-svg-9H23BVdDSPlU8ZVD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9H23BVdDSPlU8ZVD .rough-node .label,#mermaid-svg-9H23BVdDSPlU8ZVD .node .label,#mermaid-svg-9H23BVdDSPlU8ZVD .image-shape .label,#mermaid-svg-9H23BVdDSPlU8ZVD .icon-shape .label{text-align:center;}#mermaid-svg-9H23BVdDSPlU8ZVD .node.clickable{cursor:pointer;}#mermaid-svg-9H23BVdDSPlU8ZVD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9H23BVdDSPlU8ZVD .arrowheadPath{fill:#333333;}#mermaid-svg-9H23BVdDSPlU8ZVD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9H23BVdDSPlU8ZVD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9H23BVdDSPlU8ZVD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9H23BVdDSPlU8ZVD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9H23BVdDSPlU8ZVD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9H23BVdDSPlU8ZVD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9H23BVdDSPlU8ZVD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9H23BVdDSPlU8ZVD .cluster text{fill:#333;}#mermaid-svg-9H23BVdDSPlU8ZVD .cluster span{color:#333;}#mermaid-svg-9H23BVdDSPlU8ZVD 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-9H23BVdDSPlU8ZVD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9H23BVdDSPlU8ZVD rect.text{fill:none;stroke-width:0;}#mermaid-svg-9H23BVdDSPlU8ZVD .icon-shape,#mermaid-svg-9H23BVdDSPlU8ZVD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9H23BVdDSPlU8ZVD .icon-shape p,#mermaid-svg-9H23BVdDSPlU8ZVD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9H23BVdDSPlU8ZVD .icon-shape .label rect,#mermaid-svg-9H23BVdDSPlU8ZVD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9H23BVdDSPlU8ZVD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9H23BVdDSPlU8ZVD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9H23BVdDSPlU8ZVD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 检查from/to
遍历fromi
在to中寻找相同声道
reorder_mapi = j
返回TRUE
以:
text
from = [FR, FL]
to = [FL, FR]
为例。
第一次处理:
text
i = 0
from[0] = FR
函数在to中寻找:
text
to[0] = FL ×
to[1] = FR √
于是:
text
reorder_map[0] = 1
第二次处理:
text
i = 1
from[1] = FL
在to中找到:
text
to[0] = FL
于是:
text
reorder_map[1] = 0
最终得到:
text
reorder_map = [1, 0]
所以这个函数本质上建立的是:
text
from数组下标
↓
to数组下标
之间的映射。
源码中的核心逻辑可以抽象成:
cpp
for (i = 0; i < channels; i++) {
for (j = 0; j < channels; j++) {
if (from[i] == to[j]) {
reorder_map[i] = j;
break;
}
}
}
这里并没有PCM数据参与。
输入是:
text
GstAudioChannelPosition数组
输出是:
text
整数下标映射数组
因此函数完成以后,音频Buffer本身仍然保持原来的声道排列。
🌻3.2 reorder_map驱动声道重排流程
生成:
text
reorder_map
之后,才进入真正的数据重排阶段。
例如有一帧双声道S16LE数据:
text
input[0] = 100
input[1] = 200
结合声道位置:
text
from:
input[0] = FRONT_RIGHT = 100
input[1] = FRONT_LEFT = 200
目标是:
text
to:
output[0] = FRONT_LEFT
output[1] = FRONT_RIGHT
前面已经得到:
text
reorder_map = {1, 0}
应用映射:
cpp
output[reorder_map[i]] = input[i];
第一步:
text
i = 0
output[1] = input[0]
= 100
即:
text
FR → output[1]
第二步:
text
i = 1
output[0] = input[1]
= 200
即:
text
FL → output[0]
最终:
text
output[0] = 200 → FL
output[1] = 100 → FR
流程如下:
#mermaid-svg-KRM8y1LVWfiGSQIx{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-KRM8y1LVWfiGSQIx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KRM8y1LVWfiGSQIx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KRM8y1LVWfiGSQIx .error-icon{fill:#552222;}#mermaid-svg-KRM8y1LVWfiGSQIx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KRM8y1LVWfiGSQIx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KRM8y1LVWfiGSQIx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KRM8y1LVWfiGSQIx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KRM8y1LVWfiGSQIx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KRM8y1LVWfiGSQIx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KRM8y1LVWfiGSQIx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KRM8y1LVWfiGSQIx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KRM8y1LVWfiGSQIx .marker.cross{stroke:#333333;}#mermaid-svg-KRM8y1LVWfiGSQIx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KRM8y1LVWfiGSQIx p{margin:0;}#mermaid-svg-KRM8y1LVWfiGSQIx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KRM8y1LVWfiGSQIx .cluster-label text{fill:#333;}#mermaid-svg-KRM8y1LVWfiGSQIx .cluster-label span{color:#333;}#mermaid-svg-KRM8y1LVWfiGSQIx .cluster-label span p{background-color:transparent;}#mermaid-svg-KRM8y1LVWfiGSQIx .label text,#mermaid-svg-KRM8y1LVWfiGSQIx span{fill:#333;color:#333;}#mermaid-svg-KRM8y1LVWfiGSQIx .node rect,#mermaid-svg-KRM8y1LVWfiGSQIx .node circle,#mermaid-svg-KRM8y1LVWfiGSQIx .node ellipse,#mermaid-svg-KRM8y1LVWfiGSQIx .node polygon,#mermaid-svg-KRM8y1LVWfiGSQIx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KRM8y1LVWfiGSQIx .rough-node .label text,#mermaid-svg-KRM8y1LVWfiGSQIx .node .label text,#mermaid-svg-KRM8y1LVWfiGSQIx .image-shape .label,#mermaid-svg-KRM8y1LVWfiGSQIx .icon-shape .label{text-anchor:middle;}#mermaid-svg-KRM8y1LVWfiGSQIx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KRM8y1LVWfiGSQIx .rough-node .label,#mermaid-svg-KRM8y1LVWfiGSQIx .node .label,#mermaid-svg-KRM8y1LVWfiGSQIx .image-shape .label,#mermaid-svg-KRM8y1LVWfiGSQIx .icon-shape .label{text-align:center;}#mermaid-svg-KRM8y1LVWfiGSQIx .node.clickable{cursor:pointer;}#mermaid-svg-KRM8y1LVWfiGSQIx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KRM8y1LVWfiGSQIx .arrowheadPath{fill:#333333;}#mermaid-svg-KRM8y1LVWfiGSQIx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KRM8y1LVWfiGSQIx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KRM8y1LVWfiGSQIx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KRM8y1LVWfiGSQIx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KRM8y1LVWfiGSQIx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KRM8y1LVWfiGSQIx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KRM8y1LVWfiGSQIx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KRM8y1LVWfiGSQIx .cluster text{fill:#333;}#mermaid-svg-KRM8y1LVWfiGSQIx .cluster span{color:#333;}#mermaid-svg-KRM8y1LVWfiGSQIx 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-KRM8y1LVWfiGSQIx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KRM8y1LVWfiGSQIx rect.text{fill:none;stroke-width:0;}#mermaid-svg-KRM8y1LVWfiGSQIx .icon-shape,#mermaid-svg-KRM8y1LVWfiGSQIx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KRM8y1LVWfiGSQIx .icon-shape p,#mermaid-svg-KRM8y1LVWfiGSQIx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KRM8y1LVWfiGSQIx .icon-shape .label rect,#mermaid-svg-KRM8y1LVWfiGSQIx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KRM8y1LVWfiGSQIx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KRM8y1LVWfiGSQIx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KRM8y1LVWfiGSQIx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入
FR=100 FL=200
reorder_map
1 0
outputmap\[i]=inputi
输出
FL=200 FR=100
这也是为什么reorder_map表示:
text
输入channel i
↓
输出channel reorder_map[i]
例如更复杂的4声道:
text
from:
0 → FRONT_RIGHT
1 → REAR_LEFT
2 → FRONT_LEFT
3 → REAR_RIGHT
目标:
text
to:
0 → FRONT_LEFT
1 → FRONT_RIGHT
2 → REAR_LEFT
3 → REAR_RIGHT
则映射关系为:
text
FR → 1
RL → 2
FL → 0
RR → 3
最终:
text
reorder_map = {1, 2, 0, 3}
因此不管2声道还是多声道,算法本质都没有变化:
text
找到相同ChannelPosition
↓
记录目标数组下标
还需要注意几个失败条件。
对于无法参与正常位置映射的声道位置,或者某个from[i]无法在to[]中找到相同位置时,函数会返回FALSE。
所以:
text
from = [FL, FR]
to = [FL, FC]
不能生成有效映射。
原因不是顺序不同,而是两边描述的声道集合本身已经不同:
text
from存在FR
to不存在FR
gst_audio_get_channel_reorder_map()解决的是:
text
同一组声道
+
不同排列顺序
而不是增加、删除或者混音声道。
🌻4.实战案例
🌻4.1 准备声道位置和PCM数据
下面通过一个双声道最小程序验证:
text
from = [FR, FL]
转换为:
text
to = [FL, FR]
首先准备声道位置:
cpp
GstAudioChannelPosition from[2] = {
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT
};
GstAudioChannelPosition to[2] = {
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT
};
然后准备一帧简单的PCM数据:
cpp
gint16 input[2] = {
100,
200
};
结合from,这里代表:
text
input[0] = 100 → FRONT_RIGHT
input[1] = 200 → FRONT_LEFT
目标则是:
text
output[0] → FRONT_LEFT
output[1] → FRONT_RIGHT
🌻4.2 生成reorder_map并重排
调用核心函数:
cpp
gint reorder_map[2];
gboolean ret =
gst_audio_get_channel_reorder_map(
2,
from,
to,
reorder_map);
如果成功:
text
reorder_map = {1, 0}
接着按照映射关系搬动数据:
cpp
gint16 output[2] = { 0 };
for (gint i = 0; i < 2; i++) {
output[reorder_map[i]] = input[i];
}
完整的核心关系是:
text
input[0] = FR
↓
reorder_map[0] = 1
↓
output[1] = FR
input[1] = FL
↓
reorder_map[1] = 0
↓
output[0] = FL
这里为了突出gst_audio_get_channel_reorder_map()的机制,实战手工使用reorder_map完成数据搬移。
实际GStreamer代码中,如果只是希望直接重排Interleaved音频数据,也可以使用gst_audio_reorder_channels(),不必自己编写这段搬移逻辑。
🌻4.3 验证重排结果
完整测试代码:
cpp
#include <gst/gst.h>
#include <gst/audio/audio.h>
int main(int argc, char *argv[])
{
GstAudioChannelPosition from[2] = {
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT
};
GstAudioChannelPosition to[2] = {
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT
};
gint reorder_map[2] = { 0 };
gint16 input[2] = {
100, /* FRONT_RIGHT */
200 /* FRONT_LEFT */
};
gint16 output[2] = { 0 };
gst_init(&argc, &argv);
if (!gst_audio_get_channel_reorder_map(
2,
from,
to,
reorder_map)) {
g_printerr("get reorder map failed\n");
return -1;
}
g_print("reorder_map[0] = %d\n", reorder_map[0]);
g_print("reorder_map[1] = %d\n", reorder_map[1]);
for (gint i = 0; i < 2; i++)
output[reorder_map[i]] = input[i];
g_print("input : %d %d\n",
input[0], input[1]);
g_print("output: %d %d\n",
output[0], output[1]);
return 0;
}
编译:
bash
gcc test_channel_reorder.c \
-o test_channel_reorder \
$(pkg-config --cflags --libs \
gstreamer-1.0 \
gstreamer-audio-1.0)
运行:
bash
./test_channel_reorder
预期输出:
text
reorder_map[0] = 1
reorder_map[1] = 0
input : 100 200
output: 200 100
结合声道含义来看:
text
调用前:
index 0 = 100 = FRONT_RIGHT
index 1 = 200 = FRONT_LEFT
经过:
text
reorder_map = {1, 0}
之后:
text
index 0 = 200 = FRONT_LEFT
index 1 = 100 = FRONT_RIGHT
整个实战证明了:
text
from
[FR, FL]
↓
gst_audio_get_channel_reorder_map()
↓
[1, 0]
↓
按照map搬移PCM Sample
↓
to
[FL, FR]
因此要特别区分:
text
GstAudioChannelPosition
↓
描述"这个位置是什么声道"
text
reorder_map
↓
描述"输入声道应该搬到哪个输出下标"
text
PCM数据
↓
按照reorder_map真正改变排列
🌻5.总结
gst_audio_get_channel_reorder_map()就是比较from和to中的声道位置,为每个输入声道计算目标输出下标,生成后续PCM声道重排所需的reorder_map。