bytedeco / javacv

Java interface to OpenCV, FFmpeg, and more
Other
7.41k stars 1.57k forks source link

FPS filters can cause some problems #2121

Closed git-newer closed 8 months ago

git-newer commented 8 months ago

I want to use the fps filter in FFmpegFrameFilter to convert a 25fps video to a 30fps video, like this:

int[] size = {360, 640};
int frameRate = 30;
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(new File(output), size[0], size[1]);
recorder.setFormat("mp4");
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(new File(input));
FFmpegFrameFilter filter = new FFmpegFrameFilter("fps=" + frameRate, size[0], size[1]);
filter.start();
recorder.start();
grabber.start();
System.out.println("original frameRate:" + grabber.getFrameRate());
Frame frame;
int pushI = 0;
int recordI = 0;
while ((frame = grabber.grabImage()) != null) {
    filter.push(frame);
    pushI++;
    frame = filter.pullImage();
    if (null != frame) {
        recorder.record(frame);
        recordI++;
    }
}
System.out.println("recordI:" + recordI);
while (null != (frame = filter.pullImage())) {
    recorder.record(frame);
    recordI++;
}
System.out.println("pushI:" + pushI);
System.out.println("recordI:" + recordI);
recorder.close();
grabber.close();

Even though I ended up with a 30fps video, it was a few seconds shorter. I printed the number of pushes and the number of records, and they turned out to be like this when 25fps to 30fps

original frameRate:25.0
recordI:916
pushI:917
recordI:916

It looks like the filter isn't generating extra frames to fill the video. When I use this code to convert the same video from 25 fps to 60 fps, the result of the program running becomes something like this:

original frameRate:25.0
recordI:916
pushI:917
recordI:1832

This time the filter successfully filled in the extra frames, but not enough frames. My video is about 36 seconds, the original video has 916 frames, but the recordI is only 1832 frames. Theoretically there should be about 36*60=2160 frames, which is about 360 frames less, resulting in my final recorded video being about 6 seconds less than the original video. In addition, I tried it again using the ffmpeg command ffmpeg -i input.mp4 -filter "fps=60" -y 60fps.mp4, and the result was correct, the frame rate was 60fps and the video duration was the same. So I'm wondering, is there some problem with the implementation of fps filters in JavaCV? Or maybe there's something wrong with my code. Thank you for reading and look forward to your reply

saudet commented 8 months ago

Please try again with JavaCV 1.5.9.

saudet commented 8 months ago

Also we may also need to call push(null) to flush the filters, so please make sure you call push(null) somewhere.

saudet commented 8 months ago

Duplicate of #1315

saudet commented 8 months ago

Also duplicate of #287 and #1727

git-newer commented 8 months ago

Thank you for your reply I tried with JavaCV 1.5.9, but it executes with the same result. I call filter.push(null) before pull like this:

while ((frame = grabber.grabImage()) != null) {
    filter.push(frame);
    pushI++;
    filter.push(null);
    frame = filter.pullImage();
    if (null != frame) {
        recorder.record(frame);
        recordI++;
    }
}

But i got an error: "av_buffersrc_add_frame_flags() error -22: Error while feeding the filtergraph. (For more details, make sure FFmpegLogCallback.set() has been called.)" Then i tried changing filter.push(null) to here:

System.out.println("recordI:" + recordI);
filter.push(null);
while (null != (frame = filter.pullImage())) {
    recorder.record(frame);
    recordI++;
    filter.push(null);
}

The program result is:

original frameRate:25.0
recordI:916
pushI:917
recordI:917

Compared with before, the last frame pushed into the filter is indeed pulled out, but the result of this filter is still inconsistent with the result of the ffmpeg command execution, and the video duration is still shorter than the original video.