本章节讲述多媒体格式流程的操作步骤:
多媒体文件转封装格式流程1, 创建输入和输出文件的上下文
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
// 通过 avformat_open_input() 打开/创建输入文件的上下文,in_filename 多媒体文件路径
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file '%s'", in_filename);
goto end;
}
// 判断是否有视频流信息
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
// 输出多媒体文件信息
av_dump_format(ifmt_ctx, 0, in_filename, 0);
// 创建输出封装格式上下文
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
2, 创建输出流信息,并且将原文件音视频流信息的参数复制到输出音视频流中
多媒体格式转换其实只是改变视频的封装格式(即只是改变视频的壳,里面的音视频及字幕流信息是没有改变的,所以我们需要保留原有多媒体文件的参数)
如下是创建流信息及参数复制的关键代码:
// 从输入 ifmt_ctx 中遍历流信息
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *out_stream;
AVStream *in_stream = ifmt_ctx->streams[i];
// 原多媒体中的流参数
AVCodecParameters *in_codecpar = in_stream->codecpar;
// 判断是否是音频流、视频流、字幕流
if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
stream_mapping[i] = -1;
continue;
}
stream_mapping[i] = stream_index++;
// 从输出上下文中创建新的stream,并且返回该stream
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
// 拷贝原视频中的参数到输出流信息中
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
out_stream->codecpar->codec_tag = 0;
}
3, 循环读取每一帧的AVPacket
信息,并且将音频、视频、字幕流信息写入输出文件中
以下是写入输出文件的核心代码:
/*
需要写入视频的头信息,avformat_write_header 是ffmpeg一个通用的API,
它内部会根据输出多媒体的格式(mp4、mov等)来写入相应的多媒体头信息
*/
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}
while (1) {
AVStream *in_stream, *out_stream;
// 读取每一帧的 AVPacket *pkt 数据
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
in_stream = ifmt_ctx->streams[pkt.stream_index];
// 通过stream_index 判断是否是我们第二步中查找的流
if (pkt.stream_index >= stream_mapping_size ||
stream_mapping[pkt.stream_index] < 0) {
av_packet_unref(&pkt);
continue;
}
out_stream = ofmt_ctx->streams[pkt.stream_index];
// AVPacket pts、dts、duration转换
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
//packet 在流信息中的位置,封装格式改变之后pos也不一样,设置-1,表示需要重算
pkt.pos = -1;
// 写入packet 信息,av_interleaved_write_frame 与 av_write_frame 两个函数都可以写入
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
// 释放AVPacket
av_packet_unref(&pkt);
}
// 写入视频尾部信息,与 avformat_write_header 相对应
av_write_trailer(ofmt_ctx);
从上一篇文章我们了解到,AVPacket
中包含pts(显示时间戳)
和dts(解码时间戳)
,这些参数是AVPacket
显示及渲染出来的关键参数,而这些参数根据不同的封装格式得出值也不一样的,所以需要通过算法转换将原来封装格式的值转换到新的封装格式想要的值,而ffmpeg提供了av_rescale_q_rnd()
和av_rescale_q()
函数来转换。
-
av_rescale_q()
是采用默认的方式转换 -
av_rescale_q_rnd()
多一个AVRounding
参数,设置算法中数值的保留值方式
AVRounding提供以下5种方式
AV_ROUND_ZERO = 0,
// Round toward zero. 趋近于0
AV_ROUND_INF = 1,
// Round away from zero. 趋远于0
AV_ROUND_DOWN = 2,
// Round toward -infinity. 趋于更小的整数
AV_ROUND_UP = 3,
// Round toward +infinity. 趋于更大的整数
AV_ROUND_NEAR_INF = 5,
// Round to nearest and halfway cases away from zero.
// 四舍五入,小于0.5取值趋向0,大于0.5取值趋远于0
至此FFmpeg格式转换的流程就完成了,总结之前感觉FFmpeg好多API,脑子里面也是混乱的,经过这两篇文章的梳理,现在感觉对这块流程思路清晰了很多,并且发现也没什么难的。
完整代码稍后再补充。。。
网友评论