• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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