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

See Also:


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 Details | Formatted Diff | Diff
Patch (496.49 KB, patch)
2011-02-02 16:03 PST, Jer Noble
no flags Details | Formatted Diff | Diff
Patch (499.59 KB, patch)
2011-02-02 22:56 PST, Jer Noble
no flags Details | Formatted Diff | Diff
Patch (499.59 KB, patch)
2011-02-03 10:27 PST, Jer Noble
no flags Details | Formatted Diff | Diff
Patch (498.92 KB, patch)
2011-02-03 15:39 PST, Jer Noble
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Rob Coenen 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 Rob Coenen 2011-01-19 06:06:26 PST
Created attachment 79412 [details]
screenshot of the bug
Comment 2 Rob Coenen 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 Jer Noble 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 Jer Noble 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 Jer Noble 2011-01-19 14:27:04 PST
Created attachment 79485 [details]
Patch for testcase
Comment 6 Jer Noble 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 Jer Noble 2011-01-19 15:08:48 PST
Created attachment 79497 [details]
Patch
Comment 8 Rob Coenen 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 Jer Noble 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 Rob Coenen 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 Jer Noble 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 Jer Noble 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 Jer Noble 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 Eric Seidel 2011-01-20 02:25:21 PST
Comment on attachment 79497 [details]
Patch

Can we include the test case in this patch?
Comment 15 Eric Carlson 2011-01-20 08:17:31 PST
(In reply to comment #14)
> (From update of attachment 79497 [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 Jer Noble 2011-01-20 09:55:50 PST
(In reply to comment #14)
> (From update of attachment 79497 [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 Andrew Scherkus 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 Jer Noble 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 Jer Noble 2011-02-02 16:03:24 PST
Created attachment 80994 [details]
Patch
Comment 20 Andrew Scherkus 2011-02-02 17:38:49 PST
Comment on attachment 80994 [details]
Patch

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 Jer Noble 2011-02-02 22:16:08 PST
(In reply to comment #20)
> (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?

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 Andrew Scherkus 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 Jer Noble 2011-02-02 22:56:11 PST
Created attachment 81034 [details]
Patch

Fixed the WebKit/Safari Windows version of this bug as well.
Comment 24 Jer Noble 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 Jer Noble 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 Andrew Scherkus 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 Eric Carlson 2011-02-03 08:35:28 PST
Comment on attachment 81034 [details]
Patch

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 Rob Coenen 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 Rob Coenen 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 Jer Noble 2011-02-03 10:27:09 PST
Created attachment 81077 [details]
Patch
Comment 31 Andrew Scherkus 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 Jer Noble 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 Jer Noble 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 Rob Coenen 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 Jer Noble 2011-02-03 15:39:27 PST
Created attachment 81127 [details]
Patch
Comment 36 Rob Coenen 2011-02-04 15:34:31 PST
(In reply to comment #35)
> Created an attachment (id=81127) [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 Rob Coenen 2011-02-04 15:35:28 PST
(In reply to comment #36)
> (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 :-)

and just for the record: it now seems to break Chromium
Comment 38 WebKit Commit Bot 2011-02-04 15:48:46 PST
Comment on attachment 81127 [details]
Patch

Clearing flags on attachment: 81127

Committed r77690: <http://trac.webkit.org/changeset/77690>
Comment 39 WebKit Commit Bot 2011-02-04 15:48:55 PST
All reviewed patches have been landed.  Closing bug.
Comment 40 WebKit Review Bot 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