1 // Copyright 2023 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 //! ccdec, a simple decoder program using cros-codecs. Capable of computing MD5 checksums from the
6 //! input and writing the raw decoded frames to a file.
7
8 use std::borrow::Cow;
9 use std::fs::File;
10 use std::io::Read;
11 use std::io::Write;
12 use std::path::PathBuf;
13 use std::sync::Arc;
14 use std::sync::Mutex;
15 use std::thread;
16 use std::time::Duration;
17
18 use std::sync::atomic::{AtomicU64, Ordering};
19
20 use cros_codecs::bitstream_utils::IvfIterator;
21 use cros_codecs::bitstream_utils::NalIterator;
22 use cros_codecs::c2_wrapper::c2_decoder::C2DecoderWorker;
23 #[cfg(feature = "v4l2")]
24 use cros_codecs::c2_wrapper::c2_v4l2_decoder::C2V4L2Decoder;
25 #[cfg(feature = "v4l2")]
26 use cros_codecs::c2_wrapper::c2_v4l2_decoder::C2V4L2DecoderOptions;
27 #[cfg(feature = "vaapi")]
28 use cros_codecs::c2_wrapper::c2_vaapi_decoder::C2VaapiDecoder;
29 #[cfg(feature = "vaapi")]
30 use cros_codecs::c2_wrapper::c2_vaapi_decoder::C2VaapiDecoderOptions;
31 use cros_codecs::c2_wrapper::C2DecodeJob;
32 use cros_codecs::c2_wrapper::C2Status;
33 use cros_codecs::c2_wrapper::C2Wrapper;
34 use cros_codecs::c2_wrapper::DrainMode;
35 use cros_codecs::codec::h264::parser::Nalu as H264Nalu;
36 use cros_codecs::codec::h265::parser::Nalu as H265Nalu;
37 use cros_codecs::decoder::StreamInfo;
38 use cros_codecs::image_processing::nv12_to_i420;
39 use cros_codecs::utils::align_up;
40 use cros_codecs::video_frame::frame_pool::FramePool;
41 use cros_codecs::video_frame::frame_pool::PooledVideoFrame;
42 use cros_codecs::video_frame::gbm_video_frame::GbmDevice;
43 use cros_codecs::video_frame::gbm_video_frame::GbmUsage;
44 use cros_codecs::video_frame::generic_dma_video_frame::GenericDmaVideoFrame;
45 use cros_codecs::video_frame::VideoFrame;
46 use cros_codecs::video_frame::UV_PLANE;
47 use cros_codecs::video_frame::Y_PLANE;
48 use cros_codecs::DecodedFormat;
49 use cros_codecs::EncodedFormat;
50 use cros_codecs::Fourcc;
51
52 use crate::md5::md5_digest;
53 use crate::md5::MD5Context;
54 use crate::util::decide_output_file_name;
55 use crate::util::golden_md5s;
56 use crate::util::Args;
57 use crate::util::Md5Computation;
58
59 mod md5;
60 mod util;
61
62 // Returns the frame iterator for IVF file.
create_vpx_frame_iterator(input: &[u8]) -> Box<dyn Iterator<Item = Cow<[u8]>> + '_>63 fn create_vpx_frame_iterator(input: &[u8]) -> Box<dyn Iterator<Item = Cow<[u8]>> + '_> {
64 Box::new(IvfIterator::new(input).map(Cow::Borrowed))
65 }
66
main()67 fn main() {
68 env_logger::init();
69
70 let args: Args = argh::from_env();
71
72 let mut input = File::open(&args.input).expect("error opening input file");
73
74 assert!(
75 args.output_format == DecodedFormat::I420,
76 "Only I420 currently supported by VA-API ccdec"
77 );
78
79 let input = {
80 let mut buf = Vec::new();
81 input.read_to_end(&mut buf).expect("error reading input file");
82 buf
83 };
84
85 let mut output = if !args.multiple_output_files {
86 args.output.as_ref().map(|p| File::create(p).expect("error creating output file"))
87 } else {
88 None
89 };
90
91 let golden_iter = Arc::new(Mutex::new(golden_md5s(&args.golden).into_iter()));
92
93 let frame_iter =
94 match args.input_format {
95 EncodedFormat::H264 => Box::new(NalIterator::<H264Nalu>::new(&input))
96 as Box<dyn Iterator<Item = Cow<[u8]>>>,
97 EncodedFormat::H265 => Box::new(NalIterator::<H265Nalu>::new(&input))
98 as Box<dyn Iterator<Item = Cow<[u8]>>>,
99 _ => create_vpx_frame_iterator(&input),
100 };
101
102 let mut _md5_context = Arc::new(Mutex::new(MD5Context::new()));
103 let md5_context = _md5_context.clone();
104
105 let mut output_filename_idx = 0;
106 let need_per_frame_md5 = match args.compute_md5 {
107 Some(Md5Computation::Frame) => true,
108 _ => args.golden.is_some(),
109 };
110 let stream_mode = Some(Md5Computation::Stream) == args.compute_md5;
111
112 let gbm_device = Arc::new(
113 GbmDevice::open(PathBuf::from("/dev/dri/renderD128")).expect("Could not open GBM device!"),
114 );
115 let framepool = Arc::new(Mutex::new(FramePool::new(move |stream_info: &StreamInfo| {
116 <Arc<GbmDevice> as Clone>::clone(&gbm_device)
117 .new_frame(
118 Fourcc::from(b"NV12"),
119 stream_info.display_resolution,
120 stream_info.coded_resolution,
121 GbmUsage::Decode,
122 )
123 .expect("Could not allocate frame for frame pool!")
124 .to_generic_dma_video_frame()
125 .expect("Could not export GBM frame to DMA frame!")
126 })));
127 // This is a workaround to get "copy by clone" semantics for closure variable capture since
128 // Rust lacks the appropriate syntax to express this concept.
129 let _framepool = framepool.clone();
130 let framepool_hint_cb = move |stream_info: StreamInfo| {
131 (*_framepool.lock().unwrap()).resize(&stream_info);
132 };
133 let alloc_cb = move || (*framepool.lock().unwrap()).alloc();
134
135 let frames_needed = Arc::new(AtomicU64::new((*golden_iter.lock().unwrap()).len() as u64));
136
137 let _frames_needed = frames_needed.clone();
138 let on_new_frame = move |job: C2DecodeJob<PooledVideoFrame<GenericDmaVideoFrame<()>>>| {
139 if args.output.is_none() && args.compute_md5.is_none() && args.golden.is_none() {
140 return;
141 }
142
143 if job.output.is_none() {
144 // Indicates an empty "drain" signal.
145 assert_eq!(job.drain, DrainMode::EOSDrain);
146 return;
147 }
148
149 let width = job.output.as_ref().unwrap().resolution().width as usize;
150 let height = job.output.as_ref().unwrap().resolution().height as usize;
151 let luma_size = job.output.as_ref().unwrap().resolution().get_area();
152 let chroma_size = align_up(width, 2) / 2 * align_up(height, 2) / 2;
153 let mut frame_data: Vec<u8> = vec![0; luma_size + 2 * chroma_size];
154 let (dst_y, dst_uv) = frame_data.split_at_mut(luma_size);
155 let (dst_u, dst_v) = dst_uv.split_at_mut(chroma_size);
156 {
157 let src_pitches = job.output.as_ref().unwrap().get_plane_pitch();
158 let src_mapping =
159 job.output.as_ref().unwrap().map().expect("Failed to map output frame!");
160 let src_planes = src_mapping.get();
161 nv12_to_i420(
162 src_planes[Y_PLANE],
163 src_pitches[Y_PLANE],
164 dst_y,
165 width,
166 src_planes[UV_PLANE],
167 src_pitches[UV_PLANE],
168 dst_u,
169 align_up(width, 2) / 2,
170 dst_v,
171 align_up(width, 2) / 2,
172 width,
173 height,
174 );
175 }
176
177 if args.multiple_output_files {
178 let file_name = decide_output_file_name(
179 args.output.as_ref().expect("multiple_output_files need output to be set"),
180 output_filename_idx,
181 );
182
183 let mut output = File::create(file_name).expect("error creating output file");
184 output_filename_idx += 1;
185 output.write_all(&frame_data).expect("failed to write to output file");
186 } else if let Some(output) = &mut output {
187 output.write_all(&frame_data).expect("failed to write to output file");
188 }
189
190 let frame_md5: String =
191 if need_per_frame_md5 { md5_digest(&frame_data) } else { "".to_string() };
192
193 match args.compute_md5 {
194 None => (),
195 Some(Md5Computation::Frame) => println!("{}", frame_md5),
196 Some(Md5Computation::Stream) => (*md5_context.lock().unwrap()).consume(&frame_data),
197 }
198
199 if args.golden.is_some() {
200 assert_eq!(frame_md5, (*golden_iter.lock().unwrap()).next().unwrap());
201 (*_frames_needed).fetch_sub(1, Ordering::SeqCst);
202 }
203 };
204
205 let error_cb = move |_status: C2Status| {
206 panic!("Unrecoverable decoding error!");
207 };
208
209 #[cfg(feature = "vaapi")]
210 let mut decoder: C2Wrapper<_, C2DecoderWorker<_, C2VaapiDecoder>> = C2Wrapper::new(
211 Fourcc::from(args.input_format),
212 // TODO: Support other pixel formats
213 Fourcc::from(b"NV12"),
214 error_cb,
215 on_new_frame,
216 framepool_hint_cb,
217 alloc_cb,
218 C2VaapiDecoderOptions { libva_device_path: args.libva_device },
219 );
220 #[cfg(feature = "v4l2")]
221 let mut decoder: C2Wrapper<_, C2DecoderWorker<_, C2V4L2Decoder>> = C2Wrapper::new(
222 Fourcc::from(args.input_format),
223 // TODO: Support other pixel formats
224 Fourcc::from(b"NV12"),
225 error_cb,
226 on_new_frame,
227 framepool_hint_cb,
228 alloc_cb,
229 C2V4L2DecoderOptions { video_device_path: None },
230 );
231 let _ = decoder.start();
232
233 for (timestamp, input_frame) in frame_iter.enumerate() {
234 decoder.queue(vec![C2DecodeJob {
235 input: input_frame.as_ref().to_vec(),
236 timestamp: timestamp as u64,
237 ..Default::default()
238 }]);
239 }
240 decoder.drain(DrainMode::EOSDrain);
241
242 while decoder.is_alive() {
243 thread::sleep(Duration::from_millis(10));
244 }
245 decoder.stop();
246
247 assert!((*frames_needed).load(Ordering::SeqCst) == 0, "Not all frames were output.");
248
249 if stream_mode {
250 println!("{}", (*_md5_context.lock().unwrap()).flush());
251 }
252 }
253