1_流程图
2_封装
2.1_结构体
- 封装上下文
AVFormatContext
- 视频流,音频流等
AVStream
- 编码后的单元数据
AVPacket
2.2_流程
- 解封装
- 首先使用
avformat_open_input
函数打开输入文件并生成对应的上下文AVFormatContext
- 通过
av_find_best_stream
函数寻找需要的流的索引,并通过AVFormatContext->streams[index]
获得需要的流AVStream
,也可以通过循环遍历访问所有的流,流的数量可以用AVFormatContext->nb_streams
获取 - 通过
av_read_frame
函数可以得到编码后的单元数据AVPacket
,根据AVPacket.stream_index
可以判断该单元数据对应的流
- 首先使用
- 封装
- 使用
avformat_alloc_output_context2
函数创建输入文件对应的上下文AVFormatContext
- 使用
avformat_new_stream
函数创建绑定到输出上下文的流AVStream
,并且通过诸如avcodec_parameters_copy
的方法设置流的编码参数AVStream->codecpar
- 使用
avio_open
方法打开输出文件准备写入 avformat_write_header
写入文件头- 使用
av_rescale_q_rnd
等方法,根据不同流的时间基(timebase)调整AVPacket.pts
AVPacket.dts
AVPacket.duration
等 - 使用
av_interleaved_write_frame
将调整后的单元数据AVPacket
数据写入文件 av_write_trailer
写入文件尾
- 使用
3_编解码
3.1_结构体
- 编解码上下文
AVCodecContext
- 编解码器
AVCodec
- 编码后的单元数据
AVPacket
- 编码前的原始数据
AVFrame
3.2_流程
3.2_流程
- 解码
- 使用
avcodec_find_decoder
函数根据AVStream->codecpar->codecid
找到合适的解码器AVCodec
- 使用
avcodec_alloc_context3
函数创建解码上下文AVCodecContext
- 使用
avcodec_parameters_to_context
函数将 `AVStream->codecpar
中的参数拷贝到解码上下文 - 使用
avcodec_open2
函数打开解码器 - 使用
avcodec_decode_video2/avcodec_send_packet/avcodec_receive_frame
函数将AVPacket
解码成AVFrame
- 使用
- 编码
- 使用
avcodec_find_encoder
或者avcodec_find_encoder_by_name
创建合适的编码器AVCodec
- 使用
avcodec_alloc_context3
函数创建编码上下文AVCodecContext
- 手动设置上下文的参数,部分可以从解码上下文中拷贝
- 使用
avcodec_open2
函数打开编码器 - 使用
avcodec_encode_video2/avcodec_send_frame/avcodec_receive_packet
函数将AVFrame
编码成AVPacket
- 使用
4_修改原始数据
使用 avcodec_decode_video2
获得的原始数据 AVFrame
往往不能直接编辑。可以通过 av_frame_clone
等方法创建一个新的 AVFrame
,然后使用方法 av_frame_make_writable
让其变得可以修改,其中原始数据就存在 AVFrame->data
中,对于 YUV420P
类型的帧,data[i][j]
中的 i = 0,1,2
分别对应 YUV 三个分量,j
则代表具体第几个,可以用 j = y*new_frame->linesize[i] + x
来表示 (x,y)
坐标的点。
5_代码
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavutil/timestamp.h>
#define INPUT_NAME "../temp/nergigante.mp4"
#define OUTPUT_NAME "../temp/complete3.mkv"
#define ENCODER_NAME "h264_videotoolbox"
static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
tag,
av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
pkt->stream_index);
}
int main(int argc, char** argv) {
// ---------------------
// 0. 定义及初始化 ffmepg
char* input_file, *output_file;
AVFormatContext *ifmt_ctx, *ofmt_ctx;
int video_index;
AVCodec *encoder, *decoder;
AVCodecContext *dec_ctx, *enc_ctx;
AVPacket in_pkt, out_pkt;
AVFrame *ori_frame, *new_frame;
int ret;
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
av_log_set_level(AV_LOG_INFO);
// if (argc != 3) {
// av_log(NULL, AV_LOG_ERROR, "Usage: %s <input> <output>\n", argv[0]);
// return 1;
// }
// input_file = argv[1];
// output_file = argv[2];
input_file = INPUT_NAME;
output_file = OUTPUT_NAME;
// ================================================
// 1. 输入格式上下文
// ================================================
// 1.1 打开输入文件并初始化输入格式上下文
ifmt_ctx = NULL; // important
if ((ret = avformat_open_input(&ifmt_ctx, input_file, NULL, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open input file '%s'\n", input_file);
goto __END;
}
// 1.2 查看是否包含流信息
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
goto __END;
}
// 1.3 打印输入流信息
av_dump_format(ifmt_ctx, 0, input_file, 0);
// ================================================
// 2. 输出格式上下文
// ================================================
// 2.1 初始化输出格式上下文
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, output_file);
if (!ofmt_ctx) {
av_log(NULL, AV_LOG_ERROR, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto __END;
}
// 2.2 针对输入流逐一创建输出流并绑定到ofmt_ctx
for (unsigned int i = 0; i < ifmt_ctx->nb_streams; ++i) {
AVStream *out_stream;
AVStream *in_stream = ifmt_ctx->streams[i];
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto __END;
}
if ((ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to copy codec parameters\n");
goto __END;
}
out_stream->time_base = in_stream->time_base;
out_stream->codecpar->codec_tag = 0;
}
// 2.3 打开输出文件
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, output_file, AVIO_FLAG_WRITE);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", output_file);
goto __END;
}
}
// ================================================
// 3. 解码器
// ================================================
// 3.1 初始化解码器
video_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, -1);
decoder = avcodec_find_decoder(ifmt_ctx->streams[video_index]->codecpar->codec_id);
if (!decoder) {
av_log(NULL, AV_LOG_ERROR, "Failed to find %s codec\n", av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
ret = AVERROR_UNKNOWN;
goto __END;
}
// 3.2 初始化解码上下文
dec_ctx = avcodec_alloc_context3(decoder);
if (!dec_ctx) {
av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context\n");
ret = AVERROR_UNKNOWN;
goto __END;
}
if ((ret = avcodec_parameters_to_context(dec_ctx, ifmt_ctx->streams[video_index]->codecpar)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to copy %s codec parameters to decoder context\n",
av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
goto __END;
}
dec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, ifmt_ctx->streams[video_index], NULL);
// 3.3 打开解码器
if ((ret = avcodec_open2(dec_ctx, decoder, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to open decoder\n");
goto __END;
}
// ================================================
// 4. 编码器
// ================================================
// 4.1 初始化编码器
encoder = avcodec_find_encoder_by_name(ENCODER_NAME);
if (!encoder) {
av_log(NULL, AV_LOG_ERROR, "Codec %s not found\n", ENCODER_NAME);
ret = AVERROR_UNKNOWN;
goto __END;
}
// 4.2 初始化编码上下文
enc_ctx = avcodec_alloc_context3(encoder);
if (!enc_ctx) {
av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context\n");
ret = AVERROR_UNKNOWN;
goto __END;
}
// 4.3 设置编码上下文参数
enc_ctx->bit_rate = dec_ctx->bit_rate;
enc_ctx->width = dec_ctx->width;
enc_ctx->height = dec_ctx->height;
enc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
enc_ctx->time_base = dec_ctx->time_base;
enc_ctx->framerate = dec_ctx->framerate;
// important
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
// 4.4 打开编码器
if ((ret = avcodec_open2(enc_ctx, encoder, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to open encode codec.");
goto __END;
}
// 4.5 将编码器参数拷贝到输出流
if ((ret = avcodec_parameters_from_context(ofmt_ctx->streams[video_index]->codecpar, enc_ctx)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream.\n");
return ret;
}
ofmt_ctx->streams[video_index]->time_base = enc_ctx->time_base;
// 4.6 打印输出流信息
av_dump_format(ofmt_ctx, 0, output_file, 1);
// ================================================
// 5. 正式开始编解码
// ================================================
// 5.1 写入文件头
if ((ret = avformat_write_header(ofmt_ctx, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n");
goto __END;
}
// 5.2 处理每个AVPacket
while (1) {
in_pkt.data = NULL;
in_pkt.size = 0;
av_init_packet(&in_pkt);
if (av_read_frame(ifmt_ctx, &in_pkt) < 0) {
break;
}
// 判断是否是需要处理的视频流
if (in_pkt.stream_index == video_index) {
// 开始解码
av_packet_rescale_ts(&in_pkt, ifmt_ctx->streams[video_index]->time_base, dec_ctx->time_base);
if ((ret = avcodec_send_packet(dec_ctx, &in_pkt)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to decode packet\n");
goto __END;
}
while (1) {
ori_frame = av_frame_alloc();
if (!ori_frame) {
ret = AVERROR(ENOMEM);
goto __END;
}
ret = avcodec_receive_frame(dec_ctx, ori_frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
av_frame_free(&ori_frame);
av_log(NULL, AV_LOG_ERROR, "Failed to receive frame\n");
goto __END;
}
ori_frame->pts = ori_frame->best_effort_timestamp; // important
// TODO: 编辑 frame 内容
new_frame = av_frame_clone(ori_frame);
av_frame_free(&ori_frame);
// 编码frame
if ((ret = avcodec_send_frame(enc_ctx, new_frame)) < 0) {
av_frame_free(&new_frame);
av_log(NULL, AV_LOG_ERROR, "Failed to send frame\n");
return ret;
}
// 获取packet,并写入文件
while (1) {
out_pkt.data = NULL;
out_pkt.size = 0;
av_init_packet(&out_pkt);
ret = avcodec_receive_packet(enc_ctx, &out_pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
av_frame_free(&new_frame);
av_log(NULL, AV_LOG_ERROR, "Failed to receive packet\n");
return ret;
}
av_packet_rescale_ts(&out_pkt, enc_ctx->time_base, ofmt_ctx->streams[out_pkt.stream_index]->time_base);
log_packet(ofmt_ctx, &out_pkt, "encode");
if ((ret = av_interleaved_write_frame(ofmt_ctx, &out_pkt)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to write packet\n");
return ret;
}
}
av_frame_free(&new_frame);
}
} else {
av_packet_rescale_ts(&in_pkt, ifmt_ctx->streams[in_pkt.stream_index]->time_base,
ofmt_ctx->streams[in_pkt.stream_index]->time_base);
if ((ret = av_interleaved_write_frame(ofmt_ctx, &in_pkt)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to write in_pkt\n");
goto __END;
}
}
av_packet_unref(&in_pkt);
}
// 5.3 排空编码器缓存
if ((ret = avcodec_send_frame(enc_ctx, NULL)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to clear encoder cache.\n");
return ret;
}
while (1) {
out_pkt.data = NULL;
out_pkt.size = 0;
av_init_packet(&out_pkt);
ret = avcodec_receive_packet(enc_ctx, &out_pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to receive packet\n");
return ret;
}
av_packet_rescale_ts(&out_pkt, enc_ctx->time_base, ofmt_ctx->streams[out_pkt.stream_index]->time_base);
log_packet(ofmt_ctx, &out_pkt, "encode");
if ((ret = av_interleaved_write_frame(ofmt_ctx, &out_pkt)) < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to write packet\n");
return ret;
}
}
// 5.4 写入文件尾
av_write_trailer(ofmt_ctx);
ret = 0;
__END:
// 其他结构体
av_packet_unref(&in_pkt);
av_packet_unref(&out_pkt);
if (ori_frame) {
av_frame_free(&ori_frame);
}
if (new_frame) {
av_frame_free(&new_frame);
}
// 编解码上下文
if (dec_ctx) {
avcodec_free_context(&dec_ctx);
}
if (enc_ctx) {
avcodec_free_context(&enc_ctx);
}
// 输入输出格式上下文
if (ofmt_ctx) {
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&ofmt_ctx->pb);
}
avformat_free_context(ofmt_ctx);
}
if (ifmt_ctx) {
avformat_close_input(&ifmt_ctx);
}
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error occurred: %s\n", av_err2str(ret));
}
return ret;
}
全网就这一个能用的,其实震惊了。