// Copyright (c) 2019-2022, The rav1e contributors. All rights reserved // // This source code is subject to the terms of the BSD 2 Clause License and // the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License // was not distributed with this source code in the LICENSE file, you can // obtain it at www.aomedia.org/license/software. If the Alliance for Open // Media Patent License 1.0 was not distributed with this source code in the // PATENTS file, you can obtain it at www.aomedia.org/license/patent. use std::env::var; use dora_node_api::arrow::array::{UInt16Array, UInt8Array}; use dora_node_api::dora_core::config::DataId; use dora_node_api::{DoraNode, Event, IntoArrow, Metadata, Parameter}; use eyre::{Context as EyreContext, Result}; use log::warn; use rav1e::color::{ColorDescription, MatrixCoefficients}; // Encode the same tiny blank frame 30 times use rav1e::config::SpeedSettings; use rav1e::*; pub fn fill_zeros_toward_center_y_plane_in_place(y: &mut [u16], width: usize, height: usize) { assert_eq!(y.len(), width * height); for row in 0..height { let row_start = row * width; let center = width / 2; // --- Fill left half (left to center) let mut last = 0u16; for col in 0..center { let idx = row_start + col; if y[idx] == 0 { y[idx] = last; } else { last = y[idx]; } } // --- Fill right half (right to center) last = 0u16; for col in (center..width).rev() { let idx = row_start + col; if y[idx] == 0 { y[idx] = last; } else { last = y[idx]; } } } } fn bgr8_to_yuv420(bgr_data: Vec, width: usize, height: usize) -> (Vec, Vec, Vec) { let mut y_plane = vec![0; width * height]; let mut u_plane = vec![0; (width / 2) * (height / 2)]; let mut v_plane = vec![0; (width / 2) * (height / 2)]; for y in 0..height { for x in 0..width { let bgr_index = (y * width + x) * 3; let b = bgr_data[bgr_index] as f32; let g = bgr_data[bgr_index + 1] as f32; let r = bgr_data[bgr_index + 2] as f32; // Corrected YUV conversion formulas let y_value = (0.299 * r + 0.587 * g + 0.114 * b).clamp(0.0, 255.0); let u_value = (-0.14713 * r - 0.28886 * g + 0.436 * b + 128.0).clamp(0.0, 255.0); let v_value = (0.615 * r - 0.51499 * g - 0.10001 * b + 128.0).clamp(0.0, 255.0); let y_index = y * width + x; y_plane[y_index] = y_value.round() as u8; if x % 2 == 0 && y % 2 == 0 { let uv_index = (y / 2) * (width / 2) + (x / 2); u_plane[uv_index] = u_value.round() as u8; v_plane[uv_index] = v_value.round() as u8; } } } (y_plane, u_plane, v_plane) } fn get_yuv_planes(buffer: &[u8], width: usize, height: usize) -> (&[u8], &[u8], &[u8]) { // Calculate sizes of Y, U, and V planes for YUV420 format let y_size = width * height; // Y has full resolution let uv_width = width / 2; // U and V are subsampled by 2 in both dimensions let uv_height = height / 2; // U and V are subsampled by 2 in both dimensions let uv_size = uv_width * uv_height; // Size for U and V planes // Ensure the buffer has the correct size // if buffer.len() != y_size + 2 * uv_size { // panic!("Invalid buffer size for the given width and height!"); // } // Extract Y, U, and V planes let y_plane = &buffer[0..y_size]; //.to_vec(); let u_plane = &buffer[y_size..y_size + uv_size]; //.to_vec(); let v_plane = &buffer[y_size + uv_size..]; //.to_vec(); (y_plane, u_plane, v_plane) } fn send_yuv( y: &[u8], u: &[u8], v: &[u8], enc: EncoderConfig, width: usize, height: usize, node: &mut DoraNode, id: DataId, metadata: &mut Metadata, output_enconding: &str, ) -> () { // Create a new Arrow array for the YUV420 data let cfg = Config::new().with_encoder_config(enc.clone()); let mut ctx: Context = cfg.new_context().unwrap(); let mut f = ctx.new_frame(); let xdec = f.planes[0].cfg.xdec; let stride = (width + xdec) >> xdec; f.planes[0].copy_from_raw_u8(&y, stride, 1); let xdec = f.planes[1].cfg.xdec; let stride = (width + xdec) >> xdec; f.planes[1].copy_from_raw_u8(&u, stride, 1); let xdec = f.planes[2].cfg.xdec; let stride = (width + xdec) >> xdec; f.planes[2].copy_from_raw_u8(&v, stride, 1); match ctx.send_frame(f) { Ok(_) => {} Err(e) => match e { EncoderStatus::EnoughData => { warn!("Unable to send frame "); } _ => { warn!("Unable to send frame "); } }, } ctx.flush(); match ctx.receive_packet() { Ok(pkt) => match output_enconding { "avif" => { let data = pkt.data.clone(); metadata.parameters.insert( "encoding".to_string(), Parameter::String("avif".to_string()), ); let matrix_coefficients = if let Some(desc) = enc.color_description { desc.matrix_coefficients } else { MatrixCoefficients::BT709 }; let data = avif_serialize::Aviffy::new() .set_chroma_subsampling((true, true)) .set_seq_profile(0) .matrix_coefficients(match matrix_coefficients { MatrixCoefficients::Identity => { avif_serialize::constants::MatrixCoefficients::Rgb } MatrixCoefficients::BT709 => { avif_serialize::constants::MatrixCoefficients::Bt709 } MatrixCoefficients::Unspecified => { avif_serialize::constants::MatrixCoefficients::Unspecified } MatrixCoefficients::BT601 => { avif_serialize::constants::MatrixCoefficients::Bt601 } MatrixCoefficients::YCgCo => { avif_serialize::constants::MatrixCoefficients::Ycgco } MatrixCoefficients::BT2020NCL => { avif_serialize::constants::MatrixCoefficients::Bt2020Ncl } MatrixCoefficients::BT2020CL => { avif_serialize::constants::MatrixCoefficients::Bt2020Cl } _ => { warn!("color matrix coefficients"); avif_serialize::constants::MatrixCoefficients::Rgb } }) .to_vec( &data, None, width as u32, height as u32, enc.bit_depth as u8, ); let arrow = data.into_arrow(); node.send_output(id, metadata.parameters.clone(), arrow) .context("could not send output") .unwrap(); } _ => { metadata .parameters .insert("encoding".to_string(), Parameter::String("av1".to_string())); metadata .parameters .insert("height".to_string(), Parameter::Integer(enc.height as i64)); metadata .parameters .insert("width".to_string(), Parameter::Integer(enc.width as i64)); let data = pkt.data; let arrow = data.into_arrow(); node.send_output(id, metadata.parameters.clone(), arrow) .context("could not send output") .unwrap(); } }, Err(e) => match e { EncoderStatus::LimitReached => {} EncoderStatus::Encoded => {} EncoderStatus::NeedMoreData => {} _ => { panic!("Unable to receive packet",); } }, } } pub fn lib_main() -> Result<()> { let mut height = std::env::var("IMAGE_HEIGHT") .unwrap_or_else(|_| "480".to_string()) .parse() .unwrap(); let mut width = std::env::var("IMAGE_WIDTH") .unwrap_or_else(|_| "640".to_string()) .parse() .unwrap(); let speed = var("RAV1E_SPEED").map(|s| s.parse().unwrap()).unwrap_or(10); let output_encoding = var("ENCODING").unwrap_or("av1".to_string()); let mut enc = EncoderConfig { width, height, speed_settings: SpeedSettings::from_preset(speed), chroma_sampling: color::ChromaSampling::Cs420, color_description: Some(ColorDescription { matrix_coefficients: MatrixCoefficients::BT709, transfer_characteristics: color::TransferCharacteristics::BT709, color_primaries: color::ColorPrimaries::BT709, }), low_latency: true, ..Default::default() }; let cfg = Config::new().with_encoder_config(enc.clone()); cfg.validate()?; let (mut node, mut events) = DoraNode::init_from_env().context("Could not initialize dora node")?; loop { match events.recv() { Some(Event::Input { id, data, mut metadata, }) => { if let Some(Parameter::Integer(h)) = metadata.parameters.get("height") { height = *h as usize; }; if let Some(Parameter::Integer(w)) = metadata.parameters.get("width") { width = *w as usize; }; let encoding = if let Some(Parameter::String(encoding)) = metadata.parameters.get("encoding") { encoding } else { "bgr8" }; enc = EncoderConfig { width, height, speed_settings: SpeedSettings::from_preset(speed), low_latency: true, chroma_sampling: color::ChromaSampling::Cs420, color_description: Some(ColorDescription { matrix_coefficients: MatrixCoefficients::BT709, transfer_characteristics: color::TransferCharacteristics::BT709, color_primaries: color::ColorPrimaries::BT709, }), ..Default::default() }; match encoding { "mono16" => { enc.bit_depth = 12; enc.chroma_sampling = color::ChromaSampling::Cs400; } _ => {} } if encoding == "bgr8" { let buffer: &UInt8Array = data.as_any().downcast_ref().unwrap(); let buffer: Vec = buffer.values().to_vec(); let (y, u, v) = bgr8_to_yuv420(buffer, width, height); send_yuv( &y, &u, &v, enc, width, height, &mut node, id, &mut metadata, &output_encoding, ); } else if encoding == "yuv420" { let buffer: &UInt8Array = data.as_any().downcast_ref().unwrap(); let buffer = buffer.values(); //.to_vec(); let (y, u, v) = get_yuv_planes(buffer, width, height); send_yuv( &y, &u, &v, enc, width, height, &mut node, id, &mut metadata, &output_encoding, ); } else if encoding == "mono16" || encoding == "z16" { let buffer: &UInt16Array = data.as_any().downcast_ref().unwrap(); let mut buffer = buffer.values().to_vec(); if std::env::var("FILL_ZEROS") .map(|s| s != "false") .unwrap_or(true) { fill_zeros_toward_center_y_plane_in_place(&mut buffer, width, height); } let bytes: &[u8] = &bytemuck::cast_slice(&buffer); let cfg = Config::new().with_encoder_config(enc.clone()); let mut ctx: Context = cfg.new_context().unwrap(); let mut f = ctx.new_frame(); let xdec = f.planes[0].cfg.xdec; let stride = (width + xdec) >> xdec; // Multiply by 2 the stride as it is going to be width * 2 as we're converting 16-bit to 2*8-bit. f.planes[0].copy_from_raw_u8(bytes, stride * 2, 2); match ctx.send_frame(f) { Ok(_) => {} Err(e) => match e { EncoderStatus::EnoughData => { warn!("Unable to send frame "); } _ => { warn!("Unable to send frame "); } }, } ctx.flush(); match ctx.receive_packet() { Ok(pkt) => { let data = pkt.data; match output_encoding.as_str() { "avif" => { warn!("avif encoding not supported for mono16"); } _ => { metadata.parameters.insert( "encoding".to_string(), Parameter::String("av1".to_string()), ); let arrow = data.into_arrow(); node.send_output(id, metadata.parameters, arrow) .context("could not send output") .unwrap(); } } } Err(e) => match e { EncoderStatus::LimitReached => {} EncoderStatus::Encoded => {} EncoderStatus::NeedMoreData => {} _ => { panic!("Unable to receive packet",); } }, } } else if encoding == "rgb8" { let buffer: &UInt8Array = data.as_any().downcast_ref().unwrap(); let buffer: Vec = buffer.values().to_vec(); let buffer: Vec = buffer.chunks(3).flat_map(|x| [x[2], x[1], x[0]]).collect(); let (y, u, v) = bgr8_to_yuv420(buffer, width, height); send_yuv( &y, &u, &v, enc, width, height, &mut node, id, &mut metadata, &output_encoding, ); } else { unimplemented!("We haven't worked on additional encodings."); } } Some(Event::Error(_e)) => { continue; } _ => break, }; } Ok(()) } #[cfg(feature = "python")] use pyo3::{ pyfunction, pymodule, types::{PyModule, PyModuleMethods}, wrap_pyfunction, Bound, PyResult, Python, }; #[cfg(feature = "python")] #[pyfunction] fn py_main(_py: Python) -> eyre::Result<()> { lib_main() } #[cfg(feature = "python")] #[pymodule] fn dora_rav1e(_py: Python, m: Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(py_main, &m)?)?; m.add("__version__", env!("CARGO_PKG_VERSION"))?; Ok(()) }