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