本文的代码下载地址:GitHub,编译环境是 Qt 5.15.2 跟 MSVC2019_64bit 。


跟 读取文件内容 相关的结构体如下:

1,AVPacket,数据包(已编码压缩),这里面的数据通常是一帧视频的数据,或者一帧音频的数据。

AVPacket 他本身是没有编码数据的,他只是管理编码数据。


跟 读取文件内容 相关的函数如下:

1,av_packet_alloc,初始化一个 AVPacket

2,av_read_frame,从 AVFormatContext 容器里面读取一个 AVPacket,需要注意,虽然函数名是 frame,但是读取的是 AVPacket

3,av_packet_unref,减少 AVPacket 对 编码数据的引用次数。减到 0 会释放 编码数据的内存。

3,av_packet_free,释放 AVPacket 自身的内存。里面会调 av_packet_unref


下面就结合本文代码来理解上面这些函数跟 AVPacket 的使用。请看下图:




Android FFmpeg 获取视频第一帧图片 ffmpeg获取视频文件信息_数据


注意:av_read_frame 的返回值有很多种情况。

1,成功读取到一个 AVPacket 。

2,读取到文件末尾。

3,网络流有问题,或者本地硬盘坏了。导致 AVFormatContext 里面的 pb->error 有异常。

我上面的代码没有处理这些情况,我假设他会成功读取到 一个 AVPacket 。这些情况 在 ffplay.c 里面都被正确处理。请参考 ffplay.c 写出更健壮的代码。


上面的代码只读了一个 AVPacket

从上图代码可以看出,我用 av_packet_alloc 初始化了一个 AVPacket ,那是不是用 av_read_frame 读一次数据,就要调一次 av_packet_alloc 呢?

不是,因为 AVPacket 他本身是没有编码数据的,他只是管理编码数据,也就是说 av_packet_alloc 的时候,AVPacket 里面还没有编码数据的,是后面通过 av_read_frame把 AVPacket 里面的 buf 引用指向了编码数据。

如果你要调多次 av_read_frame,只需要先用 av_packet_unref 消除 AVPacket 里面对之前的编码数据的引用即可。只有最后用不到 AVPacket 的时候,才需要调 av_packet_free 来释放 AVPacket 的内存。


上面的代码运行情况如下:



Android FFmpeg 获取视频第一帧图片 ffmpeg获取视频文件信息_数据_02


AVPacket 里面有一个 stream_index 字段,代表这个 AVPacket 属于哪个流,那我们怎么知道这是一个音频包,还是视频包?在下面的字段里面:

fmt_ctx->streams[0]->codecpar->codec_type

我代码里面打印了这个值,可以看到是 0 跟 1,实际上,这是两个常量 AVMEDIA_TYPE_VIDEO ,AVMEDIA_TYPE_AUDIO 的值,如下:



Android FFmpeg 获取视频第一帧图片 ffmpeg获取视频文件信息_字段_03


因此,codec_type 等于 0 代表这是一个视频流,1 代表这是一个音频流。因此咱们打印的这个包是视频包


然后 AVPacket 里面还有一个 duration 字段,代表这帧数据要播放 多久。时间基是 11988 ,时间基就是 1秒钟分成11988份,然后这个视频包占了 500 份,也就是当前帧要播放 0.04 秒。因此这个mp4 文件大概猜测,就是1秒钟播放24个视频包。


然后 AVPacket 里面还有一个 pos 字段,这个字段就代表,data 里面的数据是从 mp4 文件的哪个位置读取出来的,我上面打印了 7个字节的数据,0 0 2 37 6 5 ff

pos 等于 48,也就是 0x30,我们可以用 HxD 来查看一下 juren-30s.mp4 文件,如下:



Android FFmpeg 获取视频第一帧图片 ffmpeg获取视频文件信息_字段_04


数据是完全一致的,size 字段代表这个视频包有多大,本文是 10201 字节大小。


最后,本文给出了循环读多个 AVPacket 的示例,读者只需把 type 变量设置成 2 即可,如下:



Android FFmpeg 获取视频第一帧图片 ffmpeg获取视频文件信息_FFmpeg_05