所以一段时间以来,我一直在制作类似视频播放器的应用程序。视频播放器接收视频流,对其进行解码并显示其内容。我是使用ffmpeg包装器用java编写的(这应该没有任何实际意义,我只是提到这一点,因为我提供的代码将用java编写,但我使用的函数只是调用ffmpeg库的相关c/c++函数)。问题是,当我调用avcodec_send_packet时,它会使我获得非常高的内存使用率,这也会随着视频的分辨率而变化。最近,我尝试解码一个4k视频流,每次调用avcodec_send_packet都会为我节省大约40 mb的内存。这在第一帧之后稳定下来。换言之,当我接收到帧时,对于随后的帧,内存使用停止攀升。对于特定的4k视频流,它攀升到了大约800mb,对于随后的avcodec_send_packet和avcodec_receive_frame调用,内存使用率没有显著攀升。如果分辨率是1080p,它会为我节省大约300兆字节的内存。考虑到一个解码的4k YUV420帧大约是12兆字节,这是一个相当高的内存使用率
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时,帧会被破坏。我将附上两张图片,比较相框的外观和实际外观。
What its supposed to look like
What it looks like when given to the decoder
如果有人问这个问题,这并不是因为我是如何显示框架的。我可以100%肯定地说,因为我已经跨越了3种不同的视频帧渲染方式。第一个是我用于ffmpeg的包装库附带的CanvasFrame,然后我切换到javafx ImageView,现在我使用的是vulkan。所有三种方法的结果都完全相同。解码器几乎在所有情况下都是vp9,并且帧是在yuv420p中。
我试着浏览了stackoverflow和其他一些地方,看到了一个有点相似的帖子,但不幸的是,我在那里找不到答案。
更新:我决定在这个问题持续存在的时候在我的程序中实现硬件加速,看看它是否会有所帮助。随着硬件加速,内存使用率显著降低,但这是意料之中的事。我遇到的更奇怪的事情是:当分辨率超过1080p时,帧仍然会损坏,除非我特别说
avcodec_find_decoder_by_name("vp9_cuvid");
。如果我使用此:
av_find_best_stream(oc, AVMEDIA_TYPE_VIDEO, -1, -1, vidCodec, 0);
这也分配了视频编解码器,它使用vp9,但帧被破坏。不知道发生了什么。我也试着做了
avcodec_find_decoder_by_name("vp9");
但这也给我留下了损坏的帧。(毕竟应该是相同的编解码器)。