简介: CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中...... 】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中...... 】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
更多原创,欢迎关注:Android系统攻城狮
🍉🍉🍉文章目录🍉🍉🍉
🌻1.前言
本篇目的:理解
avcodec_default_get_encode_buffer()如何为编码器输出包分配带引用计数的缓冲区,分析它与自定义回调和AVPacket生命周期的关系,并通过回调包装示例观察默认分配器的调用位置。
编码器生成压缩数据时,需要一块可写的输出内存。FFmpeg通过AVCodecContext.get_encode_buffer回调提供这块内存,默认实现就是avcodec_default_get_encode_buffer()。
默认实现会根据AVPacket.size分配AVBufferRef,再让AVPacket.data指向缓冲区数据。它只负责建立输出包缓冲区,不调用编码器,也不负责写文件或封装容器。
#mermaid-svg-Gp5hZtBcsZcujM4f{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-Gp5hZtBcsZcujM4f .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Gp5hZtBcsZcujM4f .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Gp5hZtBcsZcujM4f .error-icon{fill:#552222;}#mermaid-svg-Gp5hZtBcsZcujM4f .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Gp5hZtBcsZcujM4f .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Gp5hZtBcsZcujM4f .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Gp5hZtBcsZcujM4f .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Gp5hZtBcsZcujM4f .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Gp5hZtBcsZcujM4f .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Gp5hZtBcsZcujM4f .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Gp5hZtBcsZcujM4f .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Gp5hZtBcsZcujM4f .marker.cross{stroke:#333333;}#mermaid-svg-Gp5hZtBcsZcujM4f svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Gp5hZtBcsZcujM4f p{margin:0;}#mermaid-svg-Gp5hZtBcsZcujM4f .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Gp5hZtBcsZcujM4f .cluster-label text{fill:#333;}#mermaid-svg-Gp5hZtBcsZcujM4f .cluster-label span{color:#333;}#mermaid-svg-Gp5hZtBcsZcujM4f .cluster-label span p{background-color:transparent;}#mermaid-svg-Gp5hZtBcsZcujM4f .label text,#mermaid-svg-Gp5hZtBcsZcujM4f span{fill:#333;color:#333;}#mermaid-svg-Gp5hZtBcsZcujM4f .node rect,#mermaid-svg-Gp5hZtBcsZcujM4f .node circle,#mermaid-svg-Gp5hZtBcsZcujM4f .node ellipse,#mermaid-svg-Gp5hZtBcsZcujM4f .node polygon,#mermaid-svg-Gp5hZtBcsZcujM4f .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Gp5hZtBcsZcujM4f .rough-node .label text,#mermaid-svg-Gp5hZtBcsZcujM4f .node .label text,#mermaid-svg-Gp5hZtBcsZcujM4f .image-shape .label,#mermaid-svg-Gp5hZtBcsZcujM4f .icon-shape .label{text-anchor:middle;}#mermaid-svg-Gp5hZtBcsZcujM4f .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Gp5hZtBcsZcujM4f .rough-node .label,#mermaid-svg-Gp5hZtBcsZcujM4f .node .label,#mermaid-svg-Gp5hZtBcsZcujM4f .image-shape .label,#mermaid-svg-Gp5hZtBcsZcujM4f .icon-shape .label{text-align:center;}#mermaid-svg-Gp5hZtBcsZcujM4f .node.clickable{cursor:pointer;}#mermaid-svg-Gp5hZtBcsZcujM4f .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Gp5hZtBcsZcujM4f .arrowheadPath{fill:#333333;}#mermaid-svg-Gp5hZtBcsZcujM4f .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Gp5hZtBcsZcujM4f .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Gp5hZtBcsZcujM4f .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Gp5hZtBcsZcujM4f .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Gp5hZtBcsZcujM4f .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Gp5hZtBcsZcujM4f .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Gp5hZtBcsZcujM4f .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Gp5hZtBcsZcujM4f .cluster text{fill:#333;}#mermaid-svg-Gp5hZtBcsZcujM4f .cluster span{color:#333;}#mermaid-svg-Gp5hZtBcsZcujM4f 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-Gp5hZtBcsZcujM4f .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Gp5hZtBcsZcujM4f rect.text{fill:none;stroke-width:0;}#mermaid-svg-Gp5hZtBcsZcujM4f .icon-shape,#mermaid-svg-Gp5hZtBcsZcujM4f .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Gp5hZtBcsZcujM4f .icon-shape p,#mermaid-svg-Gp5hZtBcsZcujM4f .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Gp5hZtBcsZcujM4f .icon-shape .label rect,#mermaid-svg-Gp5hZtBcsZcujM4f .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Gp5hZtBcsZcujM4f .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Gp5hZtBcsZcujM4f .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Gp5hZtBcsZcujM4f :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 编码器确定输出包大小
调用get_encode_buffer回调
avcodec_default_get_encode_buffer
分配AVBufferRef
设置AVPacket.data和buf
编码器写入压缩数据
🌻2.应用场景和用法
2.1默认编码缓冲区分配
大多数应用不需要直接调用avcodec_default_get_encode_buffer()。编码器内部通过get_encode_buffer回调申请缓冲区,默认情况下该回调由FFmpeg设置为默认实现。
自定义回调适合需要接入特定内存池、统计分配行为或控制编码缓冲区来源的场景。自定义实现必须正确填写AVPacket.data和AVPacket.buf,保证缓冲区满足对齐与填充要求,并确保回调可被多个编码线程并发调用。
| 场景 | 做法 | 关注点 |
|---|---|---|
| 常规软件编码 | 使用FFmpeg默认回调 | 让FFmpeg管理包数据的引用计数 |
| 统计编码包分配 | 包装默认回调并记录申请大小 | 保留默认的内存和引用管理 |
| 接入自定义内存池 | 自定义get_encode_buffer |
返回有效AVBufferRef并实现线程安全 |
| 编码器不支持DR1 | 自定义回调中调用默认回调 | 不能直接提供其他来源的缓冲区 |
2.2函数原型与参数约束
函数原型如下:
c
int avcodec_default_get_encode_buffer (AVCodecContext *s,
AVPacket *pkt,
int flags);
| 参数或返回值 | 含义 |
|---|---|
s |
编码器上下文 |
pkt |
输出包;调用前pkt->size已由编码器设置 |
flags |
编码器提供的缓冲区用途标记 |
0 |
成功分配,pkt->buf和pkt->data已经设置 |
| 负值 | 包大小或包当前状态不合法,或分配失败 |
默认实现会检查包大小是否为负数或超过允许上限,也会拒绝已有pkt->data或pkt->buf的包。随后,它为包数据和AV_INPUT_BUFFER_PADDING_SIZE尾部填充分配引用计数缓冲区。
编码器内部的ff_get_encode_buffer()负责调用回调,并检查回调是否同时设置了data和buf。成功后,它会清零尾部填充;失败时释放包引用。输出包交给调用者后,由调用者通过av_packet_unref()等接口管理生命周期。
🌻3.调用流程剖析
3.1回调接收包大小并分配引用缓冲区
编码器知道当前输出包所需大小后,内部辅助函数会把大小写入AVPacket.size,再调用AVCodecContext.get_encode_buffer。默认情况下,这个函数指针指向avcodec_default_get_encode_buffer()。
默认实现首先检查大小范围和AVPacket是否为空。通过检查后,使用av_buffer_realloc()为pkt->size + AV_INPUT_BUFFER_PADDING_SIZE分配缓冲区,把返回的引用保存在pkt->buf,并将pkt->data指向缓冲区数据区。
#mermaid-svg-i42C2hadVNnAVCyo{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-i42C2hadVNnAVCyo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-i42C2hadVNnAVCyo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-i42C2hadVNnAVCyo .error-icon{fill:#552222;}#mermaid-svg-i42C2hadVNnAVCyo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-i42C2hadVNnAVCyo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-i42C2hadVNnAVCyo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-i42C2hadVNnAVCyo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-i42C2hadVNnAVCyo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-i42C2hadVNnAVCyo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-i42C2hadVNnAVCyo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-i42C2hadVNnAVCyo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-i42C2hadVNnAVCyo .marker.cross{stroke:#333333;}#mermaid-svg-i42C2hadVNnAVCyo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-i42C2hadVNnAVCyo p{margin:0;}#mermaid-svg-i42C2hadVNnAVCyo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-i42C2hadVNnAVCyo .cluster-label text{fill:#333;}#mermaid-svg-i42C2hadVNnAVCyo .cluster-label span{color:#333;}#mermaid-svg-i42C2hadVNnAVCyo .cluster-label span p{background-color:transparent;}#mermaid-svg-i42C2hadVNnAVCyo .label text,#mermaid-svg-i42C2hadVNnAVCyo span{fill:#333;color:#333;}#mermaid-svg-i42C2hadVNnAVCyo .node rect,#mermaid-svg-i42C2hadVNnAVCyo .node circle,#mermaid-svg-i42C2hadVNnAVCyo .node ellipse,#mermaid-svg-i42C2hadVNnAVCyo .node polygon,#mermaid-svg-i42C2hadVNnAVCyo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-i42C2hadVNnAVCyo .rough-node .label text,#mermaid-svg-i42C2hadVNnAVCyo .node .label text,#mermaid-svg-i42C2hadVNnAVCyo .image-shape .label,#mermaid-svg-i42C2hadVNnAVCyo .icon-shape .label{text-anchor:middle;}#mermaid-svg-i42C2hadVNnAVCyo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-i42C2hadVNnAVCyo .rough-node .label,#mermaid-svg-i42C2hadVNnAVCyo .node .label,#mermaid-svg-i42C2hadVNnAVCyo .image-shape .label,#mermaid-svg-i42C2hadVNnAVCyo .icon-shape .label{text-align:center;}#mermaid-svg-i42C2hadVNnAVCyo .node.clickable{cursor:pointer;}#mermaid-svg-i42C2hadVNnAVCyo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-i42C2hadVNnAVCyo .arrowheadPath{fill:#333333;}#mermaid-svg-i42C2hadVNnAVCyo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-i42C2hadVNnAVCyo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-i42C2hadVNnAVCyo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-i42C2hadVNnAVCyo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-i42C2hadVNnAVCyo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-i42C2hadVNnAVCyo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-i42C2hadVNnAVCyo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-i42C2hadVNnAVCyo .cluster text{fill:#333;}#mermaid-svg-i42C2hadVNnAVCyo .cluster span{color:#333;}#mermaid-svg-i42C2hadVNnAVCyo 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-i42C2hadVNnAVCyo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-i42C2hadVNnAVCyo rect.text{fill:none;stroke-width:0;}#mermaid-svg-i42C2hadVNnAVCyo .icon-shape,#mermaid-svg-i42C2hadVNnAVCyo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-i42C2hadVNnAVCyo .icon-shape p,#mermaid-svg-i42C2hadVNnAVCyo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-i42C2hadVNnAVCyo .icon-shape .label rect,#mermaid-svg-i42C2hadVNnAVCyo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-i42C2hadVNnAVCyo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-i42C2hadVNnAVCyo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-i42C2hadVNnAVCyo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
编码器给出pkt size
内部辅助函数调用回调
大小合法且包为空
返回错误
分配带尾部填充的AVBufferRef
设置pkt data和buf
返回编码器填充数据
内部辅助函数还会确认回调没有只设置裸数据指针而遗漏AVBufferRef。这保证编码器输出包拥有可追踪的缓冲区引用,后续可以安全地移动、引用或释放。
3.2自定义回调与默认实现的边界
avcodec_default_get_encode_buffer()是公开的默认回调,应用可以在自定义实现中调用它复用默认内存分配行为。例如先限制允许的最大包大小,再转交默认分配器。
如果编码器没有AV_CODEC_CAP_DR1能力,自定义回调必须调用默认实现,不能自行返回其他来源的缓冲区。支持自定义分配的编码器也要求回调满足线程安全、数据对齐、填充空间和引用计数等约束。
自定义分配内存时,pkt->data必须指向pkt->buf所管理的存储区。只设置pkt->data裸指针并不够,因为编码器框架需要通过引用对象维护输出数据的生命周期。
🌻4.实战案例
4.1包装默认回调并限制包大小
示例回调对申请尺寸进行上限检查,通过后调用avcodec_default_get_encode_buffer()完成分配。该方案适合加入简单的容量策略,同时继续使用FFmpeg的标准引用计数缓冲区。
4.2在打开编码器前注册回调
应在avcodec_open2()打开编码器前设置AVCodecContext.get_encode_buffer。编码器开始工作后,内部产生输出包并申请缓冲区时会进入该回调。
4.3完整代码片段
c
#include <libavcodec/avcodec.h>
#include <libavutil/error.h>
/* 限制单个编码包大小,随后交给FFmpeg默认分配器 */
static int
checked_get_encode_buffer (AVCodecContext *ctx,
AVPacket *pkt,
int flags)
{
const int64_t max_packet_size = 16 * 1024 * 1024;
/* pkt->size由编码器在调用回调前填写 */
if (pkt->size < 0 || pkt->size > max_packet_size)
return AVERROR (EINVAL);
/* 默认实现负责AVBufferRef和pkt->data的正确关联 */
return avcodec_default_get_encode_buffer (ctx, pkt, flags);
}
/* 编码器配置完成后、avcodec_open2之前注册:
*
* ctx->get_encode_buffer = checked_get_encode_buffer;
* ret = avcodec_open2 (ctx, codec, NULL);
*
* 后续编码器申请输出包缓冲区时,会执行checked_get_encode_buffer。
*/
该回调只增加包大小策略,内存仍由FFmpeg默认分配器管理。若改为自定义内存池,还必须返回有效的AVBufferRef,保证缓冲区长度包含尾部填充,并处理编码线程并发调用。
🌻5.总结
avcodec_default_get_encode_buffer()根据编码器预先填写的包大小分配引用计数缓冲区,并设置AVPacket.data与AVPacket.buf供编码器写入输出。