1 // Copyright 2024 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 use std::sync::Arc; 6 7 use v4l2r::controls::codec::VideoGopSize; 8 use v4l2r::controls::codec::VideoVP8Profile; 9 use v4l2r::controls::codec::VideoVPXMaxQp; 10 use v4l2r::controls::codec::VideoVPXMinQp; 11 use v4l2r::device::Device; 12 13 use crate::backend::v4l2::encoder::CaptureBuffers; 14 use crate::backend::v4l2::encoder::ControlError; 15 use crate::backend::v4l2::encoder::EncoderCodec; 16 use crate::backend::v4l2::encoder::InitializationError; 17 use crate::backend::v4l2::encoder::OutputBufferHandle; 18 use crate::backend::v4l2::encoder::V4L2Backend; 19 use crate::encoder::stateful::StatefulEncoder; 20 use crate::encoder::vp8::EncoderConfig; 21 use crate::encoder::vp8::VP8; 22 use crate::encoder::PredictionStructure; 23 use crate::encoder::Tunings; 24 use crate::Fourcc; 25 use crate::Resolution; 26 27 const PIXEL_FORMAT_VP8: v4l2r::PixelFormat = v4l2r::PixelFormat::from_fourcc(b"VP80"); 28 29 pub type V4L2VP8Backend<Handle, CaptureBufferz> = V4L2Backend<Handle, CaptureBufferz, VP8>; 30 31 pub type V4L2StatefulVP8Encoder<Handle, CaptureBufferz> = 32 StatefulEncoder<Handle, V4L2VP8Backend<Handle, CaptureBufferz>>; 33 34 impl<Handle, CaptureBufferz> EncoderCodec for V4L2VP8Backend<Handle, CaptureBufferz> 35 where 36 Handle: OutputBufferHandle, 37 CaptureBufferz: CaptureBuffers, 38 { apply_tunings(device: &Device, tunings: &Tunings) -> Result<(), ControlError>39 fn apply_tunings(device: &Device, tunings: &Tunings) -> Result<(), ControlError> { 40 let min_qp = VideoVPXMinQp(tunings.min_quality.clamp(0, 127) as i32); 41 Self::apply_ctrl(device, "vpx min qp", min_qp)?; 42 43 let max_qp = VideoVPXMaxQp(tunings.max_quality.clamp(0, 127) as i32); 44 Self::apply_ctrl(device, "vpx max qp", max_qp)?; 45 46 Ok(()) 47 } 48 } 49 50 impl<Handle, CaptureBufferz> V4L2VP8Backend<Handle, CaptureBufferz> 51 where 52 Handle: OutputBufferHandle, 53 CaptureBufferz: CaptureBuffers, 54 { new( device: Arc<Device>, capture_buffers: CaptureBufferz, config: EncoderConfig, fourcc: Fourcc, coded_size: Resolution, tunings: Tunings, ) -> Result<Self, InitializationError>55 pub fn new( 56 device: Arc<Device>, 57 capture_buffers: CaptureBufferz, 58 config: EncoderConfig, 59 fourcc: Fourcc, 60 coded_size: Resolution, 61 tunings: Tunings, 62 ) -> Result<Self, InitializationError> { 63 match config.pred_structure { 64 PredictionStructure::LowDelay { limit } => { 65 let limit = limit as i32; 66 67 Self::apply_ctrl(&device, "gop size", VideoGopSize(limit))?; 68 } 69 } 70 71 // TODO: allow picking profile 72 Self::apply_ctrl(&device, "vp8 profile", VideoVP8Profile::Profile0)?; 73 74 Self::create( 75 device, 76 capture_buffers, 77 fourcc, 78 coded_size, 79 config.resolution, 80 PIXEL_FORMAT_VP8, 81 tunings, 82 ) 83 } 84 } 85 86 impl<Handle, CaptureBufferz> V4L2StatefulVP8Encoder<Handle, CaptureBufferz> 87 where 88 Handle: OutputBufferHandle, 89 CaptureBufferz: CaptureBuffers, 90 { new( device: Arc<Device>, capture_buffers: CaptureBufferz, config: EncoderConfig, fourcc: Fourcc, coded_size: Resolution, tunings: Tunings, ) -> Result<Self, InitializationError>91 pub fn new( 92 device: Arc<Device>, 93 capture_buffers: CaptureBufferz, 94 config: EncoderConfig, 95 fourcc: Fourcc, 96 coded_size: Resolution, 97 tunings: Tunings, 98 ) -> Result<Self, InitializationError> { 99 Ok(Self::create( 100 tunings.clone(), 101 V4L2VP8Backend::new(device, capture_buffers, config, fourcc, coded_size, tunings)?, 102 )) 103 } 104 } 105 106 #[cfg(test)] 107 mod tests { 108 use super::*; 109 110 use std::path::PathBuf; 111 use std::sync::Arc; 112 113 use v4l2r::device::Device; 114 use v4l2r::device::DeviceConfig; 115 116 use crate::backend::v4l2::encoder::find_device_with_capture; 117 use crate::backend::v4l2::encoder::tests::perform_v4l2_encoder_dmabuf_test; 118 use crate::backend::v4l2::encoder::tests::perform_v4l2_encoder_mmap_test; 119 use crate::backend::v4l2::encoder::tests::BoPoolAllocator; 120 use crate::backend::v4l2::encoder::tests::GbmDevice; 121 use crate::backend::v4l2::encoder::v4l2_format_to_frame_layout; 122 use crate::backend::v4l2::encoder::MmapingCapture; 123 use crate::bitstream_utils::IvfFileHeader; 124 use crate::bitstream_utils::IvfFrameHeader; 125 use crate::encoder::simple_encode_loop; 126 use crate::encoder::tests::userptr_test_frame_generator; 127 use crate::encoder::RateControl; 128 use crate::utils::DmabufFrame; 129 use crate::Resolution; 130 131 #[ignore] 132 // Ignore this test by default as it requires v4l2m2m-compatible hardware. 133 #[test] test_v4l2_encoder_userptr()134 fn test_v4l2_encoder_userptr() { 135 const VISIBLE_SIZE: Resolution = Resolution { width: 500, height: 500 }; 136 const CODED_SIZE: Resolution = Resolution { width: 512, height: 512 }; 137 const FRAME_COUNT: u64 = 100; 138 139 let _ = env_logger::try_init(); 140 141 let device = find_device_with_capture(PIXEL_FORMAT_VP8).expect("no VP8 encoder found"); 142 let device = Device::open(&device, DeviceConfig::new().non_blocking_dqbuf()).expect("open"); 143 let device = Arc::new(device); 144 145 let mut encoder = V4L2StatefulVP8Encoder::new( 146 device, 147 MmapingCapture, 148 EncoderConfig { resolution: VISIBLE_SIZE, ..Default::default() }, 149 Fourcc::from(b"NM12"), 150 CODED_SIZE, 151 Tunings { rate_control: RateControl::ConstantBitrate(400_000), ..Default::default() }, 152 ) 153 .unwrap(); 154 155 let format: v4l2r::Format = encoder.backend().output_format().unwrap(); 156 let layout = v4l2_format_to_frame_layout(&format); 157 158 let mut bitstream = Vec::new(); 159 160 let file_header = IvfFileHeader::new( 161 IvfFileHeader::CODEC_VP8, 162 VISIBLE_SIZE.width as u16, 163 VISIBLE_SIZE.height as u16, 164 30, 165 FRAME_COUNT as u32, 166 ); 167 168 file_header.writo_into(&mut bitstream).unwrap(); 169 170 let buffer_size = 171 format.plane_fmt.iter().map(|plane| plane.sizeimage).max().unwrap() as usize; 172 let mut frame_producer = userptr_test_frame_generator(FRAME_COUNT, layout, buffer_size); 173 174 simple_encode_loop(&mut encoder, &mut frame_producer, |coded| { 175 // Skip Duck IVF frame header from v4l2 stream, and replace with ours with correct 176 // timestamp. 177 let frame_bitstream = &coded.bitstream[4 + 8..]; 178 179 let header = IvfFrameHeader { 180 timestamp: coded.metadata.timestamp, 181 frame_size: frame_bitstream.len() as u32, 182 }; 183 184 header.writo_into(&mut bitstream).unwrap(); 185 bitstream.extend(frame_bitstream); 186 }) 187 .expect("encode loop"); 188 189 let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true"); 190 if write_to_file { 191 use std::io::Write; 192 let mut out = std::fs::File::create("test_v4l2_encoder_userptr.vp8.ivf").unwrap(); 193 out.write_all(&bitstream).unwrap(); 194 out.flush().unwrap(); 195 } 196 } 197 198 #[ignore] 199 // Ignore this test by default as it requires v4l2m2m-compatible hardware. 200 #[test] test_v4l2_encoder_mmap()201 fn test_v4l2_encoder_mmap() { 202 const VISIBLE_SIZE: Resolution = Resolution { width: 500, height: 500 }; 203 const CODED_SIZE: Resolution = Resolution { width: 512, height: 512 }; 204 const FRAME_COUNT: u64 = 100; 205 206 let _ = env_logger::try_init(); 207 208 let device = find_device_with_capture(PIXEL_FORMAT_VP8).expect("no VP8 encoder found"); 209 let device = Device::open(&device, DeviceConfig::new().non_blocking_dqbuf()).expect("open"); 210 let device = Arc::new(device); 211 212 let encoder = V4L2StatefulVP8Encoder::new( 213 device, 214 MmapingCapture, 215 EncoderConfig { resolution: VISIBLE_SIZE, ..Default::default() }, 216 Fourcc::from(b"NM12"), 217 CODED_SIZE, 218 Tunings { rate_control: RateControl::ConstantBitrate(2_000_000), ..Default::default() }, 219 ) 220 .unwrap(); 221 222 let mut bitstream = Vec::new(); 223 224 let file_header = IvfFileHeader::new( 225 IvfFileHeader::CODEC_VP8, 226 VISIBLE_SIZE.width as u16, 227 VISIBLE_SIZE.height as u16, 228 30, 229 FRAME_COUNT as u32, 230 ); 231 232 file_header.writo_into(&mut bitstream).unwrap(); 233 234 perform_v4l2_encoder_mmap_test(FRAME_COUNT, encoder, |coded| { 235 // Skip Duck IVF frame header from v4l2 stream, and replace with ours with correct 236 // timestamp. 237 let frame_bitstream = &coded.bitstream[4 + 8..]; 238 239 let header = IvfFrameHeader { 240 timestamp: coded.metadata.timestamp, 241 frame_size: frame_bitstream.len() as u32, 242 }; 243 244 header.writo_into(&mut bitstream).unwrap(); 245 bitstream.extend(frame_bitstream); 246 }); 247 248 let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true"); 249 if write_to_file { 250 use std::io::Write; 251 let mut out = std::fs::File::create("test_v4l2_encoder_mmap.vp8.ivf").unwrap(); 252 out.write_all(&bitstream).unwrap(); 253 out.flush().unwrap(); 254 } 255 } 256 257 #[ignore] 258 // Ignore this test by default as it requires v4l2m2m-compatible hardware. 259 #[test] test_v4l2_encoder_dmabuf()260 fn test_v4l2_encoder_dmabuf() { 261 const VISIBLE_SIZE: Resolution = Resolution { width: 500, height: 500 }; 262 const CODED_SIZE: Resolution = Resolution { width: 512, height: 512 }; 263 const FRAME_COUNT: u64 = 100; 264 265 let _ = env_logger::try_init(); 266 267 let device = find_device_with_capture(PIXEL_FORMAT_VP8).expect("no VP8 encoder found"); 268 let device = Device::open(&device, DeviceConfig::new().non_blocking_dqbuf()).expect("open"); 269 let device = Arc::new(device); 270 271 let gbm = GbmDevice::open(PathBuf::from("/dev/dri/renderD128")) 272 .and_then(gbm::Device::new) 273 .expect("failed to create GBM device"); 274 275 let gbm = Arc::new(gbm); 276 277 let encoder = V4L2StatefulVP8Encoder::<DmabufFrame, _>::new( 278 device.clone(), 279 BoPoolAllocator::new(gbm.clone()), 280 EncoderConfig { resolution: VISIBLE_SIZE, ..Default::default() }, 281 Fourcc::from(b"NV12"), 282 CODED_SIZE, 283 Tunings { 284 framerate: 30, 285 rate_control: RateControl::ConstantBitrate(400_000), 286 ..Default::default() 287 }, 288 ) 289 .unwrap(); 290 291 let mut bitstream = Vec::new(); 292 293 let file_header = IvfFileHeader::new( 294 IvfFileHeader::CODEC_VP8, 295 VISIBLE_SIZE.width as u16, 296 VISIBLE_SIZE.height as u16, 297 30, 298 FRAME_COUNT as u32, 299 ); 300 301 file_header.writo_into(&mut bitstream).unwrap(); 302 303 perform_v4l2_encoder_dmabuf_test( 304 CODED_SIZE, 305 VISIBLE_SIZE, 306 FRAME_COUNT, 307 gbm, 308 encoder, 309 |coded| { 310 // Skip Duck IVF frame header from v4l2 stream, and replace with ours with correct 311 // timestamp. 312 let frame_bitstream = &coded.bitstream[4 + 8..]; 313 314 let header = IvfFrameHeader { 315 timestamp: coded.metadata.timestamp, 316 frame_size: frame_bitstream.len() as u32, 317 }; 318 319 header.writo_into(&mut bitstream).unwrap(); 320 bitstream.extend(frame_bitstream); 321 }, 322 ); 323 324 let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true"); 325 if write_to_file { 326 use std::io::Write; 327 let mut out = std::fs::File::create("test_v4l2_encoder_dmabuf.vp8.ivf").unwrap(); 328 out.write_all(&bitstream).unwrap(); 329 out.flush().unwrap(); 330 } 331 } 332 } 333