From 8f72f9966ae22d99887aa274aa6c42615a91e76f Mon Sep 17 00:00:00 2001 From: Stanislav Chizhik Date: Sun, 22 Apr 2018 10:07:38 +0700 Subject: [PATCH] * Revert default behavior of `FFmpegFrameGrabber.setTimestamp()` to previous version (pull #949) --- .../org/bytedeco/javacv/FrameGrabberTest.java | 14 +- .../bytedeco/javacv/FFmpegFrameGrabber.java | 136 +++++++++++------- 2 files changed, 91 insertions(+), 59 deletions(-) diff --git a/platform/src/test/java/org/bytedeco/javacv/FrameGrabberTest.java b/platform/src/test/java/org/bytedeco/javacv/FrameGrabberTest.java index ec35c883..ee0fa71f 100644 --- a/platform/src/test/java/org/bytedeco/javacv/FrameGrabberTest.java +++ b/platform/src/test/java/org/bytedeco/javacv/FrameGrabberTest.java @@ -337,19 +337,22 @@ public void testFFmpegFrameGrabberSeeking() throws IOException { System.out.println(); System.out.println("has video stream = "+(grabber.hasVideo()?"YES":"NO")+", has audio stream = "+(grabber.hasAudio()?"YES":"NO")); long tolerance = 1000000L + (grabber.getFrameRate() > 0.0? (long) (5000000/grabber.getFrameRate()):500000L); - Random random = new Random(); + Random random = new Random(29); - for (int frametypenum = 0; frametypenum < 3; frametypenum++) { + for (int frametypenum = 0; frametypenum < 4; frametypenum++) { long mindelta = Long.MAX_VALUE; long maxdelta = Long.MIN_VALUE; System.out.println(); - System.out.println("Seek by " + (frametypenum == 0 ? "any" : frametypenum == 1 ? "video" : "audio") + " frames"); + System.out.println("Seek by " + + (frametypenum == 0 ? "any" : frametypenum == 1 ? "video" : frametypenum == 2 ? "audio" : "old method") + + (frametypenum == 0 ? " frames" : "")); + System.out.println("--------------------"); for (int i = 0; i < 200; i++) { long timestamp = random.nextInt(length); switch (frametypenum) { case 0: - grabber.setTimestamp(timestamp); + grabber.setTimestamp(timestamp, true); break; case 1: grabber.setVideoTimestamp(timestamp); @@ -357,6 +360,9 @@ public void testFFmpegFrameGrabberSeeking() throws IOException { case 2: grabber.setAudioTimestamp(timestamp); break; + case 3: + grabber.setTimestamp(timestamp); + break; } Frame frame = grabber.grab(); diff --git a/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java b/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java index b40f3cf0..f3ab0f33 100644 --- a/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java +++ b/src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java @@ -488,9 +488,10 @@ public double getVideoFrameRate() { } /** default override of super.setFrameNumber implies setting - * of a video frame having that number */ + * of a frame close to a video frame having that number */ @Override public void setFrameNumber(int frameNumber) throws Exception { - setVideoFrameNumber(frameNumber); + if (hasVideo()) setTimestamp(Math.round(1000000L * frameNumber / getFrameRate())); + else super.frameNumber = frameNumber; } /** if there is video stream tries to seek to video frame with corresponding timestamp @@ -509,17 +510,24 @@ public void setAudioFrameNumber(int frameNumber) throws Exception { } - /** setTimestamp with disregard of the resulting frame type, video or audio */ + /** setTimestamp without checking frame content (using old code used in JavaCV versions prior to 1.4.1) */ @Override public void setTimestamp(long timestamp) throws Exception { - setTimestamp(timestamp, EnumSet.of(Frame.Type.VIDEO, Frame.Type.AUDIO)); + setTimestamp(timestamp, false); + } + + /** setTimestamp with possibility to select between old quick seek code or new code + * doing check of frame content. The frame check can be useful with corrupted files, when seeking may + * end up with an empty frame not containing video nor audio */ + public void setTimestamp(long timestamp, boolean checkFrame) throws Exception { + setTimestamp(timestamp, checkFrame ? EnumSet.of(Frame.Type.VIDEO, Frame.Type.AUDIO) : null); } - /** setTimestamp with resulting video frame type */ + /** setTimestamp with resulting video frame type if there is a video stream*/ public void setVideoTimestamp(long timestamp) throws Exception { setTimestamp(timestamp, EnumSet.of(Frame.Type.VIDEO)); } - /** setTimestamp with resulting audio frame type */ + /** setTimestamp with resulting audio frame type if there is an audio stream*/ public void setAudioTimestamp(long timestamp) throws Exception { setTimestamp(timestamp, EnumSet.of(Frame.Type.AUDIO)); } @@ -563,9 +571,9 @@ private void setTimestamp(long timestamp, EnumSet frameTypesToSeek) * by doubled estimation of frames between that "closest" position and the desired position. * * frameTypesToSeek parameter sets the preferred type of frames to seek. - * It can be chosen from three possible types: VIDEO, AUDIO or ANY. + * It can be chosen from three possible types: VIDEO, AUDIO or any of them. * The setting means only a preference in the type. That is, if VIDEO or AUDIO is - * specified but the file does not have video or audio stream - ANY type will be used instead. + * specified but the file does not have video or audio stream - any type will be used instead. * * * TODO @@ -576,56 +584,74 @@ private void setTimestamp(long timestamp, EnumSet frameTypesToSeek) * */ - boolean has_video = hasVideo(); - boolean has_audio = hasAudio(); - - if (has_video || has_audio) { - if ((frameTypesToSeek.contains(Frame.Type.VIDEO) && !has_video ) || - (frameTypesToSeek.contains(Frame.Type.AUDIO) && !has_audio )) - frameTypesToSeek = EnumSet.of(Frame.Type.VIDEO, Frame.Type.AUDIO); - - long initialSeekPosition = Long.MIN_VALUE; - long maxSeekSteps = 0; - long count = 0; - Frame seekFrame = null; - - while(count++ < 1000) { //seek to a first frame containing video or audio after avformat_seek_file(...) - seekFrame = grabFrame(true, true, false, false); - if (seekFrame == null) return; //is it better to throw NullPointerException? - EnumSet frameTypes = seekFrame.getTypes(); - frameTypes.retainAll(frameTypesToSeek); - if (!frameTypes.isEmpty()) { - initialSeekPosition = seekFrame.timestamp; - //the position closest to the requested timestamp from which it can be reached by sequential grabFrame calls - break; + if (frameTypesToSeek != null) { //new code providing check of frame content while seeking to the timestamp + boolean has_video = hasVideo(); + boolean has_audio = hasAudio(); + + if (has_video || has_audio) { + if ((frameTypesToSeek.contains(Frame.Type.VIDEO) && !has_video ) || + (frameTypesToSeek.contains(Frame.Type.AUDIO) && !has_audio )) + frameTypesToSeek = EnumSet.of(Frame.Type.VIDEO, Frame.Type.AUDIO); + + long initialSeekPosition = Long.MIN_VALUE; + long maxSeekSteps = 0; + long count = 0; + Frame seekFrame = null; + + while(count++ < 1000) { //seek to a first frame containing video or audio after avformat_seek_file(...) + seekFrame = grabFrame(true, true, false, false); + if (seekFrame == null) return; //is it better to throw NullPointerException? + EnumSet frameTypes = seekFrame.getTypes(); + frameTypes.retainAll(frameTypesToSeek); + if (!frameTypes.isEmpty()) { + initialSeekPosition = seekFrame.timestamp; + //the position closest to the requested timestamp from which it can be reached by sequential grabFrame calls + break; + } + } + if (has_video && this.getFrameRate() > 0) { + //estimation of video frame duration + double deltaTimeStamp = 1000000.0/this.getFrameRate(); + if (initialSeekPosition < timestamp - deltaTimeStamp/2) + maxSeekSteps = (long)(10*(timestamp - initialSeekPosition)/deltaTimeStamp); + } else if (has_audio && this.getAudioFrameRate() > 0) { + //estimation of audio frame duration + double deltaTimeStamp = 1000000.0/this.getAudioFrameRate(); + if (initialSeekPosition < timestamp - deltaTimeStamp/2) + maxSeekSteps = (long)(10*(timestamp - initialSeekPosition)/deltaTimeStamp); + } else + //zero frameRate + if (initialSeekPosition < timestamp - 1L) maxSeekSteps = 1000; + + count = 0; + while(count < maxSeekSteps) { + seekFrame = grabFrame(true, true, false, false); + if (seekFrame == null) return; //is it better to throw NullPointerException? + EnumSet frameTypes = seekFrame.getTypes(); + frameTypes.retainAll(frameTypesToSeek); + if (!frameTypes.isEmpty()) { + count++; + if (this.timestamp >= timestamp - 1) break; + } } - } - if (has_video && this.getFrameRate() > 0) { - //estimation of video frame duration - double deltaTimeStamp = 1000000.0/this.getFrameRate(); - if (initialSeekPosition < timestamp - deltaTimeStamp/2) - maxSeekSteps = (long)(10*(timestamp - initialSeekPosition)/deltaTimeStamp); - } else if (has_audio && this.getAudioFrameRate() > 0) { - //estimation of audio frame duration - double deltaTimeStamp = 1000000.0/this.getAudioFrameRate(); - if (initialSeekPosition < timestamp - deltaTimeStamp/2) - maxSeekSteps = (long)(10*(timestamp - initialSeekPosition)/deltaTimeStamp); - } else - //zero frameRate - if (initialSeekPosition < timestamp - 1L) maxSeekSteps = 1000; + frameGrabbed = true; + } + } else { //old quick seeking code used in JavaCV versions prior to 1.4.1 + /* comparing to timestamp +/- 1 avoids rouding issues for framerates + which are no proper divisors of 1000000, e.g. where + av_frame_get_best_effort_timestamp in grabFrame sets this.timestamp + to ...666 and the given timestamp has been rounded to ...667 + (or vice versa) + */ + int count = 0; // prevent infinite loops with corrupted files + while (this.timestamp > timestamp + 1 && grabFrame(true, true, false, false) != null && count++ < 1000) { + // flush frames if seeking backwards + } count = 0; - while(count < maxSeekSteps) { - seekFrame = grabFrame(true, true, false, false); - if (seekFrame == null) return; //is it better to throw NullPointerException? - EnumSet frameTypes = seekFrame.getTypes(); - frameTypes.retainAll(frameTypesToSeek); - if (!frameTypes.isEmpty()) { - count++; - if (this.timestamp >= timestamp - 1) break; - } + while (this.timestamp < timestamp - 1 && grabFrame(true, true, false, false) != null && count++ < 1000) { + // decode up to the desired frame } - frameGrabbed = true; } } @@ -646,7 +672,7 @@ public int getLengthInVideoFrames() { // best guess... return (int) Math.round(getLengthInTime() * getFrameRate() / 1000000L); } - + public int getLengthInAudioFrames() { // best guess... double afr = getAudioFrameRate();