• 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::VideoHEVCLevel;
9 use v4l2r::controls::codec::VideoHEVCMaxQp;
10 use v4l2r::controls::codec::VideoHEVCMinQp;
11 use v4l2r::controls::codec::VideoHEVCProfile;
12 use v4l2r::controls::codec::VideoPrependSpsPpsToIdr;
13 use v4l2r::device::Device;
14 
15 use crate::backend::v4l2::encoder::CaptureBuffers;
16 use crate::backend::v4l2::encoder::ControlError;
17 use crate::backend::v4l2::encoder::EncoderCodec;
18 use crate::backend::v4l2::encoder::InitializationError;
19 use crate::backend::v4l2::encoder::OutputBufferHandle;
20 use crate::backend::v4l2::encoder::UnsupportedError;
21 use crate::backend::v4l2::encoder::V4L2Backend;
22 use crate::codec::h265::parser::Level;
23 use crate::codec::h265::parser::Profile;
24 use crate::encoder::h265::EncoderConfig;
25 use crate::encoder::h265::H265;
26 use crate::encoder::stateful::StatefulEncoder;
27 use crate::encoder::PredictionStructure;
28 use crate::encoder::Tunings;
29 use crate::Fourcc;
30 use crate::Resolution;
31 
32 const PIXEL_FORMAT_HEVC: v4l2r::PixelFormat = v4l2r::PixelFormat::from_fourcc(b"HEVC");
33 
34 pub type V4L2H265Backend<Handle, CaptureBufferz> = V4L2Backend<Handle, CaptureBufferz, H265>;
35 
36 pub type V4L2StatefulH265Encoder<Handle, CaptureBufferz> =
37     StatefulEncoder<Handle, V4L2H265Backend<Handle, CaptureBufferz>>;
38 
39 impl From<Level> for VideoHEVCLevel {
from(value: Level) -> Self40     fn from(value: Level) -> Self {
41         match value {
42             Level::L1 => VideoHEVCLevel::L1_0,
43             Level::L2 => VideoHEVCLevel::L2_0,
44             Level::L2_1 => VideoHEVCLevel::L2_1,
45             Level::L3 => VideoHEVCLevel::L3_0,
46             Level::L3_1 => VideoHEVCLevel::L3_1,
47             Level::L4 => VideoHEVCLevel::L4_0,
48             Level::L4_1 => VideoHEVCLevel::L4_1,
49             Level::L5 => VideoHEVCLevel::L5_0,
50             Level::L5_1 => VideoHEVCLevel::L5_1,
51             Level::L5_2 => VideoHEVCLevel::L5_2,
52             Level::L6 => VideoHEVCLevel::L6_0,
53             Level::L6_1 => VideoHEVCLevel::L6_1,
54             Level::L6_2 => VideoHEVCLevel::L6_2,
55         }
56     }
57 }
58 
59 impl<Handle, CaptureBufferz> EncoderCodec for V4L2H265Backend<Handle, CaptureBufferz>
60 where
61     Handle: OutputBufferHandle,
62     CaptureBufferz: CaptureBuffers,
63 {
apply_tunings(device: &Device, tunings: &Tunings) -> Result<(), ControlError>64     fn apply_tunings(device: &Device, tunings: &Tunings) -> Result<(), ControlError> {
65         let min_qp = VideoHEVCMinQp(tunings.min_quality.clamp(1, 51) as i32);
66         Self::apply_ctrl(device, "hevc min qp", min_qp)?;
67 
68         let max_qp = VideoHEVCMaxQp(tunings.max_quality.clamp(1, 51) as i32);
69         Self::apply_ctrl(device, "hevc max qp", max_qp)?;
70 
71         Ok(())
72     }
73 }
74 
75 impl<Handle, CaptureBufferz> V4L2H265Backend<Handle, CaptureBufferz>
76 where
77     Handle: OutputBufferHandle,
78     CaptureBufferz: CaptureBuffers,
79 {
new( device: Arc<Device>, capture_buffers: CaptureBufferz, config: EncoderConfig, fourcc: Fourcc, coded_size: Resolution, tunings: Tunings, ) -> Result<Self, InitializationError>80     pub fn new(
81         device: Arc<Device>,
82         capture_buffers: CaptureBufferz,
83         config: EncoderConfig,
84         fourcc: Fourcc,
85         coded_size: Resolution,
86         tunings: Tunings,
87     ) -> Result<Self, InitializationError> {
88         match config.pred_structure {
89             PredictionStructure::LowDelay { limit } => {
90                 let limit = limit as i32;
91 
92                 Self::apply_ctrl(&device, "gop size", VideoGopSize(limit))?;
93             }
94         }
95 
96         Self::apply_ctrl(&device, "prepend SPS PPS to IDR", VideoPrependSpsPpsToIdr(true))?;
97 
98         let profile = match config.profile {
99             Profile::Main => VideoHEVCProfile::Main,
100             Profile::Main10 => VideoHEVCProfile::Main10,
101             Profile::MainStill => VideoHEVCProfile::MainStill,
102 
103             _ => {
104                 log::error!("Unrecognized h265 profile for v4l2 backend: {:?}", config.profile);
105                 return Err(InitializationError::Unsupported(UnsupportedError::Profile));
106             }
107         };
108 
109         Self::apply_ctrl(&device, "h265 profile", profile)?;
110 
111         Self::apply_ctrl(&device, "h265 level", VideoHEVCLevel::from(config.level))?;
112 
113         Self::create(
114             device,
115             capture_buffers,
116             fourcc,
117             coded_size,
118             config.resolution,
119             PIXEL_FORMAT_HEVC,
120             tunings,
121         )
122     }
123 }
124 
125 impl<Handle, CaptureBufferz> V4L2StatefulH265Encoder<Handle, CaptureBufferz>
126 where
127     Handle: OutputBufferHandle,
128     CaptureBufferz: CaptureBuffers,
129 {
new( device: Arc<Device>, capture_buffers: CaptureBufferz, config: EncoderConfig, fourcc: Fourcc, coded_size: Resolution, tunings: Tunings, ) -> Result<Self, InitializationError>130     pub fn new(
131         device: Arc<Device>,
132         capture_buffers: CaptureBufferz,
133         config: EncoderConfig,
134         fourcc: Fourcc,
135         coded_size: Resolution,
136         tunings: Tunings,
137     ) -> Result<Self, InitializationError> {
138         Ok(Self::create(
139             tunings.clone(),
140             V4L2H265Backend::new(device, capture_buffers, config, fourcc, coded_size, tunings)?,
141         ))
142     }
143 }
144 
145 #[cfg(test)]
146 mod tests {
147     use super::*;
148 
149     use std::path::PathBuf;
150     use std::sync::Arc;
151 
152     use v4l2r::device::Device;
153     use v4l2r::device::DeviceConfig;
154 
155     use crate::backend::v4l2::encoder::find_device_with_capture;
156     use crate::backend::v4l2::encoder::tests::perform_v4l2_encoder_dmabuf_test;
157     use crate::backend::v4l2::encoder::tests::perform_v4l2_encoder_mmap_test;
158     use crate::backend::v4l2::encoder::tests::BoPoolAllocator;
159     use crate::backend::v4l2::encoder::tests::GbmDevice;
160     use crate::backend::v4l2::encoder::v4l2_format_to_frame_layout;
161     use crate::backend::v4l2::encoder::MmapingCapture;
162     use crate::encoder::simple_encode_loop;
163     use crate::encoder::tests::userptr_test_frame_generator;
164     use crate::encoder::RateControl;
165     use crate::utils::DmabufFrame;
166     use crate::Resolution;
167 
168     #[ignore]
169     // Ignore this test by default as it requires v4l2m2m-compatible hardware.
170     #[test]
test_v4l2_encoder_userptr()171     fn test_v4l2_encoder_userptr() {
172         const VISIBLE_SIZE: Resolution = Resolution { width: 500, height: 500 };
173         const CODED_SIZE: Resolution = Resolution { width: 512, height: 512 };
174         const FRAME_COUNT: u64 = 100;
175 
176         let _ = env_logger::try_init();
177 
178         let device = find_device_with_capture(PIXEL_FORMAT_HEVC).expect("no H265 encoder found");
179         let device = Device::open(&device, DeviceConfig::new().non_blocking_dqbuf()).expect("open");
180         let device = Arc::new(device);
181 
182         let mut encoder = V4L2StatefulH265Encoder::new(
183             device,
184             MmapingCapture,
185             EncoderConfig { resolution: VISIBLE_SIZE, ..Default::default() },
186             Fourcc::from(b"NM12"),
187             CODED_SIZE,
188             Tunings { rate_control: RateControl::ConstantBitrate(400_000), ..Default::default() },
189         )
190         .unwrap();
191 
192         let format: v4l2r::Format = encoder.backend().output_format().unwrap();
193         let layout = v4l2_format_to_frame_layout(&format);
194 
195         let mut bitstream = Vec::new();
196         let buffer_size =
197             format.plane_fmt.iter().map(|plane| plane.sizeimage).max().unwrap() as usize;
198         let mut frame_producer = userptr_test_frame_generator(FRAME_COUNT, layout, buffer_size);
199 
200         simple_encode_loop(&mut encoder, &mut frame_producer, |coded| {
201             bitstream.extend(coded.bitstream)
202         })
203         .expect("encode loop");
204 
205         let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
206         if write_to_file {
207             use std::io::Write;
208             let mut out = std::fs::File::create("test_v4l2_encoder_userptr.h265").unwrap();
209             out.write_all(&bitstream).unwrap();
210             out.flush().unwrap();
211         }
212     }
213 
214     #[ignore]
215     // Ignore this test by default as it requires v4l2m2m-compatible hardware.
216     #[test]
test_v4l2_encoder_mmap()217     fn test_v4l2_encoder_mmap() {
218         const VISIBLE_SIZE: Resolution = Resolution { width: 500, height: 500 };
219         const CODED_SIZE: Resolution = Resolution { width: 512, height: 512 };
220         const FRAME_COUNT: u64 = 100;
221 
222         let _ = env_logger::try_init();
223 
224         let device = find_device_with_capture(PIXEL_FORMAT_HEVC).expect("no H265 encoder found");
225         let device = Device::open(&device, DeviceConfig::new().non_blocking_dqbuf()).expect("open");
226         let device = Arc::new(device);
227 
228         let encoder = V4L2StatefulH265Encoder::new(
229             device,
230             MmapingCapture,
231             EncoderConfig { resolution: VISIBLE_SIZE, ..Default::default() },
232             Fourcc::from(b"NM12"),
233             CODED_SIZE,
234             Tunings { rate_control: RateControl::ConstantBitrate(400_000), ..Default::default() },
235         )
236         .unwrap();
237 
238         let mut bitstream = Vec::new();
239         perform_v4l2_encoder_mmap_test(FRAME_COUNT, encoder, |coded| {
240             bitstream.extend(coded.bitstream)
241         });
242 
243         let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
244         if write_to_file {
245             use std::io::Write;
246             let mut out = std::fs::File::create("test_v4l2_encoder_mmap.h265").unwrap();
247             out.write_all(&bitstream).unwrap();
248             out.flush().unwrap();
249         }
250     }
251 
252     #[ignore]
253     // Ignore this test by default as it requires v4l2m2m-compatible hardware.
254     #[test]
test_v4l2_encoder_dmabuf()255     fn test_v4l2_encoder_dmabuf() {
256         const VISIBLE_SIZE: Resolution = Resolution { width: 500, height: 500 };
257         const CODED_SIZE: Resolution = Resolution { width: 512, height: 512 };
258         const FRAME_COUNT: u64 = 100;
259 
260         let _ = env_logger::try_init();
261 
262         let device = find_device_with_capture(PIXEL_FORMAT_HEVC).expect("no H265 encoder found");
263         let device = Device::open(&device, DeviceConfig::new().non_blocking_dqbuf()).expect("open");
264         let device = Arc::new(device);
265 
266         let gbm = GbmDevice::open(PathBuf::from("/dev/dri/renderD128"))
267             .and_then(gbm::Device::new)
268             .expect("failed to create GBM device");
269 
270         let gbm = Arc::new(gbm);
271 
272         let encoder = V4L2StatefulH265Encoder::<DmabufFrame, _>::new(
273             device.clone(),
274             BoPoolAllocator::new(gbm.clone()),
275             EncoderConfig { resolution: VISIBLE_SIZE, ..Default::default() },
276             Fourcc::from(b"NV12"),
277             CODED_SIZE,
278             Tunings {
279                 framerate: 30,
280                 rate_control: RateControl::ConstantBitrate(400_000),
281                 ..Default::default()
282             },
283         )
284         .unwrap();
285 
286         let mut bitstream = Vec::new();
287 
288         perform_v4l2_encoder_dmabuf_test(
289             CODED_SIZE,
290             VISIBLE_SIZE,
291             FRAME_COUNT,
292             gbm,
293             encoder,
294             |coded| bitstream.extend(coded.bitstream),
295         );
296 
297         let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
298         if write_to_file {
299             use std::io::Write;
300             let mut out = std::fs::File::create("test_v4l2_encoder_dmabuf.h265").unwrap();
301             out.write_all(&bitstream).unwrap();
302             out.flush().unwrap();
303         }
304     }
305 }
306