----------------------------------------
authhor:hjjdebug
data: 2022-02-07
----------------------------------------
demuxing_decoding.c 解读
****************************************
特点: 能够同时操作音频和视频, 比较接近于实际的碼流. 是常用的操作流程.
程序分析:
1. avformat 登场.
if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0)
if (avformat_find_stream_info(fmt_ctx, NULL) < 0)
采用了avio 的方式来读取来打开和读取输入文件, 见avio.c分析
2. 利用fmt_ctx来打开video_dec_ctx 及 audio_dec_ctx, 不用再制定codec
if (open_codec_context(&video_stream_idx, &video_dec_ctx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0)
if (open_codec_context(&audio_stream_idx, &audio_dec_ctx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0)
从ctx中来获取参数:
video_stream = fmt_ctx->streams[video_stream_idx];
audio_stream = fmt_ctx->streams[audio_stream_idx];
width = video_dec_ctx->width;
height = video_dec_ctx->height;
pix_fmt = video_dec_ctx->pix_fmt;
3. 导出fmt_ctx 信息. 很好用的函数!
av_dump_format(fmt_ctx, 0, src_filename, 0);
4. 读取pkt, 不再用av_parser, 而是利用fmt_ctx, av_read_frame 函数进行.
然后video_dec_ctx,解包video包,audio_dec_ctx,解包audio包. context包含必要信息,可读可写.
while (av_read_frame(fmt_ctx, pkt) >= 0) {
if (pkt->stream_index == video_stream_idx)
ret = decode_packet(video_dec_ctx, pkt);
else if (pkt->stream_index == audio_stream_idx)
ret = decode_packet(audio_dec_ctx, pkt);
av_packet_unref(pkt);
}
5. 利用context, 解码包decode_packet(AVCodecContext *dec,const AVPacket *pkt)
流程还是send_parcket, receive_frame, 根据dec->codec->type 来分别保存音频frame或视频frame
------------------------------------------------------------
补充: 怎样从fmt_ctx来打开video 或audio context
------------------------------------------------------------
1. 首先根据类型type(AVMEDIA_TYPE_VIDEO, AVMEDIA_TYPE_AUDIO) 来找到流的索引号及流
ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
返回值为流索引,找到了索引即拿到了流
stream_index = ret;
st = fmt_ctx->streams[stream_index];
2. 根据流的codec_id 找到decoder
dec = avcodec_find_decoder(st->codecpar->codec_id);
3. 由decoder 分配codec_context
*dec_ctx = avcodec_alloc_context3(dec);
4. 初始化dec_ctx
ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar))
5. 打开decoder
ret = avcodec_open2(*dec_ctx, dec, &opts))
************************************************************************************
保存视频,可以用av_image_copy, 再写入到文件. 它是把 frame-data 三行数据YUV合并到一行
************************************************************************************
/* copy decoded frame to destination buffer:
* this is required since rawvideo expects non aligned data */
av_image_copy(video_dst_data, video_dst_linesize,
(const uint8_t **)(frame->data), frame->linesize,
pix_fmt, width, height);
/* write to rawvideo file */
fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
************************************************************************************
保存音频,把数据写到文件
************************************************************************************
size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample(frame->format);
fwrite(frame->extended_data[0], 1, unpadded_linesize, audio_dst_file);
------------------------------------------------------------
补充2: AVFormatContext 概念
------------------------------------------------------------
一个合格的音视频文件,能够被avformat_open_input打开,创建一个AVFormatContext 对象fmt_ctx
再通过find_stream_info 进一步填充信息。
fmt_ctx 有节目的概念 AVPogram, 一个fmt_ctx 可以包含多套节目(fmt_ctx->nb_programs,fmt_ctx->programs[0-nb_programs-1])数组。
每一个节目有节目id, program->id, 及若干个节目流构成(program->nb_stream_indexes, program->stream_index[0-nb_stream_indexes]),
例如节目中音频流对应一个音频stream_index, 视频流对应一个视频stream_index, 找到了流的index, 就可以找到流 fmp_ctx->stream[index]
找到了流,就可以找到流的一切属性。
当然,常见的,简单的fmt_ctx,只包含一套节目,而且一套节目只包含2个流,音频流,视频流。