Skip to content

Commit

Permalink
* Revert default behavior of FFmpegFrameGrabber.setTimestamp() to …
Browse files Browse the repository at this point in the history
…previous version (pull #949)
  • Loading branch information
anotherche authored and saudet committed Apr 22, 2018
1 parent 8833f48 commit 8f72f99
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 59 deletions.
14 changes: 10 additions & 4 deletions platform/src/test/java/org/bytedeco/javacv/FrameGrabberTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -337,26 +337,32 @@ 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);
break;
case 2:
grabber.setAudioTimestamp(timestamp);
break;
case 3:
grabber.setTimestamp(timestamp);
break;
}

Frame frame = grabber.grab();
Expand Down
136 changes: 81 additions & 55 deletions src/main/java/org/bytedeco/javacv/FFmpegFrameGrabber.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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));
}
Expand Down Expand Up @@ -563,9 +571,9 @@ private void setTimestamp(long timestamp, EnumSet<Frame.Type> 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
Expand All @@ -576,56 +584,74 @@ private void setTimestamp(long timestamp, EnumSet<Frame.Type> 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<Frame.Type> 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<Frame.Type> 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<Frame.Type> 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<Frame.Type> 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;
}
}
Expand All @@ -646,7 +672,7 @@ public int getLengthInVideoFrames() {
// best guess...
return (int) Math.round(getLengthInTime() * getFrameRate() / 1000000L);
}

public int getLengthInAudioFrames() {
// best guess...
double afr = getAudioFrameRate();
Expand Down

0 comments on commit 8f72f99

Please sign in to comment.