简介:CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课🚀
专题四:Android15快速自定义与集成音效实战课🚀
专题五:Android15音频策略实战课🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例)🚀
人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
🍉🍉🍉文章目录🍉🍉🍉
- 🌻1.前言
- 🌻2.应用场景和用法
- 2.1解码输出Buffer分配场景
- 2.2函数原型与返回语义
- 🌻3.调用流程剖析
- 3.1解码子类到输出Buffer
- 3.2协商、分配与回退
- 🌻4.实战案例
- 4.1准备真实解码管线
- 4.2观察协商与Buffer分配
- 4.3完整验证命令与关键结果
- 🌻5.总结
🌻1.前言
本篇目的:理解
gst_audio_decoder_allocate_output_buffer()如何为GstAudioDecoder子类创建可写输出Buffer,并通过真实解码管线验证协商、分配、写入和提交之间的关系。
GstAudioDecoder子类完成编码数据解析后,需要把解码得到的PCM写入一个GstBuffer。
这个Buffer不能只使用普通分配方式创建。输出格式可能刚刚发生变化,下游可能通过ALLOCATION查询提供了专用GstAllocator和GstAllocationParams,srcPad也可能处于需要重新协商的状态。
gst_audio_decoder_allocate_output_buffer()负责检查这些状态,必要时触发输出协商,再使用当前分配参数创建Buffer。如果协商分配失败,它还会回退到默认分配器。
这个函数只负责创建输出Buffer,不负责解码PCM、不负责填充数据、不负责设置时间戳,也不负责调用gst_audio_decoder_finish_frame()把Buffer提交给下游。
🌻2.应用场景和用法
2.1解码输出Buffer分配场景
gst_audio_decoder_allocate_output_buffer()主要用于以下场景:
| 场景 | 调用动作 | 得到的结果 |
|---|---|---|
| 普通解码输出 | 根据PCM样本数和BPF计算size后调用 | 得到可写输出Buffer |
| 输出格式刚刚改变 | 先设置新的输出格式,再调用 | 函数内部可以触发重新协商 |
| 下游提供专用分配器 | 使用下游协商得到的allocator和params | 输出Buffer遵循当前分配约束 |
| 解码器输出长度可变 | 每次根据实际解码结果重新计算size | 避免固定Buffer容量不足 |
| 解码器需要重试 | 释放容量不足的旧Buffer后再次调用 | 按新的容量重新分配 |
| 协商或专用分配失败 | 进入内部回退路径 | 尝试使用默认分配器创建Buffer |
size的单位是字节,不是样本数,也不是解码帧数。
例如S16立体声输出中,一个AudioFrame占4字节。如果解码得到480个AudioFrame,调用参数应该是:
size = 480 × 4 = 1920字节如果解码器返回0个样本,不能把size设置为0继续调用。源码在函数入口检查size > 0,零值会触发GLib前置条件检查并返回NULL。
2.2函数原型与返回语义
函数原型如下:
GstBuffer*gst_audio_decoder_allocate_output_buffer(GstAudioDecoder*dec,gsize size);| 参数或返回值 | 含义 |
|---|---|
dec | 有效的GstAudioDecoder对象,通常是当前解码子类实例 |
size | 需要分配的输出Buffer字节数,必须大于0 |
| 返回值 | 新创建的GstBuffer,返回所有权由调用者持有,失败时返回NULL |
最小调用方式如下:
GstBuffer*outbuf;outbuf=gst_audio_decoder_allocate_output_buffer(dec,size);if(outbuf==NULL)returnGST_FLOW_ERROR;调用前,解码子类需要先确定PCM输出长度,并且已经通过gst_audio_decoder_set_output_format()或gst_audio_decoder_set_output_caps()设置输出格式。
调用过程中,函数会持有GstAudioDecoder的流锁,检查srcPad是否需要重新协商。必要时,它会重新设置srcPadCaps并获取下游提供的分配参数。
调用成功后,调用者可以映射Buffer、写入PCM,然后将Buffer交给:
gst_audio_decoder_finish_frame(dec,outbuf,frames);finish_frame()会接管Buffer所有权。调用者不应在成功提交后再次释放同一个Buffer。
几个相邻接口的职责不同:
| 接口 | 作用 |
|---|---|
gst_audio_decoder_set_output_format() | 设置输出音频格式并标记输出格式变化 |
gst_audio_decoder_negotiate() | 主动执行输出Caps和分配协商 |
gst_audio_decoder_get_allocator() | 查询当前分配器和分配参数 |
gst_audio_decoder_allocate_output_buffer() | 按照当前协商状态创建输出Buffer |
gst_audio_decoder_finish_frame() | 接收解码Buffer并交给基类完成时间戳和下游处理 |
gst_buffer_new_allocate() | 直接创建Buffer,不处理GstAudioDecoder的协商状态 |
这个函数是同步执行的,不启动新的线程,不触发解码回调,也不负责填充PCM内容。
🌻3.调用流程剖析
3.1解码子类到输出Buffer
GstAudioDecoder基类把输入数据交给子类的handle_frame虚函数。子类调用具体解码库得到PCM样本,再根据样本数、声道数和采样格式计算字节数。
典型调用关系如下:
size=samples*bytes_per_frame;outbuf=gst_audio_decoder_allocate_output_buffer(dec,size);if(outbuf==NULL)returnGST_FLOW_ERROR;gst_buffer_map(outbuf,&map,GST_MAP_WRITE);decode_to_pcm(map.data,map.size);gst_buffer_unmap(outbuf,&map);returngst_audio_decoder_finish_frame(dec,outbuf,frames);Vorbis和Opus解码器都采用了类似路径。
Opus解码器还会根据实际解码结果处理容量不足的情况。第一次分配的Buffer不够时,它会解除映射、释放旧Buffer、增大样本容量,再次调用分配函数。
函数返回的只是一个已经分配好的Buffer。PCM数据仍然由解码子类写入,时间戳、Duration和下游提交则由finish_frame()继续处理。
3.2协商、分配与回退
GStreamer1.24.2中的核心逻辑可以概括为:
g_return_val_if_fail(size>0,NULL);GST_AUDIO_DECODER_STREAM_LOCK(dec);needs_reconfigure=gst_pad_check_reconfigure(dec->srcpad);if(dec->priv->ctx.output_format_changed||(GST_AUDIO_INFO_IS_VALID(&dec->priv->ctx.info)&&needs_reconfigure)){if(!gst_audio_decoder_negotiate_unlocked(dec)){gst_pad_mark_reconfigure(dec->srcpad);gotofallback;}}buffer=gst_buffer_new_allocate(dec->priv->ctx.allocator,size,&dec->priv->ctx.params);if(!buffer)gotofallback;GST_AUDIO_DECODER_STREAM_UNLOCK(dec);returnbuffer;fallback:buffer=gst_buffer_new_allocate(NULL,size,NULL);GST_AUDIO_DECODER_STREAM_UNLOCK(dec);returnbuffer;函数首先检查size,然后持有流锁。
如果输出格式发生变化,或者srcPad被标记为需要重新协商,函数会调用gst_audio_decoder_negotiate_unlocked()。协商成功后,ctx.allocator和ctx.params会更新为当前配置。
正常分配路径使用:
gst_buffer_new_allocate(ctx.allocator,size,&ctx.params);如果协商失败,函数会标记srcPad继续需要协商,并使用默认分配器回退。如果当前分配器创建Buffer失败,也会进入回退路径。
因此,分配函数返回成功并不代表一定使用了下游专用内存。回退路径可能返回普通系统内存Buffer,调用者需要结合调试日志和下游能力判断最终内存类型。
回退分配只负责尽量创建Buffer,不负责修复输出Caps,也不保证满足下游对专用内存、对齐方式或内存池的要求。
🌻4.实战案例
4.1准备真实解码管线
使用audiotestsrc生成原始音频,再通过opusenc编码,最后交给opusdec解码。
opusdec是GstAudioDecoder的子类,它会在解码PCM后调用gst_audio_decoder_allocate_output_buffer()。
先确认插件存在:
gst-inspect-1.0 opusenc opusdec再准备测试管线:
audiotestsrc num-buffers=20wave=sine!\audioconvert!audioresample!\audio/x-raw,format=S16LE,rate=48000,channels=2!\opusenc!opusdec!fakesinksync=false4.2观察协商与Buffer分配
为audiodecoder打开调试日志:
GST_DEBUG_NO_COLOR=1GST_DEBUG="audiodecoder:6"\gst-launch-1.0\audiotestsrc num-buffers=20wave=sine!\audioconvert!audioresample!\audio/x-raw,format=S16LE,rate=48000,channels=2!\opusenc!opusdec!fakesinksync=false正常情况下,管线会完成编码、解码并收到EOS。重点观察以下日志信息:
alloc src buffer setting src caps ALLOCATION这些日志分别对应输出Buffer分配入口、srcPad输出Caps设置和下游分配参数处理。
如果日志中出现:
Failed to negotiate, fallback allocation说明输出协商没有成功,函数已经进入默认分配器回退路径。此时仍可能得到一个普通GstBuffer,但不应再假设它来自下游提供的专用allocator。
如果输入或输出样本数为0,解码子类不得继续以size = 0调用分配函数,应直接处理无输出帧或丢弃当前解码帧。
4.3完整验证命令与关键结果
set-opipefail gst-inspect-1.0 opusenc opusdec>/tmp/gst_opus_inspect.logGST_DEBUG_NO_COLOR=1\GST_DEBUG="audiodecoder:6"\gst-launch-1.0\audiotestsrc num-buffers=20wave=sine!\audioconvert!audioresample!\audio/x-raw,format=S16LE,rate=48000,channels=2!\opusenc!opusdec!fakesinksync=false\2>&1|tee/tmp/gst_audio_decoder_allocate.log验证重点:
1. gst-inspect-1.0能够找到opusenc和opusdec。 2. gst-launch-1.0正常结束并收到EOS。 3. audiodecoder调试日志出现alloc src buffer。 4. 输出格式变化时能够看到setting src caps或ALLOCATION相关日志。 5. 没有出现size > 0断言失败。这组命令验证了完整链路:opusdec计算PCM输出容量,GstAudioDecoder检查协商状态并创建Buffer,解码子类写入PCM,最后通过finish_frame()提交解码结果。
🌻5.总结
gst_audio_decoder_allocate_output_buffer()就是根据解码器当前输出协商状态和分配参数创建可写GstBuffer,它不负责解码、填充PCM或提交下游数据。