有一段时间我一直在制作类似视频播放器应用程序的东西。视频播放器接收视频流,对其进行解码并显示其内容。我正在使用 ffmpeg 包装器在 java 中编写它(这应该没有任何实际意义,我只是提到,因为我提供的代码将在 java 中,但我使用的函数只是调用相关的 c/c++ 函数ffmpeg 库)。问题是,当我调用 avcodec_send_packet 时,它会导致内存使用量非常高,而且内存使用量也会随着视频的分辨率而变化。最近,我尝试解码 4k 视频流,每次调用 avcodec_send_packet 都会占用大约 40 MB 的内存。这在第一帧之后稳定下来。换句话说,当我收到该帧时,后续帧的内存使用量将停止攀升。对于特定的 4k 视频流,它攀升至约 800 mb,并且对于后续的 avcodec_send_packet 和 avcodec_receive_frame 调用,内存使用量并没有显着攀升。如果分辨率为 1080p,则需要大约 300 MB 的内存。考虑到一个解码的 4k YUV420 帧约为 12 MB,这是一个相当高的内存使用量
public Optional<Boolean> decodeVideoFrame(AVPacket pkt,boolean readPacket,boolean keyFrames,boolean doProcessing) throws Exception {
int ret;
// Decode video frame
if (readPacket) {
ret = avcodec_send_packet(video_c, pkt);
if (pkt.data() == null && pkt.size() == 0) {
pkt.stream_index(-1);
}
if (ret == AVERROR_EAGAIN() || ret == AVERROR_EOF()) {
// The video codec may have buffered some frames
} else if (ret < 0) {
// Ignore errors to emulate the behavior of the old API
// throw new Exception("avcodec_send_packet() error " + ret + ": Error sending a video packet for decoding.");
}
}
// Did we get a video frame?
while (true) {
ret = avcodec_receive_frame(video_c, picture);
if (ret == AVERROR_EAGAIN() || ret == AVERROR_EOF()) {
if (pkt.data() == null && pkt.size() == 0) {
return Optional.empty();
} else {
return Optional.of(true);
}
} else if (ret < 0) {
// Ignore errors to emulate the behavior of the old API
// throw new Exception("avcodec_receive_frame() error " + ret + ": Error during video decoding.");
return Optional.of(true);
}
这几乎是所有相关代码以及数据包分配:
AVPacket pkt = av_packet_alloc();
pkt.stream_index(-1);
int ret;
while(true){
if((ret = av_read_frame(grabber.getFormatContext(),pkt))<0){
if (ret == AVERROR_EAGAIN()) {
try {
Thread.sleep(10);
continue;
} catch (InterruptedException ex) {
// reset interrupt
Thread.interrupted();
}
}
av_packet_unref(pkt);
pkt.deallocate();
return null;
}
break;
}
return pkt;
(这是在一个单独的方法中)。由此产生的另一个奇怪的效果是,当视频分辨率高于 1080p 时,帧会损坏。我将附上两张图片来比较框架的预期外观和实际外观。 如果有人问这个问题,这并不是因为我显示框架的方式。我可以 100% 肯定地说,因为我已经尝试过 3 种不同的视频帧渲染方式。第一个是 CanvasFrame,它附带了我用于 ffmpeg 的包装库,然后我切换到 javafx ImageView,现在我使用 vulkan。所有三种方法的结果完全相同。解码器几乎在所有情况下都是 vp9 并且帧是 yuv420p。
我尝试浏览 stackoverflow 和其他一些地方,我看到了一篇有点类似的帖子,但不幸的是我在那里找不到答案。
使用完毕后,您应该确保使用
av_frame_free
正确取消引用视频帧。
每个解码器都需要一些开销来存储参考帧,这可能是您观察到的高内存使用率的根源之一。从消费者方面影响这一点几乎是不可能的。当您控制该视频的编码步骤时,您可以修改一些参数(例如关键帧距离)以降低解码器的内存开销。
我注意到的另一件事:当您从
AVERROR_EAGAIN
收到 avcodec_send_packet
时,您需要确保在使用 avcodec_receive_frame
收集帧后再次发送此数据包。否则,您将在后续帧中收到伪影(但是,这并不能解释您提到的损坏的帧)。
损坏的帧很可能是由于错误的 YUV -> RGB 转换造成的。也许某些数据丢失(U 和/或 V 平面)。然而,没有代码可以给出任何判断。