From 69803ec6cd1bb1952c672e53556e3d3a875a77ba Mon Sep 17 00:00:00 2001 From: Aleksander Date: Fri, 5 Jun 2026 12:08:25 +0200 Subject: [PATCH] wgui: dav1d video playback works --- uidev/assets/gui/video.xml | 6 +- uidev/assets/video.ivf | Bin 0 -> 43911 bytes wgui/src/components/video.rs | 129 ++++++++++++++++++++++++++++--- wgui/src/video_dec.rs | 143 +++++++++++++++++++++++++++-------- 4 files changed, 233 insertions(+), 45 deletions(-) create mode 100644 uidev/assets/video.ivf diff --git a/uidev/assets/gui/video.xml b/uidev/assets/gui/video.xml index 622a8b54..0a82b94c 100644 --- a/uidev/assets/gui/video.xml +++ b/uidev/assets/gui/video.xml @@ -1,9 +1,9 @@ - +
-
diff --git a/uidev/assets/video.ivf b/uidev/assets/video.ivf new file mode 100644 index 0000000000000000000000000000000000000000..5f43016367d921757a6cb8584f6152819a0503a2 GIT binary patch literal 43911 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 { @@ -16,11 +26,18 @@ pub struct Params<'a> { pub src: Option>, } -struct State {} +struct State { + demuxer: IvfReader, + decoder: video_dec::Av1Decoder, + self_ref: Weak, + glyph_id: usize, +} struct Data { #[allow(dead_code)] id_container: WidgetID, + + id_image: WidgetID, } #[allow(dead_code)] @@ -39,16 +56,88 @@ impl ComponentTrait for ComponentVideo { &mut self.base } - fn refresh(&self, _data: &mut RefreshData) { - // nothing to do + fn refresh(&self, data: &mut RefreshData) { + let mut state = self.state.borrow_mut(); + + match self.read_frame(&mut state, data.layout) { + Ok(playing) => { + if !playing { + if let Err(e) = state.play(data.layout) { + log::error!("{e}"); + } + return; + } + } + Err(e) => { + log::error!("read_frame failed: {e}"); + return; + } + } + + if let Some(component) = state.self_ref.upgrade() { + data.layout.defer_component_refresh(Component(component)); + } } } -impl ComponentVideo {} +impl ComponentVideo { + fn read_frame(&self, state: &mut State, layout: &mut Layout) -> anyhow::Result { + let Some(mut image) = layout.state.widgets.get_as::(self.data.id_image) else { + return Ok(false); + }; + + let rgbx_frame = match state.decoder.read_frame(&mut state.demuxer)? { + video_dec::ReadFrameResult::Ok(rgbx_frame) => rgbx_frame, + video_dec::ReadFrameResult::EndOfFile => { + log::info!("got EOF"); + return Ok(false); + } + }; + + let Some(buffer) = ImageBuffer::from_raw(rgbx_frame.width.into(), rgbx_frame.height.into(), rgbx_frame.data) else { + anyhow::bail!("ImageBuffer failed"); + }; + + let glyph_content = CustomGlyphContent::Image(buffer); + + image.set_content( + &mut layout.alterables, + Some(CustomGlyphData { + id: state.glyph_id, + content: Arc::new(glyph_content), + }), + ); + + // force-update image content (FIXME: stream image data directly instead) + state.glyph_id += 1; + + Ok(true) + } +} + +impl State { + fn play(&mut self, layout: &mut Layout) -> anyhow::Result<()> { + self.decoder = Av1Decoder::new()?; + self.demuxer.rewind(); + if let Some(component) = self.self_ref.upgrade() { + layout.defer_component_refresh(Component(component)); + } + Ok(()) + } +} pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc)> { let style = params.style; + let Some(src) = params.src else { + anyhow::bail!("missing src"); + }; + + let video_data = ess.layout.state.globals.get_asset(src)?; + + let demuxer = IvfReader::new(video_data)?; + let decoder = Av1Decoder::new()?; + let (root, _) = ess.layout.add_child( ess.parent, WidgetRectangle::create(WidgetRectangleParams { @@ -58,10 +147,30 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul style, )?; - let id_container = root.id; - let data = Rc::new(Data { id_container }); + let (image, _) = ess.layout.add_child( + root.id, + WidgetImage::create(Default::default()), + taffy::Style { + size: taffy::Size { + width: percent(1.0), + height: percent(1.0), + }, + ..Default::default() + }, + )?; - let state = Rc::new(RefCell::new(State {})); + let id_container = root.id; + let data = Rc::new(Data { + id_container, + id_image: image.id, + }); + + let state = Rc::new(RefCell::new(State { + demuxer, + decoder, + self_ref: Default::default(), + glyph_id: 0, + })); let base = ComponentBase { id: root.id, @@ -70,6 +179,8 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul let video = Rc::new(ComponentVideo { base, data, state }); + video.state.borrow_mut().self_ref = Rc::downgrade(&video); + ess.layout.defer_component_refresh(Component(video.clone())); Ok((root, video)) } diff --git a/wgui/src/video_dec.rs b/wgui/src/video_dec.rs index f55d1253..b12c241e 100644 --- a/wgui/src/video_dec.rs +++ b/wgui/src/video_dec.rs @@ -2,13 +2,14 @@ use std::ptr::null_mut; use bytes::{Buf, Bytes}; use dav1d_sys::{ - Dav1dContext, Dav1dData, Dav1dPicture, Dav1dSettings, dav1d_close, dav1d_data_unref, dav1d_default_settings, + DAV1D_ERR_AGAIN, Dav1dContext, Dav1dData, Dav1dPicture, Dav1dSettings, dav1d_close, dav1d_default_settings, dav1d_get_picture, dav1d_open, dav1d_picture_unref, dav1d_send_data, }; pub struct IvfReader { - _file_data: Vec, // Used by `reader`, do not remove! + file_data: Vec, // Used by `reader`, do not remove! reader: Bytes, + header_size: usize, pub cur_frame: u32, pub num_frames: u32, pub framerate: f32, @@ -39,6 +40,11 @@ pub struct IvfReader { const IVF_MAGIC: [u8; 4] = [0x44, 0x4B, 0x49, 0x46]; +pub enum IvfReadFrameResult<'a> { + Ok((&'a [u8], u64 /* pts */)), + EndOfFile, +} + impl IvfReader { pub fn new(file_data: Vec) -> anyhow::Result { // safety: both reader and file_data are located in the same struct @@ -80,8 +86,11 @@ impl IvfReader { log::info!("IvfReader: width {header_width}, height {header_height}, framerate {framerate}"); + let header_size = file_data.len() - reader.remaining(); + Ok(IvfReader { - _file_data: file_data, + header_size, + file_data, reader, cur_frame: 0, width: header_width, @@ -91,14 +100,24 @@ impl IvfReader { }) } + pub fn rewind(&mut self) { + self.reader = Bytes::from_static(unsafe { std::mem::transmute::<&[u8], &'static [u8]>(&self.file_data) }); + // do not read header again + self.reader.advance(self.header_size); + } + // Read demuxed video packet to a chunk - pub fn read_frame(&mut self) -> anyhow::Result<(&[u8], u64 /* pts */)> { + pub fn read_frame(&mut self) -> anyhow::Result> { + if self.reader.remaining() == 0 { + return Ok(IvfReadFrameResult::EndOfFile); + } + let frame_size = self.reader.try_get_u32_le()? as usize; let frame_pts = self.reader.try_get_u64_le()?; if frame_size > 8 * 1024 * 1024 { // something went really wrong - anyhow::bail!("Invalid frame size"); + anyhow::bail!("Invalid frame size {frame_size}"); } // SAFETY: reader.chunk() slice lifetime is the same as Self, no risk here. @@ -113,14 +132,15 @@ impl IvfReader { self.cur_frame += 1; - Ok((chunk_a, frame_pts)) + Ok(IvfReadFrameResult::Ok((chunk_a, frame_pts))) } } -pub struct RgbFrame { - width: u16, - height: u16, - data: Vec, +// RGB with 255 A +pub struct RgbxFrame { + pub width: u16, + pub height: u16, + pub data: Vec, } fn yuv_to_rgb(y: u8, u: u8, v: u8) -> (u8, u8, u8) { @@ -138,7 +158,7 @@ fn yuv_to_rgb(y: u8, u: u8, v: u8) -> (u8, u8, u8) { ) } -impl RgbFrame { +impl RgbxFrame { // Simple, temporary, best-effort yuv420->rgb converter. Not performant at all, but at least it works. // Temporary till we get native YUV support in shaders (as 3 vulkan textures). // SAFETY: this function expects YUV420 data only. @@ -151,15 +171,15 @@ impl RgbFrame { data_v: *const u8, stride_luma: usize, stride_chroma: usize, - ) -> RgbFrame { + ) -> RgbxFrame { unsafe { - let channels = 3; // rgb + let channels = 4; // rgbx - let rgb_size = width as usize * height as usize * channels; - let mut rgb = Vec::::with_capacity(rgb_size); - rgb.set_len(rgb_size); + let rgbx_size = width as usize * height as usize * channels; + let mut rgbx = Vec::::with_capacity(rgbx_size); + rgbx.set_len(rgbx_size); - let ptr_rgb = rgb.as_mut_ptr(); + let ptr_rgbx = rgbx.as_mut_ptr(); for y in 0..height as usize { for x in 0..width as usize { @@ -169,16 +189,17 @@ impl RgbFrame { let (r, g, b) = yuv_to_rgb(val_y, val_u, val_v); let pos = y * (width as usize * channels) + x * channels; - *ptr_rgb.add(pos) = r; - *ptr_rgb.add(pos + 1) = g; - *ptr_rgb.add(pos + 2) = b; + *ptr_rgbx.add(pos) = r; + *ptr_rgbx.add(pos + 1) = g; + *ptr_rgbx.add(pos + 2) = b; + *ptr_rgbx.add(pos + 3) = 255; } } - RgbFrame { + RgbxFrame { width, height, - data: rgb, + data: rgbx, } } } @@ -196,6 +217,23 @@ impl Drop for Av1Decoder { } } +enum GetPictureResult { + Ok, + Again, + Failed(i32), +} + +enum ReadFrameIterResult { + Ok(RgbxFrame), + Again, + EndOfFile, +} + +pub enum ReadFrameResult { + Ok(RgbxFrame), + EndOfFile, +} + impl Av1Decoder { pub fn new() -> anyhow::Result { unsafe { @@ -211,12 +249,25 @@ impl Av1Decoder { } } - pub fn read_frame(&mut self, reader: &mut IvfReader) -> anyhow::Result { - let (packet, pts) = reader.read_frame()?; + fn get_picture(&mut self, picture: &mut Dav1dPicture) -> GetPictureResult { + let ret = unsafe { dav1d_get_picture(self.ctx, picture) }; + if ret == DAV1D_ERR_AGAIN { + return GetPictureResult::Again; + } - let mut ret; + if ret < 0 { + return GetPictureResult::Failed(ret); + } + GetPictureResult::Ok + } + + fn send_data(&mut self, reader: &mut IvfReader) -> anyhow::Result { unsafe { + let (packet, pts) = match reader.read_frame()? { + IvfReadFrameResult::Ok((packet, pts)) => (packet, pts), + IvfReadFrameResult::EndOfFile => return Ok(false), + }; let mut data: Dav1dData = std::mem::zeroed(); data.data = packet.as_ptr(); data.sz = packet.len(); @@ -225,17 +276,31 @@ impl Av1Decoder { data.m.offset = -1; data.m.duration = 0; - ret = dav1d_send_data(self.ctx, &raw mut data); - dav1d_data_unref(&raw mut data); + let ret = dav1d_send_data(self.ctx, &raw mut data); if ret < 0 { anyhow::bail!("dav1d_send_data failed: {ret}"); } + Ok(true) + } + } + + fn read_frame_iter(&mut self, reader: &mut IvfReader) -> anyhow::Result { + unsafe { let mut picture: Dav1dPicture = std::mem::zeroed(); - ret = dav1d_get_picture(self.ctx, &raw mut picture); - if ret < 0 { - dav1d_picture_unref(&raw mut picture); - anyhow::bail!("dav1d_get_picture failure: {ret}") + match self.get_picture(&mut picture) { + GetPictureResult::Ok => {} + GetPictureResult::Again => { + dav1d_picture_unref(&raw mut picture); + if !self.send_data(reader)? { + return Ok(ReadFrameIterResult::EndOfFile); + } + return Ok(ReadFrameIterResult::Again); + } + GetPictureResult::Failed(code) => { + dav1d_picture_unref(&raw mut picture); + anyhow::bail!("dav1d_get_picture failed: {code}"); + } } let pic_y = picture.data[0]; // luma Y @@ -244,7 +309,7 @@ impl Av1Decoder { let stride_luma = picture.stride[0]; let stride_chroma = picture.stride[1]; - let rgb = RgbFrame::from_yuv( + let rgbx = RgbxFrame::from_yuv( reader.width, reader.height, pic_y as *const u8, @@ -256,7 +321,19 @@ impl Av1Decoder { dav1d_picture_unref(&raw mut picture); - Ok(rgb) + Ok(ReadFrameIterResult::Ok(rgbx)) + } + } + + pub fn read_frame(&mut self, reader: &mut IvfReader) -> anyhow::Result { + loop { + match self.read_frame_iter(reader)? { + ReadFrameIterResult::Ok(rgbx_frame) => { + return Ok(ReadFrameResult::Ok(rgbx_frame)); + } + ReadFrameIterResult::Again => { /* loop again */ } + ReadFrameIterResult::EndOfFile => return Ok(ReadFrameResult::EndOfFile), + } } } }