Bug 52697 - Frame accurate seeking isn't always accurate
: Frame accurate seeking isn't always accurate
Status: RESOLVED FIXED
: WebKit
Media Elements
: 528+ (Nightly build)
: All Mac OS X 10.6
: P2 Normal
Assigned To:
: http://www.massive-interactive.nl/htm...
: HTML5
: 53824
:
  Show dependency treegraph
 
Reported: 2011-01-18 19:07 PST by
Modified: 2011-02-04 17:29 PST (History)


Attachments
screenshot of the bug (464.73 KB, image/png)
2011-01-19 06:06 PST, Rob Coenen
no flags Details
Patch for testcase (607 bytes, text/plain)
2011-01-19 14:27 PST, Jer Noble
no flags Details
Patch (1.55 KB, patch)
2011-01-19 15:08 PST, Jer Noble
no flags Review Patch | Details | Formatted Diff | Diff
Patch (496.49 KB, patch)
2011-02-02 16:03 PST, Jer Noble
no flags Review Patch | Details | Formatted Diff | Diff
Patch (499.59 KB, patch)
2011-02-02 22:56 PST, Jer Noble
no flags Review Patch | Details | Formatted Diff | Diff
Patch (499.59 KB, patch)
2011-02-03 10:27 PST, Jer Noble
no flags Review Patch | Details | Formatted Diff | Diff
Patch (498.92 KB, patch)
2011-02-03 15:39 PST, Jer Noble
no flags Review Patch | Details | Formatted Diff | Diff


Note

You need to log in before you can comment on or make changes to this bug.


Description From 2011-01-18 19:07:37 PST
Please see this testcase that shows Safari is not always seeking to the correct frame when attempting frame accurate seeking. 
The testcase is in the bug URL. 
Chrome had a similar bug and fixed it:
http://code.google.com/p/chromium/issues/detail?id=69499.  We should fix it too.

Using the single step "|> +1 frame" button, each click should advance by one frame, and the time in the top right of the video frame should match the computed time beside it in the surrounding content.
(Try frame stepping to 00:00:00:13, the calculated timecode is correctly 00:00:00:13 but the burned-in timecode shows that the frame is stuck at 00:00:00:12 2x then on the netx click bth burned in and calculated timecodes jump to 00:00:00:14. Frame 00:00:00:13 gets never shown)

The testcase uses MP4, WebM, and Ogg.  I can reproduce the bug with the H264 file.

When single stepping, we seem to be 1 or 2 frames off the expected time.  One frame of this is caused by bugs in the seeking logic.
------- Comment #1 From 2011-01-19 06:06:26 PST -------
Created an attachment (id=79412) [details]
screenshot of the bug
------- Comment #2 From 2011-01-19 06:07:06 PST -------
added a screenshot with the actual bug: video should be at frame 00:00:00:13 but gets stuck at frame 00:00:00:12
------- Comment #3 From 2011-01-19 13:37:13 PST -------
I believe the problem at 00:13 is due to a rounding error:

- The sample containing the "00:13" frame begins at 46800/90000 (where 90000 is the media time scale).
- This converts to 0.52000000000000002 in float math.  
- The test case attempts to set the current time to 0.52
- JavaScript converts that to float 0.5199999809265137.
- The media engine jumps precisely to 0.5199999809265137.
- This is before the beginning of the desired sample, and so sample isn't shown at that time.
------- Comment #4 From 2011-01-19 13:57:30 PST -------
Unfortunately, seeking directly to a frame boundary will always be problematic in floating-point math.  Chromium solved this problem by effectively shifting the start time of every frame earlier by 1/2 the previous frame's duration.  This means Chromium will always be off by 1/2 a frame.  You can verify this in the Chromium nightlies, and loading the testcase URL, and setting the video's time to 0.51.  It will seek to the frame containing 00:13, even though it's well within the 00:12 frame's duration.

I don't recommend we fix this bug in the same way as chromium.
------- Comment #5 From 2011-01-19 14:27:04 PST -------
Created an attachment (id=79485) [details]
Patch for testcase
------- Comment #6 From 2011-01-19 14:28:49 PST -------
The original testcase has a cascading float imprecision bug.  I've attached a patch for the testcase that addresses that problem, as well as addressing the original float-impresion problem by adding a small epsilon to the frame calculation.
------- Comment #7 From 2011-01-19 15:08:48 PST -------
Created an attachment (id=79497) [details]
Patch
------- Comment #8 From 2011-01-19 16:14:54 PST -------
I have updated the testcase at http://www.massive-interactive.nl/html5_video/smpte_test_universal.html with the patch provided
------- Comment #9 From 2011-01-19 16:27:31 PST -------
(In reply to comment #8)
> I have updated the testcase at http://www.massive-interactive.nl/html5_video/smpte_test_universal.html with the patch provided

Whoops, looks like that patch I provided now breaks when you skip backwards.  I shouldn't have switched the epsilon to negative if you were skipping backwards.

Rob, once this WebKit patch lands, the "var epsilon" parameter should be able to be set to 0, and still get frame-accurate seeking within WebKit.
------- Comment #10 From 2011-01-19 17:46:15 PST -------
I have build Webkit with the patch and it now correctly displays frame 00:00:00:13.
But after some testing I got the timecode at 00:00:20:13 burned in, but calculated = 00:00:20:14

video.currentTime = 20.559600830078125 at this point

when set in Chromium video.currentTime to 20.559600830078125 it correctly seeks to 00:00:20:14
------- Comment #11 From 2011-01-19 22:50:25 PST -------
(In reply to comment #10)
> I have build Webkit with the patch and it now correctly displays frame 00:00:00:13.
> But after some testing I got the timecode at 00:00:20:13 burned in, but calculated = 00:00:20:14
> 
> video.currentTime = 20.559600830078125 at this point

Technically, that's correct.  20.5596s works 513.99s at a 25fps rate.  That time is within the 513rd frame's duration, so the video should be displaying 00:20:13.  You likely have a bug in your secondsToTimecode function.

> when set in Chromium video.currentTime to 20.559600830078125 it correctly seeks to 00:00:20:14

That's a bug in Chromium then.
------- Comment #12 From 2011-01-19 22:51:33 PST -------
(In reply to comment #11)
> Technically, that's correct.  20.5596s works 513.99s at a 25fps rate. 

Whoops, that should read "513.99 frames"
------- Comment #13 From 2011-01-19 22:57:35 PST -------
(In reply to comment #11)
>  You likely have a bug in your secondsToTimecode function.

Indeed, it appears the correct calculation for "frames" in secondsToTimeCode() is the following:

var frames = Math.floor((time%1)*fps);

Because you're rounding your frames calculation to the nearest frame, you're off by a frame half the time.
------- Comment #14 From 2011-01-20 02:25:21 PST -------
(From update of attachment 79497 [details])
Can we include the test case in this patch?
------- Comment #15 From 2011-01-20 08:17:31 PST -------
(In reply to comment #14)
> (From update of attachment 79497 [details] [details])
> Can we include the test case in this patch?

I agree that a test case would be useful. I suspect that one of the existing test files has frame times that are suitable.
------- Comment #16 From 2011-01-20 09:55:50 PST -------
(In reply to comment #14)
> (From update of attachment 79497 [details] [details])
> Can we include the test case in this patch?

The test case media is rather large. (3Mb for the .mp4, 5.6Mb each for the .ogv and .webm versions.)  However, as Eric suggests, we may have appropriate media already in the LayoutTests.
------- Comment #17 From 2011-01-20 23:06:53 PST -------
I guess what I had before matches the correct behvaiour Jer described!

The bug in chromium was that seeking to an exact frame position would end up at the previous frame due to a simple < versus <= bug.  I'm happy to change my fix to make it consistent instead of the midpoint calculation fix I added last week.
------- Comment #18 From 2011-02-02 13:13:40 PST -------
(In reply to comment #15)
> I agree that a test case would be useful. I suspect that one of the existing test files has frame times that are suitable.

It looks like test.ogv isn't going to be suitable for this task, as the sample durations vary by quite a bit.  I'll try to recreate .ogv, .webm, and .mp4 versions with stable frame durations.
------- Comment #19 From 2011-02-02 16:03:24 PST -------
Created an attachment (id=80994) [details]
Patch
------- Comment #20 From 2011-02-02 17:38:49 PST -------
(From update of attachment 80994 [details])
Nice test!!  Appreciate the ogv version -- we'll get someone to generate the baselines.

View in context: https://bugs.webkit.org/attachment.cgi?id=80994&action=review

> LayoutTests/media/video-frame-accurate-seek.html:7
> +<video oncanplaythrough='event.target.currentTime=(14.6/25)'></video>

Sanity check: is this supposed to be 14/25 as opposed to 14.6?
------- Comment #21 From 2011-02-02 22:16:08 PST -------
(In reply to comment #20)
> (From update of attachment 80994 [details] [details])
> Nice test!!  Appreciate the ogv version -- we'll get someone to generate the baselines.
> 
> View in context: https://bugs.webkit.org/attachment.cgi?id=80994&action=review
> 
> > LayoutTests/media/video-frame-accurate-seek.html:7
> > +<video oncanplaythrough='event.target.currentTime=(14.6/25)'></video>
> 
> Sanity check: is this supposed to be 14/25 as opposed to 14.6?

Yes, that was intentional: I added that check for Chromium. :)  In the latest nightly, jumping to time 14.6/25 jumps to frame 15.

Note that WebKit/Safari Windows will fail this test at first; I've got a similar fix there as well.
------- Comment #22 From 2011-02-02 22:48:03 PST -------
Appreciate it :P

I checked Chromium 10.0.648.11 dev mac/linux and it's displaying 12/12/14.

I think 13/25 is producing 0.51999998092651367188 and we don't properly convert it to 520000 microseconds internally or something.  Math is hard :(
------- Comment #23 From 2011-02-02 22:56:11 PST -------
Created an attachment (id=81034) [details]
Patch

Fixed the WebKit/Safari Windows version of this bug as well.
------- Comment #24 From 2011-02-02 23:14:31 PST -------
(In reply to comment #22)
> Appreciate it :P
> 
> I checked Chromium 10.0.648.11 dev mac/linux and it's displaying 12/12/14.
> 
> I think 13/25 is producing 0.51999998092651367188 and we don't properly convert it to 520000 microseconds internally or something.  Math is hard :(

Interesting, because now Chromium's behavior is exactly the same as WebKit/QTKit.  I'm not entirely sure how Ogg/Theora represents time internally, but here's what was causing the problem in the QuickTime media engine.  Maybe it can help you figure out what's going on in Chromium:

QuickTime .mov files (as well as MPEG-4 .mp4 files) represent time as a ratio of two integers: the TimeValue / TimeScale.  (TimeScale is the number of TimeValues per second.) But WebKit represents time as a floating-point number of seconds.  So to convert from a float, to a QuickTime TimeValue, we multiply the float by the TimeScale, and cast the result back to an integer value.

In this test-25fps.mp4 file, the TimeScale is 2500, and each frame is 100 TimeValue long.  The math worked out like this:

(13/25) * 2500 = 1299.999; 
static_cast<long>(1299.999) = 1299; // static_cast truncates

Since frame 13 starts at TimeValue 1300, we display frame 12.  The fix was to round to the nearest integer instead of truncating.  

Practically speaking, TimeScales are never smaller than 100*FPS, so the maximum inaccuracy this rounding adds is +- 0.5% of a frame duration.
------- Comment #25 From 2011-02-02 23:29:19 PST -------
What the..? How did that platform/gtk/Skipped change get in there?  Let me post a new patch.
------- Comment #26 From 2011-02-02 23:38:57 PST -------
Found the culprit, complete with ironic comment (by me nonetheless!!)

  // Try to preserve as much accuracy as possible.
  float microseconds = seconds * base::Time::kMicrosecondsPerSecond;
  base::TimeDelta seek_time =
      base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds));

Changing the static_cast<int64> to llroundf() does the trick.

Turns out this was in our webkit<->chromium glue code, which is why it was caught by a layout test but not the C++ unit test I wrote.  Thanks again for drawing my attention to this!
------- Comment #27 From 2011-02-03 08:35:28 PST -------
(From update of attachment 81034 [details])
View in context: https://bugs.webkit.org/attachment.cgi?id=81034&action=review

> LayoutTests/media/video-frame-accurate-seek.html:9
> +<script src="media-file.js"></script>
> +<script src="video-paint-test.js"></script>
> +<body onload="setSrcByTagName('video', findMediaFile('video', 'content/test-25fps')); init()">
> +<p>Test that setting currentTime is frame-accurate. The three videos below should be showing frames 12, 13, and 14.</p>
> +<video oncanplaythrough='event.target.currentTime=(12/25)'></video>
> +<video oncanplaythrough='event.target.currentTime=(13/25)'></video>
> +<video oncanplaythrough='event.target.currentTime=(14.6/25)'></video>
> +</div>
> +</body>

As long as you are going to update the patch, can you clean up this new test by adding html and head elements? Bonus points for formatting it to make it easier to read.


> LayoutTests/platform/gtk/Skipped:5618
> +
> +# This media test fails; possibly because the AAC codec is not installed on the test bots:
> +# https://bugs.webkit.org/show_bug.cgi?id=53125
> +media/audio-mpeg4-supported.html

As you note, this doesn't belong in this patch.
------- Comment #28 From 2011-02-03 10:16:42 PST -------
(In reply to comment #26)
> Found the culprit, complete with ironic comment (by me nonetheless!!)
> 
>   // Try to preserve as much accuracy as possible.
>   float microseconds = seconds * base::Time::kMicrosecondsPerSecond;
>   base::TimeDelta seek_time =
>       base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds));
> 
> Changing the static_cast<int64> to llroundf() does the trick.
> 
> Turns out this was in our webkit<->chromium glue code, which is why it was caught by a layout test but not the C++ unit test I wrote.  Thanks again for drawing my attention to this!

I have applied this fix but it's only fixing the 00:00:00:13 issue.

video.currentTime = 189.92606401443481 @2FPS should set the video to 00:00:07:15 but the actually displayed frame is 00:00:07:14
still 1 frame off...
------- Comment #29 From 2011-02-03 10:22:30 PST -------
(In reply to comment #28)
> (In reply to comment #26)
> > Found the culprit, complete with ironic comment (by me nonetheless!!)
> > 
> >   // Try to preserve as much accuracy as possible.
> >   float microseconds = seconds * base::Time::kMicrosecondsPerSecond;
> >   base::TimeDelta seek_time =
> >       base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds));
> > 
> > Changing the static_cast<int64> to llroundf() does the trick.
> > 
> > Turns out this was in our webkit<->chromium glue code, which is why it was caught by a layout test but not the C++ unit test I wrote.  Thanks again for drawing my attention to this!
> 
> I have applied this fix but it's only fixing the 00:00:00:13 issue.
> 
> video.currentTime = 189.92606401443481 @25FPS should set the video to 00:00:07:15 but the actually displayed frame is 00:00:07:14
> still 1 frame off...
^^ should be 25 FPS

BTW testing with fractions such as

video.currentTime = 188/25, video.currentTime = 189/25, video.currentTime = 190/25 is not the correct way to test, a this will always produce exact frame times.
When using a slider on a video player you'll always end up with complete other fractions such as the one I found (7.597042560577393) falls in between 189/25 and 190/25
------- Comment #30 From 2011-02-03 10:27:09 PST -------
Created an attachment (id=81077) [details]
Patch
------- Comment #31 From 2011-02-03 10:53:50 PST -------
I tested with:
  189.92606401443481 / 25 = 7.597042561

...and internally that timestamp is represented as 7597043 microseconds.

The first frame that matches the seek condition is 7560000 (7.56s = 07:14 @ 25fps) since each frame duration is 40000 that means that the next frame is 7600000 and is actually past the requested seek point.

Again not an expert here -- just trying to make numbers work and tests pass :)
------- Comment #32 From 2011-02-03 11:10:40 PST -------
(In reply to comment #28)
> video.currentTime = 189.92606401443481 @2FPS should set the video to 00:00:07:15 but the actually displayed frame is 00:00:07:14
> still 1 frame off…

Rob, your secondsToTimecode() function is still producing incorrect values.  I think you'll see that if you uncomment the "apple's approach" calculations, you  will start producing correct timecode strings.
------- Comment #33 From 2011-02-03 11:14:50 PST -------
(In reply to comment #29)
> BTW testing with fractions such as
> 
> video.currentTime = 188/25, video.currentTime = 189/25, video.currentTime = 190/25 is not the correct way to test, a this will always produce exact frame times.
> When using a slider on a video player you'll always end up with complete other fractions such as the one I found (7.597042560577393) falls in between 189/25 and 190/25

That's actually incorrect.  The original bug was being caused because such fractional numbers as 13/25 were /not/ producing exact frame times.
------- Comment #34 From 2011-02-03 12:00:25 PST -------
(In reply to comment #32)
> (In reply to comment #28)
> > video.currentTime = 189.92606401443481 @2FPS should set the video to 00:00:07:15 but the actually displayed frame is 00:00:07:14
> > still 1 frame off…
> 
> Rob, your secondsToTimecode() function is still producing incorrect values.  I think you'll see that if you uncomment the "apple's approach" calculations, you  will start producing correct timecode strings.

Jer- I did that, the reason I commented it out again is that the problem with that approach is that when I click "+1 frame" (starting at 00:00:00:00) the video goes to 00:00:00:01 but the calculated timecode remains 00:00:00:00
then at the 2nd click video is at 00:00:00:01, calculated at 00:00:00:02,
the 3th gives 00:00:00:03 vs 00:00:00:02,
the 4th gives 00:00:00:04 vs 00:00:00:03
and then the 5th gives 00:00:00:05 vs 00:00:00:05 ....
------- Comment #35 From 2011-02-03 15:39:27 PST -------
Created an attachment (id=81127) [details]
Patch
------- Comment #36 From 2011-02-04 15:34:31 PST -------
(In reply to comment #35)
> Created an attachment (id=81127) [details] [details]
> Patch

cool, I have updated the javascript code on http://www.massive-interactive.nl/html5_video/smpte_test_universal.html and added new .mp4, .ogv and .webm test files.

it now works perfectly on WebKit using the patches added to this ticket. (only tested Mac) I'm finally unable to break it by puncing in weird fractions nor by scrubbing around like a maniac :-)
------- Comment #37 From 2011-02-04 15:35:28 PST -------
(In reply to comment #36)
> (In reply to comment #35)
> > Created an attachment (id=81127) [details] [details] [details]
> > Patch
> 
> cool, I have updated the javascript code on http://www.massive-interactive.nl/html5_video/smpte_test_universal.html and added new .mp4, .ogv and .webm test files.
> 
> it now works perfectly on WebKit using the patches added to this ticket. (only tested Mac) I'm finally unable to break it by puncing in weird fractions nor by scrubbing around like a maniac :-)

and just for the record: it now seems to break Chromium
------- Comment #38 From 2011-02-04 15:48:46 PST -------
(From update of attachment 81127 [details])
Clearing flags on attachment: 81127

Committed r77690: <http://trac.webkit.org/changeset/77690>
------- Comment #39 From 2011-02-04 15:48:55 PST -------
All reviewed patches have been landed.  Closing bug.
------- Comment #40 From 2011-02-04 17:29:17 PST -------
http://trac.webkit.org/changeset/77690 might have broken Qt Linux Release
The following tests are not passing:
media/video-frame-accurate-seek.html