(b) 接着打开媒体流文件并读取基本信息
AVFormatContext *formatCtx = avformat_alloc_context(); AVCodec *pCodec = NULL; AVCodecParameters *pCodecParam = NULL; int video_stream_index = -1; if (avformat_open_input(&formatCtx, argv[1], NULL, NULL) < 0) { printf("Error: cannot open the input file!\n"); exit(1); } if (avformat_find_stream_info(formatCtx, NULL) < 0) { printf("Error: fail to find stream info!\n"); exit(1); }函数 avformat_open_input 会根据提供的文件路径判断文件格式,然后决定使用什么样的解封装器。
而 avformat_find_stream_info 方法的作用就是把所有 Stream 流的 MetaData 信息填充好。方法内部会先查找对应的解码器,打开解码器,利用 Demuxer 中的 read_packet 函数读取一段数据进行解码,解码的信息越多分析出的流信息就越准确。
这一段代码之后的 for 循环主要是将分析的结果输出到屏幕。
(c) 解析帧数据信息
AVCodecContext *pCodecContext = avcodec_alloc_context3(pCodec); if(avcodec_parameters_to_context(pCodecContext, pCodecParam) < 0) { printf("Failed to fill the codec context!\n"); exit(1); } if(avcodec_open2(pCodecContext, pCodec, NULL) < 0) { printf("Failed to initialize the avcodec context!\n"); exit(1); } AVFrame *pFrame = av_frame_alloc(); AVPacket *pPacket = av_packet_alloc(); int packet_cnt = 10; while(av_read_frame(formatCtx, pPacket)>=0) { if (pPacket->stream_index == video_stream_index) { printf("AVPacket pts: %" PRId64 "\n", pPacket->pts); if (decode_packet(pPacket, pCodecContext, pFrame)<0) break; if (--packet_cnt<=0) break; } av_packet_unref(pPacket); }函数 avcodec_open2 用来打开编解码器,无论是编码过程还是解码过程都会用到。输入参数有三个,第一个是 AVCodecContext,解码过程由 FFmpeg 引擎填充。第二个参数是解码器,第三个参数一般会传递 NULL。
使用 av_read_frame 读取出来的数据是 AVPacket,在早期版本中开放给开发者的是 av_read_packet 函数,但需要开发者自己来处理 AVPacket 中的数据不能被解码器完全处理完的情况,即需要把未处理完的压缩数据缓存起来的问题,所以现在提供了该函数。对于视频流,一个AVPacket只包含一个AVFrame,最终将得到一个 AVPacket 的结构体。
main.cc 全部代码如下
#include <cstdio> // fopen, fclose, fwrite #include <inttypes.h> #ifdef __APPLE__ extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" #include "libavutil/pixdesc.h" } #endif static int decode_packet(AVPacket *pPacket, AVCodecContext *pCodecContext, AVFrame *pFrame); void write_to_pgm(const char* file_path, AVFrame* decoded_frame); int main(const int argc, char* argv[]) { avformat_network_init(); av_register_all(); if (argc < 2) { printf("Please set the file path to open...\n"); exit(1); } printf("Open file %s...\n", argv[1]); AVFormatContext *formatCtx = avformat_alloc_context(); AVCodec *pCodec = NULL; AVCodecParameters *pCodecParam = NULL; int video_stream_index = -1; if (avformat_open_input(&formatCtx, argv[1], NULL, NULL) < 0) { printf("Error: cannot open the input file!\n"); exit(1); } if (avformat_find_stream_info(formatCtx, NULL) < 0) { printf("Error: fail to find stream info!\n"); exit(1); } printf("There are %d streams in the given file\n", formatCtx->nb_streams); for (int i = 0; i < formatCtx->nb_streams; ++i) { AVStream *stream = formatCtx->streams[i]; AVCodecParameters *pLocalCodecParam = stream->codecpar; AVCodec *pLocalCodec = avcodec_find_decoder(pLocalCodecParam->codec_id); if (NULL==pLocalCodec) { printf("Error: unsupported codec found!\n"); continue; } if (pLocalCodecParam->codec_type == AVMEDIA_TYPE_VIDEO) { if (-1==video_stream_index) { video_stream_index = i; pCodec = pLocalCodec; pCodecParam = pLocalCodecParam; } printf("Info of video stream:\n"); printf("\tCodec %s ID %d bit_rate %lld kb/s\n", pLocalCodec->name, pLocalCodec->id, pLocalCodecParam->bit_rate/1024); printf("\tAVStream->r_frame_rate: %d/%d\n", stream->r_frame_rate.num, stream->r_frame_rate.den); printf("\tResolution=%dx%d\n", pLocalCodecParam->width, pLocalCodecParam->height); } else if (pLocalCodecParam->codec_type == AVMEDIA_TYPE_AUDIO) { printf("Info of audio stream:\n"); printf("\tCodec %s ID %d bit_rate %lld kb/s\n", pLocalCodec->name, pLocalCodec->id, pLocalCodecParam->bit_rate/1024); printf("\tchannels=%d\n\tsample_rate=%d\n", pLocalCodecParam->channels, pLocalCodecParam->sample_rate); } printf("\tAVStream->time_base: %d/%d\n", stream->time_base.num, stream->time_base.den); printf("\tAVStream->start_time: %" PRId64 "\n", stream->start_time); printf("\tAVStream->duration: %" PRId64 "\n", stream->duration); } AVCodecContext *pCodecContext = avcodec_alloc_context3(pCodec); if(avcodec_parameters_to_context(pCodecContext, pCodecParam) < 0) { printf("Failed to fill the codec context!\n"); exit(1); } if(avcodec_open2(pCodecContext, pCodec, NULL) < 0) { printf("Failed to initialize the avcodec context!\n"); exit(1); } AVFrame *pFrame = av_frame_alloc(); AVPacket *pPacket = av_packet_alloc(); int packet_cnt = 10; while(av_read_frame(formatCtx, pPacket)>=0) { if (pPacket->stream_index == video_stream_index) { printf("AVPacket pts: %" PRId64 "\n", pPacket->pts); if (decode_packet(pPacket, pCodecContext, pFrame)<0) break; if (--packet_cnt<=0) break; } av_packet_unref(pPacket); } avformat_close_input(&formatCtx); av_packet_free(&pPacket); av_frame_free(&pFrame); avcodec_free_context(&pCodecContext); return 0; } static int decode_packet(AVPacket *pPacket, AVCodecContext *pCodecContext, AVFrame *pFrame) { int ret = avcodec_send_packet(pCodecContext, pPacket); if (ret < 0) { printf("Error while sending a packet to the decoder: %s", av_err2str(ret)); return ret; } while (ret >= 0) { ret = avcodec_receive_frame(pCodecContext, pFrame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { printf("Error while receiving a frame from the decoder: %s", av_err2str(ret)); return ret; } else if (ret >= 0) { printf("Frame %d: type=%c, size=%d bytes, format=%d, pts=%d key_frame %d [DTS %d]\n", pCodecContext->frame_number, av_get_picture_type_char(pFrame->pict_type), pFrame->pkt_size, pFrame->format, pFrame->pts, pFrame->key_frame, pFrame->coded_picture_number); char file_to_write[100]; sprintf(file_to_write, "/Users/gcxyb/phillee/misc_codes/ffmpeg_based_test/build/frame-%02d.pgm", pCodecContext->frame_number); if (pFrame->format==AV_PIX_FMT_YUV420P) { write_to_pgm(file_to_write, pFrame) } } } } void write_to_pgm(const char* file_path, AVFrame* decoded_frame) { FILE *fout = fopen(file_path, "w"); fprintf(fout, "P5\n%d %d\n%d\n", decoded_frame->width, decoded_frame->height, 255); for (int line_id=0; line_id<decoded_frame->height; ++line_id) { int ret = fwrite(decoded_frame->data[0]+line_id*decoded_frame->linesize[0], 1, decoded_frame->width, fout); if (ret < 0) exit(1); } fclose(fout); }文件夹结构
. ├── CMakeLists.txt └── main.cc编译测试
$ mkdir build $ cd build $ cmake .. $ make $ ./myplayer_test /path/to/the/media/file(全文完)
参考资料[1] FFMPEG编译问题记录 https://www.cnblogs.com/phillee/p/13813156.html
[2] FFmpeg Compilation Guide https://trac.ffmpeg.org/wiki/CompilationGuide
[3] pgm
[4]