--- /dev/null
+use std::collections::VecDeque;
+use std::rc::Rc;
+
+use nihav_core::codecs::*;
+use nihav_core::io::byteio::*;
+use nihav_core::io::bitreader::*;
+
+use libva::*;
+
+use crate::{HWDecoder, HWDecoderCore, HWWrapper, fill_frame, profile_name};
+
+const NUM_REFS: usize = 8;
+const MAX_SB_FRAMES: usize = 8;
+
+trait ReadBitsSgn {
+ fn read_sgn(&mut self, n: u8) -> BitReaderResult<i32>;
+}
+
+impl<'a> ReadBitsSgn for BitReader<'a> {
+ fn read_sgn(&mut self, n: u8) -> BitReaderResult<i32> {
+ let val = self.read(n)? as i32;
+ if self.read_bool()? {
+ Ok(-val)
+ } else {
+ Ok(val)
+ }
+ }
+}
+
+const VP9_PROFILES: &[i32; 4] = &[
+ VAProfile::VAProfileVP9Profile0, VAProfile::VAProfileVP9Profile1,
+ VAProfile::VAProfileVP9Profile2, VAProfile::VAProfileVP9Profile3
+];
+
+enum QFrame {
+ Frame(bool, bool, Picture<PictureEnd>),
+ ShowAgain(u32),
+}
+
+#[allow(dead_code)]
+struct VaapiInternals {
+ display: Rc<Display>,
+ context: Rc<Context>,
+ surfaces: Vec<Surface>,
+ ifmt: VAImageFormat,
+ queue: VecDeque<(u64, QFrame)>,
+ ref_pics: Vec<Picture<PictureSync>>,
+}
+
+#[derive(Default)]
+struct FrameState {
+ width: u16,
+ height: u16,
+ profile: u8,
+ depth: u8,
+ cspace: u8,
+ crange: bool,
+ profile_supp: [bool; 4],
+ sub_x: u8,
+ sub_y: u8,
+ seg_tree_probs: [u8; 7],
+ seg_pred_probs: [u8; 3],
+ refresh_frame_flags: u8,
+}
+
+fn read_prob(br: &mut BitReader) -> DecoderResult<u8> {
+ if br.read_bool()? {
+ Ok(br.read(8)? as u8)
+ } else {
+ Ok(255)
+ }
+}
+
+impl FrameState {
+ fn read_color_config(&mut self, br: &mut BitReader) -> DecoderResult<()> {
+ if self.profile >= 2 {
+ self.depth = if br.read_bool()? { 12 } else { 10 };
+ } else {
+ self.depth = 8;
+ }
+ self.cspace = br.read(3)? as u8;
+ if self.cspace != 7 {
+ self.crange = br.read_bool()?;
+ if self.profile == 1 || self.profile == 3 {
+ self.sub_x = br.read(1)? as u8;
+ self.sub_y = br.read(1)? as u8;
+ let reserved_zero = br.read_bool()?;
+ validate!(!reserved_zero);
+ } else {
+ self.sub_x = 1;
+ self.sub_y = 1;
+ }
+ } else {
+ self.crange = true;
+ if self.profile == 1 || self.profile == 3 {
+ self.sub_x = 0;
+ self.sub_y = 0;
+ let reserved_zero = br.read_bool()?;
+ validate!(!reserved_zero);
+ }
+ }
+ Ok(())
+ }
+ fn read_frame_size(&mut self, br: &mut BitReader) -> DecoderResult<()> {
+ let width1 = br.read(16)? as u16;
+ let height1 = br.read(16)? as u16;
+ validate!(width1 < 16384 && height1 < 16384);
+ self.width = width1 + 1;
+ self.height = height1 + 1;
+ Ok(())
+ }
+ fn read_render_size(&mut self, br: &mut BitReader) -> DecoderResult<()> {
+ if br.read_bool()? {
+ let _render_width1 = br.read(16)? as u16;
+ let _render_height1 = br.read(16)? as u16;
+ }
+ Ok(())
+ }
+ fn do_frame(&mut self, vactx: &mut VaapiInternals, src: &[u8], timestamp: u64, ref_frames: &mut [u32; NUM_REFS]) -> DecoderResult<()> {
+ let mut br = BitReader::new(src, BitReaderMode::BE);
+ let frame_marker = br.read(2)?;
+ if frame_marker != 2 {
+ return Err(DecoderError::InvalidData);
+ }
+ let profile = br.read(2)? as usize;
+ if !self.profile_supp[profile] {
+ println!("Unsupported {}", profile_name(VP9_PROFILES[profile]));
+ return Err(DecoderError::NotImplemented);
+ }
+ if profile == 3 {
+ let reserved_zero = br.read_bool()?;
+ if reserved_zero {
+ return Err(DecoderError::InvalidData);
+ }
+ }
+ if br.read_bool()? {
+ let idx = br.read(3)? as usize;
+ if ref_frames[idx] == VA_INVALID_ID {
+ return Err(DecoderError::MissingReference);
+ }
+ vactx.queue.push_back((timestamp, QFrame::ShowAgain(ref_frames[idx])));
+ return Ok(());
+ }
+ let frame_type = br.read(1)?;
+ let show_frame = br.read_bool()?;
+ let error_resilient_mode = br.read_bool()?;
+
+ let intra_only;
+ let mut reset_frame_context = 0;
+ let mut allow_high_precision_mv = false;
+ let mut interp_type = 0;
+ let mut ref_frame_idx = [0; 3];
+ let mut ref_frame_sign_bias = [0; 3];
+ if frame_type == 0 {
+ let sync_code = br.read(24)?;
+ if sync_code != 0x498342 {
+ return Err(DecoderError::InvalidData);
+ }
+ self.read_color_config(&mut br)?;
+ self.read_frame_size(&mut br)?;
+ self.read_render_size(&mut br)?;
+ intra_only = true;
+ self.refresh_frame_flags = 0xFF;
+ } else {
+ if self.width == 0 {
+ return Err(DecoderError::MissingReference)?;
+ }
+ intra_only = !show_frame && br.read_bool()?;
+ if !error_resilient_mode {
+ reset_frame_context = br.read(2)?;
+ }
+ if intra_only {
+ let sync_code = br.read(24)?;
+ if sync_code != 0x498342 {
+ return Err(DecoderError::InvalidData);
+ }
+ if profile > 0 {
+ self.read_color_config(&mut br)?;
+ } else {
+ self.cspace = 1;
+ self.sub_x = 1;
+ self.sub_y = 1;
+ self.depth = 8;
+ }
+ self.refresh_frame_flags = br.read(8)? as u8;
+ self.read_frame_size(&mut br)?;
+ self.read_render_size(&mut br)?;
+ } else {
+ self.refresh_frame_flags = br.read(8)? as u8;
+ for (idx, sign_bias) in ref_frame_idx.iter_mut().zip(ref_frame_sign_bias.iter_mut()) {
+ *idx = br.read(3)?;
+ *sign_bias = br.read(1)?;
+ }
+ let mut found = false;
+ for _i in 0..3 {
+ if br.read_bool()? {
+ found = true;
+ break;
+ }
+ }
+ if !found {
+ self.read_frame_size(&mut br)?;
+ }
+ self.read_render_size(&mut br)?;
+
+ allow_high_precision_mv = br.read_bool()?;
+ if br.read_bool()? {
+ interp_type = 4;
+ } else {
+ const INTERP_TYPES: [u8; 4] = [1, 0, 2, 3];
+ let idx = br.read(2)? as usize;
+ interp_type = INTERP_TYPES[idx];
+ }
+ }
+ }
+
+ let refresh_frame_context;
+ let frame_par_dec_mode;
+ if !error_resilient_mode {
+ refresh_frame_context = br.read_bool()?;
+ frame_par_dec_mode = br.read_bool()?;
+ } else {
+ refresh_frame_context = false;
+ frame_par_dec_mode = true;
+ }
+ let frame_context_idx = br.read(2)?;
+
+ let loop_filter_level = br.read(6)? as i8;
+ let loop_filter_sharpness = br.read(3)? as u8;
+ let loop_filter_delta = br.read_bool()?;
+ let mut update_ref_delta = [false; 4];
+ let mut lf_ref_deltas = [0i8; 4];
+ let mut upd_mode_delta = [false; 2];
+ let mut lf_mode_deltas = [0i8; 2];
+ if loop_filter_delta {
+ let loop_filter_delta_update = br.read_bool()?;
+ if loop_filter_delta_update {
+ for (upd_ref, lf_ref) in update_ref_delta.iter_mut().zip(lf_ref_deltas.iter_mut()) {
+ *upd_ref = br.read_bool()?;
+ if *upd_ref {
+ *lf_ref = br.read_sgn(6)? as i8;
+ }
+ }
+ for (upd_ref, lf_ref) in upd_mode_delta.iter_mut().zip(lf_mode_deltas.iter_mut()) {
+ *upd_ref = br.read_bool()?;
+ if *upd_ref {
+ *lf_ref = br.read_sgn(6)? as i8;
+ }
+ }
+ }
+ }
+
+ let base_q_idx = br.read(8)? as i32;
+ let dq_y_dc = if br.read_bool()? { br.read_sgn(4)? } else { 0 };
+ let dq_uv_dc = if br.read_bool()? { br.read_sgn(4)? } else { 0 };
+ let dq_uv_ac = if br.read_bool()? { br.read_sgn(4)? } else { 0 };
+ let lossless = base_q_idx == 0 && dq_y_dc == 0 && dq_uv_dc == 0 && dq_uv_ac == 0;
+
+ let q_y_dc = (base_q_idx + dq_y_dc).clamp(0, 255) as i16;
+ let q_y_ac = base_q_idx as i16;
+ let q_uv_dc = (base_q_idx + dq_uv_dc).clamp(0, 255) as i16;
+ let q_uv_ac = (base_q_idx + dq_uv_ac).clamp(0, 255) as i16;
+ let seg_enabled = br.read_bool()?;
+ let mut seg_update_map = false;
+ let mut seg_temp_update = false;
+ if seg_enabled {
+ seg_update_map = br.read_bool()?;
+ if seg_update_map {
+ for prob in self.seg_tree_probs.iter_mut() {
+ *prob = read_prob(&mut br)?;
+ }
+ seg_temp_update = br.read_bool()?;
+ if seg_temp_update {
+ for prob in self.seg_pred_probs.iter_mut() {
+ *prob = read_prob(&mut br)?;
+ }
+ } else {
+ for prob in self.seg_pred_probs.iter_mut() {
+ *prob = 255;
+ }
+ }
+ }
+ let seg_update_data = br.read_bool()?;
+ if seg_update_data {
+unimplemented!();
+ }
+ }
+
+ let sb64_cols = (((self.width + 7) >> 3) + 7) >> 3;
+ let mut min_log2_tile_cols = 0;
+ while (64 << min_log2_tile_cols) < sb64_cols {
+ min_log2_tile_cols += 1;
+ }
+ let mut max_log2_tile_cols = 1;
+ while (sb64_cols >> max_log2_tile_cols) >= 4 {
+ max_log2_tile_cols += 1;
+ }
+ max_log2_tile_cols -= 1;
+ let mut tile_cols_log2 = min_log2_tile_cols;
+ while (tile_cols_log2 < max_log2_tile_cols) && br.read_bool()? {
+ tile_cols_log2 += 1;
+ }
+ let mut tile_rows_log2 = br.read(1)? as u8;
+ if tile_rows_log2 == 1 && br.read_bool()? {
+ tile_rows_log2 += 1;
+ }
+
+ let header_size = br.read(16)? as u16;
+ let bytes_read = (br.tell() + 7) / 8;
+
+ if vactx.surfaces.is_empty() {
+panic!("ran out of free surfaces");
+// return Err(DecoderError::AllocError);
+ }
+ let surface = vactx.surfaces.pop().unwrap();
+ //let surface_id = surface.id();
+ let mut pic = Picture::new(timestamp, vactx.context.clone(), surface);
+
+ let pic_fields = VP9PicFields::new(self.sub_x.into(), self.sub_y.into(), frame_type, show_frame as u32,
+ error_resilient_mode as u32, intra_only as u32, allow_high_precision_mv as u32,
+ interp_type.into(), frame_par_dec_mode as u32,
+ reset_frame_context, refresh_frame_context as u32, frame_context_idx,
+ seg_enabled as u32, seg_temp_update as u32, seg_update_map as u32,
+ // last, golden, altref
+ ref_frame_idx[0], ref_frame_sign_bias[0],
+ ref_frame_idx[1], ref_frame_sign_bias[1],
+ ref_frame_idx[2], ref_frame_sign_bias[2],
+ lossless as u32);
+ let pic_param = PictureParameterBufferVP9::new(self.width, self.height, *ref_frames, &pic_fields,
+ loop_filter_level as u8, loop_filter_sharpness, tile_rows_log2, tile_cols_log2,
+ bytes_read as u8, header_size, self.seg_tree_probs,
+ self.seg_pred_probs, profile as u8, self.depth);
+ let pic_param = BufferType::PictureParameter(PictureParameter::VP9(pic_param));
+ let buf = vactx.context.create_buffer(pic_param).map_err(|_| DecoderError::Bug)?;
+ pic.add_buffer(buf);
+
+ let lf_scale = if loop_filter_level >= 32 { 2 } else { 1 };
+ let mut filter_level = [[0; 2]; 4];
+ filter_level[0] = [loop_filter_level as u8; 2];
+ for (i, (fl, &delta)) in filter_level.iter_mut().zip(lf_ref_deltas.iter()).enumerate() {
+ for (val, &mode) in fl.iter_mut().zip(lf_mode_deltas.iter()) {
+ *val = (loop_filter_level + (delta + if i > 0 { mode } else { 0 }) * lf_scale).clamp(0, 63) as u8;
+ }
+ }
+ let seg_flags = VP9SegmentFlags::new(0, 0, 0); // TODO support segmented stuff?
+ let mut seg_params = std::array::from_fn(|_i| SegmentParameterVP9::new(&seg_flags, [[0; 2]; 4], 0, 0, 0, 0));
+ seg_params[0] = SegmentParameterVP9::new(&seg_flags, filter_level, q_y_ac, q_y_dc, q_uv_ac, q_uv_dc);
+ let slc_param = SliceParameterBufferVP9::new(src.len() as u32, 0, VASliceDataFlag::All as u32, seg_params);
+ let slc_param = BufferType::SliceParameter(SliceParameter::VP9(slc_param));
+ let buf = vactx.context.create_buffer(slc_param).map_err(|_| DecoderError::Bug)?;
+ pic.add_buffer(buf);
+
+ let pic_data = BufferType::SliceData(src.to_vec());
+ let buf = vactx.context.create_buffer(pic_data).map_err(|_| DecoderError::Bug)?;
+ pic.add_buffer(buf);
+
+ let cur_id = pic.surface().id();
+ for (i, rf) in ref_frames.iter_mut().enumerate() {
+ if (self.refresh_frame_flags & (1 << i)) != 0 {
+ *rf = cur_id;
+ }
+ }
+
+ let bpic = pic.begin().expect("begin");
+ let rpic = bpic.render().expect("render");
+ let epic = rpic.end().expect("end");
+
+ vactx.queue.push_back((timestamp, QFrame::Frame(frame_type == 0, show_frame, epic)));
+
+ Ok(())
+ }
+}
+
+pub struct VaapiVP9Decoder {
+ info: NACodecInfoRef,
+ vaapi: Option<VaapiInternals>,
+ needs_derive: bool,
+ tb_num: u32,
+ tb_den: u32,
+ out_frm: NABufferType,
+ fstate: FrameState,
+ ref_ids: [u32; NUM_REFS],
+}
+
+impl Default for VaapiVP9Decoder {
+ fn default() -> Self {
+ Self {
+ info: NACodecInfoRef::default(),
+ vaapi: None,
+ needs_derive: false,
+ tb_num: 0,
+ tb_den: 0,
+ out_frm: NABufferType::None,
+ fstate: FrameState::default(),
+ ref_ids: [VA_INVALID_ID; NUM_REFS],
+ }
+ }
+}
+
+impl VaapiVP9Decoder {
+ pub fn new() -> Self { Self::default() }
+}
+
+impl HWDecoderCore for VaapiVP9Decoder {
+ fn init(&mut self, info: NACodecInfoRef) -> DecoderResult<()> {
+ if let NACodecTypeInfo::Video(vinfo) = info.get_properties() {
+ let width = (vinfo.get_width() + 15) & !15;
+ let height = (vinfo.get_height() + 15) & !15;
+
+ let display = Display::open_silently().ok_or_else(||{ println!(" cannot open display"); DecoderError::InvalidData})?;
+
+ let num_surfaces = NUM_REFS + MAX_SB_FRAMES;
+
+ for (supp, &va_profile) in self.fstate.profile_supp.iter_mut().zip(VP9_PROFILES.iter()) {
+ if let Ok(profiles) = display.query_config_profiles() {
+ *supp = profiles.contains(&va_profile);
+ if let Ok(points) = display.query_config_entrypoints(va_profile) {
+ if !points.contains(&VAEntrypoint::VAEntrypointVLD) {
+ *supp = false;
+ }
+ }
+ } else {
+ return Err(DecoderError::Bug);
+ }
+ }
+
+ let needs_derive= if let Ok(vendor) = display.query_vendor_string() {
+ vendor.contains("Kaby Lake")
+ } else { false };
+
+ let config = display.create_config(vec![
+ VAConfigAttrib { type_: VAConfigAttribType::VAConfigAttribRTFormat, value: RTFormat::YUV420.into() },
+ ], VP9_PROFILES[0], VAEntrypoint::VAEntrypointVLD).map_err(|_| {
+println!("config creation failed!");
+ DecoderError::Bug
+ })?;
+ let surfaces = display.create_surfaces(RTFormat::YUV420, None, width as u32, height as u32, Some(UsageHint::Decoder.into()), num_surfaces as u32).map_err(|_| DecoderError::AllocError)?;
+ let context = display.create_context(&config, width as i32, height as i32, Some(&surfaces), true).map_err(|_| DecoderError::Bug)?;
+
+ let image_formats = display.query_image_formats().map_err(|_| DecoderError::Bug)?;
+ validate!(!image_formats.is_empty());
+ let mut ifmt = image_formats[0];
+ for fmt in image_formats.iter() {
+ if fmt.bits_per_pixel == 12 {
+ ifmt = *fmt;
+ break;
+ }
+ }
+
+ let ref_pics = Vec::with_capacity(NUM_REFS);
+ let queue = VecDeque::with_capacity(MAX_SB_FRAMES);
+ self.vaapi = Some(VaapiInternals { display, context, surfaces, ifmt, ref_pics, queue });
+ self.needs_derive = needs_derive;
+
+ let vinfo = NAVideoInfo::new(vinfo.get_width(), vinfo.get_height(), false, YUV420_FORMAT);
+ self.info = NACodecInfo::new_ref(info.get_name(), NACodecTypeInfo::Video(vinfo), info.get_extradata()).into_ref();
+ self.out_frm = alloc_video_buffer(vinfo, 4)?;
+
+ Ok(())
+ } else {
+ Err(DecoderError::InvalidData)
+ }
+ }
+ fn decode(&mut self, pkt: &NAPacket) -> DecoderResult<()> {
+ let src = pkt.get_buffer();
+ validate!(!src.is_empty());
+ let vactx = if let Some(ref mut ctx) = self.vaapi { ctx } else { return Err(DecoderError::Bug) };
+
+ let timestamp = pkt.get_pts().unwrap_or(0);
+ self.tb_num = pkt.ts.tb_num;
+ self.tb_den = pkt.ts.tb_den;
+
+ let mut frame_sizes = [0; 8];
+ let last_byte = src[src.len() - 1];
+ if (last_byte & 0xE0) == 0xC0 {
+ let width = usize::from((last_byte >> 3) & 3) + 1;
+ let nframes = usize::from(last_byte & 7) + 1;
+ let sfidx_size = nframes * width + 2;
+ validate!(src.len() >= nframes + sfidx_size);
+ let mut rest_size = src.len() - sfidx_size;
+ let idx_pos = src.len() - sfidx_size + 1;
+ for (i, fsize) in frame_sizes.iter_mut().enumerate().take(nframes) {
+ let size_src = &src[idx_pos + width * i..];
+ *fsize = match width {
+ 1 => usize::from(size_src[0]),
+ 2 => usize::from(read_u16le(size_src).unwrap_or_default()),
+ 3 => read_u24le(size_src).unwrap_or_default() as usize,
+ _ => read_u32le(size_src).unwrap_or_default() as usize,
+ };
+ validate!(*fsize > 0 && *fsize <= rest_size);
+ rest_size -= *fsize;
+ }
+ } else {
+ frame_sizes[0] = src.len();
+ }
+
+ let mut offset = 0;
+ for &fsize in frame_sizes.iter() {
+ if fsize == 0 { break; }
+ self.fstate.do_frame(vactx, &src[offset..][..fsize], timestamp, &mut self.ref_ids)?;
+ offset += fsize;
+ }
+
+ Ok(())
+ }
+ fn get_frame(&mut self) -> Option<NAFrameRef> {
+ if let Some(ref mut vactx) = self.vaapi {
+ while let Some((ts, entry)) = vactx.queue.pop_front() {
+ match entry {
+ QFrame::Frame(intra, show_frame, epic) => {
+ if let Ok(pic) = epic.sync() {
+ if show_frame {
+ let _ = fill_frame(vactx.ifmt, &pic, &mut self.out_frm, self.needs_derive);
+ }
+ let id = pic.surface().id();
+ if self.ref_ids.contains(&id) {
+ vactx.ref_pics.push(pic);
+ } else if let Ok(surf) = pic.take_surface() {
+ vactx.surfaces.push(surf);
+ } else {
+ panic!("can't take surface");
+ }
+ let mut idx = 0;
+ while idx < vactx.ref_pics.len() {
+ if !self.ref_ids.contains(&vactx.ref_pics[idx].surface().id()) {
+ let pic = vactx.ref_pics.remove(idx);
+ if let Ok(surf) = pic.take_surface() {
+ vactx.surfaces.push(surf);
+ } else {
+ panic!("can't take surface");
+ }
+ } else {
+ idx += 1;
+ }
+ }
+ if !show_frame {
+ continue;
+ }
+ let ts = NATimeInfo::new(Some(ts), None, None, self.tb_num, self.tb_den);
+ return Some(NAFrame::new(ts, if intra { FrameType::I } else { FrameType::P }, intra, self.info.clone(), self.out_frm.clone()).into_ref());
+ } else {
+ panic!("sync failed");
+ }
+ },
+ QFrame::ShowAgain(id) => {
+ if let Some(pic) = vactx.ref_pics.iter().find(|p| p.surface().id() == id) {
+ let _ = fill_frame(vactx.ifmt, pic, &mut self.out_frm, self.needs_derive);
+ let ts = NATimeInfo::new(Some(ts), None, None, self.tb_num, self.tb_den);
+ return Some(NAFrame::new(ts, FrameType::P, false, self.info.clone(), self.out_frm.clone()).into_ref());
+ }
+ },
+ }
+ }
+ }
+ None
+ }
+ fn get_last_frames(&mut self) -> Option<NAFrameRef> {
+ self.get_frame()
+ }
+ fn flush(&mut self) {
+ if let Some(ref mut vactx) = self.vaapi {
+ for pic in vactx.ref_pics.drain(..) {
+ let surface = pic.take_surface().expect("take surface");
+ vactx.surfaces.push(surface);
+ }
+ for el in self.ref_ids.iter_mut() {
+ *el = VA_INVALID_ID;
+ }
+ }
+ }
+}
+
+impl NAOptionHandler for VaapiVP9Decoder {
+ fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+ fn set_options(&mut self, _options: &[NAOption]) {}
+ fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+fn new_vp9_vadec() -> Box<dyn HWDecoderCore> {
+ Box::new(VaapiVP9Decoder::new())
+}
+
+pub fn new_vp9_hwdec() -> Box<dyn HWDecoder + Send> {
+ Box::new(HWWrapper::new(new_vp9_vadec))
+}
+
+#[cfg(test)]
+mod test {
+ use nihav_core::codecs::*;
+ use nihav_core::io::byteio::*;
+ use nihav_core::demuxers::{RegisteredDemuxers, create_demuxer};
+ use nihav_commonfmt::generic_register_all_demuxers;
+ use super::new_vp9_vadec;
+ use std::io::prelude::*;
+
+ fn decode_vp9(name: &str, dname: &str, dmx_reg: &RegisteredDemuxers, opfx: &str, limit: Option<usize>) -> DecoderResult<()> {
+ let dmx_f = dmx_reg.find_demuxer(dname).expect("demuxer exists");
+ let file = std::fs::File::open(name).expect("file exists");
+ let mut br = FileReader::new_read(file);
+ let mut dmx = create_demuxer(dmx_f, &mut br).expect("create demuxer");
+
+ let mut vstream_id = 0;
+ let mut dec = new_vp9_vadec();
+ for stream in dmx.get_streams() {
+ if stream.get_media_type() == StreamType::Video {
+ dec.init(stream.get_info()).expect("inited");
+ vstream_id = stream.get_id();
+ break;
+ }
+ }
+
+ let mut frameno = 0;
+ while let Ok(pkt) = dmx.get_frame() {
+ if pkt.get_stream().get_id() != vstream_id {
+ continue;
+ }
+ dec.decode(&pkt).expect("decoded");
+ let frm = dec.get_last_frames().expect("get frame");
+ let timestamp = frm.get_pts().unwrap_or(0);
+
+ let pic = frm.get_buffer().get_vbuf().expect("got picture");
+
+ let nname = format!("assets/test_out/{}{:06}_{}.pgm", opfx, timestamp, frameno);
+ frameno += 1;
+ let mut file = std::fs::File::create(&nname).expect("create file");
+ let (w, h) = pic.get_dimensions(0);
+ file.write_all(format!("P5\n{} {}\n255\n", w, h * 3 / 2).as_bytes()).expect("header written");
+ let data = pic.get_data();
+ for yline in data.chunks(pic.get_stride(0)).take(h) {
+ file.write_all(&yline[..w]).expect("Y line written");
+ }
+ for (uline, vline) in data[pic.get_offset(1)..].chunks(pic.get_stride(1))
+ .zip(data[pic.get_offset(2)..].chunks(pic.get_stride(2))).take(h / 2) {
+ file.write_all(&uline[..w / 2]).expect("U line written");
+ file.write_all(&vline[..w / 2]).expect("V line written");
+ }
+ if Some(frameno) == limit { break; }
+ }
+ Ok(())
+ }
+
+
+ // samples if not specified are from VP9 test vectors
+
+ #[test]
+ fn test_vp9() {
+ let mut dmx_reg = RegisteredDemuxers::new();
+ generic_register_all_demuxers(&mut dmx_reg);
+
+ decode_vp9("assets/Duck/vp90-2-00-quantizer-00.webm", "matroska", &dmx_reg, "hw", Some(32)).unwrap();
+ }
+}