简介: CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中...... 】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中...... 】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
更多原创,欢迎关注:Android系统攻城狮

🍉🍉🍉文章目录🍉🍉🍉
🌻1.前言
本篇目的:理解
av_write_frame()如何把AVPacket送入封装器,分析它对包顺序和所有权的要求,并通过单流Remux案例观察写包流程。
编码器或解复用器提供的AVPacket还需要经过封装,才能成为容器文件中的数据。av_write_frame()把包直接交给封装器,不负责缓存或重排包。调用者必须安排封装所需的包顺序,并在调用后自行管理包的引用。
#mermaid-svg-1q5iTiJQFwJ5KlqY{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-1q5iTiJQFwJ5KlqY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1q5iTiJQFwJ5KlqY .error-icon{fill:#552222;}#mermaid-svg-1q5iTiJQFwJ5KlqY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1q5iTiJQFwJ5KlqY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1q5iTiJQFwJ5KlqY .marker.cross{stroke:#333333;}#mermaid-svg-1q5iTiJQFwJ5KlqY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1q5iTiJQFwJ5KlqY p{margin:0;}#mermaid-svg-1q5iTiJQFwJ5KlqY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1q5iTiJQFwJ5KlqY .cluster-label text{fill:#333;}#mermaid-svg-1q5iTiJQFwJ5KlqY .cluster-label span{color:#333;}#mermaid-svg-1q5iTiJQFwJ5KlqY .cluster-label span p{background-color:transparent;}#mermaid-svg-1q5iTiJQFwJ5KlqY .label text,#mermaid-svg-1q5iTiJQFwJ5KlqY span{fill:#333;color:#333;}#mermaid-svg-1q5iTiJQFwJ5KlqY .node rect,#mermaid-svg-1q5iTiJQFwJ5KlqY .node circle,#mermaid-svg-1q5iTiJQFwJ5KlqY .node ellipse,#mermaid-svg-1q5iTiJQFwJ5KlqY .node polygon,#mermaid-svg-1q5iTiJQFwJ5KlqY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1q5iTiJQFwJ5KlqY .rough-node .label text,#mermaid-svg-1q5iTiJQFwJ5KlqY .node .label text,#mermaid-svg-1q5iTiJQFwJ5KlqY .image-shape .label,#mermaid-svg-1q5iTiJQFwJ5KlqY .icon-shape .label{text-anchor:middle;}#mermaid-svg-1q5iTiJQFwJ5KlqY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1q5iTiJQFwJ5KlqY .rough-node .label,#mermaid-svg-1q5iTiJQFwJ5KlqY .node .label,#mermaid-svg-1q5iTiJQFwJ5KlqY .image-shape .label,#mermaid-svg-1q5iTiJQFwJ5KlqY .icon-shape .label{text-align:center;}#mermaid-svg-1q5iTiJQFwJ5KlqY .node.clickable{cursor:pointer;}#mermaid-svg-1q5iTiJQFwJ5KlqY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1q5iTiJQFwJ5KlqY .arrowheadPath{fill:#333333;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1q5iTiJQFwJ5KlqY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1q5iTiJQFwJ5KlqY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1q5iTiJQFwJ5KlqY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1q5iTiJQFwJ5KlqY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1q5iTiJQFwJ5KlqY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1q5iTiJQFwJ5KlqY .cluster text{fill:#333;}#mermaid-svg-1q5iTiJQFwJ5KlqY .cluster span{color:#333;}#mermaid-svg-1q5iTiJQFwJ5KlqY 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-1q5iTiJQFwJ5KlqY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1q5iTiJQFwJ5KlqY rect.text{fill:none;stroke-width:0;}#mermaid-svg-1q5iTiJQFwJ5KlqY .icon-shape,#mermaid-svg-1q5iTiJQFwJ5KlqY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1q5iTiJQFwJ5KlqY .icon-shape p,#mermaid-svg-1q5iTiJQFwJ5KlqY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1q5iTiJQFwJ5KlqY .icon-shape .label rect,#mermaid-svg-1q5iTiJQFwJ5KlqY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1q5iTiJQFwJ5KlqY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1q5iTiJQFwJ5KlqY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1q5iTiJQFwJ5KlqY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 准备好的AVPacket
av_write_frame
封装器写包入口
输出容器数据
🌻2.应用场景和用法
2.1调用者自行控制写包顺序
av_write_frame()适合调用者已经掌握包顺序的场景,例如:
| 场景 | 调用者需要保证 |
|---|---|
| 单流封装 | 当前流的DTS符合封装器要求 |
| 自定义多流调度 | 音频、视频等包已经按容器需要交织 |
| Remux | 包的stream_index指向输出流,时间戳已换算到输出流时间基 |
| 需要直接写包 | 不依赖libavformat的交织队列 |
如果多路音视频包未按容器要求交织,直接调用该接口可能使输出包顺序不合适。希望libavformat负责交织时,应使用av_interleaved_write_frame()。
2.2函数原型与包所有权
函数原型如下:
c
int av_write_frame (AVFormatContext *s, AVPacket *pkt);
| 参数 | 含义 |
|---|---|
s |
已创建输出流并成功写入文件头的封装上下文 |
pkt |
要写入的包,stream_index必须对应s->streams中的输出流 |
返回值小于0表示发生错误,0表示成功。传入NULL可以请求部分封装器立即刷新内部缓存;不支持该操作的封装器会返回"已刷新且没有更多数据"的状态值。
对于常规引用计数包,av_write_frame()不接管调用者持有的AVPacket。调用者在使用后仍需调用av_packet_unref()。包的PTS和DTS应使用对应输出流的时间基;同一流后续包的DTS必须严格递增,除非封装器允许非严格时间戳。
相邻接口的职责不同:
| 接口 | 作用 |
|---|---|
avformat_write_header() |
初始化封装器并写入文件头 |
av_packet_rescale_ts() |
将包时间戳换算到输出流时间基 |
av_write_frame() |
直接把包交给封装器,不负责交织 |
av_interleaved_write_frame() |
按包时间戳交织多个流的包 |
av_write_trailer() |
完成封装并写入文件尾 |
av_packet_unref() |
释放调用者持有的包引用 |
🌻3.调用流程剖析
3.1从公开接口进入封装器
av_write_frame()先处理AVPacket,再进入公共写包逻辑。公共逻辑检查包和流索引,准备输入包,并按配置检查是否需要应用自动Bitstream Filter。随后,非交织路径直接进入封装器的写包函数。
这一条路径不会把包放进libavformat的交织队列,也不会替调用者重新安排不同流的先后顺序。
#mermaid-svg-YjtPBJnaVHvIRlMj{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-YjtPBJnaVHvIRlMj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YjtPBJnaVHvIRlMj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YjtPBJnaVHvIRlMj .error-icon{fill:#552222;}#mermaid-svg-YjtPBJnaVHvIRlMj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YjtPBJnaVHvIRlMj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YjtPBJnaVHvIRlMj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YjtPBJnaVHvIRlMj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YjtPBJnaVHvIRlMj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YjtPBJnaVHvIRlMj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YjtPBJnaVHvIRlMj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YjtPBJnaVHvIRlMj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YjtPBJnaVHvIRlMj .marker.cross{stroke:#333333;}#mermaid-svg-YjtPBJnaVHvIRlMj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YjtPBJnaVHvIRlMj p{margin:0;}#mermaid-svg-YjtPBJnaVHvIRlMj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YjtPBJnaVHvIRlMj .cluster-label text{fill:#333;}#mermaid-svg-YjtPBJnaVHvIRlMj .cluster-label span{color:#333;}#mermaid-svg-YjtPBJnaVHvIRlMj .cluster-label span p{background-color:transparent;}#mermaid-svg-YjtPBJnaVHvIRlMj .label text,#mermaid-svg-YjtPBJnaVHvIRlMj span{fill:#333;color:#333;}#mermaid-svg-YjtPBJnaVHvIRlMj .node rect,#mermaid-svg-YjtPBJnaVHvIRlMj .node circle,#mermaid-svg-YjtPBJnaVHvIRlMj .node ellipse,#mermaid-svg-YjtPBJnaVHvIRlMj .node polygon,#mermaid-svg-YjtPBJnaVHvIRlMj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YjtPBJnaVHvIRlMj .rough-node .label text,#mermaid-svg-YjtPBJnaVHvIRlMj .node .label text,#mermaid-svg-YjtPBJnaVHvIRlMj .image-shape .label,#mermaid-svg-YjtPBJnaVHvIRlMj .icon-shape .label{text-anchor:middle;}#mermaid-svg-YjtPBJnaVHvIRlMj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YjtPBJnaVHvIRlMj .rough-node .label,#mermaid-svg-YjtPBJnaVHvIRlMj .node .label,#mermaid-svg-YjtPBJnaVHvIRlMj .image-shape .label,#mermaid-svg-YjtPBJnaVHvIRlMj .icon-shape .label{text-align:center;}#mermaid-svg-YjtPBJnaVHvIRlMj .node.clickable{cursor:pointer;}#mermaid-svg-YjtPBJnaVHvIRlMj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YjtPBJnaVHvIRlMj .arrowheadPath{fill:#333333;}#mermaid-svg-YjtPBJnaVHvIRlMj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YjtPBJnaVHvIRlMj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YjtPBJnaVHvIRlMj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YjtPBJnaVHvIRlMj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YjtPBJnaVHvIRlMj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YjtPBJnaVHvIRlMj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YjtPBJnaVHvIRlMj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YjtPBJnaVHvIRlMj .cluster text{fill:#333;}#mermaid-svg-YjtPBJnaVHvIRlMj .cluster span{color:#333;}#mermaid-svg-YjtPBJnaVHvIRlMj 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-YjtPBJnaVHvIRlMj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YjtPBJnaVHvIRlMj rect.text{fill:none;stroke-width:0;}#mermaid-svg-YjtPBJnaVHvIRlMj .icon-shape,#mermaid-svg-YjtPBJnaVHvIRlMj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YjtPBJnaVHvIRlMj .icon-shape p,#mermaid-svg-YjtPBJnaVHvIRlMj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YjtPBJnaVHvIRlMj .icon-shape .label rect,#mermaid-svg-YjtPBJnaVHvIRlMj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YjtPBJnaVHvIRlMj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YjtPBJnaVHvIRlMj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YjtPBJnaVHvIRlMj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入AVPacket
检查包和流索引
准备包并处理Bitstream Filter
直接调用封装器写包
返回写入状态
3.2调用前后维护时间戳和包引用
Remux时,输入包的stream_index属于输入上下文,不能直接当作输出流索引使用。调用者需要先完成输入流到输出流的映射,再用输入、输出流各自的时间基换算PTS、DTS和duration。
调用av_write_frame()后,调用者仍持有包引用,因此可以在调用返回后执行av_packet_unref()。封装器写包失败时,返回负数,调用者应停止当前写入流程并完成资源清理。
🌻4.实战案例
4.1准备单流Remux上下文
案例从输入文件中选择一个视频流;如果没有视频流,则选择一个音频流。输出文件只包含这一个流,因此调用者只需保证该流内部的包顺序,避免把多流交织问题混入av_write_frame()的验证中。
4.2换算时间戳并直接写包
每个输入包先经过流索引检查,再把时间戳从输入流时间基换算到输出流时间基。调用av_write_frame()后,程序主动释放包引用,体现该接口不会接管调用者的包所有权。
4.3完整代码
c
#include <libavformat/avformat.h>
#include <libavutil/error.h>
#include <stdio.h>
static void
print_error (const char *where, int err)
{
char message[AV_ERROR_MAX_STRING_SIZE];
av_strerror (err, message, sizeof (message));
fprintf (stderr, "%s: %s\n", where, message);
}
int
main (int argc, char **argv)
{
AVFormatContext *input = NULL;
AVFormatContext *output = NULL;
AVPacket *packet = NULL;
AVStream *input_stream;
AVStream *output_stream;
int selected_stream = -1;
int ret = 0;
int header_written = 0;
if (argc != 3) {
fprintf (stderr, "Usage: %s input_file output_file\n", argv[0]);
return 1;
}
if ((ret = avformat_open_input (&input, argv[1], NULL, NULL)) < 0)
goto end;
if ((ret = avformat_find_stream_info (input, NULL)) < 0)
goto end;
/* 优先选择视频流;没有视频流时再选择音频流 */
selected_stream = av_find_best_stream (
input, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (selected_stream < 0) {
selected_stream = av_find_best_stream (
input, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
}
if (selected_stream < 0) {
ret = selected_stream;
goto end;
}
input_stream = input->streams[selected_stream];
/* 根据输出文件名创建封装上下文 */
ret = avformat_alloc_output_context2 (
&output, NULL, NULL, argv[2]);
if (ret < 0 || output == NULL) {
if (ret >= 0)
ret = AVERROR_UNKNOWN;
goto end;
}
output_stream = avformat_new_stream (output, NULL);
if (output_stream == NULL) {
ret = AVERROR_UNKNOWN;
goto end;
}
ret = avcodec_parameters_copy (
output_stream->codecpar,
input_stream->codecpar);
if (ret < 0)
goto end;
/* 让封装器根据输出格式选择codec_tag */
output_stream->codecpar->codec_tag = 0;
output_stream->time_base = input_stream->time_base;
if (!(output->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open (
&output->pb,
argv[2],
AVIO_FLAG_WRITE);
if (ret < 0)
goto end;
}
ret = avformat_write_header (output, NULL);
if (ret < 0)
goto end;
header_written = 1;
packet = av_packet_alloc ();
if (packet == NULL) {
ret = AVERROR_ENOMEM;
goto end;
}
while ((ret = av_read_frame (input, packet)) >= 0) {
if (packet->stream_index != selected_stream) {
/* 忽略未选择的流,并释放当前输入包 */
av_packet_unref (packet);
continue;
}
/* 使用文件头写入后的输出时间基换算时间戳 */
av_packet_rescale_ts (
packet,
input_stream->time_base,
output_stream->time_base);
packet->stream_index = output_stream->index;
packet->pos = -1;
/* 该接口不接管常规包引用,调用后由本程序释放 */
ret = av_write_frame (output, packet);
av_packet_unref (packet);
if (ret < 0)
goto end;
}
if (ret != AVERROR_EOF)
goto end;
ret = 0;
end:
if (header_written) {
int trailer_ret = av_write_trailer (output);
if (ret >= 0)
ret = trailer_ret;
}
av_packet_free (&packet);
if (output != NULL &&
!(output->oformat->flags & AVFMT_NOFILE) &&
output->pb != NULL) {
avio_closep (&output->pb);
}
avformat_free_context (output);
avformat_close_input (&input);
if (ret < 0) {
print_error ("Remux failed", ret);
return 1;
}
return 0;
}
🌻5.总结
av_write_frame()把包直接交给封装器写入,包顺序和包引用仍由调用者负责。