diff --git a/Cargo.toml b/Cargo.toml index f8deb5a2..a26fff08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,18 @@ edition = "2021" [features] default = [] -backend = ["anyhow", "byteorder", "thiserror", "crc32fast", "nix", "gbm", "gbm-sys", "drm", "drm-fourcc", "zerocopy"] +backend = ["anyhow", "byteorder", "thiserror", "crc32fast", "nix", "drm", "drm-fourcc", "zerocopy"] vaapi = ["libva", "backend"] v4l2 = ["v4l2r", "backend"] +gbm = ["dep:gbm", "gbm-sys"] +h265 = [] +av1 = [] +c2-wrapper = ["backend"] [dependencies] anyhow = { version = "1.0.75", optional = true } byteorder = { version = "1.4.3", optional = true } -libva = { version = "0.0.12", package = "cros-libva", optional = true } +libva = { version = "0.0.13", package = "cros-libva", optional = true } v4l2r = { version = "0.0.5", package = "v4l2r", optional = true } log = { version = "0", features = ["release_max_level_debug"] } thiserror = { version = "1.0.58", optional = true } diff --git a/src/backend/v4l2/encoder.rs b/src/backend/v4l2/encoder.rs index 80898215..035d97f5 100644 --- a/src/backend/v4l2/encoder.rs +++ b/src/backend/v4l2/encoder.rs @@ -523,6 +523,7 @@ where "bitrate mode", match rate_control { RateControl::ConstantBitrate(_) => VideoBitrateMode::ConstantBitrate, + RateControl::VariableBitrate { .. } => todo!(), RateControl::ConstantQuality(_) => VideoBitrateMode::ConstantQuality, }, )?; @@ -1217,6 +1218,7 @@ pub(crate) mod tests { timestamp: self.counter, layout: self.frame_layout.clone(), force_keyframe: false, + force_idr: false, }; let handle = TestMmapFrame { meta: meta.clone(), frame_count: self.max_count }; @@ -1326,6 +1328,7 @@ pub(crate) mod tests { timestamp: self.counter, layout: layout.clone(), force_keyframe: false, + force_idr: false, }; let frame = DmabufFrame { fds, layout }; diff --git a/src/backend/vaapi/decoder.rs b/src/backend/vaapi/decoder.rs index fb291ce4..dfbeef98 100644 --- a/src/backend/vaapi/decoder.rs +++ b/src/backend/vaapi/decoder.rs @@ -191,7 +191,7 @@ impl VaapiDecodedHandle { } pub struct VaapiBackend { - pub display: Rc, + pub display: Arc, pub context: Rc, stream_info: StreamInfo, // TODO: We should try to support context reuse @@ -200,7 +200,7 @@ pub struct VaapiBackend { } impl VaapiBackend { - pub(crate) fn new(display: Rc, supports_context_reuse: bool) -> Self { + pub(crate) fn new(display: Arc, supports_context_reuse: bool) -> Self { let init_stream_info = StreamInfo { format: DecodedFormat::NV12, coded_resolution: Resolution::from((16, 16)), diff --git a/src/backend/vaapi/encoder.rs b/src/backend/vaapi/encoder.rs index 095c5b50..796f0c6c 100644 --- a/src/backend/vaapi/encoder.rs +++ b/src/backend/vaapi/encoder.rs @@ -5,6 +5,7 @@ use std::any::Any; use std::marker::PhantomData; use std::rc::Rc; +use std::sync::Arc; use anyhow::anyhow; use libva::Config; @@ -17,6 +18,7 @@ use libva::PictureEnd; use libva::Surface; use libva::SurfaceMemoryDescriptor; use libva::UsageHint; +use libva::VAEntrypoint; use libva::VAEntrypoint::VAEntrypointEncSlice; use libva::VAEntrypoint::VAEntrypointEncSliceLP; use libva::VAProfile; @@ -52,11 +54,14 @@ impl From for StatelessBackendError { pub(crate) fn tunings_to_libva_rc( tunings: &Tunings, ) -> StatelessBackendResult { - let bits_per_second = tunings.rate_control.bitrate_target().unwrap_or(0); + let bits_per_second = tunings.rate_control.bitrate_maximum().unwrap_or(0); + let target_bits_per_second = tunings.rate_control.bitrate_target().unwrap_or(bits_per_second); + let bits_per_second = u32::try_from(bits_per_second).map_err(|e| anyhow::anyhow!(e))?; + let target_bits_per_second = + u32::try_from(target_bits_per_second).map_err(|e| anyhow::anyhow!(e))?; - // At the moment we don't support variable bitrate therefore target 100% - const TARGET_PERCENTAGE: u32 = 100; + let target_percentage: u32 = 100 * target_bits_per_second / bits_per_second; // Window size in ms that the RC should apply to const WINDOW_SIZE: u32 = 1_500; @@ -70,10 +75,10 @@ pub(crate) fn tunings_to_libva_rc, - _va_profile: VAProfile::Type, + va_profile: VAProfile::Type, + entrypoint: VAEntrypoint::Type, scratch_pool: VaSurfacePool<()>, _phantom: PhantomData<(M, H)>, } @@ -172,7 +178,7 @@ where H: std::borrow::Borrow>, { pub fn new( - display: Rc, + display: Arc, va_profile: VAProfile::Type, fourcc: Fourcc, coded_size: Resolution, @@ -185,6 +191,7 @@ where .ok_or_else(|| StatelessBackendError::UnsupportedFormat)?; let rt_format = format_map.rt_format; + let entrypoint = if low_power { VAEntrypointEncSliceLP } else { VAEntrypointEncSlice }; let va_config = display.create_config( vec![ @@ -198,7 +205,7 @@ where }, ], va_profile, - if low_power { VAEntrypointEncSliceLP } else { VAEntrypointEncSlice }, + entrypoint, )?; let context = display.create_context::( @@ -210,7 +217,7 @@ where )?; let mut scratch_pool = VaSurfacePool::new( - Rc::clone(&display), + Arc::clone(&display), rt_format, Some(UsageHint::USAGE_HINT_ENCODER), coded_size, @@ -223,7 +230,8 @@ where va_config, context, scratch_pool, - _va_profile: va_profile, + va_profile, + entrypoint, _phantom: Default::default(), }) } @@ -271,6 +279,51 @@ where Ok(Reconstructed(surface)) } + + pub(crate) fn supports_max_frame_size(&self) -> StatelessBackendResult { + let display = self.context().display(); + let mut attrs = [libva::VAConfigAttrib { + type_: libva::VAConfigAttribType::VAConfigAttribMaxFrameSize, + value: 0, + }]; + + display.get_config_attributes(self.va_profile, self.entrypoint, &mut attrs)?; + + if attrs[0].value == libva::VA_ATTRIB_NOT_SUPPORTED { + return Ok(false); + } + Ok(true) + } + + pub(crate) fn supports_quality_range(&self, quality: u32) -> StatelessBackendResult { + let display = self.context().display(); + let mut attrs = [libva::VAConfigAttrib { + type_: libva::VAConfigAttribType::VAConfigAttribEncQualityRange, + value: 0, + }]; + + display.get_config_attributes(self.va_profile, self.entrypoint, &mut attrs)?; + + if attrs[0].value == libva::VA_ATTRIB_NOT_SUPPORTED || quality > attrs[0].value { + return Ok(false); + } + Ok(true) + } + + pub(crate) fn supports_packed_header(&self, header: u32) -> StatelessBackendResult { + let display = self.context().display(); + let mut attrs = [libva::VAConfigAttrib { + type_: libva::VAConfigAttribType::VAConfigAttribEncPackedHeaders, + value: 0, + }]; + + display.get_config_attributes(self.va_profile, self.entrypoint, &mut attrs)?; + + if attrs[0].value == libva::VA_ATTRIB_NOT_SUPPORTED || (header & attrs[0].value) == 0 { + return Ok(false); + } + Ok(true) + } } impl StatelessEncoderBackendImport for VaapiBackend @@ -491,6 +544,7 @@ pub(crate) mod tests { layout: self.frame_layout.clone(), force_keyframe: false, timestamp: self.counter, + force_idr: false, }; self.counter += 1; diff --git a/src/backend/vaapi/surface_pool.rs b/src/backend/vaapi/surface_pool.rs index a04ae912..937405a4 100644 --- a/src/backend/vaapi/surface_pool.rs +++ b/src/backend/vaapi/surface_pool.rs @@ -8,6 +8,7 @@ use std::collections::BTreeMap; use std::collections::VecDeque; use std::rc::Rc; use std::rc::Weak; +use std::sync::Arc; use libva::Display; use libva::Surface; @@ -79,7 +80,7 @@ impl Drop for PooledVaSurface { } struct VaSurfacePoolInner { - display: Rc, + display: Arc, rt_format: u32, usage_hint: Option, coded_resolution: Resolution, @@ -133,7 +134,7 @@ impl VaSurfacePool { /// * `usage_hint` - hint about how the surfaces from this pool will be used. /// * `coded_resolution` - resolution of the surfaces. pub fn new( - display: Rc, + display: Arc, rt_format: u32, usage_hint: Option, coded_resolution: Resolution, diff --git a/src/bitstream_utils.rs b/src/bitstream_utils.rs index d9ac0310..f8f787af 100644 --- a/src/bitstream_utils.rs +++ b/src/bitstream_utils.rs @@ -510,15 +510,18 @@ impl BitWriter { } /// Immediately outputs any cached bits to [`std::io::Write`] - pub fn flush(&mut self) -> BitWriterResult<()> { + /// and returns the number of trailing bits in the last byte. + pub fn flush(&mut self) -> BitWriterResult { + let mut num_trailing_bits = 0; if self.nth_bit != 0 { self.out.write_all(&[self.curr_byte])?; + num_trailing_bits = 8 - self.nth_bit; self.nth_bit = 0; self.curr_byte = 0; } self.out.flush()?; - Ok(()) + Ok(num_trailing_bits) } /// Returns `true` if ['Self`] hold data that wasn't written to [`std::io::Write`] diff --git a/src/c2_wrapper/c2_decoder.rs b/src/c2_wrapper/c2_decoder.rs index 4d8f9156..554957da 100644 --- a/src/c2_wrapper/c2_decoder.rs +++ b/src/c2_wrapper/c2_decoder.rs @@ -32,7 +32,7 @@ use crate::decoder::StreamInfo; use crate::image_processing::convert_video_frame; use crate::video_frame::frame_pool::FramePool; use crate::video_frame::frame_pool::PooledVideoFrame; -#[cfg(feature = "vaapi")] +#[cfg(feature = "gbm")] use crate::video_frame::gbm_video_frame::{GbmDevice, GbmUsage, GbmVideoFrame}; #[cfg(feature = "v4l2")] use crate::video_frame::v4l2_mmap_video_frame::V4l2MmapVideoFrame; @@ -62,7 +62,7 @@ pub trait C2DecoderBackend { ) -> Result, String>; } -#[cfg(feature = "vaapi")] +#[cfg(feature = "gbm")] type AuxiliaryVideoFrame = GbmVideoFrame; #[cfg(feature = "v4l2")] type AuxiliaryVideoFrame = V4l2MmapVideoFrame; @@ -189,7 +189,7 @@ where ), ) } else { - #[cfg(feature = "vaapi")] + #[cfg(feature = "gbm")] { let gbm_device = Arc::new( GbmDevice::open(PathBuf::from("/dev/dri/renderD128")) diff --git a/src/c2_wrapper/c2_encoder.rs b/src/c2_wrapper/c2_encoder.rs index d21bd451..ae1f21ce 100644 --- a/src/c2_wrapper/c2_encoder.rs +++ b/src/c2_wrapper/c2_encoder.rs @@ -250,6 +250,7 @@ where timestamp: job.timestamp, layout: Default::default(), force_keyframe: false, + force_idr: false, }; #[cfg(feature = "vaapi")] let encode_result = self.encoder.encode(meta, frame); diff --git a/src/codec/h264/nalu_writer.rs b/src/codec/h264/nalu_writer.rs index 90eaec6a..db6c4bf8 100644 --- a/src/codec/h264/nalu_writer.rs +++ b/src/codec/h264/nalu_writer.rs @@ -177,7 +177,8 @@ impl NaluWriter { /// Writes a H.264 NALU header. pub fn write_header(&mut self, idc: u8, _type: u8) -> NaluWriterResult<()> { self.0.flush()?; - self.0.inner_mut().write_header(idc, _type)?; + let _num_bytes = self.0.inner_mut().write_header(idc, _type)?; + // self.0.bits_written += num_bytes * 8; Ok(()) } @@ -185,6 +186,11 @@ impl NaluWriter { pub fn aligned(&self) -> bool { !self.0.has_data_pending() } + + /// Returns the number of trailing bits in the last byte. + pub fn flush(&mut self) -> NaluWriterResult { + Ok(self.0.flush()?) + } } #[cfg(test)] diff --git a/src/codec/h264/synthesizer.rs b/src/codec/h264/synthesizer.rs index b28596cb..7aeddd49 100644 --- a/src/codec/h264/synthesizer.rs +++ b/src/codec/h264/synthesizer.rs @@ -9,11 +9,16 @@ use crate::codec::h264::nalu_writer::NaluWriterError; use crate::codec::h264::parser::HrdParams; use crate::codec::h264::parser::NaluType; use crate::codec::h264::parser::Pps; +use crate::codec::h264::parser::SliceHeader; +#[cfg(feature = "vaapi")] +use crate::codec::h264::parser::SliceType; use crate::codec::h264::parser::Sps; use crate::codec::h264::parser::DEFAULT_4X4_INTER; use crate::codec::h264::parser::DEFAULT_4X4_INTRA; use crate::codec::h264::parser::DEFAULT_8X8_INTER; use crate::codec::h264::parser::DEFAULT_8X8_INTRA; +#[cfg(feature = "vaapi")] +use crate::encoder::stateless::h264::IsReference; mod private { pub trait NaluStruct {} @@ -23,6 +28,8 @@ impl private::NaluStruct for Sps {} impl private::NaluStruct for Pps {} +impl private::NaluStruct for SliceHeader {} + #[derive(Debug)] pub enum SynthesizerError { Unsupported, @@ -445,6 +452,291 @@ impl<'n, W: Write> Synthesizer<'n, Pps, W> { } } +#[cfg(feature = "vaapi")] +pub type NumTrailingBits = u8; + +#[cfg(feature = "vaapi")] +impl<'n, W: Write> Synthesizer<'n, SliceHeader, W> { + pub fn synthesize( + header: &'n SliceHeader, + sps: &'n Sps, + pps: &'n Pps, + is_idr: bool, + is_ref: IsReference, + writer: W, + ep_enabled: bool, + ) -> SynthesizerResult { + let mut s = Self { writer: NaluWriter::::new(writer, ep_enabled), nalu: header }; + + let ref_idc = if is_idr { + 3 + } else if is_ref == IsReference::LongTerm { + 2 + } else if is_ref == IsReference::ShortTerm { + 1 + } else { + 0 + }; + let nalu_type = if is_idr { NaluType::SliceIdr } else { NaluType::Slice }; + s.writer.write_header(ref_idc, nalu_type as u8)?; + s.slice_header_data(sps, pps, is_idr, is_ref)?; + let num_trailing_bits = s.writer.flush()?; + Ok(num_trailing_bits) + } + + fn slice_header_data( + &mut self, + sps: &Sps, + pps: &Pps, + is_idr: bool, + is_ref: IsReference, + ) -> SynthesizerResult<()> { + let hdr = self.nalu; + + self.ue(hdr.first_mb_in_slice)?; + self.ue(hdr.slice_type as u32)?; + self.ue(hdr.pic_parameter_set_id)?; + + if sps.separate_colour_plane_flag { + self.u(2, hdr.colour_plane_id)?; + } + + let frame_num_bits = sps.log2_max_frame_num_minus4 as usize + 4; + self.u(frame_num_bits, hdr.frame_num)?; + + if !sps.frame_mbs_only_flag { + self.u(1, hdr.field_pic_flag as u32)?; + if hdr.field_pic_flag { + self.u(1, hdr.bottom_field_flag as u32)?; + } + } + + if is_idr { + self.ue(hdr.idr_pic_id)?; + } + + if sps.pic_order_cnt_type == 0 { + let pic_order_cnt_lsb_bits = sps.log2_max_pic_order_cnt_lsb_minus4 as usize + 4; + self.u(pic_order_cnt_lsb_bits, hdr.pic_order_cnt_lsb)?; + if pps.bottom_field_pic_order_in_frame_present_flag && !hdr.field_pic_flag { + self.se(hdr.delta_pic_order_cnt_bottom)?; + } + } + + if sps.pic_order_cnt_type == 1 && !sps.delta_pic_order_always_zero_flag { + self.se(hdr.delta_pic_order_cnt[0])?; + if pps.bottom_field_pic_order_in_frame_present_flag && !hdr.field_pic_flag { + self.se(hdr.delta_pic_order_cnt[1])?; + } + } + + if pps.redundant_pic_cnt_present_flag { + self.ue(hdr.redundant_pic_cnt)?; + } + + if hdr.slice_type == SliceType::B { + self.u(1, hdr.direct_spatial_mv_pred_flag as u32)?; + } + + if hdr.slice_type == SliceType::P + || hdr.slice_type == SliceType::Sp + || hdr.slice_type == SliceType::B + { + self.u(1, hdr.num_ref_idx_active_override_flag as u32)?; + if hdr.num_ref_idx_active_override_flag { + self.ue(hdr.num_ref_idx_l0_active_minus1)?; + if hdr.slice_type == SliceType::B { + self.ue(hdr.num_ref_idx_l1_active_minus1)?; + } + } + } + + self.ref_pic_list_modification(hdr)?; + + if (pps.weighted_pred_flag + && (hdr.slice_type == SliceType::P || hdr.slice_type == SliceType::Sp)) + || (pps.weighted_bipred_idc == 1 && hdr.slice_type == SliceType::B) + { + self.pred_weight_table(hdr, sps)?; + } + + if is_ref != IsReference::No { + self.dec_ref_pic_marking(hdr, is_idr)?; + } + + if pps.entropy_coding_mode_flag + && hdr.slice_type != SliceType::I + && hdr.slice_type != SliceType::Si + { + self.ue(hdr.cabac_init_idc)?; + } + + self.se(hdr.slice_qp_delta)?; + + if hdr.slice_type == SliceType::Sp || hdr.slice_type == SliceType::Si { + if hdr.slice_type == SliceType::Sp { + self.u(1, hdr.sp_for_switch_flag as u32)?; + } + self.se(hdr.slice_qs_delta)?; + } + + if pps.deblocking_filter_control_present_flag { + self.ue(hdr.disable_deblocking_filter_idc)?; + if hdr.disable_deblocking_filter_idc != 1 { + self.se(hdr.slice_alpha_c0_offset_div2)?; + self.se(hdr.slice_beta_offset_div2)?; + } + } + + if pps.num_slice_groups_minus1 > 0 { + // Slice groups are not supported, this should have been caught earlier + return Err(SynthesizerError::Unsupported); + } + + Ok(()) + } + + fn ref_pic_list_modification(&mut self, hdr: &SliceHeader) -> SynthesizerResult<()> { + let slice_type_mod5 = hdr.slice_type as u8 % 5; + + if slice_type_mod5 != 2 && slice_type_mod5 != 4 { + self.u(1, hdr.ref_pic_list_modification_flag_l0 as u32)?; + if hdr.ref_pic_list_modification_flag_l0 { + for modification in &hdr.ref_pic_list_modification_l0 { + self.ue(modification.modification_of_pic_nums_idc)?; + if modification.modification_of_pic_nums_idc == 0 + || modification.modification_of_pic_nums_idc == 1 + { + self.ue(modification.abs_diff_pic_num_minus1)?; + } else if modification.modification_of_pic_nums_idc == 2 { + self.ue(modification.long_term_pic_num)?; + } + } + self.ue(3u32)?; + } + } + + if slice_type_mod5 == 1 { + self.u(1, hdr.ref_pic_list_modification_flag_l1 as u32)?; + if hdr.ref_pic_list_modification_flag_l1 { + for modification in &hdr.ref_pic_list_modification_l1 { + self.ue(modification.modification_of_pic_nums_idc)?; + if modification.modification_of_pic_nums_idc == 0 + || modification.modification_of_pic_nums_idc == 1 + { + self.ue(modification.abs_diff_pic_num_minus1)?; + } else if modification.modification_of_pic_nums_idc == 2 { + self.ue(modification.long_term_pic_num)?; + } + } + self.ue(3u32)?; + } + } + + Ok(()) + } + + fn pred_weight_table(&mut self, hdr: &SliceHeader, sps: &Sps) -> SynthesizerResult<()> { + let pwt = &hdr.pred_weight_table; + let chroma_array_type = sps.chroma_array_type(); + + self.ue(pwt.luma_log2_weight_denom)?; + if chroma_array_type != 0 { + self.ue(pwt.chroma_log2_weight_denom)?; + } + + let num_ref_idx_l0 = (hdr.num_ref_idx_l0_active_minus1 + 1) as usize; + for i in 0..num_ref_idx_l0 { + let luma_weight_l0_flag = pwt.luma_weight_l0[i] != (1 << pwt.luma_log2_weight_denom); + self.u(1, luma_weight_l0_flag as u32)?; + if luma_weight_l0_flag { + self.se(pwt.luma_weight_l0[i])?; + self.se(pwt.luma_offset_l0[i])?; + } + + if chroma_array_type != 0 { + let default_weight = 1 << pwt.chroma_log2_weight_denom; + let chroma_weight_l0_flag = pwt.chroma_weight_l0[i][0] != default_weight + || pwt.chroma_weight_l0[i][1] != default_weight + || pwt.chroma_offset_l0[i][0] != 0 + || pwt.chroma_offset_l0[i][1] != 0; + self.u(1, chroma_weight_l0_flag as u32)?; + if chroma_weight_l0_flag { + for j in 0..2 { + self.se(pwt.chroma_weight_l0[i][j])?; + self.se(pwt.chroma_offset_l0[i][j])?; + } + } + } + } + + if hdr.slice_type as u8 % 5 == 1 { + let num_ref_idx_l1 = (hdr.num_ref_idx_l1_active_minus1 + 1) as usize; + for i in 0..num_ref_idx_l1 { + let luma_weight_l1_flag = + pwt.luma_weight_l1[i] != (1 << pwt.luma_log2_weight_denom) as i16; + self.u(1, luma_weight_l1_flag as u32)?; + if luma_weight_l1_flag { + self.se(pwt.luma_weight_l1[i])?; + self.se(pwt.luma_offset_l1[i])?; + } + + if chroma_array_type != 0 { + let default_weight = (1 << pwt.chroma_log2_weight_denom) as i16; + let chroma_weight_l1_flag = pwt.chroma_weight_l1[i][0] != default_weight + || pwt.chroma_weight_l1[i][1] != default_weight + || pwt.chroma_offset_l1[i][0] != 0 + || pwt.chroma_offset_l1[i][1] != 0; + self.u(1, chroma_weight_l1_flag as u32)?; + if chroma_weight_l1_flag { + for j in 0..2 { + self.se(pwt.chroma_weight_l1[i][j])?; + self.se(pwt.chroma_offset_l1[i][j])?; + } + } + } + } + } + + Ok(()) + } + + fn dec_ref_pic_marking(&mut self, hdr: &SliceHeader, is_idr: bool) -> SynthesizerResult<()> { + let rpm = &hdr.dec_ref_pic_marking; + + if is_idr { + self.u(1, rpm.no_output_of_prior_pics_flag as u32)?; + self.u(1, rpm.long_term_reference_flag as u32)?; + } else { + self.u(1, rpm.adaptive_ref_pic_marking_mode_flag as u32)?; + if rpm.adaptive_ref_pic_marking_mode_flag { + for marking in &rpm.inner { + self.ue(marking.memory_management_control_operation)?; + if marking.memory_management_control_operation == 1 + || marking.memory_management_control_operation == 3 + { + self.ue(marking.difference_of_pic_nums_minus1)?; + } + if marking.memory_management_control_operation == 2 { + self.ue(marking.long_term_pic_num)?; + } + if marking.memory_management_control_operation == 3 + || marking.memory_management_control_operation == 6 + { + self.ue(marking.long_term_frame_idx)?; + } + if marking.memory_management_control_operation == 4 { + self.ue(marking.max_long_term_frame_idx.to_value_plus1())?; + } + } + self.ue(0u32)?; + } + } + + Ok(()) + } +} + #[cfg(test)] mod tests { use std::io::Cursor; diff --git a/src/decoder/stateless.rs b/src/decoder/stateless.rs index 439ce1b9..bd53d39a 100644 --- a/src/decoder/stateless.rs +++ b/src/decoder/stateless.rs @@ -11,8 +11,10 @@ //! combining a codec codec to a [backend](crate::backend), after which bitstream units can be //! submitted through the [`StatelessDecoder::decode`] method. +#[cfg(feature = "av1")] pub mod av1; pub mod h264; +#[cfg(feature = "h265")] pub mod h265; pub mod vp8; pub mod vp9; diff --git a/src/decoder/stateless/h264/vaapi.rs b/src/decoder/stateless/h264/vaapi.rs index 8dab7da9..a16753f2 100644 --- a/src/decoder/stateless/h264/vaapi.rs +++ b/src/decoder/stateless/h264/vaapi.rs @@ -3,6 +3,7 @@ // found in the LICENSE file. use std::rc::Rc; +use std::sync::Arc; use anyhow::anyhow; use anyhow::Context as AnyhowContext; @@ -546,7 +547,7 @@ impl StatelessH264DecoderBackend for VaapiBackend { impl StatelessDecoder> { // Creates a new instance of the decoder using the VAAPI backend. pub fn new_vaapi( - display: Rc, + display: Arc, blocking_mode: BlockingMode, ) -> Result { Self::new(VaapiBackend::new(display, false), blocking_mode) diff --git a/src/decoder/stateless/vp8/vaapi.rs b/src/decoder/stateless/vp8/vaapi.rs index db85665b..299660d8 100644 --- a/src/decoder/stateless/vp8/vaapi.rs +++ b/src/decoder/stateless/vp8/vaapi.rs @@ -4,6 +4,7 @@ use std::convert::TryFrom; use std::rc::Rc; +use std::sync::Arc; use anyhow::anyhow; use anyhow::Context; @@ -290,7 +291,7 @@ impl StatelessVp8DecoderBackend for VaapiBackend { impl StatelessDecoder> { // Creates a new instance of the decoder using the VAAPI backend. pub fn new_vaapi( - display: Rc, + display: Arc, blocking_mode: BlockingMode, ) -> Result { Self::new(VaapiBackend::new(display, false), blocking_mode) diff --git a/src/decoder/stateless/vp9/vaapi.rs b/src/decoder/stateless/vp9/vaapi.rs index 8acaa5c0..d227927b 100644 --- a/src/decoder/stateless/vp9/vaapi.rs +++ b/src/decoder/stateless/vp9/vaapi.rs @@ -3,6 +3,7 @@ // found in the LICENSE file. use std::rc::Rc; +use std::sync::Arc; use anyhow::anyhow; use anyhow::Context; @@ -290,7 +291,7 @@ impl StatelessVp9DecoderBackend for VaapiBackend { impl StatelessDecoder> { // Creates a new instance of the decoder using the VAAPI backend. pub fn new_vaapi( - display: Rc, + display: Arc, blocking_mode: BlockingMode, ) -> Result { Self::new(VaapiBackend::new(display, true), blocking_mode) diff --git a/src/encoder.rs b/src/encoder.rs index 43da14be..af70a6c7 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -4,8 +4,10 @@ use std::fmt; +#[cfg(feature = "av1")] pub mod av1; pub mod h264; +#[cfg(feature = "h265")] pub mod h265; pub mod vp8; pub mod vp9; @@ -13,6 +15,7 @@ pub mod vp9; pub mod stateful; pub mod stateless; +#[cfg(feature = "av1")] use crate::codec::av1::synthesizer::SynthesizerError as AV1SynthesizerError; use crate::codec::h264::synthesizer::SynthesizerError as H264SynthesizerError; use crate::encoder::stateful::StatefulBackendError; @@ -25,6 +28,9 @@ pub enum RateControl { /// The encoder shall maintain the constant bitrate ConstantBitrate(u64), + /// The encoder shall target a bitrate value, but it may vary until the maximum bitrate + VariableBitrate { target: u64, maximum: u64 }, + /// The encoder shall maintain codec specific quality parameter constant (eg. QP for H.264) /// disregarding bitrate. ConstantQuality(u32), @@ -39,6 +45,15 @@ impl RateControl { pub(crate) fn bitrate_target(&self) -> Option { match self { RateControl::ConstantBitrate(target) => Some(*target), + RateControl::VariableBitrate { target, .. } => Some(*target), + RateControl::ConstantQuality(_) => None, + } + } + + pub(crate) fn bitrate_maximum(&self) -> Option { + match self { + RateControl::ConstantBitrate(bitrate) => Some(*bitrate), + RateControl::VariableBitrate { maximum, .. } => Some(*maximum), RateControl::ConstantQuality(_) => None, } } @@ -64,6 +79,12 @@ pub struct Tunings { pub min_quality: u32, /// Maximum value of codec specific quality parameter constant (eg. QP for H.264) pub max_quality: u32, + /// Buffer size, in bits, used for rate control + pub rc_buffer_size: Option, + /// Maximum frame size, in bits + pub max_frame_size: Option, + /// Codec and implementation defined quality + pub quality: Option, } impl Default for Tunings { @@ -73,6 +94,9 @@ impl Default for Tunings { framerate: 30, min_quality: 0, max_quality: u32::MAX, + rc_buffer_size: None, + max_frame_size: None, + quality: None, } } } @@ -83,6 +107,7 @@ pub struct FrameMetadata { pub timestamp: u64, pub layout: FrameLayout, pub force_keyframe: bool, + pub force_idr: bool, } /// Encoder's coded output with contained frame. @@ -113,6 +138,7 @@ pub enum EncodeError { StatelessBackendError(StatelessBackendError), StatefulBackendError(StatefulBackendError), H264SynthesizerError(H264SynthesizerError), + #[cfg(feature = "av1")] AV1SynthesizerError(AV1SynthesizerError), } @@ -126,6 +152,7 @@ impl fmt::Display for EncodeError { EncodeError::StatelessBackendError(x) => write!(f, "{}", x.to_string()), EncodeError::StatefulBackendError(x) => write!(f, "{}", x.to_string()), EncodeError::H264SynthesizerError(x) => write!(f, "{}", x.to_string()), + #[cfg(feature = "av1")] EncodeError::AV1SynthesizerError(x) => write!(f, "{}", x.to_string()), } } @@ -149,6 +176,7 @@ impl From for EncodeError { } } +#[cfg(feature = "av1")] impl From for EncodeError { fn from(err: AV1SynthesizerError) -> Self { EncodeError::AV1SynthesizerError(err) @@ -409,8 +437,12 @@ pub(crate) mod tests { panic!("Unrecognized frame layout used during test"); } - let meta = - FrameMetadata { timestamp, layout: frame.layout.clone(), force_keyframe: false }; + let meta = FrameMetadata { + timestamp, + layout: frame.layout.clone(), + force_keyframe: false, + force_idr: false, + }; (meta, frame) }) diff --git a/src/encoder/stateless.rs b/src/encoder/stateless.rs index be9e87a1..3b9e70f9 100644 --- a/src/encoder/stateless.rs +++ b/src/encoder/stateless.rs @@ -14,7 +14,7 @@ use crate::encoder::Tunings; use crate::encoder::VideoEncoder; use crate::BlockingMode; -#[cfg(feature = "vaapi")] +#[cfg(all(feature = "vaapi", feature = "av1"))] pub mod av1; #[cfg(feature = "vaapi")] pub mod h264; diff --git a/src/encoder/stateless/av1/vaapi.rs b/src/encoder/stateless/av1/vaapi.rs index 9449fb7b..f9f41f1f 100644 --- a/src/encoder/stateless/av1/vaapi.rs +++ b/src/encoder/stateless/av1/vaapi.rs @@ -675,8 +675,12 @@ mod tests { upload_test_frame_nv12(&display, &surface, 0.0); - let input_meta = - FrameMetadata { layout: frame_layout, force_keyframe: false, timestamp: 0 }; + let input_meta = FrameMetadata { + layout: frame_layout, + force_keyframe: false, + timestamp: 0, + force_idr: false, + }; let input = backend.import_picture(&input_meta, surface).unwrap(); diff --git a/src/encoder/stateless/h264/predictor.rs b/src/encoder/stateless/h264/predictor.rs index 9141e6d0..cb019b4f 100644 --- a/src/encoder/stateless/h264/predictor.rs +++ b/src/encoder/stateless/h264/predictor.rs @@ -263,7 +263,6 @@ impl } fn apply_tunings(&mut self, _tunings: &Tunings) -> EncodeResult<()> { - self.new_sequence(); Ok(()) } } diff --git a/src/encoder/stateless/h264/vaapi.rs b/src/encoder/stateless/h264/vaapi.rs index 39e1122f..f9590344 100644 --- a/src/encoder/stateless/h264/vaapi.rs +++ b/src/encoder/stateless/h264/vaapi.rs @@ -5,11 +5,14 @@ use std::any::Any; use std::borrow::Borrow; use std::rc::Rc; +use std::sync::Arc; use anyhow::Context; use libva::BufferType; use libva::Display; use libva::EncCodedBuffer; +use libva::EncPackedHeaderParameter; +use libva::EncPackedHeaderType; use libva::EncPictureParameter; use libva::EncPictureParameterBufferH264; use libva::EncSequenceParameter; @@ -28,6 +31,7 @@ use libva::VAProfile; use libva::VA_INVALID_ID; use libva::VA_PICTURE_H264_LONG_TERM_REFERENCE; use libva::VA_PICTURE_H264_SHORT_TERM_REFERENCE; +use log::warn; use crate::backend::vaapi::encoder::tunings_to_libva_rc; use crate::backend::vaapi::encoder::CodedOutputPromise; @@ -37,6 +41,8 @@ use crate::codec::h264::parser::Pps; use crate::codec::h264::parser::Profile; use crate::codec::h264::parser::SliceHeader; use crate::codec::h264::parser::Sps; +use crate::codec::h264::synthesizer::Synthesizer; +use crate::codec::h264::synthesizer::SynthesizerResult; use crate::encoder::h264::EncoderConfig; use crate::encoder::h264::H264; use crate::encoder::stateless::h264::predictor::MAX_QP; @@ -345,6 +351,42 @@ where header.slice_beta_offset_div2, ))) } + + fn build_enc_packed_slice_param_and_data( + request: &Request<'_, H>, + ) -> SynthesizerResult<(BufferType, BufferType)> { + const ENABLE_EMULATION_PREVENTION: bool = false; + let (buffer, length_in_bits) = + Self::build_slice_header(request, ENABLE_EMULATION_PREVENTION)?; + let packed_slice_param = + BufferType::EncPackedHeaderParameter(EncPackedHeaderParameter::new( + EncPackedHeaderType::Slice, + length_in_bits as u32, + ENABLE_EMULATION_PREVENTION, + )); + let packed_slice_data = BufferType::EncPackedHeaderData(buffer); + Ok((packed_slice_param, packed_slice_data)) + } + + fn build_slice_header( + request: &Request<'_, H>, + ep_enabled: bool, + ) -> SynthesizerResult<(Vec, usize)> { + let mut buffer = Vec::new(); + + let num_trailing_bits = Synthesizer::::synthesize( + &request.header, + &request.sps, + &request.pps, + request.is_idr, + request.dpb_meta.is_reference, + &mut buffer, + ep_enabled, + )?; + + let length_in_bits = buffer.len() * 8 - num_trailing_bits as usize; + Ok((buffer, length_in_bits)) + } } impl StatelessH264EncoderBackend for VaapiBackend @@ -386,6 +428,17 @@ where .map(|entry| entry as Rc) .collect(); + let packed_slice_header_buffers = if self + .supports_packed_header(libva::VA_ENC_PACKED_HEADER_SLICE)? + { + Some( + Self::build_enc_packed_slice_param_and_data(&request) + .map_err(|e| anyhow::anyhow!("Failed to build packed slice header: {}", e))?, + ) + } else { + None + }; + // Clone picture using [`Picture::new_from_same_surface`] to avoid // creatig a shared cell picture between its references and processed // picture. @@ -406,26 +459,60 @@ where picture.add_buffer(self.context().create_buffer(seq_param)?); picture.add_buffer(self.context().create_buffer(pic_param)?); picture.add_buffer(self.context().create_buffer(slice_param)?); + + if let Some((packed_slice_param, packed_slice_data)) = packed_slice_header_buffers { + picture.add_buffer(self.context().create_buffer(packed_slice_param)?); + picture.add_buffer(self.context().create_buffer(packed_slice_data)?); + } + picture.add_buffer(self.context().create_buffer(rc_param)?); picture.add_buffer(self.context().create_buffer(framerate_param)?); + if let Some(rc_buffer_size) = request.tunings.rc_buffer_size { + let hrd_buffer_size = rc_buffer_size as u32; + let hrd_buffer_fullness = hrd_buffer_size * 3 / 4; + + let hrd_param = BufferType::EncMiscParameter(libva::EncMiscParameter::HRD( + libva::EncMiscParameterHRD::new(hrd_buffer_size, hrd_buffer_fullness), + )); + picture.add_buffer(self.context().create_buffer(hrd_param)?); + } + + if let Some(max_frame_size) = request.tunings.max_frame_size { + if self.supports_max_frame_size()? { + let max_frame_size_param = + BufferType::EncMiscParameter(libva::EncMiscParameter::MaxFrameSize( + libva::EncMiscParameterBufferMaxFrameSize::new(max_frame_size as u32), + )); + picture.add_buffer(self.context().create_buffer(max_frame_size_param)?); + } else { + warn!("Max frame size not supported"); + } + } + + if let Some(quality) = request.tunings.quality { + if self.supports_quality_range(quality)? { + let quality_param = + BufferType::EncMiscParameter(libva::EncMiscParameter::QualityLevel( + libva::EncMiscParameterBufferQualityLevel::new(quality), + )); + picture.add_buffer(self.context().create_buffer(quality_param)?); + } else { + warn!("Quality level not supported"); + } + } + // Start processing the picture encoding let picture = picture.begin().context("picture begin")?; let picture = picture.render().context("picture render")?; let picture = picture.end().context("picture end")?; - // HACK: Make sure that slice nalu start code is at least 4 bytes. - // TODO: Use packed headers to supply slice header with nalu start code of size 4 and get - // rid of this hack. - let mut coded_output = request.coded_output; - coded_output.push(0); - // libva will handle the synchronization of reconstructed surface with implicit fences. // Therefore return the reconstructed frame immediately. let reference_promise = ReadyPromise::from(recon); let bitstream_promise = - CodedOutputPromise::new(picture, references, coded_buf, coded_output); + CodedOutputPromise::new(picture, references, coded_buf, request.coded_output); Ok((reference_promise, bitstream_promise)) } @@ -433,7 +520,7 @@ where impl StatelessEncoder>> { pub fn new_vaapi( - display: Rc, + display: Arc, config: EncoderConfig, fourcc: Fourcc, coded_size: Resolution, @@ -449,6 +536,38 @@ impl StatelessEncoder libva::VA_RC_CBR, + RateControl::VariableBitrate { .. } => libva::VA_RC_VBR, + RateControl::ConstantQuality(_) => libva::VA_RC_CQP, + }; + + let backend = + VaapiBackend::new(display, va_profile, fourcc, coded_size, bitrate_control, low_power)?; + + Self::new_h264(backend, config, blocking_mode) + } +} + +impl> + 'static> + StatelessEncoder> +{ + pub fn new_native_vaapi( + display: Arc, + config: EncoderConfig, + fourcc: Fourcc, + coded_size: Resolution, + low_power: bool, + blocking_mode: BlockingMode, + ) -> EncodeResult { + let va_profile = match config.profile { + Profile::Baseline => VAProfile::VAProfileH264ConstrainedBaseline, + Profile::Main => VAProfile::VAProfileH264Main, + Profile::High => VAProfile::VAProfileH264High, + _ => return Err(StatelessBackendError::UnsupportedProfile.into()), + }; + + let bitrate_control = match config.initial_tunings.rate_control { + RateControl::ConstantBitrate(_) => libva::VA_RC_CBR, + RateControl::VariableBitrate { .. } => libva::VA_RC_VBR, RateControl::ConstantQuality(_) => libva::VA_RC_CQP, }; @@ -543,8 +662,12 @@ pub(super) mod tests { upload_test_frame_nv12(&display, &surface, 0.0); - let input_meta = - FrameMetadata { layout: frame_layout, force_keyframe: false, timestamp: 0 }; + let input_meta = FrameMetadata { + layout: frame_layout, + force_keyframe: false, + timestamp: 0, + force_idr: false, + }; let pic = backend.import_picture(&input_meta, surface).unwrap(); @@ -656,7 +779,7 @@ pub(super) mod tests { ], }; - let mut encoder = VaapiH264Encoder::new_vaapi( + let mut encoder = VaapiH264Encoder::new_native_vaapi( Rc::clone(&display), config, frame_layout.format.0, diff --git a/src/encoder/stateless/predictor.rs b/src/encoder/stateless/predictor.rs index 1dddf185..cf8d5aca 100644 --- a/src/encoder/stateless/predictor.rs +++ b/src/encoder/stateless/predictor.rs @@ -89,7 +89,7 @@ where // SAFETY: checked in loop condition let (_, tunings) = self.tunings_queue.pop_front().unwrap(); - log::info!("Applying tuning {tunings:?}"); + log::debug!("Applying tuning {tunings:?}"); self.apply_tunings(&tunings)?; self.tunings = tunings; } @@ -97,13 +97,48 @@ where Ok(()) } + fn adjust_tunings_for_wrap(&mut self, old_counter: usize) { + if old_counter == 0 { + return; + } + + for (when_counter, _) in self.tunings_queue.iter_mut() { + if *when_counter >= old_counter { + *when_counter -= old_counter; + } else { + *when_counter = 0; + } + } + } + + fn increment_counter(&mut self) { + let old_counter = self.counter; + self.counter = self.counter.wrapping_add(1) % (self.limit as usize); + + if self.counter == 0 && old_counter != 0 { + // Counter wrapped around naturally at the limit + self.adjust_tunings_for_wrap(old_counter); + } + } + fn next_request(&mut self) -> EncodeResult> { log::trace!("Pending frames in the queue: {}", self.queue.len()); let mut requests = Vec::new(); - while let Some((input, meta)) = self.queue.pop_front() { + while let Some((input, mut meta)) = self.queue.pop_front() { self.pop_tunings()?; + if meta.force_idr { + self.adjust_tunings_for_wrap(self.counter); + self.counter = 0; + meta.force_keyframe = true; + } + + if self.counter == 0 && !meta.force_idr { + // Natural IDR due to limit wrap - ensure metadata reflects this + meta.force_idr = true; + } + if self.counter == 0 || meta.force_keyframe { log::trace!("Requesting keyframe/IDR for timestamp={}", meta.timestamp); // If first frame in the sequence or forced IDR then clear references and create @@ -114,7 +149,7 @@ where let request = self.request_keyframe(input, meta, self.counter == 0)?; requests.push(request); - self.counter = self.counter.wrapping_add(1) % (self.limit as usize); + self.increment_counter(); } else if self.references.is_empty() { log::trace!("Awaiting more reconstructed frames"); // There is no enough frames reconstructed @@ -125,7 +160,7 @@ where let request = self.request_interframe(input, meta)?; requests.push(request); - self.counter = self.counter.wrapping_add(1) % (self.limit as usize); + self.increment_counter(); break; } @@ -243,6 +278,7 @@ mod tests { planes: vec![], }, force_keyframe, + force_idr: false, } } diff --git a/src/encoder/stateless/vp9/vaapi.rs b/src/encoder/stateless/vp9/vaapi.rs index 16f7cf4d..1a7a83d2 100644 --- a/src/encoder/stateless/vp9/vaapi.rs +++ b/src/encoder/stateless/vp9/vaapi.rs @@ -5,6 +5,7 @@ use std::any::Any; use std::borrow::Borrow; use std::rc::Rc; +use std::sync::Arc; use anyhow::Context; use libva::BufferType; @@ -257,7 +258,7 @@ where impl StatelessEncoder>> { pub fn new_vaapi( - display: Rc, + display: Arc, config: EncoderConfig, fourcc: Fourcc, coded_size: Resolution, @@ -266,6 +267,7 @@ impl StatelessEncoder EncodeResult { let bitrate_control = match config.initial_tunings.rate_control { RateControl::ConstantBitrate(_) => libva::VA_RC_CBR, + RateControl::VariableBitrate { .. } => libva::VA_RC_VBR, RateControl::ConstantQuality(_) => libva::VA_RC_CQP, }; @@ -364,8 +366,12 @@ pub(super) mod tests { upload_test_frame_nv12(&display, &surface, 0.0); - let input_meta = - FrameMetadata { layout: frame_layout, force_keyframe: false, timestamp: 0 }; + let input_meta = FrameMetadata { + layout: frame_layout, + force_keyframe: false, + force_idr: false, + timestamp: 0, + }; let pic = backend.import_picture(&input_meta, surface).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index a8bcbce7..f27136b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ pub mod codec; #[cfg(feature = "backend")] pub mod backend; -#[cfg(feature = "backend")] +#[cfg(feature = "c2-wrapper")] pub mod c2_wrapper; #[cfg(feature = "backend")] pub mod decoder; @@ -270,9 +270,11 @@ impl From for Fourcc { #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum EncodedFormat { H264, + #[cfg(feature = "h265")] H265, VP8, VP9, + #[cfg(feature = "av1")] AV1, } @@ -282,9 +284,11 @@ impl FromStr for EncodedFormat { fn from_str(s: &str) -> Result { match s { "h264" | "H264" => Ok(EncodedFormat::H264), + #[cfg(feature = "h265")] "h265" | "H265" => Ok(EncodedFormat::H265), "vp8" | "VP8" => Ok(EncodedFormat::VP8), "vp9" | "VP9" => Ok(EncodedFormat::VP9), + #[cfg(feature = "av1")] "av1" | "AV1" => Ok(EncodedFormat::AV1), _ => Err("unrecognized input format. Valid values: h264, h265, vp8, vp9, av1"), } @@ -295,9 +299,11 @@ impl From for EncodedFormat { fn from(fourcc: Fourcc) -> EncodedFormat { match fourcc.to_string().as_str() { "H264" => EncodedFormat::H264, + #[cfg(feature = "h265")] "HEVC" => EncodedFormat::H265, "VP80" => EncodedFormat::VP8, "VP90" => EncodedFormat::VP9, + #[cfg(feature = "av1")] "AV1F" => EncodedFormat::AV1, _ => todo!("Fourcc {} not yet supported", fourcc), } @@ -308,9 +314,11 @@ impl From for Fourcc { fn from(format: EncodedFormat) -> Fourcc { match format { EncodedFormat::H264 => Fourcc::from(b"H264"), + #[cfg(feature = "h265")] EncodedFormat::H265 => Fourcc::from(b"HEVC"), EncodedFormat::VP8 => Fourcc::from(b"VP80"), EncodedFormat::VP9 => Fourcc::from(b"VP90"), + #[cfg(feature = "av1")] EncodedFormat::AV1 => Fourcc::from(b"AV1F"), } } diff --git a/src/video_frame.rs b/src/video_frame.rs index 2cd8a2be..7371e5e0 100644 --- a/src/video_frame.rs +++ b/src/video_frame.rs @@ -4,9 +4,7 @@ use std::cell::RefCell; use std::fmt::Debug; -#[cfg(feature = "vaapi")] -use std::rc::Rc; -#[cfg(feature = "v4l2")] +#[cfg(any(feature = "vaapi", feature = "v4l2"))] use std::sync::Arc; use crate::utils::align_up; @@ -16,7 +14,7 @@ use crate::Fourcc; use crate::Resolution; pub mod frame_pool; -#[cfg(feature = "backend")] +#[cfg(feature = "gbm")] pub mod gbm_video_frame; #[cfg(feature = "backend")] pub mod generic_dma_video_frame; @@ -274,7 +272,7 @@ pub trait VideoFrame: Send + Sync + Sized + Debug + 'static { fn process_dqbuf(&mut self, device: Arc, format: &Format, buf: &V4l2Buffer); #[cfg(feature = "vaapi")] - fn to_native_handle(&self, display: &Rc) -> Result; + fn to_native_handle(&self, display: &Arc) -> Result; } // Rust has restrictions about implementing foreign types, so this is a stupid workaround to get diff --git a/src/video_frame/frame_pool.rs b/src/video_frame/frame_pool.rs index b186b72c..66803174 100644 --- a/src/video_frame/frame_pool.rs +++ b/src/video_frame/frame_pool.rs @@ -3,8 +3,6 @@ // found in the LICENSE file. use std::collections::VecDeque; -#[cfg(feature = "vaapi")] -use std::rc::Rc; use std::sync::Arc; use std::sync::Mutex; use std::sync::Weak; @@ -74,7 +72,7 @@ impl VideoFrame for PooledVideoFrame { } #[cfg(feature = "vaapi")] - fn to_native_handle(&self, display: &Rc) -> Result { + fn to_native_handle(&self, display: &Arc) -> Result { self.inner.as_ref().unwrap().to_native_handle(display) } } diff --git a/src/video_frame/generic_dma_video_frame.rs b/src/video_frame/generic_dma_video_frame.rs index fffc9609..ec2e80eb 100644 --- a/src/video_frame/generic_dma_video_frame.rs +++ b/src/video_frame/generic_dma_video_frame.rs @@ -12,11 +12,9 @@ use std::iter::zip; use std::num::NonZeroUsize; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd}; use std::ptr::NonNull; -#[cfg(feature = "vaapi")] -use std::rc::Rc; use std::slice; use std::sync::atomic::{fence, Ordering}; -#[cfg(feature = "v4l2")] +#[cfg(any(feature = "vaapi", feature = "v4l2"))] use std::sync::Arc; use crate::video_frame::{ReadMapping, VideoFrame, WriteMapping}; @@ -514,7 +512,7 @@ impl VideoFrame for GenericDmaVideoFrame { fn process_dqbuf(&mut self, _device: Arc, _format: &Format, _buf: &V4l2Buffer) {} #[cfg(feature = "vaapi")] - fn to_native_handle(&self, display: &Rc) -> Result { + fn to_native_handle(&self, display: &Arc) -> Result { if self.is_compressed() { return Err("Compressed buffer export to VA-API is not currently supported".to_string()); } @@ -537,8 +535,7 @@ impl VideoFrame for GenericDmaVideoFrame { Some(u32::from(self.layout.format.0)), self.resolution().width, self.resolution().height, - // TODO: Should we add USAGE_HINT_ENCODER support? - Some(UsageHint::USAGE_HINT_DECODER), + Some(UsageHint::USAGE_HINT_ENCODER), vec![self.clone()], ) .map_err(|_| "Error importing GenericDmaVideoFrame to VA-API".to_string())?;