音视频学习:FFMpeg基础--1
1. 解封装流程文件/网络流↓avformat_open_input(AVFormatContext **test_context, ...)打开文件和识别封装格式↓avformat_find_stream_info()获取音视频详细信息↓av_read_frame()得到AVPacket结构体供解码器解码↓avformat_close_input()最后关闭解复用器avformat_open_input()的作用是什么avformat_open_input()是 FFmpeg 解封装流程的入口函数主要负责打开输入媒体源文件或网络流创建并初始化AVFormatContext建立底层AVIOContext自动探测封装格式如 MP4、FLV、MKV、RTSP 等调用对应 Demuxer 的read_header()解析媒体头部并创建初步的AVStream信息。它不会深入分析媒体数据或解码音视频帧流的详细参数如帧率、部分编码参数等通常需要后续调用avformat_find_stream_info()进一步分析获得例如FLV格式。avformat_open_input()和avformat_find_stream_info()的区别avformat_open_input()打开媒体源识别容器建立解封装环境。avformat_find_stream_info()分析媒体流完善AVStream和AVCodecParameters信息为后续解码做准备。1.1 AVFormatContext相关AVFormatContext 用来描述一个输入或输出媒体容器Container可以理解为整个媒体文件或媒体流的管理者管理多个 AVStream而每个 AVStream 对应一路音频或视频流。示例代码更容易理解#include stdio.h #include libavformat/avformat.h int main(int argc, char **argv) { //打开网络流。这里如果只需要读取本地媒体文件不需要用到网络功能可以不用加上这一句 // avformat_network_init(); const char *default_filename believe.mp4; //AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体 AVFormatContext *ifmt_ctx NULL; // 输入文件的demux int videoindex -1; // 视频索引 int audioindex -1; // 音频索引 // 打开文件主要是探测协议类型如果是网络文件则创建网络链接 int ret avformat_open_input(ifmt_ctx, in_filename, NULL, NULL); if (ret 0) //如果打开媒体文件失败打印失败原因 { char buf[1024] { 0 }; av_strerror(ret, buf, sizeof(buf) - 1); printf(open %s failed:%s\n, in_filename, buf); goto failed; }接下来对avformat_open_input()和avformat_find_stream_info()前后的AVFormatContext结构体进行打印比较printf_s(\n avformat_open_input后的上下文打印:%s \n, in_filename); // !如果是FLV则AVFormatContext中填充了很少的信息在下面avformat_find_stream_info后会填充详细信息; // !但对于MP4则会填充很多信息,avformat_find_stream_info()前后AVFormatContext结构体中的内容是一样的 av_dump_format(ifmt_ctx, 0, in_filename, 0); printf_s(\n av_dump_format finish \n\n); ret avformat_find_stream_info(ifmt_ctx, NULL); if (ret 0) //如果打开媒体文件失败打印失败原因 { char buf[1024] { 0 }; av_strerror(ret, buf, sizeof(buf) - 1); printf(avformat_find_stream_info %s failed:%s\n, in_filename, buf); goto failed; } //打开媒体文件成功 printf_s(\n av_dump_format in_filename:%s \n, in_filename); av_dump_format(ifmt_ctx, 0, in_filename, 0); printf_s(\n av_dump_format finish \n\n);/*首先是mp4文件的前后对比几乎一致*/ avformat_open_input后的上下文打印:believe.mp4 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from believe.mp4: Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 encoder : Lavf56.38.102 comment : www.ieway.cn Duration: 00:03:42.53, bitrate: N/A Stream #0:0(und): Video: h264 (avc1 / 0x31637661), none, 1920x1080, 150 kb/s, 14.46 fps, 15 tbr, 15360 tbn (default) Metadata: handler_name : VideoHandler Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 48000 Hz, 2 channels, 128 kb/s (default) Metadata: handler_name : SoundHandler av_dump_format finish av_dump_format in_filename:believe.mp4 Input #0, mov,mp4,m4a,3gp,3g2,mj2, from believe.mp4: Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 encoder : Lavf56.38.102 comment : www.ieway.cn Duration: 00:03:42.53, start: 0.000000, bitrate: 281 kb/s Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 1920x1080, 150 kb/s, 14.46 fps, 15 tbr, 15360 tbn, 30 tbc (default) Metadata: handler_name : VideoHandler Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default) Metadata: handler_name : SoundHandler av_dump_format finish // 然后是FLV格式的前后对比avformat_open_input()后信息很少avformat_find_stream_info()后信息详细 avformat_open_input后的上下文打印:believe.flv Input #0, flv, from believe.flv: Duration: N/A, start: 0.000000, bitrate: N/A av_dump_format finish av_dump_format in_filename:believe.flv Input #0, flv, from believe.flv: Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 comment : www.ieway.cn encoder : Lavf58.29.100 Duration: 00:03:42.53, start: 0.000000, bitrate: 286 kb/s Stream #0:0: Video: h264 (Constrained Baseline), yuv420p(progressive), 1920x1080, 150 kb/s, 14.46 fps, 15 tbr, 1k tbn, 30 tbc Stream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp, 128 kb/s av_dump_format finish 打印一些成员变量媒体文件详细信息// url: 调用avformat_open_input读取到的媒体文件的路径/名字 printf(media name:%s\n, ifmt_ctx-url); // nb_streams: nb_streams媒体流数量 printf(stream number:%d\n, ifmt_ctx-nb_streams); // bit_rate: 媒体文件的码率,单位为bps printf(media average ratio:%lldkbps\n,(int64_t)(ifmt_ctx-bit_rate/1000)); // 时间 int total_seconds, hour, minute, second; // duration: 媒体文件时长单位微妙 total_seconds (ifmt_ctx-duration) / AV_TIME_BASE; // 1000us 1ms, 1000ms 1秒 hour total_seconds / 3600; minute (total_seconds % 3600) / 60; second (total_seconds % 60); //通过上述运算可以得到媒体文件的总时长 printf(total duration: %02d:%02d:%02d\n, hour, minute, second); printf(\n);获取各媒体流AVStream信息/* * 老版本通过遍历的方式读取媒体文件视频和音频的信息 * 新版本的FFmpeg新增加了函数av_find_best_stream也可以取得同样的效果 */ for (uint32_t i 0; i ifmt_ctx-nb_streams; i) { AVStream *in_stream ifmt_ctx-streams[i];// 音频流、视频流、字幕流 //如果是音频流则打印音频的信息 if (AVMEDIA_TYPE_AUDIO in_stream-codecpar-codec_type) { printf(----- Audio info:\n); // index: 每个流成分在ffmpeg解复用分析后都有唯一的index作为标识 printf(index:%d\n, in_stream-index); // sample_rate: 音频编解码器的采样率单位为Hz printf(samplerate:%dHz\n, in_stream-codecpar-sample_rate); // codecpar-format: 音频采样格式 if (AV_SAMPLE_FMT_FLTP in_stream-codecpar-format) { printf(sampleformat:AV_SAMPLE_FMT_FLTP\n); } else if (AV_SAMPLE_FMT_S16P in_stream-codecpar-format) { printf(sampleformat:AV_SAMPLE_FMT_S16P\n); } // channels: 音频信道数目 printf(channel number:%d\n, in_stream-codecpar-channels); // codec_id: 音频压缩编码格式 if (AV_CODEC_ID_AAC in_stream-codecpar-codec_id) { printf(audio codec:AAC\n); } else if (AV_CODEC_ID_MP3 in_stream-codecpar-codec_id) { printf(audio codec:MP3\n); } else { printf(audio codec_id:%d\n, in_stream-codecpar-codec_id); } // 音频总时长单位为秒。注意如果把单位放大为毫秒或者微妙音频总时长跟视频总时长不一定相等的 // !值等于 AV_NOPTS_VALUE 代表容器未提供时长直播流、部分 FLV、裸流经常无效 if(in_stream-duration ! AV_NOPTS_VALUE) { // duration表示单条媒体流以当前流自己的 AVStream-time_base 为刻度单位的总播放时长不是文件总时长AVFormatContext::duration // time_base是个结构体里面有分子和分母(不存储浮点数以这种形式存储分子分母) // av_q2d()的结果就是单位时间(单位时间/采样频率) // 因此换算成真实秒数公式如下 int duration_audio (in_stream-duration) * av_q2d(in_stream-time_base); //将音频总时长转换为时分秒的格式打印到控制台上 printf(audio duration: %02d:%02d:%02d\n, duration_audio / 3600, (duration_audio % 3600) / 60, (duration_audio % 60)); } else { // 等于AV_NOPTS_VALUE意为无效的值 printf(audio duration unknown); } printf(\n); audioindex i; // 获取音频的索引 } else if (AVMEDIA_TYPE_VIDEO in_stream-codecpar-codec_type) //如果是视频流则打印视频的信息 { printf(----- Video info:\n); printf(index:%d\n, in_stream-index); // avg_frame_rate: 视频帧率,单位为fps表示每秒出现多少帧 printf(fps:%lffps\n, av_q2d(in_stream-avg_frame_rate)); if (AV_CODEC_ID_MPEG4 in_stream-codecpar-codec_id) //视频压缩编码格式 { printf(video codec:MPEG4\n); } else if (AV_CODEC_ID_H264 in_stream-codecpar-codec_id) //视频压缩编码格式 { printf(video codec:H264\n); } else { printf(video codec_id:%d\n, in_stream-codecpar-codec_id); } // 视频帧宽度和帧高度 printf(width:%d height:%d\n, in_stream-codecpar-width, in_stream-codecpar-height); //视频总时长单位为秒。注意如果把单位放大为毫秒或者微妙音频总时长跟视频总时长不一定相等的 if(in_stream-duration ! AV_NOPTS_VALUE) { int duration_video (in_stream-duration) * av_q2d(in_stream-time_base); printf(video duration: %02d:%02d:%02d\n, duration_video / 3600, (duration_video % 3600) / 60, (duration_video % 60)); //将视频总时长转换为时分秒的格式打印到控制台上 } else { printf(video duration unknown); } printf(\n); videoindex i; } }获取前10个AVPacke并打印其信息其中pkt-duration * av_q2d(ifmt_ctx-streams[audioindex]-time_base))计算出来为此AVPacke的时间视频为一帧的时长音频则为一帧音频n个采样点一个编码单元的时长。AVPacket *pkt av_packet_alloc(); int pkt_count 0; int print_max_count 10; printf(\n-----av_read_frame start\n); while (1) { ret av_read_frame(ifmt_ctx, pkt); // 分离为音频packet和视频packet if (ret 0) { printf(av_read_frame end\n); break; } if(pkt_count print_max_count) { if (pkt-stream_index audioindex) { printf(audio pts: %lld\n, pkt-pts); printf(audio dts: %lld\n, pkt-dts); printf(audio size: %d\n, pkt-size); printf(audio pos: %lld\n, pkt-pos); // pos参数是这一包数据在整个码流中的位置需要转16进制 printf(audio duration: %lf\n\n, pkt-duration * av_q2d(ifmt_ctx-streams[audioindex]-time_base)); } else if (pkt-stream_index videoindex) { printf(video pts: %lld\n, pkt-pts); printf(video dts: %lld\n, pkt-dts); printf(video size: %d\n, pkt-size); printf(video pos: %lld\n, pkt-pos); printf(video duration: %lf\n\n, pkt-duration * av_q2d(ifmt_ctx-streams[videoindex]-time_base)); } else { printf(unknown stream_index:\n, pkt-stream_index); } } // 释放内部buf av_packet_unref(pkt); } if(pkt) av_packet_free(pkt); failed: if(ifmt_ctx) avformat_close_input(ifmt_ctx); getchar(); //加上这一句防止程序打印完信息马上退出 return 0; }学习资料0voice · GitHub