Linux Gstreamer深度解析之gst_audio_channel_reorder_map调用流程与实战(十八)

简介: 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数据,而是根据fromto两个声道位置数组,计算出一个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

它常用于:

  1. 不同多声道音频格式之间转换声道顺序。
  2. 外部音频库的声道顺序转换为GStreamer需要的顺序。
  3. 硬件输出声道顺序与GStreamer声道顺序不一致时建立映射。
  4. 自定义Element处理多声道PCM数据时预先生成声道映射。
  5. 多个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

fromto必须描述相同的一组声道位置,只允许排列顺序不同;如果某个输入声道在目标数组中找不到对应位置,函数会返回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

随后还会验证fromto中的声道位置是否合法。

核心流程可以简化为:
#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()就是比较fromto中的声道位置,为每个输入声道计算目标输出下标,生成后续PCM声道重排所需的reorder_map

相关推荐
新时代牛马1 小时前
docker run 起不来?从dockerd、containerd 到runc 一条线讲透
运维·docker·容器
TomEval2 小时前
【App 自动化】14 - App 自动化基础与环境
运维·自动化
cc_yy_zh2 小时前
学习使用kali抓包
服务器·学习·php
盛世宏博智慧档案2 小时前
户外配电柜为什么要装POE供电以太网温湿度传感器?
服务器·网络·人工智能·监控·温湿度·配电柜
新时代牛马2 小时前
Linux 防火墙:nftables 与iptables 兼容层
linux·运维·服务器
xhbh6662 小时前
中小企业 Redis 运维,如何安全归档 RDB、AOF 备份文件?
运维·数据库·缓存·数据备份·文件备份·同步备份·号码备份
是逍遥子没错2 小时前
一个斜杠,击穿整座网关:API网关路径前缀绕过认证实战
运维·服务器·web安全·网络安全·渗透测试·系统安全
Fluxart.ai3 小时前
GPT Image 2.5 有使用次数限制吗?额度、并发、频率限制说明
服务器·前端·gpt
其实防守也摸鱼3 小时前
每天一个知识点——RCE漏洞
运维·服务器·数据库·windows·安全·github·漏洞