mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 14:48:08 -05:00
app/vid: make video decoding fast now. turns out i just need release mode for rav1d
This commit is contained in:
@@ -20,10 +20,6 @@ freetype-rs = { version = "0.37.0", features = ["bundled"] }
|
||||
image = "0.25.9"
|
||||
qoi = "0.4.1"
|
||||
|
||||
# AV1 video decoding - rav1d with Rust API from leo030303 fork
|
||||
# Disable default features to avoid NASM dependency (asm)
|
||||
rav1d = { git = "https://github.com/leo030303/rav1d", branch = "add-rust-api", default-features = false, features = ["bitdepth_8"] }
|
||||
|
||||
tracing = "0.1.44"
|
||||
glam = "0.30.9"
|
||||
#zmq = "0.10.0"
|
||||
@@ -69,6 +65,15 @@ fluent = "0.17.0"
|
||||
unic-langid = { version = "0.9.6", features = ["unic-langid-macros"] }
|
||||
indoc = "2.0.7"
|
||||
|
||||
# AV1 video decoding - rav1d with Rust API from leo030303 fork
|
||||
# Disable default features to avoid NASM dependency (asm)
|
||||
rav1d = { git = "https://github.com/leo030303/rav1d", branch = "add-rust-api", features = ["bitdepth_8"] }
|
||||
|
||||
# This makes a HUGE difference to decoding speed.
|
||||
# Over 160s to like 2s.
|
||||
[profile.dev.package.rav1d]
|
||||
opt-level = 3
|
||||
|
||||
[features]
|
||||
emulate-android = []
|
||||
enable-plugins = []
|
||||
|
||||
@@ -59,7 +59,6 @@ mod text;
|
||||
mod text2;
|
||||
mod ui;
|
||||
mod util;
|
||||
mod video;
|
||||
|
||||
use crate::{
|
||||
app::{App, AppPtr},
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2025 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! rav1d AV1 video decoder wrapper
|
||||
//!
|
||||
//! This module provides a Rust wrapper around the rav1d AV1 decoder.
|
||||
|
||||
use rav1d::{Decoder as Rav1dDecoderInner, Picture, PlanarImageComponent, Rav1dError};
|
||||
|
||||
pub type DecoderResult<T> = Result<T, Rav1dError>;
|
||||
|
||||
macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui:video", $($arg)*); } }
|
||||
|
||||
/// A decoded frame containing RGBA data
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DecodedFrame {
|
||||
/// Frame width in pixels
|
||||
pub width: u32,
|
||||
/// Frame height in pixels
|
||||
pub height: u32,
|
||||
/// RGBA pixel data (width * height * 4 bytes)
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// rav1d AV1 video decoder wrapper
|
||||
///
|
||||
/// This wraps the rav1d decoder and provides automatic planar GBR to RGBA conversion.
|
||||
pub struct Rav1dDecoder {
|
||||
/// Inner decoder from rav1d
|
||||
decoder: Rav1dDecoderInner,
|
||||
}
|
||||
|
||||
impl Rav1dDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self { decoder: Rav1dDecoderInner::new().unwrap() }
|
||||
}
|
||||
|
||||
/// Send AV1 bitstream data to the decoder without getting a frame
|
||||
pub fn send_data(&mut self, data: &[u8]) -> DecoderResult<()> {
|
||||
let data = data.to_vec();
|
||||
match self.decoder.send_data(data, None, None, None) {
|
||||
Ok(_) => {}
|
||||
Err(Rav1dError::TryAgain) => {
|
||||
while let Err(Rav1dError::TryAgain) = self.decoder.send_pending_data() {
|
||||
// Continue sending pending data
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the next decoded frame from the decoder
|
||||
pub fn get_pic(&mut self) -> DecoderResult<DecodedFrame> {
|
||||
let now = std::time::Instant::now();
|
||||
let pix = self.decoder.get_picture();
|
||||
t!("decoder get pix: {:?}", now.elapsed());
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
let res = pix.map(|pic| Self::conv(pic));
|
||||
t!("decoder conv: {:?}", now.elapsed());
|
||||
res
|
||||
}
|
||||
|
||||
/// Decode AV1 bitstream data and get all available frames
|
||||
/// Returns a vector of frames (may be empty if decoder needs more data)
|
||||
pub fn decode(&mut self, data: &[u8]) -> DecoderResult<Vec<DecodedFrame>> {
|
||||
self.send_data(data)?;
|
||||
|
||||
let mut frames = Vec::new();
|
||||
loop {
|
||||
match self.get_pic() {
|
||||
Ok(frame) => frames.push(frame),
|
||||
Err(Rav1dError::TryAgain) => break,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(frames)
|
||||
}
|
||||
|
||||
/// Convert a rav1d Picture from planar GBR to RGBA
|
||||
fn conv(pic: Picture) -> DecodedFrame {
|
||||
let g_plane = pic.plane(PlanarImageComponent::Y);
|
||||
let b_plane = pic.plane(PlanarImageComponent::U);
|
||||
let r_plane = pic.plane(PlanarImageComponent::V);
|
||||
|
||||
let g_stride = pic.stride(PlanarImageComponent::Y) as usize;
|
||||
let b_stride = pic.stride(PlanarImageComponent::U) as usize;
|
||||
let r_stride = pic.stride(PlanarImageComponent::V) as usize;
|
||||
|
||||
let width = pic.width() as usize;
|
||||
let height = pic.height() as usize;
|
||||
|
||||
let mut rgba = vec![0u8; width * height * 4];
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let g_idx = y * g_stride + x;
|
||||
let b_idx = y * b_stride + x;
|
||||
let r_idx = y * r_stride + x;
|
||||
|
||||
let r = r_plane[r_idx];
|
||||
let g = g_plane[g_idx];
|
||||
let b = b_plane[b_idx];
|
||||
|
||||
let out_idx = (y * width + x) * 4;
|
||||
rgba[out_idx] = r;
|
||||
rgba[out_idx + 1] = g;
|
||||
rgba[out_idx + 2] = b;
|
||||
rgba[out_idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
DecodedFrame { width: width as u32, height: height as u32, data: rgba }
|
||||
}
|
||||
|
||||
/// Flush the decoder to get any remaining frames
|
||||
pub fn flush(&mut self) -> DecoderResult<Vec<DecodedFrame>> {
|
||||
self.decoder.flush();
|
||||
let mut frames = Vec::new();
|
||||
loop {
|
||||
match self.get_pic() {
|
||||
Ok(frame) => frames.push(frame),
|
||||
Err(Rav1dError::TryAgain) => break,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(frames)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2025 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod decoder;
|
||||
mod ivf;
|
||||
mod yuv_conv;
|
||||
|
||||
pub use decoder::{DecodedFrame, Rav1dDecoder};
|
||||
pub use ivf::{IvfHeader, IvfStreamingDemuxer};
|
||||
pub use yuv_conv::yuv420p_to_rgba;
|
||||
@@ -1,175 +0,0 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2025 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! YUV to RGBA color space conversion
|
||||
//!
|
||||
//! This module provides functions to convert YUV420P planar format
|
||||
//! to RGBA format for GPU rendering.
|
||||
|
||||
/// Convert YUV420P planar format to RGBA
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `y_plane` - Y (luminance) plane data
|
||||
/// * `u_plane` - U (chrominance) plane data
|
||||
/// * `v_plane` - V (chrominance) plane data
|
||||
/// * `width` - Frame width in pixels
|
||||
/// * `height` - Frame height in pixels
|
||||
/// * `y_stride` - Y plane stride (bytes per row)
|
||||
/// * `u_stride` - U plane stride (bytes per row)
|
||||
/// * `v_stride` - V plane stride (bytes per row)
|
||||
///
|
||||
/// # Returns
|
||||
/// A Vec<u8> containing RGBA data (width * height * 4 bytes)
|
||||
///
|
||||
/// # Color Conversion
|
||||
/// Uses BT.601 standard for YUV to RGB conversion:
|
||||
/// ```text
|
||||
/// R = Y + 1.402 * (V - 128)
|
||||
/// G = Y - 0.344 * (U - 128) - 0.714 * (V - 128)
|
||||
/// B = Y + 1.772 * (U - 128)
|
||||
/// ```
|
||||
pub fn yuv420p_to_rgba(
|
||||
y_plane: &[u8],
|
||||
u_plane: &[u8],
|
||||
v_plane: &[u8],
|
||||
width: usize,
|
||||
height: usize,
|
||||
y_stride: usize,
|
||||
u_stride: usize,
|
||||
v_stride: usize,
|
||||
) -> Vec<u8> {
|
||||
let mut rgba = vec![0u8; width * height * 4];
|
||||
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
let y_idx = y * y_stride + x;
|
||||
let u_idx = (y / 2) * u_stride + (x / 2);
|
||||
let v_idx = (y / 2) * v_stride + (x / 2);
|
||||
|
||||
let y_val = y_plane[y_idx] as i32;
|
||||
let u_val = u_plane[u_idx] as i32 - 128;
|
||||
let v_val = v_plane[v_idx] as i32 - 128;
|
||||
|
||||
// BT.601 YUV to RGB conversion
|
||||
let r = clamp((y_val as f32) + 1.402 * (v_val as f32));
|
||||
let g = clamp((y_val as f32) - 0.344136 * (u_val as f32) - 0.714136 * (v_val as f32));
|
||||
let b = clamp((y_val as f32) + 1.772 * (u_val as f32));
|
||||
|
||||
let out_idx = (y * width + x) * 4;
|
||||
rgba[out_idx] = r;
|
||||
rgba[out_idx + 1] = g;
|
||||
rgba[out_idx + 2] = b;
|
||||
// Alpha
|
||||
rgba[out_idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
rgba
|
||||
}
|
||||
|
||||
/// Clamp a floating point value to 0-255 range and convert to u8
|
||||
#[inline]
|
||||
fn clamp(value: f32) -> u8 {
|
||||
value.round().clamp(0.0, 255.0) as u8
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_yuv420p_to_rgba_basic() {
|
||||
// Create a small test frame (4x4)
|
||||
let width = 4;
|
||||
let height = 4;
|
||||
|
||||
// Y plane - full resolution
|
||||
let y_plane: Vec<u8> = (0..16).map(|i| (i * 16) as u8).collect();
|
||||
|
||||
// U and V planes - half resolution (2x2)
|
||||
let u_plane: Vec<u8> = vec![128, 128, 128, 128];
|
||||
let v_plane: Vec<u8> = vec![128, 128, 128, 128];
|
||||
|
||||
let y_stride = width;
|
||||
let u_stride = width / 2;
|
||||
let v_stride = width / 2;
|
||||
|
||||
let rgba = yuv420p_to_rgba(
|
||||
&y_plane, &u_plane, &v_plane, width, height, y_stride, u_stride, v_stride,
|
||||
);
|
||||
|
||||
// Check output size
|
||||
assert_eq!(rgba.len(), width * height * 4);
|
||||
|
||||
// With U=V=128 (neutral chroma), RGB should equal Y
|
||||
for i in 0..16 {
|
||||
let expected_y = (i * 16) as u8;
|
||||
// R
|
||||
assert_eq!(rgba[i * 4], expected_y);
|
||||
// G
|
||||
assert_eq!(rgba[i * 4 + 1], expected_y);
|
||||
// B
|
||||
assert_eq!(rgba[i * 4 + 2], expected_y);
|
||||
// A
|
||||
assert_eq!(rgba[i * 4 + 3], 255);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clamp() {
|
||||
assert_eq!(clamp(-10.0), 0);
|
||||
assert_eq!(clamp(0.0), 0);
|
||||
assert_eq!(clamp(127.5), 128);
|
||||
assert_eq!(clamp(255.0), 255);
|
||||
assert_eq!(clamp(300.0), 255);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_yuv_to_rgb_conversion() {
|
||||
// Test specific color conversions
|
||||
// Black: Y=0, U=128, V=128
|
||||
let black = yuv_to_rgb(0, 128, 128);
|
||||
assert_eq!(black, [0, 0, 0]);
|
||||
|
||||
// White: Y=255, U=128, V=128
|
||||
let white = yuv_to_rgb(255, 128, 128);
|
||||
assert_eq!(white, [255, 255, 255]);
|
||||
|
||||
// Red: Y=76, U=85, V=255 (approximate pure red in BT.601)
|
||||
let red = yuv_to_rgb(76, 85, 255);
|
||||
// R should be high
|
||||
assert!(red[0] > 200);
|
||||
// G should be low
|
||||
assert!(red[1] < 50);
|
||||
// B should be low
|
||||
assert!(red[2] < 50);
|
||||
}
|
||||
|
||||
/// Helper function to convert a single YUV pixel to RGB
|
||||
fn yuv_to_rgb(y: u8, u: u8, v: u8) -> [u8; 3] {
|
||||
let y_val = y as i32;
|
||||
let u_val = u as i32 - 128;
|
||||
let v_val = v as i32 - 128;
|
||||
|
||||
let r = clamp((y_val as f32) + 1.402 * (v_val as f32));
|
||||
let g = clamp((y_val as f32) - 0.344136 * (u_val as f32) - 0.714136 * (v_val as f32));
|
||||
let b = clamp((y_val as f32) + 1.772 * (u_val as f32));
|
||||
|
||||
[r, g, b]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user