本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
AVPacket
和 AVFrame
是 FFmpeg 编解码 api 中最常用的两个结构体,每一帧视频画面或者音频数据从视频文件中通过 av_read_frame
读取到结构体 AVPacket
中,再用 avcodec_send_packet
接口将 AVPacket
送入解码器,最后通过 avcodec_receive_frame
把解码后的数据读取到结构体 AVFrame
中,这就是一帧完整的解码过程。所以,对 AVPacket
和 AVFrame
的用法,尤其是有关内存申请释放相关的用法极为重要,否则虽然一次泄漏一点点儿,但一个视频文件下来成百上千帧之后,泄漏的可不是一点点儿了。
AVPacket pkt;
av_init_packet(&pkt);
while (av_read_frame(format_context_, &pkt) >= 0) {
if (pkt.stream_index == streamidx) {
avcodec_send_packet(codec_context_, &pkt);
while ((ret = avcodec_receive_frame(codec_context_, frame_)) != 0) {
if (ret == AVERROR_EOF) break;
avcodec_send_packet(codec_context_, nullptr);
}
av_packet_unref(&pkt);
break;
}
}
这么写有问题吗?
有!严重的内存泄漏。每次循环会有 6MB 内存没有被释放。av_read_frame
会产生 6MB 的堆内存。如果不进行 av_packet_unref
,则会导致内存泄漏。即使是同一个栈变量 pkt
,即使出了这个栈变量的作用范围、这个栈变量被系统收回,那些每次产生的 6MB 堆内存们,也不会被收回。
泄漏的原因在于,当 ·av_read_frame() < 0
时,它也产生了堆内存,也需要用 av_packet_unref
进行释放;当 pkt.stream_idx != streamidx
时,也需要用 av_packet_unref
进行释放。
所以,正确的写法如下:
AVPacket pkt;
av_init_packet(&pkt);
while (av_read_frame(format_context_, &pkt) >= 0) {
if (pkt.stream_index == streamidx) {
avcodec_send_packet(codec_context_, &pkt);
while ((ret = avcodec_receive_frame(codec_context_, frame_)) != 0) {
if (ret == AVERROR_EOF) break;
avcodec_send_packet(codec_context_, nullptr);
}
av_packet_unref(&pkt);
break;
} else {
av_packet_unref(&pkt);
}
}
av_packet_unref(&pkt);
然而这种做法到了 FFmpeg 5.0 版本以后也成了不推荐的写法。因为接口 av_init_packet
已经被弃用(deprecated),再这么写可能在严格的编译环境中是编译不过的,根据 FFmpeg 5.0 packet.h 中的写法,sizeof(AVPacket) 也将随着 av_init_packet
去除。正确的使用方法是直接使用 av_packet_alloc
申请即初始化。而且貌似是只能通过这种方法获取,写成栈上的局部变量这种方法也不合理,因为其 AVPacket
结构体中并未定义构造方法,而 av_packet_alloc
方法中,除了 av_malloc
一块内存给 AVPacket
之外,还通过 get_packet_default
给申请的 AVPacket
赋默认值。因此,最好的用法还是 AVPacket *pkt = av_packet_alloc()
。