-
Notifications
You must be signed in to change notification settings - Fork 140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Method to directly render the VideoDecoder to a Canvas element #88
Comments
Edit: ImageBitmapOptions is in the spec and in Chrome's implementation. If it doesn't work in Chrome Canary please file a Chromium bug. In Chrome's implementation, createImageBitmap does indeed do a conversion to RGB. There are optimizations we still plan to make, but it's not yet certain what faction of cases will continue to require a conversion. There isn't currently a way to bind WebGL textures to images, but this is something that operating systems can usually do. I've heard that WebXR is investigating whether there is a path forward there. I'm hopeful that WebGPU will be more amenable to this sort of usage. There has been some discussion but no agreed plan, see gpuweb/gpuweb#625. |
Thanks for the implementation details, that is good to know. I look forward to any future optimizations. Looking into VideoFrame interface, it does seem like the YUV Planes are accessible for reading (though it presumably requires a memcpy). I can use that to render the YUV onto a webgl canvas context. This will likely be more efficient than performing a userspace YUV->RGB->flipY->render. https://wicg.github.io/web-codecs/#plane If there's a better method to render h264 nal units directly to a canvas than what I described above, please let me know! |
I made the changes to draw the VideoFrame yuv planes via webgl and can confirm that the rendering is significantly faster. Latency and CPU usage both decreased dramatically. Setting up and rendering via a GL context is more involved, so convenience methods to do this would be great. Right now, the createImageBitmap/transferImageFromBitmap is deceptively expensive and slow. Problematically, it is seemingly the fastest/direct way to draw to the screen/canvas. |
Update: On ChromeOS, and other hardware accelerated platforms, VideoFrame.planes may be null. Furthermore, VIewFrame.createImageBitmap is returning blank/black frames. This means on some (indeterminable at runtime) platforms, the decoder is completely non-functional, as the frames can't be retrieved in any fashion. Chrome 86. |
For ChromeOS issue, there is a bug at https://bugs.chromium.org/p/chromium/issues/detail?id=1137947 |
+1 to jchen's suggestion. For folks reading along: the case of null planes for hw-decoded frames is a known limitation of chrome's current impl, but the bug in createImageBitmap() breaks the intended workaround. In addition to getting the bug fixed, we're planning to spec an API for format conversion (e.g. null -> i420). |
@chcunningham Unfortunately, even the createImageBitmap is an unusable workaround for my use case, as it does an unaccelerated YUV to RGB conversion and then memcpy that onto the canvas. There is significant latency and freezing on low power devices. It's actually faster to use a WebAssembly h264 decoder, because the YUV planes are accessible and can be rendered directly with WebGL. The ideal solution as mentioned in title is just a way to render directly to a canvas from a decoder. The conversions, while nice to have, are a roundabout way to get the decoder rendering to an element, which I imagine will be the primary use case. |
will get a chrome://gpu for the bug shortly. |
Agreed. We're tracking these optimizations here |
Can this also apply to have a zero-copy from a canvas to a VideoEncoder, in cases where hardware acceleration is available? |
@koush and others, we have some updates!
Please let us know how it works!
@AlexVestin, most encoders will require that we at least do an RGB -> YUV conversion before encoding. This conversion can happen on the GPU, so its not awful. Still, there are some additional copies in the path of Canvas -> ImageBitmap -> Encoder which could be better optimized. Can you share more on your use case? I may be able to suggest a more efficient path. |
It looks like you can feed VideoFrame directly to texImage2d! This is great!
|
@chcunningham I implemented this in a test app this morning, and unfortunately I am seeing quite a bit of overhead when using VideoFrame as opposed to an HTMLVideoElement with texImage2d to process frames from my webcam on both PC and Mac. It takes roughly twice as long when using the VideoFrame method. Is this expected? FWIW I've done quite a bit of benchmarking on this stuff while trying to find the most optimal way to manipulate a camera feed, and these numbers look pretty close to what I was seeing for the total time when using window.createImageBitmap on the HTMLVideoElement and then passing the resulting ImageBitmap into texImage2d. |
Have implemented the new VideoFrame canvas rendering and can confirm that this is working with hardware acceleration on my Mac now. Prior to this Canary build, the previous method had no way to render the frame, as createImageBitmap was returning a blank bitmap. |
@chcunningham
The use case is encoding frame by frame from a canvas for animation rendering, in cases where the user is not be able to encode it in real time |
We're merging the video element and VideoFrame paths, so hopefully you won't see any differences shortly. There are a couple bugs with the VideoFrame path on Canary that I hope to resolve this week. There may be some slowness introduced on macOS where previously we weren't waiting on the appropriate sync tokens, but it shouldn't be any slower than the video element path. |
Also, @BenV if you have a public page for benchmarking I'd love to use it for experimenting with changes to this path. |
@dalecurtis I don't have a public page currently but will work on getting one set up and will keep you posted. Thanks! |
@dalecurtis While I still plan on creating a public benchmark I'm happy to report that with the latest Chrome Canary I no longer see any speed penalty in my internal test, and the VideoFrame path is actually a bit faster for me on both Mac/Windows. I do seem to be losing my WebGL context fairly frequently on my Windows machine, although this could be related to some other issue with the Canary build. My entire window (including the Chrome UI) flashes to black and I get Sorry if this is the wrong place to ask, but are there plans for making VideoFrame objects transferable? It would be ideal to be able to send them to a worker for rendering. Thanks so much for your work on this, we're very excited about these new APIs. |
@BenV Great to hear! Our perf tests indicates the new video-element/VideoFrame path is 12-60% faster, so good to hear that's matching your results. Regarding the black flashes, do you have any crashes in chrome://crashes when this happens? Usually it means the gpu process has crashed. If you provide the crash ids I can look into it. VideoFrames should already be transferrable. Are you seeing issues doing that? @tguilbert-google @sandersdan |
@dalecurtis I am not seeing any problems with transferring VideoFrames, just did not realize that was already possible, that is great news! I do not see anything in chrome://crashes, I did verify that this is also happening in the nightly build. I'll keep trying to narrow this down and keep you posted. It does appear to work fine for my 2D canvas code path on this machine -- the issue only occurs with WebGL/texImage2D. Is there anything else you would like me to do that would be helpful? Things I have already quickly tried without success are:
Edit: Just noticed my GPU process memory footprint is growing indefinitely while using VideoFrame with texImage2D, so that's a clue. Quite possibly a problem on my end, but I believe the only difference is passing the VideoFrame instead of the HTMLVideoElement. Will keep investigating. |
Ah sounds like OOM then, could be a leak on our side. You should be able to call texImage2D(frame) and then immediately call frame.close() -- if you have a test page that reproduces let me know and I'll take a look. |
(Oops, misclicked closing the issue) A quick clarification: transferrable VideoFrames have not yet landed, but the CL is out for review. |
I think I can reproduce the leak with my simple test page, https://storage.googleapis.com/dalecurtis/webgl-camera.html will take a look. |
Was just about to link this repro case but it happens for me on yours as well: https://codesandbox.io/s/videoframe-webgl-test-vjtb3 Thanks again for being so responsive on this issue, it is very much appreciated! Please let me know if I can do anything else to test or help out in any way. |
https://bugs.chromium.org/p/chromium/issues/detail?id=1182476 tracks the leak. Still digging. |
I can confirm the leak is fixed for me in the Chromium nightly, nice work, and thanks again! |
I've now updated the explainer to recommend drawImage and texImage for painting VideoFrames. Also, I've filed #158 to update the html spec to include VideoFrame as a CanvasImageSource. I think all issues in this thread are now addressed, but please re-open if I've missed anything. Thanks so much for the feedback! |
@dalecurtis I am fighting a bug where calling frame.close() immediately after texImage2D produces a black texture, and only on Windows (Chrome 113) machines in our testing. We are processing 64 frame chunks in a web worker, and typically on the problematic machines the last 2-5 frames in the sequence end up black. Adding a 10 ms timeout before calling frame.close() resolves the issue, but this is not desired since it can slow down the decoder and timers add overhead. Do you have any ideas for ways to prevent this issue? |
Please file an issue at https://crbug.com/new with the chrome://gpu details and simple reproduction if it doesn't reproduce using https://w3c.github.io/webcodecs/samples/video-decode-display/ |
I just wanted to follow up here so you know the outcome @dalecurtis -- the issue was there was a race condition between closing the decoder and calling textImg2D with a few of the frames at the end of the segment of video being decoded, resulting in occasional cases of closing the decoder before textImg2D on the frames. I would think we would get some sort of error message in this case (would have saved a lot of time debugging!), but instead were ending up with empty textures. |
That's a bug; frames should remain valid even if the decoder is closed. (On some platforms, implementations may need to keep the decoder alive as long as there are outstanding frames.) |
Currently, sending data from the VideoDecoder to Canvas involves:
Under the hood, this I suspect this converts VIdeoFrame YUV to ImageBitmap RGB, rather than directly rendering the YUV planes of the video frame in the webgl canvas. (Please let me know if I'm wrong)
There's also some lifecycle management on the frames and bitmaps to close and destroy them.
Ideally, the video frames (or perhaps decoder) should have a method to directly render to a canvas. This is similar to how Android's decoder operates, and for lack of better description "feels" like it is closer to achieving zero copy.
The text was updated successfully, but these errors were encountered: