FFmpeg 复用全流程解析

目录


封装,是将视频和音频封装到容器中,即MP4、MKV、FLV、AVI等;

前面已经学习编码解码像素格式转换解复用,现在继续了解最后一个,复用

综合前面的所学知识点,解复用-解码-编码-复用成一个新的视频。

大致流程图:
在这里插入图片描述

整体流程:

					开始(begin)1. 打开视频文件 (avformat_open_input)2. 查找流信息 (avformat_find_stream_info)3. 获得流索引 (for 遍历流)4. 查找视频解码器 (avcodec_find_decoder)5. 创建解码器上下文 (avcodec_alloc_context3)6. 将流参数复制到解码器上下文 (avcodec_parameters_to_context)7. 打开解码器 (avcodec_open2)8. 查找视频编码器 (avcodec_find_encoder)9. 创建编码器上下文 (avcodec_alloc_context3)10. 设置编码器基础参数 (宽/高/像素格式/帧率/时间基)11. 打开编码器 (avcodec_open2)12. 创建复用上下文 (avformat_alloc_output_context2)⭐️
						↓
					13. 创建音视频流 (avformat_new_stream 视频+音频)⭐️
						↓
					14. 打开io (avio_open2)⭐️
						↓
					15. 写入文件头 (avformat_write_header)⭐️
						↓
					16. 读取数据包 (av_read_frame)17. 发送数据包到解码器 (avcodec_send_packet)18. 接收解码帧 (avcodec_receive_frame)19. 帧PTS重缩放:输入时间基→编码时间基 (av_rescale_q)20. 发送帧到编码器 (avcodec_send_frame)21. 接收编码帧 (avcodec_receive_packet)22. 重新计算pts/dts/duration (av_rescale)23. 交错写入文件 (av_interleaved_write_frame)⭐️
						↓
					24. 冲刷解码器 (avcodec_send_packet NULL)25. 冲刷编码器 (avcodec_send_frame NULL)26. 写入文件尾部 (av_write_trailer)⭐️
						↓
					27. 释放所有资源
						↓
					结束(end)

包含头文件:

#include <iostream>
#include <string>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}

1. 复用函数

1.1 avformat_alloc_output_context2

avformat_alloc_output_context2 是 FFmpeg 库中用于‌多媒体封装的核心初始化函数。与 avformat_open_input 不同的是,它不负责打开文件进行读取,而是负责‌创建并初始化一个用于写入数据的上下文环境‌,确定输出的容器格式(如 MP4、FLV、MKV 等)。

①函数原型

int avformat_alloc_output_context2(
    AVFormatContext **ctx, 
    const AVOutputFormat *oformat, 
    const char *format_name, 
    const char *filename
);

②参数说明

参数类型说明
ctxAVFormatContext **输出参数,指向创建好的输出格式上下文。调用前可传 NULL,函数内部自动分配内存
oformatconst AVOutputFormat *强制指定输出封装格式,传 NULL 时 FFmpeg 自动通过其他参数匹配格式
format_nameconst char *字符串形式指定输出格式名称(如 “mp4”、“flv”),传 NULL 时按文件名后缀推断
filenameconst char *输出文件/推流地址,支持本地文件路径、RTMP 推流地址等,作为格式推断的兜底依据

③返回值

  • 成功:返回 0
  • 失败:失败返回负的错误码(如 AVERROR(ENOMEM)、AVERROR_MUXER_NOT_FOUND 等)

④使用示例

std::string out_file_path = "out_file.mp4";
AVFormatContext *mux_ctx = nullptr;

re = avformat_alloc_output_context2(&mux_ctx, NULL, NULL, out_file_path.c_str());
if (0 > re) {
    std::cout << "avformat_alloc_output_context2 failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

1.2 avformat_new_stream

avformat_new_stream 是 FFmpeg 库中用于‌创建媒体流(Stream)‌的核心函数。在多媒体封装过程中,它负责向 AVFormatContext 中添加一个新的流通道(如视频流、音频流或字幕流),并分配相应的内存空间。

它是连接“格式上下文”与“具体编码数据”的桥梁。只有创建了 Stream,才能后续配置编码参数、写入头部信息以及推送音视频数据包。

①函数原型

AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);

②参数说明

参数类型说明
sAVFormatContext *‌输入参数‌。指向格式上下文的指针。必须是之前通过 avformat_alloc_output_context2() 创建的‌输出上下文‌。不能是输入上下文(解复用场景下由 demuxer 自动创建流)。
cconst AVCodec *‌可选参数‌。指向编解码器的指针。- 若为 ‌NULL‌(推荐):仅分配 AVStream 和 AVCodecParameters,不设置具体编码参数,需后续手动填充 codecpar。- 若‌非 NULL‌:FFmpeg 会根据该编解码器的默认属性初始化流的 codec_type 和 codec_id,但现代 FFmpeg 开发中通常仍建议传 NULL 以保留最大控制权。

③返回值

  • 成功:返回指向新创建的 AVStream 结构体的指针
  • 失败:返回 NULL(通常是因为内存分配失败 AVERROR(ENOMEM))

④使用示例

AVStream *stream = avformat_new_stream(mux_ctx, NULL);      // 新建流

1.3 avio_open2

avio_open2 是 FFmpeg 库中用于‌打开输入/输出资源‌的核心底层函数。它的主要任务是创建一个 AVIOContext 上下文,并建立与指定 URL(可以是本地文件路径,也可以是网络流地址如 RTMP、HTTP、UDP 等)的连接,为后续的数据读写操作提供统一的接口。

①函数原型

int avio_open2(AVIOContext **s, 
               const char *url, 
               int flags, 
               const AVIOInterruptCB *int_cb, 
               AVDictionary **options);

②参数说明

参数类型说明
sAVIOContext ‌**‌输出参数。指向创建好的 AVIOContext 指针。函数内部会分配内存并初始化该结构体,包含读写缓冲区及底层协议的操作函数指针。若失败,*s 被设为 NULL。
urlconst char *‌资源地址‌。支持广义 URL:- 本地文件:“C:/video/test.mp4” 或 “./output.flv”- 网络流:“rtmp://server/live/stream”、“http://example.com/video.ts”、“udp://@239.1.1.1:1234”
flagsint‌打开标志‌。控制资源的访问模式:- AVIO_FLAG_READ:只读- AVIO_FLAG_WRITE:只写- AVIO_FLAG_READ_WRITE:读写(注意:某些协议可能不支持同时读写)
int_cbconst AVIOInterruptCB *‌中断回调‌。可选参数。用于在阻塞操作(如网络连接、文件读取)期间检查是否应中止操作。传 NULL 表示不使用中断机制。
optionsAVDictionary ‌**‌协议私有选项。可选参数。字典形式,用于传递特定协议的配置项(如 HTTP 的超时时间、RTMP 的 app/playpath 等)。函数返回后,未识别的选项会保留在字典中,已使用的会被移除。传 NULL 表示使用默认选项。

③返回值

  • 成功:成功返回 0
  • 失败:返回负的错误码(如 AVERROR(EIO)、AVERROR(ENOMEM)、AVERROR_PROTOCOL_NOT_FOUND 等)

④使用示例

re = avio_open2(&mux_ctx->pb, out_file_path.c_str(), AVIO_FLAG_WRITE, NULL, NULL);
if (0 > re) {
    std::cout << "avio_open2 failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

1.4 avformat_write_header

avformat_write_header 是 FFmpeg 封装流程中的‌关键里程碑函数‌。它的主要作用是将媒体文件的‌元数据(Metadata)‌和‌流结构信息‌写入输出文件的头部(Header)。

只有成功调用该函数之后,才能开始向文件写入音视频数据包(AVPacket)。它标志着封装过程从“参数配置阶段”进入“数据写入阶段”。

①函数原型

int avformat_write_header(AVFormatContext *s, AVDictionary **options);

②参数说明

参数类型说明
sAVFormatContext *‌输入/输出参数‌。指向已通过 avformat_alloc_output_context2 初始化、且已通过 avformat_new_stream 添加了所有流的格式上下文。函数执行过程中可能会修改 s 内部的某些字段(如流的 time_base、全局头部数据等)。
optionsAVDictionary ‌**‌可选参数。指向复用器(Muxer)私有选项的字典指针。函数返回后,未被复用器识别的选项会保留在字典中,已被识别并使用的选项会被移除。传 NULL 表示使用默认配置。

③返回值

  • 成功:返回 >= 0 的值
  • 失败:返回负的错误码(如 AVERROR_INVALIDDATA 参数无效、AVERROR(EIO) 写入失败等)

④使用示例

re = avformat_write_header(mux_ctx, NULL);
if (0 > re) {
    std::cout << "avformat_write_header failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

1.5 av_rescale_q

av_rescale_q 是 FFmpeg 库中最常用的‌时间基转换‌工具函数。在多媒体处理中,不同的组件(编码器、解码器、容器、滤镜)往往使用不同的时间单位(Time Base)来表示时间戳(PTS/DTS)。av_rescale_q 的作用就是将一个数值(通常是时间戳或时长)从一种时间基‌精确换算‌到另一种时间基,避免使用浮点数运算带来的精度损失。

①函数原型

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq);

②参数说明

参数类型说明
aint64_t待转换的原始数值。通常是 PTS(显示时间戳)、DTS(解码时间戳)或 duration(时长),单位由 bq 决定。
bqAVRational‌源时间基‌(Source Timebase)。定义了数值 a 的单位。例如 {1, 25} 表示 a 的单位是 1/25 秒(即帧数)。
cqAVRational‌目标时间基‌(Destination Timebase)。定义了返回值的单位。例如 {1, 1000} 表示返回值单位是毫秒。

③返回值

  • 返回 转换后的 int64_t 数值
  • 数学含义‌:返回 a * bq / cq 的结果

④核心原理
时间基 (Time Base) 是什么?
AVRational 是一个分数,由分子(num)和分母(den)组成,如 {1, 90000} 表示 1/90000 秒。

时间戳的物理意义‌:时间戳 pts 乘以时间基 time_base 才是真实的秒数。
公式:真实时间(秒) = pts * time_base.num / time_base.den

换算公式
要将一个时间戳从时间基 A 转换到时间基 B,本质上是先算出真实时间,再除以目标时间基。

推导过程‌
1)真实时间 = a * bq.num / bq.den
2)目标时间戳 = 真实时间 / 目标时间基 = 真实时间 * cq.den / cq.num
3)合并公式:结果 = a * bq.num / bq.den * cq.den / cq.num
4)整理得:结果 = (a * bq.num * cq.den) / (bq.den * cq.num)

av_rescale_q 内部使用了 64 位整数运算来执行这个乘法和除法,并通过特殊的取整策略避免溢出和精度丢失。它等价于 av_rescale(a, bq.num * cq.den, bq.den * cq.num)。

⑤使用示例

frame->pts = av_rescale_q(frame->pts, demux_ctx->streams[in_video_index]->time_base, encodec_ctx->time_base);

pkt->duration = av_rescale_q(pkt->duration, cur_time_base, target_time_base);

1.6 av_rescale_q_rnd

av_rescale_q_rnd 是 FFmpeg 库中最底层、最灵活的‌时间基转换‌函数。它是 av_rescale_q 的增强版本,区别在于允许调用者显式指定‌舍入模式‌(Rounding Mode)。

在多媒体处理中,时间戳通常是整数,当换算结果不是整数时,需要决定如何取整(向上取整、向下取整、四舍五入等)。av_rescale_q_rnd 正是为这种需要精确控制取整策略的场景而设计。

①函数原型

int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq, enum AVRounding rnd);

②参数说明

参数类型说明
aint64_t待转换的原始数值。通常是 PTS、DTS 或 duration,单位由 bq 决定。
bqAVRational‌源时间基‌(Source Timebase)。数值 a 的单位,例如 {1, 25} 表示 a 以 1/25 秒为单位。
cqAVRational‌目标时间基‌(Destination Timebase)。返回值的单位,例如 {1, 1000} 表示返回值以毫秒为单位。
rndenum AVRounding‌舍入模式‌。指定当换算结果不是整数时如何取整。是该函数与 av_rescale_q 的核心区别。

③返回值

  • 返回 转换后的 int64_t 数值。具体舍入方式由 rnd 参数决定
  • 数学含义‌:返回 a * bq / cq 的结果,并按指定规则取整

④舍入模式 (AVRounding) 详解
enum AVRounding 定义了 FFmpeg 中所有的取整策略。常用的枚举值及含义如下:

枚举值数值说明等价 C 表达式 (a/b)示例 (5/2=2.5)示例 (-5/2=-2.5)
AV_ROUND_ZERO0‌向零取整‌(截断)。结果向 0 的方向靠近,即直接丢掉小数部分。a / b (整数除法)2-2
AV_ROUND_INF1‌向无穷取整‌。结果的绝对值只增不减,远离 0。3-3
AV_ROUND_DOWN2‌向下取整‌(向负无穷)。结果总是小于或等于真实值。floor(a / b)2-3
AV_ROUND_UP3‌向上取整‌(向正无穷)。结果总是大于或等于真实值。ceil(a / b)3-2
AV_ROUND_NEAR_INF5‌四舍五入,对半向无穷‌。最接近标准的“四舍五入”。小数部分大于 0.5 则向无穷方向进 1;等于 0.5 时也向无穷方向进 1。3-3
AV_ROUND_PASS_MINMAX8192‌特殊标志位‌,需与其他模式按位或。表示如果输入是 INT64_MIN 或 INT64_MAX,则不做转换直接返回原值,避免溢出。通常用于处理 AV_NOPTS_VALUE。

⑤核心原理
为什么需要不同的舍入模式?
在音视频同步或帧对齐场景中,取整方向直接影响播放体验:

  • ‌向下取整 (Round Down)‌:适用于计算‌开始时间‌。确保实际播放时间不会早于预期(避免黑屏或卡顿)。
  • ‌向上取整 (RoundUp)‌:适用于计算‌结束时间‌或‌时长‌。确保完整覆盖所有数据(避免丢尾帧)。 ‌
  • 四舍五入 (Nearnf)‌:适用于普通的数值转换,追求统计意义上的最小误差。

‌av_rescale_q‌:简单方便,适用于大多数常规转换场景。
‌av_rescale_q_rnd‌:精确可控,适用于对时间戳精度敏感、需要明确取整方向的场景。

⑥使用示例

pkt->pts = av_rescale_q_rnd(pkt->pts, cur_time_base,target_time_base,
                            (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                            
pkt->dts = av_rescale_q_rnd(pkt->dts, cur_time_base,target_time_base,
                            (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));

1.7 av_interleaved_write_frame

av_interleaved_write_frame 是 FFmpeg 封装流程中‌写入音视频数据包的标准推荐函数‌。它负责将编码完成的 AVPacket 交付给复用器,‌内部会自动对多个流的数据包按解码时间(DTS)进行交错排序‌,确保输出文件中媒体数据的排列符合播放器的解码需求,避免音画不同步或缓冲溢出问题。

与简单的 av_write_frame(立即写入)不同,它引入了内部缓存机制,是绝大多数包含音视频多流的封装场景的首选。

①函数原型

int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);

②参数说明

参数类型说明
sAVFormatContext *‌输出格式上下文‌。必须是已通过 avformat_write_header 完成头部写入的输出上下文。
pktAVPacket *‌待写入的数据包‌。- 其 pts/dts/duration 必须已经转换为对应输出流的 time_base 单位。- 其 stream_index 必须正确指向 s->streams 中的对应流。- 传 NULL 表示刷新内部缓存,将所有排队的包写出。

③返回值

  • 成功:返回 0
  • 失败:返回负的错误码(如 AVERROR(EINVAL) 参数无效、AVERROR(EIO) 写入失败等)

④使用示例

re = av_interleaved_write_frame(mux_ctx, pkt);
if (0 != re) {
    std::cout << "av_interleaved_write_frame failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

1.8 av_write_trailer

av_write_trailer 是 FFmpeg 封装流程中的‌收尾函数‌。它标志着媒体文件写入的结束,负责刷新所有缓存数据、写入文件尾部的元数据(如 MP4 的 moov box、MKV 的 SeekHead 等),并完成输出文件的最终结构。

它与 avformat_write_header 遥相呼应,缺一不可。漏掉这一步会导致输出文件损坏、无法播放或时长显示为 0。

①函数原型

int av_write_trailer(AVFormatContext *s);

②参数说明

参数类型说明
sAVFormatContext *‌输出格式上下文‌。必须是已通过 avformat_write_header 完成头部写入的输出上下文。函数执行后,内部缓存被清空,部分流信息可能被改写。

③返回值

  • 成功:返回 0
  • 失败:返回负的错误码(如 AVERROR(EIO) 写入失败、AVERROR(ENOMEM) 内存不足等)

④使用示例

re = av_write_trailer(mux_ctx);
if (0 != re) {
    std::cout << "av_write_trailer failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2. 编码流程

2.1 打开视频文件 (avformat_open_input)

re = avformat_open_input(&demux_ctx, in_file_path.c_str(), NULL, NULL);
if (0 != re) {
    std::cout << "avformat_open_input failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.2 查找流信息 (avformat_find_stream_info)

re = avformat_find_stream_info(demux_ctx, NULL);
if (0 > re) {
    std::cout << "avformat_find_stream_info failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.3 获得流索引 (for 遍历流)

for (int i = 0; i < demux_ctx->nb_streams; ++i) {
    if (AVMEDIA_TYPE_VIDEO == demux_ctx->streams[i]->codecpar->codec_type) {
        in_video_index = i;
    } else if (AVMEDIA_TYPE_AUDIO == demux_ctx->streams[i]->codecpar->codec_type) {
        in_audio_index = i;
    } else {
        std::cout << "other index: " << i;
    }
}

2.4 查找视频解码器 (avcodec_find_decoder)

decodec = avcodec_find_decoder(demux_ctx->streams[in_video_index]->codecpar->codec_id);
if (!decodec) {
    std::cout << "avcodec_find_decoder failed!" << std::endl;
    goto Failed;
}

2.5 创建解码器上下文 (avcodec_alloc_context3)

decodec_ctx = avcodec_alloc_context3(decodec);
if (!decodec_ctx) {
    std::cout << "avcodec_alloc_context3 failed!" << std::endl;
    goto Failed;
}

2.6 将流参数复制到解码器上下文 (avcodec_parameters_to_context)

re = avcodec_parameters_to_context(decodec_ctx, demux_ctx->streams[in_video_index]->codecpar);
if (0 > re) {
    std::cout << "avcodec_parameters_to_contextv failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.7 打开解码器 (avcodec_open2)

re = avcodec_open2(decodec_ctx, decodec, NULL);
if (0 != re) {
    std::cout << "avcodec_open2 failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.8 查找视频编码器 (avcodec_find_encoder)

encodec = avcodec_find_encoder(demux_ctx->streams[in_video_index]->codecpar->codec_id);
if (!encodec) {
    std::cout << "avcodec_find_encoder failed!" << std::endl;
    goto Failed;
}

2.9 创建编码器上下文 (avcodec_alloc_context3)

encodec_ctx = avcodec_alloc_context3(encodec);
if (!encodec_ctx) {
    std::cout << "avcodec_alloc_context3 failed!" << std::endl;
    goto Failed;
}

2.10 设置编码器基础参数 (宽/高/像素格式/帧率/时间基)

{
    encodec_ctx->width = decodec_ctx->width;
    encodec_ctx->height = decodec_ctx->height;
    encodec_ctx->pix_fmt = decodec_ctx->pix_fmt;

    // time_base
    AVRational framerate = demux_ctx->streams[in_video_index]->avg_frame_rate;
    if (0 >= framerate.den || 0 >= framerate.num) {
        // 根据参数‘猜测’结果
        framerate = av_guess_frame_rate(demux_ctx, demux_ctx->streams[in_video_index], NULL);
    }
    if (0 >= framerate.den || 0 >= framerate.num) {
        framerate = (AVRational){ 25, 1 };
    }
    encodec_ctx->framerate = framerate;
    encodec_ctx->time_base = av_inv_q(framerate);

    // 像素宽高比
    encodec_ctx->sample_aspect_ratio = decodec_ctx->sample_aspect_ratio;
}

2.11 打开编码器 (avcodec_open2)

re = avcodec_open2(encodec_ctx, encodec, NULL);
if (0 != re) {
    std::cout << "avcodec_open2 failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.12 创建复用上下文 (avformat_alloc_output_context2)⭐️

re = avformat_alloc_output_context2(&mux_ctx, NULL, NULL, out_file_path.c_str());
if (0 > re) {
    std::cout << "avformat_alloc_output_context2 failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.13 创建音视频流 (avformat_new_stream 视频+音频)⭐️

// 创建【视频流】
if (0 <= in_video_index) {
    AVStream *stream = avformat_new_stream(mux_ctx, NULL);      // 新建视频流
    stream->time_base = encodec_ctx->time_base;                 // 输出流 time_base 先与编码器保持一致
    out_video_index = stream->index;
    avcodec_parameters_from_context(stream->codecpar, encodec_ctx);     // 把编码器参数写进流
}
// 创建【音频流】:走“透传”——不解码不编码,直接复制参数、原样搬运压缩包
if (0 <= in_audio_index) {
    AVStream *stream = avformat_new_stream(mux_ctx, NULL);
    avcodec_parameters_copy(stream->codecpar, demux_ctx->streams[in_audio_index]->codecpar);
    stream->time_base = demux_ctx->streams[in_audio_index]->time_base;
    out_audio_index = stream->index;
}

2.14 打开io (avio_open2)⭐️

re = avio_open2(&mux_ctx->pb, out_file_path.c_str(), AVIO_FLAG_WRITE, NULL, NULL);
if (0 > re) {
    std::cout << "avio_open2 failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.15 写入文件头 (avformat_write_header)⭐️

re = avformat_write_header(mux_ctx, NULL);
if (0 > re) {
    std::cout << "avformat_write_header failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.16 读取数据包 (av_read_frame)

re = av_read_frame(demux_ctx, pkt);
// 已经读完
if (AVERROR_EOF == re) {
    break;
}
if (0 > re) {
    std::cout << "av_read_frame failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.17 发送数据包到解码器 (avcodec_send_packet)

re = avcodec_send_packet(decodec_ctx, pkt);
av_packet_unref(pkt);
if (0 > re) {
    std::cout << "avcodec_send_packet failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.18 接收解码帧 (avcodec_receive_frame)

re = avcodec_receive_frame(decodec_ctx, frame);
// 还未有完整的数据 | 已经读完
if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
    break;
}

if (0 > re) {
    std::cout << "avcodec_receive_frame failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.19 帧PTS重缩放:输入时间基→编码时间基 (av_rescale_q)

frame->pts = av_rescale_q(frame->pts, demux_ctx->streams[in_video_index]->time_base, encodec_ctx->time_base);

2.20 发送帧到编码器 (avcodec_send_frame)

re = avcodec_send_frame(encodec_ctx, frame);
av_frame_unref(frame);
if (0 > re) {
    std::cout << "avcodec_send_frame failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.21 接收编码帧 (avcodec_receive_packet)

re = avcodec_receive_packet(encodec_ctx, pkt);
// 还未有完整的数据 | 已经读完
if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
    break;
}

if (0 > re) {
    std::cout << "avcodec_receive_packet failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.22 重新计算pts/dts/duration (av_rescale)

void rescale_packet_ts(AVPacket *&pkt, AVRational cur_time_base, AVRational target_time_base) {
    pkt->pts = av_rescale_q_rnd(pkt->pts, cur_time_base,target_time_base,
                                (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
    pkt->dts = av_rescale_q_rnd(pkt->dts, cur_time_base,target_time_base,
                                (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
    pkt->duration = av_rescale_q(pkt->duration, cur_time_base, target_time_base);
    pkt->pos = -1;
}

rescale_packet_ts(pkt, encodec_ctx->time_base, mux_ctx->streams[out_video_index]->time_base);

2.23 交错写入文件 (av_interleaved_write_frame)⭐️

re = av_interleaved_write_frame(mux_ctx, pkt);
av_packet_unref(pkt);
if (0 != re) {
    std::cout << "av_interleaved_write_frame failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.24 冲刷解码器 (avcodec_send_packet NULL)

re = avcodec_send_packet(decodec_ctx, NULL);
if (0 > re) {
    std::cout << "avcodec_send_packet failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}
while (1) {
    // 接收解码
    re = avcodec_receive_frame(decodec_ctx, frame);
    // 还未有完整的数据 | 已经读完
    if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
        break;
    }

    if (0 > re) {
        std::cout << "avcodec_receive_frame failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }


    frame->pts = av_rescale_q(frame->pts, demux_ctx->streams[in_video_index]->time_base, encodec_ctx->time_base);


    // 发送编码
    re = avcodec_send_frame(encodec_ctx, frame);
    av_frame_unref(frame);
    if (0 > re) {
        std::cout << "avcodec_send_frame failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }


    while (1) {
        // 接收编码
        re = avcodec_receive_packet(encodec_ctx, pkt);
        // 还未有完整的数据 | 已经读完
        if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
            break;
        }

        if (0 > re) {
            std::cout << "avcodec_receive_packet failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }


        // 重新计算pts、dts、duration
        rescale_packet_ts(pkt, encodec_ctx->time_base, mux_ctx->streams[out_video_index]->time_base);

        // 写入文件
        re = av_interleaved_write_frame(mux_ctx, pkt);
        av_packet_unref(pkt);
        if (0 != re) {
            std::cout << "av_interleaved_write_frame failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }

        std::cout << "- ";
    }
}

2.25 冲刷编码器 (avcodec_send_frame NULL)

re = avcodec_send_frame(encodec_ctx, NULL);
if (0 > re) {
    std::cout << "avcodec_send_frame failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}
while (1) {
    // 接收编码
    re = avcodec_receive_packet(encodec_ctx, pkt);
    // 还未有完整的数据 | 已经读完
    if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
        break;
    }

    if (0 > re) {
        std::cout << "avcodec_receive_packet failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }


    // 重新计算pts、dts、duration
    rescale_packet_ts(pkt, encodec_ctx->time_base, mux_ctx->streams[out_video_index]->time_base);

    // 写入文件
    re = av_interleaved_write_frame(mux_ctx, pkt);
    av_packet_unref(pkt);
    if (0 != re) {
        std::cout << "av_interleaved_write_frame failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    std::cout << "= ";
}

2.26 写入文件尾部 (av_write_trailer)⭐️

re = av_write_trailer(mux_ctx);
if (0 != re) {
    std::cout << "av_write_trailer failed!" << std::endl;
    PrintErr(re);
    goto Failed;
}

2.27 释放所有资源

avformat_close_input(&demux_ctx);
avcodec_free_context(&decodec_ctx);
avcodec_free_context(&encodec_ctx);
if (mux_ctx) {
    if (mux_ctx->pb) {
        avio_closep(&mux_ctx->pb);
    }
    avformat_free_context(mux_ctx);
}

av_packet_free(&pkt);
av_frame_free(&frame);

3. 代码合集

mp4 → 解封装 → 解码 → 编码 → 封装 → mp4

#include <iostream>
#include <string>


extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}


// 打印错误信息
void PrintErr(int err) {
    char buf[1024] = { 0 };
    av_strerror(err, buf, sizeof(buf));
    std::cout << buf << std::endl;
}


// 重新计算pts、dts、duration
void rescale_packet_ts(AVPacket *&pkt, AVRational cur_time_base, AVRational target_time_base) {
    pkt->pts = av_rescale_q_rnd(pkt->pts, cur_time_base,target_time_base,
                                (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
    pkt->dts = av_rescale_q_rnd(pkt->dts, cur_time_base,target_time_base,
                                (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
    pkt->duration = av_rescale_q(pkt->duration, cur_time_base, target_time_base);
    pkt->pos = -1;
}

int main(void) {

    // 解复用
    std::string in_file_path = "警惕M从口出.mp4";
    AVFormatContext *demux_ctx = NULL;
    int in_video_index = -1;
    int in_audio_index = -1;


    // 解码
    const AVCodec *decodec = NULL;
    AVCodecContext *decodec_ctx = NULL;


    // 编码
    const AVCodec *encodec = NULL;
    AVCodecContext *encodec_ctx = NULL;


    // 复用
    std::string out_file_path = "out_file.mp4";
    AVFormatContext *mux_ctx = nullptr;
    int out_video_index = -1;
    int out_audio_index = -1;


    // 其它
    AVPacket *pkt = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();
    int re = -1;



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 解复用

    // 1.打开视频文件
    re = avformat_open_input(&demux_ctx, in_file_path.c_str(), NULL, NULL);
    if (0 != re) {
        std::cout << "avformat_open_input failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }


    // 2.查找流信息
    re = avformat_find_stream_info(demux_ctx, NULL);
    if (0 > re) {
        std::cout << "avformat_find_stream_info failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }


    // 3.获得流索引
    for (int i = 0; i < demux_ctx->nb_streams; ++i) {
        if (AVMEDIA_TYPE_VIDEO == demux_ctx->streams[i]->codecpar->codec_type) {
            in_video_index = i;
        } else if (AVMEDIA_TYPE_AUDIO == demux_ctx->streams[i]->codecpar->codec_type) {
            in_audio_index = i;
        } else {
            std::cout << "other index: " << i;
        }
    }

    std::cout << "in video index: " << in_video_index << "\nin audio index: " << in_audio_index << std::endl;

    av_dump_format(demux_ctx, in_video_index, in_file_path.c_str(), 0);


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 解码

    // 4.查找视频解码器
    decodec = avcodec_find_decoder(demux_ctx->streams[in_video_index]->codecpar->codec_id);
    if (!decodec) {
        std::cout << "avcodec_find_decoder failed!" << std::endl;
        goto Failed;
    }

    // 5.创建解码器上下文
    decodec_ctx = avcodec_alloc_context3(decodec);
    if (!decodec_ctx) {
        std::cout << "avcodec_alloc_context3 failed!" << std::endl;
        goto Failed;
    }

    // 6.将流参数复制到解码器上下文
    re = avcodec_parameters_to_context(decodec_ctx, demux_ctx->streams[in_video_index]->codecpar);
    if (0 > re) {
        std::cout << "avcodec_parameters_to_contextv failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    // 7.打开解码器
    re = avcodec_open2(decodec_ctx, decodec, NULL);
    if (0 != re) {
        std::cout << "avcodec_open2 failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 编码

    // 8.查找视频编码器
    encodec = avcodec_find_encoder(demux_ctx->streams[in_video_index]->codecpar->codec_id);
    if (!encodec) {
        std::cout << "avcodec_find_encoder failed!" << std::endl;
        goto Failed;
    }

    // 9.创建编码器上下文
    encodec_ctx = avcodec_alloc_context3(encodec);
    if (!encodec_ctx) {
        std::cout << "avcodec_alloc_context3 failed!" << std::endl;
        goto Failed;
    }

    // 10.设置编码器基础参数
    {
        encodec_ctx->width = decodec_ctx->width;
        encodec_ctx->height = decodec_ctx->height;
        encodec_ctx->pix_fmt = decodec_ctx->pix_fmt;

        // time_base
        AVRational framerate = demux_ctx->streams[in_video_index]->avg_frame_rate;
        if (0 >= framerate.den || 0 >= framerate.num) {
            // 根据参数‘猜测’结果
            framerate = av_guess_frame_rate(demux_ctx, demux_ctx->streams[in_video_index], NULL);
        }
        if (0 >= framerate.den || 0 >= framerate.num) {
            framerate = (AVRational){ 25, 1 };
        }
        encodec_ctx->framerate = framerate;
        encodec_ctx->time_base = av_inv_q(framerate);

        // 像素宽高比
        encodec_ctx->sample_aspect_ratio = decodec_ctx->sample_aspect_ratio;
    }



    // 11.打开编码器
    re = avcodec_open2(encodec_ctx, encodec, NULL);
    if (0 != re) {
        std::cout << "avcodec_open2 failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 复用

    // 12.创建复用上下文
    re = avformat_alloc_output_context2(&mux_ctx, NULL, NULL, out_file_path.c_str());
    if (0 > re) {
        std::cout << "avformat_alloc_output_context2 failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    // 13.创建音视频流
    // 创建【视频流】
    if (0 <= in_video_index) {
        AVStream *stream = avformat_new_stream(mux_ctx, NULL);      // 新建视频流
        stream->time_base = encodec_ctx->time_base;                 // 输出流 time_base 先与编码器保持一致
        out_video_index = stream->index;
        avcodec_parameters_from_context(stream->codecpar, encodec_ctx);     // 把编码器参数写进流
    }
    // 创建【音频流】:走“透传”——不解码不编码,直接复制参数、原样搬运压缩包
    if (0 <= in_audio_index) {
        AVStream *stream = avformat_new_stream(mux_ctx, NULL);
        avcodec_parameters_copy(stream->codecpar, demux_ctx->streams[in_audio_index]->codecpar);
        stream->time_base = demux_ctx->streams[in_audio_index]->time_base;
        out_audio_index = stream->index;
    }

    std::cout << "out video index: " << out_video_index << "\nout audio index: " << out_audio_index << std::endl;

    // 14.打开io
    re = avio_open2(&mux_ctx->pb, out_file_path.c_str(), AVIO_FLAG_WRITE, NULL, NULL);
    if (0 > re) {
        std::cout << "avio_open2 failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    av_dump_format(mux_ctx, out_video_index, out_file_path.c_str(), 1);

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


    // 15.写入文件头
    re = avformat_write_header(mux_ctx, NULL);
    if (0 > re) {
        std::cout << "avformat_write_header failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }




    while (1) {
        // 16.读取数据包
        re = av_read_frame(demux_ctx, pkt);
        // 已经读完
        if (AVERROR_EOF == re) {
            break;
        }
        if (0 > re) {
            std::cout << "av_read_frame failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }



        if (in_video_index == pkt->stream_index) {
            // 17.发送数据包到解码器
            re = avcodec_send_packet(decodec_ctx, pkt);
            av_packet_unref(pkt);
            if (0 > re) {
                std::cout << "avcodec_send_packet failed!" << std::endl;
                PrintErr(re);
                goto Failed;
            }

            while (1) {
                // 18.接收解码帧
                re = avcodec_receive_frame(decodec_ctx, frame);
                // 还未有完整的数据 | 已经读完
                if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
                    break;
                }

                if (0 > re) {
                    std::cout << "avcodec_receive_frame failed!" << std::endl;
                    PrintErr(re);
                    goto Failed;
                }


                // 19.把帧的时间单位“翻译”成编码器能看懂的单位
                // 将解码后视频帧的显示时间戳(PTS),从“输入流的时间基”换算成“编码器的时间基”
                frame->pts = av_rescale_q(frame->pts, demux_ctx->streams[in_video_index]->time_base, encodec_ctx->time_base);


                // 20.发送数据包到编码器
                re = avcodec_send_frame(encodec_ctx, frame);
                av_frame_unref(frame);
                if (0 > re) {
                    std::cout << "avcodec_send_frame failed!" << std::endl;
                    PrintErr(re);
                    goto Failed;
                }


                while (1) {
                    // 21.接收编码帧
                    re = avcodec_receive_packet(encodec_ctx, pkt);
                    // 还未有完整的数据 | 已经读完
                    if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
                        break;
                    }

                    if (0 > re) {
                        std::cout << "avcodec_receive_packet failed!" << std::endl;
                        PrintErr(re);
                        goto Failed;
                    }


                    // 22.重新计算pts、dts、duration
                    rescale_packet_ts(pkt, encodec_ctx->time_base, mux_ctx->streams[out_video_index]->time_base);

                    // 23.交错写入文件
                    re = av_interleaved_write_frame(mux_ctx, pkt);
                    av_packet_unref(pkt);
                    if (0 != re) {
                        std::cout << "av_interleaved_write_frame failed!" << std::endl;
                        PrintErr(re);
                        goto Failed;
                    }

                    std::cout << ". ";
                }
            }

        } else if (in_audio_index == pkt->stream_index) {
            // 重新计算pts、dts、duration
            // 音频是"透传"——包是从解复用器直接搬过来的,它的 pts/dts/duration 单位是输入音频流的 time_base(1/48000),从头到尾没碰过编码器
            rescale_packet_ts(pkt, demux_ctx->streams[in_audio_index]->time_base, mux_ctx->streams[out_audio_index]->time_base);


            // 交错写入文件
            re = av_interleaved_write_frame(mux_ctx, pkt);
            av_packet_unref(pkt);
            if (0 != re) {
                std::cout << "av_interleaved_write_frame failed!" << std::endl;
                PrintErr(re);
                goto Failed;
            }

        } else {
            std::cout << "other index: " << pkt->stream_index << std::endl;
            av_packet_unref(pkt);
        }
    }


    // 24.冲刷解码器
    re = avcodec_send_packet(decodec_ctx, NULL);
    if (0 > re) {
        std::cout << "avcodec_send_packet failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }
    while (1) {
        // 接收解码
        re = avcodec_receive_frame(decodec_ctx, frame);
        // 还未有完整的数据 | 已经读完
        if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
            break;
        }

        if (0 > re) {
            std::cout << "avcodec_receive_frame failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }


        frame->pts = av_rescale_q(frame->pts, demux_ctx->streams[in_video_index]->time_base, encodec_ctx->time_base);


        // 发送编码
        re = avcodec_send_frame(encodec_ctx, frame);
        av_frame_unref(frame);
        if (0 > re) {
            std::cout << "avcodec_send_frame failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }


        while (1) {
            // 接收编码
            re = avcodec_receive_packet(encodec_ctx, pkt);
            // 还未有完整的数据 | 已经读完
            if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
                break;
            }

            if (0 > re) {
                std::cout << "avcodec_receive_packet failed!" << std::endl;
                PrintErr(re);
                goto Failed;
            }


            // 重新计算pts、dts、duration
            rescale_packet_ts(pkt, encodec_ctx->time_base, mux_ctx->streams[out_video_index]->time_base);

            // 写入文件
            re = av_interleaved_write_frame(mux_ctx, pkt);
            av_packet_unref(pkt);
            if (0 != re) {
                std::cout << "av_interleaved_write_frame failed!" << std::endl;
                PrintErr(re);
                goto Failed;
            }

            std::cout << "- ";
        }
    }



    // 25.冲刷编码器
    re = avcodec_send_frame(encodec_ctx, NULL);
    if (0 > re) {
        std::cout << "avcodec_send_frame failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }
    while (1) {
        // 接收编码
        re = avcodec_receive_packet(encodec_ctx, pkt);
        // 还未有完整的数据 | 已经读完
        if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
            break;
        }

        if (0 > re) {
            std::cout << "avcodec_receive_packet failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }


        // 重新计算pts、dts、duration
        rescale_packet_ts(pkt, encodec_ctx->time_base, mux_ctx->streams[out_video_index]->time_base);

        // 写入文件
        re = av_interleaved_write_frame(mux_ctx, pkt);
        av_packet_unref(pkt);
        if (0 != re) {
            std::cout << "av_interleaved_write_frame failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }

        std::cout << "= ";
    }


    // 26.写入文件尾部
    re = av_write_trailer(mux_ctx);
    if (0 != re) {
        std::cout << "av_write_trailer failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }


    std::cout << "\nend!" << std::endl;


Failed:
    // 27.释放所有资源
    avformat_close_input(&demux_ctx);
    avcodec_free_context(&decodec_ctx);
    avcodec_free_context(&encodec_ctx);
    if (mux_ctx) {
        if (mux_ctx->pb) {
            avio_closep(&mux_ctx->pb);
        }
        avformat_free_context(mux_ctx);
    }

    av_packet_free(&pkt);
    av_frame_free(&frame);

    return 0;
}

4. 视频效果截图

在这里插入图片描述

需要注意的是,当前编码器使用默认参数(低码率),输出文件可能不够清晰。

可以在打开编码器之前添加(第 11 步之前):

av_opt_set(encodec_ctx->priv_data, "crf", "18", 0);        // 高质量(数值越小越清晰)
av_opt_set(encodec_ctx->priv_data, "preset", "medium", 0); // 平衡速度与压缩率
encodec_ctx->bit_rate = 2000000;                           // 2 Mbps		根据实际情况设置

5. 进阶

上方的代码在特殊点上会出问题的,在设置编码参数时,像素格式直接从解码器中复制;

encodec_ctx->pix_fmt = decodec_ctx->pix_fmt;

因为最终使用libx264编码,需要的格式是yuv420p的格式,如果解码出来的格式不是yuv420p,例如rgb24等,将其赋值给编码器,最终会在avcodec_open2时报错;

所以,为了解决这个问题,需要sws_scale像素格式转换,将其它格式转换成yuv420p即可解决!

如下给出实现代码,从上述代码中修改得来,其中画面大小会缩小一半:

// 大小为原来的一半
encodec_ctx->width = decodec_ctx->width / 2;
encodec_ctx->height = decodec_ctx->height / 2;
// 指定格式为yuv420p
encodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;

实现代码:

解复用 → 解码 → 像素转换 → 编码 → 复用

#include <iostream>
#include <string>


extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}


// 打印错误信息
void PrintErr(int err) {
    char buf[1024] = { 0 };
    av_strerror(err, buf, sizeof(buf));
    std::cout << buf << std::endl;
}


// 重新计算pts、dts、duration
void rescale_packet_ts(AVPacket *&pkt, AVRational cur_time_base, AVRational target_time_base) {
    pkt->pts = av_rescale_q_rnd(pkt->pts, cur_time_base,target_time_base,
                                (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
    pkt->dts = av_rescale_q_rnd(pkt->dts, cur_time_base,target_time_base,
                                (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
    pkt->duration = av_rescale_q(pkt->duration, cur_time_base, target_time_base);
    pkt->pos = -1;
}

int main(void) {

    // 解复用
    std::string in_file_path = "video1.mp4";
    AVFormatContext *demux_ctx = NULL;
    int in_video_index = -1;
    int in_audio_index = -1;


    // 解码
    const AVCodec *decodec = NULL;
    AVCodecContext *decodec_ctx = NULL;


    // 编码
    const AVCodec *encodec = NULL;
    AVCodecContext *encodec_ctx = NULL;


    // 复用
    std::string out_file_path = "out_file.mp4";
    AVFormatContext *mux_ctx = nullptr;
    int out_video_index = -1;
    int out_audio_index = -1;


    // 像素格式转换
    SwsContext *sws_ctx = nullptr;
    AVFrame *sws_frame = av_frame_alloc();


    // 其它
    AVPacket *pkt = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();
    int re = -1;



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 解复用

    // 1.打开视频文件
    re = avformat_open_input(&demux_ctx, in_file_path.c_str(), NULL, NULL);
    if (0 != re) {
        std::cout << "avformat_open_input failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }


    // 2.查找流信息
    re = avformat_find_stream_info(demux_ctx, NULL);
    if (0 > re) {
        std::cout << "avformat_find_stream_info failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }


    // 3.获得流索引
    for (int i = 0; i < demux_ctx->nb_streams; ++i) {
        if (AVMEDIA_TYPE_VIDEO == demux_ctx->streams[i]->codecpar->codec_type) {
            in_video_index = i;
        } else if (AVMEDIA_TYPE_AUDIO == demux_ctx->streams[i]->codecpar->codec_type) {
            in_audio_index = i;
        } else {
            std::cout << "other index: " << i;
        }
    }

    std::cout << "in video index: " << in_video_index << "\nin audio index: " << in_audio_index << std::endl;

    av_dump_format(demux_ctx, in_video_index, in_file_path.c_str(), 0);


    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 解码

    // 4.查找视频解码器
    decodec = avcodec_find_decoder(demux_ctx->streams[in_video_index]->codecpar->codec_id);
    if (!decodec) {
        std::cout << "avcodec_find_decoder failed!" << std::endl;
        goto Failed;
    }

    // 5.创建解码器上下文
    decodec_ctx = avcodec_alloc_context3(decodec);
    if (!decodec_ctx) {
        std::cout << "avcodec_alloc_context3 failed!" << std::endl;
        goto Failed;
    }

    // 6.将流参数复制到解码器上下文
    re = avcodec_parameters_to_context(decodec_ctx, demux_ctx->streams[in_video_index]->codecpar);
    if (0 > re) {
        std::cout << "avcodec_parameters_to_contextv failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    // 7.打开解码器
    re = avcodec_open2(decodec_ctx, decodec, NULL);
    if (0 != re) {
        std::cout << "avcodec_open2 failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 编码

    // 8.查找视频编码器
    encodec = avcodec_find_encoder(demux_ctx->streams[in_video_index]->codecpar->codec_id);
    if (!encodec) {
        std::cout << "avcodec_find_encoder failed!" << std::endl;
        goto Failed;
    }

    // 9.创建编码器上下文
    encodec_ctx = avcodec_alloc_context3(encodec);
    if (!encodec_ctx) {
        std::cout << "avcodec_alloc_context3 failed!" << std::endl;
        goto Failed;
    }

    // 10.设置编码器基础参数
    {
        // 大小为原来的一半
        encodec_ctx->width = decodec_ctx->width / 2;
        encodec_ctx->height = decodec_ctx->height / 2;
        // 指定格式为yuv420p
        encodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;

        // time_base
        AVRational framerate = demux_ctx->streams[in_video_index]->avg_frame_rate;
        if (0 >= framerate.den || 0 >= framerate.num) {
            // 根据参数‘猜测’结果
            framerate = av_guess_frame_rate(demux_ctx, demux_ctx->streams[in_video_index], NULL);
        }
        if (0 >= framerate.den || 0 >= framerate.num) {
            framerate = (AVRational){ 25, 1 };
        }
        encodec_ctx->framerate = framerate;
        encodec_ctx->time_base = av_inv_q(framerate);

        // 像素宽高比
        encodec_ctx->sample_aspect_ratio = decodec_ctx->sample_aspect_ratio;
    }



    // 11.打开编码器
    re = avcodec_open2(encodec_ctx, encodec, NULL);
    if (0 != re) {
        std::cout << "avcodec_open2 failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 复用

    // 12.创建复用上下文
    re = avformat_alloc_output_context2(&mux_ctx, NULL, NULL, out_file_path.c_str());
    if (0 > re) {
        std::cout << "avformat_alloc_output_context2 failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    // 13.创建音视频流
    // 创建【视频流】
    if (0 <= in_video_index) {
        AVStream *stream = avformat_new_stream(mux_ctx, NULL);      // 新建视频流
        stream->time_base = encodec_ctx->time_base;                 // 输出流 time_base 先与编码器保持一致
        out_video_index = stream->index;
        avcodec_parameters_from_context(stream->codecpar, encodec_ctx);     // 把编码器参数写进流
    }
    // 创建【音频流】:走“透传”——不解码不编码,直接复制参数、原样搬运压缩包
    if (0 <= in_audio_index) {
        AVStream *stream = avformat_new_stream(mux_ctx, NULL);
        avcodec_parameters_copy(stream->codecpar, demux_ctx->streams[in_audio_index]->codecpar);
        stream->time_base = demux_ctx->streams[in_audio_index]->time_base;
        out_audio_index = stream->index;
    }

    std::cout << "out video index: " << out_video_index << "\nout audio index: " << out_audio_index << std::endl;

    // 14.打开io
    re = avio_open2(&mux_ctx->pb, out_file_path.c_str(), AVIO_FLAG_WRITE, NULL, NULL);
    if (0 > re) {
        std::cout << "avio_open2 failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }

    av_dump_format(mux_ctx, out_video_index, out_file_path.c_str(), 1);

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////




    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /// 像素格式转换

    sws_ctx = sws_getContext(decodec_ctx->width, decodec_ctx->height, (AVPixelFormat)decodec_ctx->pix_fmt,
                             encodec_ctx->width, encodec_ctx->height, (AVPixelFormat)encodec_ctx->pix_fmt,
                             SWS_BILINEAR, NULL, NULL, NULL);
    if (!sws_ctx) {
        std::cout << "sws_getContext failed! " << std::endl;
        return -1;
    }

    // 分配内存,用于接收转换后的一帧图像
    sws_frame->width = encodec_ctx->width;
    sws_frame->height = encodec_ctx->height;
    sws_frame->format = encodec_ctx->pix_fmt;
    re = av_frame_get_buffer(sws_frame, 32);
    if (0 > re) {
        std::cout << "av_frame_get_buffer failed! " << std::endl;
        PrintErr(re);
        goto Failed;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////





    // 15.写入文件头
    re = avformat_write_header(mux_ctx, NULL);
    if (0 > re) {
        std::cout << "avformat_write_header failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }




    while (1) {
        // 16.读取数据包
        re = av_read_frame(demux_ctx, pkt);
        // 已经读完
        if (AVERROR_EOF == re) {
            break;
        }
        if (0 > re) {
            std::cout << "av_read_frame failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }



        if (in_video_index == pkt->stream_index) {
            // 17.发送数据包到解码器
            re = avcodec_send_packet(decodec_ctx, pkt);
            av_packet_unref(pkt);
            if (0 > re) {
                std::cout << "avcodec_send_packet failed!" << std::endl;
                PrintErr(re);
                goto Failed;
            }

            while (1) {
                // 18.接收解码帧
                re = avcodec_receive_frame(decodec_ctx, frame);
                // 还未有完整的数据 | 已经读完
                if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
                    break;
                }

                if (0 > re) {
                    std::cout << "avcodec_receive_frame failed!" << std::endl;
                    PrintErr(re);
                    goto Failed;
                }


                // 像素格式转换
                re = sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, sws_frame->data, sws_frame->linesize);
                if (0 > re) {
                    std::cout << "sws_scale failed! " << std::endl;
                    PrintErr(re);
                    goto Failed;
                }

                // 19.把帧的时间单位“翻译”成编码器能看懂的单位
                // 将解码后视频帧的显示时间戳(PTS),从“输入流的时间基”换算成“编码器的时间基”
                sws_frame->pts = av_rescale_q(frame->pts, demux_ctx->streams[in_video_index]->time_base, encodec_ctx->time_base);
                av_frame_unref(frame);

                // 20.发送数据包到编码器
                re = avcodec_send_frame(encodec_ctx, sws_frame);
                if (0 > re) {
                    std::cout << "avcodec_send_frame failed!" << std::endl;
                    PrintErr(re);
                    goto Failed;
                }


                while (1) {
                    // 21.接收编码帧
                    re = avcodec_receive_packet(encodec_ctx, pkt);
                    // 还未有完整的数据 | 已经读完
                    if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
                        break;
                    }

                    if (0 > re) {
                        std::cout << "avcodec_receive_packet failed!" << std::endl;
                        PrintErr(re);
                        goto Failed;
                    }


                    // 22.重新计算pts、dts、duration
                    rescale_packet_ts(pkt, encodec_ctx->time_base, mux_ctx->streams[out_video_index]->time_base);

                    // 23.写入文件
                    re = av_interleaved_write_frame(mux_ctx, pkt);
                    av_packet_unref(pkt);
                    if (0 != re) {
                        std::cout << "av_interleaved_write_frame failed!" << std::endl;
                        PrintErr(re);
                        goto Failed;
                    }

                    std::cout << ". ";
                }
            }

        } else if (in_audio_index == pkt->stream_index) {
            // 重新计算pts、dts、duration
            // 音频是"透传"——包是从解复用器直接搬过来的,它的 pts/dts/duration 单位是输入音频流的 time_base(1/48000),从头到尾没碰过编码器
            rescale_packet_ts(pkt, demux_ctx->streams[in_audio_index]->time_base, mux_ctx->streams[out_audio_index]->time_base);


            // 写入文件
            re = av_interleaved_write_frame(mux_ctx, pkt);
            av_packet_unref(pkt);
            if (0 != re) {
                std::cout << "av_interleaved_write_frame failed!" << std::endl;
                PrintErr(re);
                goto Failed;
            }

        } else {
            std::cout << "other index: " << pkt->stream_index << std::endl;
            av_packet_unref(pkt);
        }
    }


    // 24.冲刷解码器
    re = avcodec_send_packet(decodec_ctx, NULL);
    if (0 > re) {
        std::cout << "avcodec_send_packet failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }
    while (1) {
        // 接收解码
        re = avcodec_receive_frame(decodec_ctx, frame);
        // 还未有完整的数据 | 已经读完
        if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
            break;
        }

        if (0 > re) {
            std::cout << "avcodec_receive_frame failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }


        // 像素格式转换
        re = sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, sws_frame->data, sws_frame->linesize);
        if (0 > re) {
            std::cout << "sws_scale failed! " << std::endl;
            PrintErr(re);
            goto Failed;
        }
        sws_frame->pts = av_rescale_q(frame->pts, demux_ctx->streams[in_video_index]->time_base, encodec_ctx->time_base);
        av_frame_unref(frame);


        // 发送编码
        re = avcodec_send_frame(encodec_ctx, sws_frame);
        if (0 > re) {
            std::cout << "avcodec_send_frame failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }


        while (1) {
            // 接收编码
            re = avcodec_receive_packet(encodec_ctx, pkt);
            // 还未有完整的数据 | 已经读完
            if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
                break;
            }

            if (0 > re) {
                std::cout << "avcodec_receive_packet failed!" << std::endl;
                PrintErr(re);
                goto Failed;
            }


            // 重新计算pts、dts、duration
            rescale_packet_ts(pkt, encodec_ctx->time_base, mux_ctx->streams[out_video_index]->time_base);

            // 写入文件
            re = av_interleaved_write_frame(mux_ctx, pkt);
            av_packet_unref(pkt);
            if (0 != re) {
                std::cout << "av_interleaved_write_frame failed!" << std::endl;
                PrintErr(re);
                goto Failed;
            }

            std::cout << "- ";
        }
    }



    // 25.冲刷编码器
    re = avcodec_send_frame(encodec_ctx, NULL);
    if (0 > re) {
        std::cout << "avcodec_send_frame failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }
    while (1) {
        // 接收编码
        re = avcodec_receive_packet(encodec_ctx, pkt);
        // 还未有完整的数据 | 已经读完
        if (AVERROR(EAGAIN) == re || AVERROR_EOF == re) {
            break;
        }

        if (0 > re) {
            std::cout << "avcodec_receive_packet failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }


        // 重新计算pts、dts、duration
        rescale_packet_ts(pkt, encodec_ctx->time_base, mux_ctx->streams[out_video_index]->time_base);

        // 写入文件
        re = av_interleaved_write_frame(mux_ctx, pkt);
        av_packet_unref(pkt);
        if (0 != re) {
            std::cout << "av_interleaved_write_frame failed!" << std::endl;
            PrintErr(re);
            goto Failed;
        }

        std::cout << "= ";
    }


    // 写入文件尾部
    re = av_write_trailer(mux_ctx);
    if (0 != re) {
        std::cout << "av_write_trailer failed!" << std::endl;
        PrintErr(re);
        goto Failed;
    }


    std::cout << "\nend!" << std::endl;


Failed:
    // 26.释放所有资源
    avformat_close_input(&demux_ctx);
    avcodec_free_context(&decodec_ctx);
    avcodec_free_context(&encodec_ctx);
    if (mux_ctx) {
        if (mux_ctx->pb) {
            avio_closep(&mux_ctx->pb);
        }
        avformat_free_context(mux_ctx);
    }

    if (sws_ctx) sws_freeContext(sws_ctx);
    av_frame_free(&sws_frame);
    av_packet_free(&pkt);
    av_frame_free(&frame);


    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cpp_learners

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值