简介:CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课🚀
专题四:Android15快速自定义与集成音效实战课🚀
专题五:Android15音频策略实战课🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例)🚀
人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
🍉🍉🍉文章目录🍉🍉🍉
- 🌻1.前言
- 🌻2.应用场景和用法
- 🌻2.1 应用场景
- 🌻2.2 函数用法
- 🌻3.调用流程剖析
- 🌻3.1 校验参数并建立转换器对象
- 🌻3.2 组装转换链并选择执行路径
- 🌻4.实战案例
- 🌻4.1 准备输入和输出格式
- 🌻4.2 创建并使用转换器
- 🌻4.3 验证转换结果
- 🌻5.总结
🌻1.前言
本篇目的:理解
gst_audio_converter_new()如何根据输入、输出音频信息创建可复用的GstAudioConverter,以及它为什么只是建立转换方案,而不是立即处理音频数据。
音频处理过程中,输入和输出格式经常存在差异:
输入:S16、48000Hz、2声道、交错布局 输出:F32、48000Hz、2声道、交错布局也可能同时发生采样率、声道数量或内存布局变化。如果每次处理Buffer时都重新判断这些差异,转换逻辑会分散在数据处理路径中,难以管理和优化。
gst_audio_converter_new()接收两个GstAudioInfo,分别描述输入和输出音频格式,然后创建一个不透明的GstAudioConverter对象。它会在创建阶段检查参数、保存配置,并预先组织后续需要使用的转换链。
需要注意:
gst_audio_converter_new() ≠ 立即转换一块音频数据它负责建立转换器和转换方案。真正的数据处理由后续的gst_audio_converter_samples()或gst_audio_converter_convert()完成。
🌻2.应用场景和用法
🌻2.1 应用场景
GstAudioConverter可以理解成一份已经准备好的音频转换方案。
输入和输出的差异由两个GstAudioInfo描述:
in_info = 输入格式 out_info = 输出格式gst_audio_converter_new()根据这两个对象决定是否需要:
格式转换 通道混音 采样率转换 交错与非交错布局转换 量化、抖动和噪声整形常见应用场景主要有以下几类。
第一类是audioconvertElement根据协商后的输入Caps和输出Caps创建转换器。Caps确定后,Element把Caps解析成GstAudioInfo,再将转换器保存到Element内部,后续逐个处理音频Buffer。
第二类是audioresample等需要持续处理音频流的Element。转换器创建后可以被重复使用,避免每个Buffer都重新构造格式转换逻辑。
第三类是自定义音频处理模块需要组合多种转换操作。例如输入是整数格式,输出是浮点格式,同时需要调整采样率和声道布局,这些操作可以由同一个GstAudioConverter统一组织。
第四类是转换配置可能发生变化的场景。如果只是输入和输出采样率变化,并且创建时启用了GST_AUDIO_CONVERTER_FLAG_VARIABLE_RATE,可以使用gst_audio_converter_update_config()更新速率。格式、声道数或布局发生变化时,通常需要释放旧转换器并重新创建。
对象关系可以概括为:
gst_audio_converter_new()不是单独的声道重排接口,也不是单独的采样率转换接口:
只交换声道位置: gst_audio_reorder_channels() 只执行采样率转换: GstAudioResampler 组合格式、通道、采样率和布局转换: GstAudioConverter🌻2.2 函数用法
函数原型:
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,调用者不应在函数返回后继续释放或使用它。
返回值:
成功:返回新创建的GstAudioConverter* 失败:返回NULL失败通常表示:
输入或输出信息为空 混音矩阵尺寸不正确 输入输出声道数不同且缺少必要的定位信息 转换方案无法建立最小调用方式:
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。使用完成后应调用:
gst_audio_converter_free(converter);不能使用gst_object_unref()释放。
调用前:
只有输入和输出格式描述 没有可执行的转换链调用后:
得到GstAudioConverter 保存输入和输出信息 保存转换配置 建立内部转换链 确定是否支持原地处理或旁路处理🌻3.调用流程剖析
🌻3.1 校验参数并建立转换器对象
gst_audio_converter_new()首先检查输入和输出GstAudioInfo指针:
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不为空,函数会先读取其中的混音矩阵配置,并检查矩阵尺寸是否匹配输入和输出声道数。
例如:
输入2声道,输出1声道 混音矩阵必须有1行、2列如果输入输出声道数不同,并且任意一方是未定位声道,同时又没有提供混音矩阵,函数会拒绝创建转换器。
参数检查通过后,函数分配转换器对象:
convert=g_new0(GstAudioConverter,1);convert->flags=flags;convert->in=*in_info;convert->out=*out_info;这一步有两个关键结果:
flags被复制到转换器内部 in_info和out_info的内容被复制到转换器内部调用者仍然拥有原来的GstAudioInfo,但之后修改原对象不会自动改变已经创建的转换器。
函数随后创建默认配置:
convert->config=gst_structure_new_empty("GstAudioConverter");如果调用者传入了config,构造函数会通过gst_audio_converter_update_config()复制配置内容,并释放传入的GstStructure。
这部分仍然没有处理任何音频Sample,只是在为后续转换建立合法的输入条件和内部对象。
🌻3.2 组装转换链并选择执行路径
创建对象后,函数按照固定顺序调用内部chain_*()函数,组织音频转换阶段:
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() | 将内部格式打包为输出格式 |
转换链建立后,函数还会进行路径优化。
当输入输出格式相同、布局相同、没有重采样,并且声道混音器判断为旁路时,转换器会设置:
convert = converter_passthrough in_place = TRUE passthrough = TRUE这表示后续处理不需要真正转换数据,并且可以在原内存中完成。
如果格式发生变化,或者需要重采样、混音和布局转换,转换器通常会使用通用转换路径,并根据中间阶段决定是否分配临时内存。
最后,setup_allocators()根据输出格式和各转换阶段的可写性配置内存分配策略。
整个构造过程是同步的:
不进入Pipeline 不发送协议消息 不触发异步回调 不处理音频Buffer构造完成后,应用再通过:
gst_audio_converter_samples()或:
gst_audio_converter_convert()提交音频数据。
如果需要计算输出空间,可以先调用:
gst_audio_converter_get_out_frames()获取预计输出Frame数量。
🌻4.实战案例
🌻4.1 准备输入和输出格式
下面构造一个最小格式转换案例:
输入:S16、48000Hz、2声道、交错布局 输出:F32、48000Hz、2声道、交错布局输入和输出的采样率、声道数和布局相同,只有Sample格式不同。
因此该案例主要验证:
gst_audio_converter_new() ↓ 创建格式转换器 ↓ gst_audio_converter_convert() ↓ 分配F32输出数据🌻4.2 创建并使用转换器
创建转换器:
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");return1;}随后使用gst_audio_converter_convert()处理输入数据。该接口会根据输入Frame数量计算输出Frame数量,并分配输出内存。
输出内存由调用者负责释放:
g_free(output);转换器本身则使用:
gst_audio_converter_free(converter);🌻4.3 验证转换结果
完整测试代码:
#include<gst/gst.h>#include<gst/audio/audio.h>intmain(intargc,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");return1;}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);return1;}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);return0;}编译:
gcc audio_converter_new.c-oaudio_converter_new\$(pkg-config--cflags--libs\gstreamer-audio-1.0)运行:
./audio_converter_new关键输出应类似:
supports-inplace = FALSE passthrough = FALSE input-size = 8 output-size = 16输入数组包含4个S16Sample:
4 × 2Byte = 8Byte它表示2个双声道Audio Frame。
输出格式是F32,每个Sample占4Byte:
4个Sample × 4Byte = 16Byte因此输出大小从8Byte变成16Byte,说明转换器没有原地复用输入内存,而是按照输出格式分配了新的结果区域。
这个案例验证了三个事实:
gst_audio_converter_new()创建可复用的转换器对象 gst_audio_converter_convert()才真正处理音频数据 格式变化时通常不能使用旁路和原地转换🌻5.总结
gst_audio_converter_new()就是根据输入输出GstAudioInfo和配置创建一条可复用的音频转换链,后续由其他接口使用它完成格式、声道、采样率或布局转换。