本章節(jié)講述多媒體格式流程的操作步驟:
1, 創(chuàng)建輸入和輸出文件的上下文
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
// 通過 avformat_open_input() 打開/創(chuàng)建輸入文件的上下文吃环,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);
// 創(chuàng)建輸出封裝格式上下文
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
2, 創(chuàng)建輸出流信息郁轻,并且將原文件音視頻流信息的參數(shù)復(fù)制到輸出音視頻流中
多媒體格式轉(zhuǎn)換其實只是改變視頻的封裝格式(即只是改變視頻的殼,里面的音視頻及字幕流信息是沒有改變的好唯,所以我們需要保留原有多媒體文件的參數(shù))
如下是創(chuàng)建流信息及參數(shù)復(fù)制的關(guān)鍵代碼:
// 從輸入 ifmt_ctx 中遍歷流信息
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *out_stream;
AVStream *in_stream = ifmt_ctx->streams[i];
// 原多媒體中的流參數(shù)
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++;
// 從輸出上下文中創(chuàng)建新的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;
}
// 拷貝原視頻中的參數(shù)到輸出流信息中
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, 循環(huán)讀取每一幀的AVPacket
信息它浅,并且將音頻镣煮、視頻、字幕流信息寫入輸出文件中
以下是寫入輸出文件的核心代碼:
/*
需要寫入視頻的頭信息镊折,avformat_write_header 是ffmpeg一個通用的API介衔,
它內(nèi)部會根據(jù)輸出多媒體的格式(mp4炎咖、mov等)來寫入相應(yīng)的多媒體頭信息
*/
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 數(shù)據(jù)
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轉(zhuǎn)換
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也不一樣粹胯,設(shè)置-1,表示需要重算
pkt.pos = -1;
// 寫入packet 信息渊抽,av_interleaved_write_frame 與 av_write_frame 兩個函數(shù)都可以寫入
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 相對應(yīng)
av_write_trailer(ofmt_ctx);
從上一篇文章我們了解到栈幸,AVPacket
中包含pts(顯示時間戳)
和dts(解碼時間戳)
,這些參數(shù)是AVPacket
顯示及渲染出來的關(guān)鍵參數(shù)速址,而這些參數(shù)根據(jù)不同的封裝格式得出值也不一樣的芍锚,所以需要通過算法轉(zhuǎn)換將原來封裝格式的值轉(zhuǎn)換到新的封裝格式想要的值蔓榄,而ffmpeg提供了av_rescale_q_rnd()
和av_rescale_q()
函數(shù)來轉(zhuǎn)換甥郑。
-
av_rescale_q()
是采用默認的方式轉(zhuǎn)換 -
av_rescale_q_rnd()
多一個AVRounding
參數(shù)荤西,設(shè)置算法中數(shù)值的保留值方式
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. 趨于更小的整數(shù)
AV_ROUND_UP = 3,
// Round toward +infinity. 趨于更大的整數(shù)
AV_ROUND_NEAR_INF = 5,
// Round to nearest and halfway cases away from zero.
// 四舍五入,小于0.5取值趨向0,大于0.5取值趨遠于0
至此FFmpeg格式轉(zhuǎn)換的流程就完成了邪锌,總結(jié)之前感覺FFmpeg好多API觅丰,腦子里面也是混亂的,經(jīng)過這兩篇文章的梳理舶胀,現(xiàn)在感覺對這塊流程思路清晰了很多,并且發(fā)現(xiàn)也沒什么難的糖赔。
完整代碼稍后再補充放典。基茵。拱层。