X-Git-Url: https://git.nihav.org/?p=nihav.git;a=blobdiff_plain;f=nihav-duck%2Fsrc%2Fcodecs%2Fvp7enc%2Fmod.rs;fp=nihav-duck%2Fsrc%2Fcodecs%2Fvp7enc%2Fmod.rs;h=67d4bedf4295f6fa5208f20e5a33f212297ba0ce;hp=0000000000000000000000000000000000000000;hb=c5d5793c1fd18882a32acabb8141a221b0a97b61;hpb=b922b48d3b003b2f4b84755541fd9dc4be8f22f6 diff --git a/nihav-duck/src/codecs/vp7enc/mod.rs b/nihav-duck/src/codecs/vp7enc/mod.rs new file mode 100644 index 0000000..67d4bed --- /dev/null +++ b/nihav-duck/src/codecs/vp7enc/mod.rs @@ -0,0 +1,610 @@ +use nihav_core::codecs::*; +use nihav_core::io::byteio::*; + +mod blocks; +mod coder; +use coder::*; +mod frame_coder; +use frame_coder::*; +mod mb_coding; +mod models; +use models::*; +mod motion_est; +use motion_est::MVSearchMode; +mod rdo; +use rdo::*; + +#[derive(PartialEq,Debug)] +enum EncodingState { + Intra, + Refinement, + JustEncode, +} + +#[allow(dead_code)] +struct VP7Encoder { + stream: Option, + pkt: Option, + version: u8, + key_int: u8, + frmcount: u8, + width: usize, + height: usize, + mb_w: usize, + mb_h: usize, + + metric: RateDistMetric, + fenc: FrameEncoder, + pmodels: VP7Models, + br_ctl: BitRateControl, + + last_frame: NABufferType, + gold_frame: NABufferType, + last_gold: bool, + me_mode: MVSearchMode, + me_range: i16, + lf_level: Option, + + mbt_depth: usize, + mb_weight: Vec, + mb_map: Vec, + mb_map2: Vec, + qframes: Vec, + frm_pool: NAVideoBufferPool, + i_frame: NAVideoBufferRef, + enc_state: EncodingState, +} + +impl VP7Encoder { + fn new() -> Self { + let vt = alloc_video_buffer(NAVideoInfo::new(24, 24, false, YUV420_FORMAT), 4).unwrap(); + let mc_buf1 = vt.get_vbuf().unwrap(); + let vt = alloc_video_buffer(NAVideoInfo::new(24, 24, false, YUV420_FORMAT), 4).unwrap(); + let mc_buf2 = vt.get_vbuf().unwrap(); + let vt = alloc_video_buffer(NAVideoInfo::new(24, 24, false, YUV420_FORMAT), 4).unwrap(); + let i_frame = vt.get_vbuf().unwrap(); + Self { + stream: None, + pkt: None, + version: 0, + key_int: 10, + frmcount: 0, + width: 0, + height: 0, + mb_w: 0, + mb_h: 0, + + metric: RateDistMetric::new(), + fenc: FrameEncoder::new(mc_buf1, mc_buf2), + pmodels: VP7Models::new(), + br_ctl: BitRateControl::new(), + + last_frame: NABufferType::None, + gold_frame: NABufferType::None, + last_gold: false, + me_mode: MVSearchMode::default(), + me_range: 16, + lf_level: None, + + mbt_depth: 0, + mb_weight: Vec::new(), + mb_map: Vec::new(), + mb_map2: Vec::new(), + qframes: Vec::new(), + frm_pool: NAVideoBufferPool::new(0), + i_frame, + enc_state: EncodingState::JustEncode, + } + } + fn encode_frame(&mut self, frm: &NAFrame) -> EncoderResult<()> { + let buf = frm.get_buffer(); + if let Some(ref vbuf) = buf.get_vbuf() { + self.fenc.set_me_params(self.me_mode, self.me_range, self.version); + self.fenc.load_frame(vbuf); + + let mut dbuf = Vec::with_capacity(4); + let mut gw = GrowableMemoryWriter::new_write(&mut dbuf); + let mut bw = ByteWriter::new(&mut gw); + + let is_intra = self.frmcount == 0; + let golden_frame = is_intra; + + self.br_ctl.set_key_interval(self.key_int); + let cur_quant = self.br_ctl.get_frame_quant(is_intra); + + if let Some(level) = self.lf_level { + self.fenc.loop_params.loop_filter_level = level; + } else { + self.fenc.loop_params.loop_filter_level = if cur_quant <= 16 { 0 } else { (cur_quant / 4) as u8 }; + } + + if is_intra { + self.pmodels.reset(); + let mbt_frames = self.mbt_depth.min(self.key_int as usize); + self.fenc.intra_blocks(cur_quant, &self.metric, &self.pmodels, if mbt_frames > 0 { Some(&self.mb_weight) } else { None }); + } else { + let gold_ref = if !self.last_gold { &self.gold_frame } else { &NABufferType::None }; + self.fenc.inter_blocks(cur_quant, &self.metric, &self.pmodels, &self.last_frame, gold_ref); + } + + let mut stats = VP7ModelsStat::new(); + let mut models = self.pmodels; + self.fenc.generate_models(is_intra, &mut stats); + stats.generate(&mut models, is_intra); + + bw.write_u24le(0)?; // frame tag + if self.version == 0 { + bw.write_byte(0)?; // unused + } + + let start = bw.tell(); + + let mut bc = BoolEncoder::new(&mut bw); + if is_intra { + bc.put_bits(self.width as u32, 12)?; + bc.put_bits(self.height as u32, 12)?; + bc.put_bits(0, 2)?; // scale vertical + bc.put_bits(0, 2)?; // scale horizontal + } + + self.fenc.encode_features(&mut bc, cur_quant, &models)?; + + bc.put_bits(cur_quant as u32, 7)?; // y_ac_q + bc.put_bool(false, 128)?; // y_dc_q + bc.put_bool(false, 128)?; // y2_ac_q + bc.put_bool(false, 128)?; // y2_dc_q + bc.put_bool(false, 128)?; // uv_ac_q + bc.put_bool(false, 128)?; // uv_dc_q + + if !is_intra { + bc.put_bool(false, 128)?; // update golden frame + } + + let has_fading = self.version == 0 || is_intra; + if self.version != 0 { + bc.put_bool(true, 128)?; // keep probabilities + if !is_intra { + bc.put_bool(false, 128)?; // has fading feature + } + } + if has_fading { + bc.put_bool(false, 128)?; // fading + } + + if self.version == 0 { + bc.put_bool(self.fenc.loop_params.lf_simple, 128)?; + } + + // scan + bc.put_bool(false, 128)?; + + if self.version != 0 { + bc.put_bool(self.fenc.loop_params.lf_simple, 128)?; + } + + bc.put_bits(u32::from(self.fenc.loop_params.loop_filter_level), 6)?; + bc.put_bits(u32::from(self.fenc.loop_params.loop_sharpness), 3)?; + + encode_dct_coef_prob_upd(&mut bc, &models.coef_probs, &self.pmodels.coef_probs)?; + + if !is_intra { + bc.put_byte(models.prob_intra_pred)?; + bc.put_byte(models.prob_last_pred)?; + + let ymode_differs = models.kf_ymode_prob != self.pmodels.kf_ymode_prob; + bc.put_bool(ymode_differs, 128)?; + if ymode_differs { + for &el in models.kf_ymode_prob.iter() { + bc.put_byte(el)?; + } + } + + let uvmode_differs = models.kf_uvmode_prob != self.pmodels.kf_uvmode_prob; + bc.put_bool(uvmode_differs, 128)?; + if uvmode_differs { + for &el in models.kf_uvmode_prob.iter() { + bc.put_byte(el)?; + } + } + + encode_mv_prob_upd(&mut bc, &models.mv_probs, &self.pmodels.mv_probs)?; + } + + self.fenc.encode_mb_types(&mut bc, is_intra, &models)?; + + bc.flush()?; + let end = bw.tell(); + + let mut bc = BoolEncoder::new(&mut bw); + self.fenc.encode_residues(&mut bc, &models)?; + bc.flush()?; + + bw.seek(SeekFrom::Start(0))?; + bw.write_u24le((((end - start) as u32) << 4) | + (u32::from(self.version) << 1) | + if is_intra { 0 } else { 1 })?; + + let cur_size = dbuf.len(); + + self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, is_intra, dbuf)); + + self.pmodels = models; + + if self.key_int > 0 { + self.frmcount += 1; + } + if self.frmcount == self.key_int { + self.frmcount = 0; + } + + if let Some(ref mut vbuf) = self.last_frame.get_vbuf() { + let mut frm = NASimpleVideoFrame::from_video_buf(vbuf).unwrap(); + self.fenc.reconstruct_frame(&mut frm, is_intra); + } + self.last_gold = golden_frame; + if golden_frame { + let mut dfrm = self.gold_frame.get_vbuf().unwrap(); + let src = self.last_frame.get_vbuf().unwrap(); + + let dst = dfrm.get_data_mut().unwrap(); + dst.copy_from_slice(src.get_data()); + } + + self.br_ctl.update(cur_size); + if self.br_ctl.has_bitrate() { + let tgt_size = (self.br_ctl.get_target_size(is_intra) / 8) as usize; + self.metric.adjust_br(cur_size, tgt_size); + } + + Ok(()) + } else { + Err(EncoderError::InvalidParameters) + } + } +} + +impl NAEncoder for VP7Encoder { + fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult { + match encinfo.format { + NACodecTypeInfo::None => { + let mut ofmt = EncodeParameters::default(); + ofmt.format = NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, false, YUV420_FORMAT)); + Ok(ofmt) + }, + NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), + NACodecTypeInfo::Video(vinfo) => { + let outinfo = NAVideoInfo::new((vinfo.width + 15) & !15, (vinfo.height + 15) & !15, false, YUV420_FORMAT); + let mut ofmt = *encinfo; + ofmt.format = NACodecTypeInfo::Video(outinfo); + Ok(ofmt) + } + } + } + fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult { + match encinfo.format { + NACodecTypeInfo::None => Err(EncoderError::FormatError), + NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), + NACodecTypeInfo::Video(vinfo) => { + if vinfo.format != YUV420_FORMAT { + return Err(EncoderError::FormatError); + } + if ((vinfo.width | vinfo.height) & 15) != 0 { + return Err(EncoderError::FormatError); + } + if (vinfo.width | vinfo.height) >= (1 << 12) { + return Err(EncoderError::FormatError); + } + + let out_info = NAVideoInfo::new(vinfo.width, vinfo.height, false, vinfo.format); + let info = NACodecInfo::new("vp7", NACodecTypeInfo::Video(out_info), None); + let mut stream = NAStream::new(StreamType::Video, stream_id, info, encinfo.tb_num, encinfo.tb_den, 0); + stream.set_num(stream_id as usize); + let stream = stream.into_ref(); + + self.last_frame = alloc_video_buffer(out_info, 4)?; + self.gold_frame = alloc_video_buffer(out_info, 4)?; + + self.stream = Some(stream.clone()); + + self.width = vinfo.width; + self.height = vinfo.height; + self.mb_w = (vinfo.width + 15) >> 4; + self.mb_h = (vinfo.height + 15) >> 4; + self.fenc.resize(self.mb_w, self.mb_h); + + self.br_ctl.set_params(encinfo.tb_num, encinfo.tb_den, encinfo.bitrate, self.key_int, self.mb_w * self.mb_h); + + self.frm_pool.reset(); + self.frm_pool.set_dec_bufs(self.mbt_depth + 1); + self.frm_pool.prealloc_video(out_info, 4)?; + self.i_frame = self.frm_pool.get_free().unwrap(); + self.mb_weight.resize(self.mb_w * self.mb_h, 0); + self.mb_map.resize(self.mb_w * self.mb_h, 0); + self.mb_map2.resize(self.mb_w * self.mb_h, 0); + self.qframes.clear(); + self.enc_state = if self.mbt_depth.min(self.key_int as usize) > 0 { + EncodingState::Intra + } else { + EncodingState::JustEncode + }; + + Ok(stream) + }, + } + } + fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> { + if let Some(ref vbuf) = frm.get_buffer().get_vbuf() { + let mbt_frames = self.mbt_depth.min(self.key_int as usize); + if !self.qframes.is_empty() || (mbt_frames > 0 && self.enc_state != EncodingState::JustEncode) { + if let Some(dbuf) = self.frm_pool.get_copy(vbuf) { + let newfrm = NAFrame::new(frm.ts, frm.frame_type, frm.key, frm.get_info(), NABufferType::Video(dbuf)); + if self.enc_state == EncodingState::Intra { + for (i, el) in self.mb_map.iter_mut().enumerate() { + *el = i; + } + for el in self.mb_weight.iter_mut() { + *el = 1; + } + let frm = NASimpleVideoFrame::from_video_buf(&mut self.i_frame).unwrap(); + let src = vbuf.get_data(); + for plane in 0..3 { + let soff = vbuf.get_offset(plane); + let sstride = vbuf.get_stride(plane); + let copy_len = sstride.min(frm.stride[plane]); + for (dst, src) in frm.data[frm.offset[plane]..].chunks_mut(frm.stride[plane]).zip(src[soff..].chunks(sstride)).take(frm.height[plane]) { + dst[..copy_len].copy_from_slice(&src[..copy_len]); + } + } + self.enc_state = EncodingState::Refinement; + } else { + self.fenc.set_me_params(self.me_mode, self.me_range, self.version); + self.fenc.load_frame(vbuf); + self.fenc.mb_tree_search(self.i_frame.clone(), &self.mb_map, &mut self.mb_map2, &mut self.mb_weight); + std::mem::swap(&mut self.mb_map, &mut self.mb_map2); + } + self.qframes.push(newfrm); + Ok(()) + } else { + self.enc_state = EncodingState::JustEncode; + self.encode_frame(frm) + } + } else { + self.encode_frame(frm) + } + } else { + Err(EncoderError::FormatError) + } + } + fn get_packet(&mut self) -> EncoderResult> { + let mbt_frames = self.mbt_depth.min(self.key_int as usize); + if self.qframes.len() >= mbt_frames { + self.enc_state = EncodingState::JustEncode; + } + if self.pkt.is_none() && !self.qframes.is_empty() && self.enc_state == EncodingState::JustEncode { + let frm = self.qframes.remove(0); + self.encode_frame(&frm)?; + if self.qframes.is_empty() && self.mbt_depth > 0 && self.frmcount == 0 { + self.enc_state = EncodingState::Intra; + } + } + let mut npkt = None; + std::mem::swap(&mut self.pkt, &mut npkt); + Ok(npkt) + } + fn flush(&mut self) -> EncoderResult<()> { + self.frmcount = 0; + self.enc_state = EncodingState::JustEncode; + Ok(()) + } +} + +const VERSION_OPTION: &str = "version"; +const LF_LEVEL_OPTION: &str = "lf_level"; +const LF_SHARP_OPTION: &str = "lf_sharpness"; +const LF_SIMPLE_OPTION: &str = "lf_simple"; +const QUANT_OPTION: &str = "quant"; +const MV_SEARCH_OPTION: &str = "mv_mode"; +const MV_RANGE_OPTION: &str = "mv_range"; +const MBTREE_DEPTH: &str = "mbtree_depth"; + +const ENCODER_OPTS: &[NAOptionDefinition] = &[ + NAOptionDefinition { + name: KEYFRAME_OPTION, description: KEYFRAME_OPTION_DESC, + opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) }, + NAOptionDefinition { + name: VERSION_OPTION, description: "internal codec version", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(1)) }, + NAOptionDefinition { + name: LF_LEVEL_OPTION, description: "loop filter level (-1 = automatic)", + opt_type: NAOptionDefinitionType::Int(Some(-1), Some(63)) }, + NAOptionDefinition { + name: LF_SHARP_OPTION, description: "loop filter sharpness", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(7)) }, + NAOptionDefinition { + name: LF_SIMPLE_OPTION, description: "use simple loop filter", + opt_type: NAOptionDefinitionType::Bool }, + NAOptionDefinition { + name: QUANT_OPTION, description: "force fixed quantiser for encoding", + opt_type: NAOptionDefinitionType::Int(Some(-1), Some(127)) }, + NAOptionDefinition { + name: MV_SEARCH_OPTION, description: "motion search mode", + opt_type: NAOptionDefinitionType::String(Some(&["sea", "dia", "hex", "epzs"])) }, + NAOptionDefinition { + name: MV_RANGE_OPTION, description: "motion search range (in pixels)", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(30)) }, + NAOptionDefinition { + name: MBTREE_DEPTH, description: "number of frames in MB tree analysis buffer", + opt_type: NAOptionDefinitionType::Int(Some(0), Some(128)) }, +]; + +impl NAOptionHandler for VP7Encoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { ENCODER_OPTS } + fn set_options(&mut self, options: &[NAOption]) { + for option in options.iter() { + for opt_def in ENCODER_OPTS.iter() { + if opt_def.check(option).is_ok() { + match option.name { + KEYFRAME_OPTION => { + if let NAValue::Int(intval) = option.value { + self.key_int = intval as u8; + } + }, + VERSION_OPTION => { + if let NAValue::Int(intval) = option.value { + self.version = intval as u8; + } + }, + LF_LEVEL_OPTION => { + if let NAValue::Int(intval) = option.value { + self.lf_level = if intval < 0 { None } else { Some(intval as u8) }; + } + }, + LF_SHARP_OPTION => { + if let NAValue::Int(intval) = option.value { + self.fenc.loop_params.loop_sharpness = intval as u8; + } + }, + LF_SIMPLE_OPTION => { + if let NAValue::Bool(flag) = option.value { + self.fenc.loop_params.lf_simple = flag; + } + }, + QUANT_OPTION => { + if let NAValue::Int(intval) = option.value { + self.br_ctl.set_quant(if intval < 0 { None } else { Some(intval as usize) }); + } + }, + MV_SEARCH_OPTION => { + if let NAValue::String(ref string) = option.value { + if let Ok(mv_mode) = string.parse::() { + self.me_mode = mv_mode; + } + } + }, + MV_RANGE_OPTION => { + if let NAValue::Int(intval) = option.value { + self.me_range = intval as i16; + } + }, + MBTREE_DEPTH => { + if let NAValue::Int(intval) = option.value { + self.mbt_depth = intval as usize; + } + }, + _ => {}, + }; + } + } + } + } + fn query_option_value(&self, name: &str) -> Option { + match name { + KEYFRAME_OPTION => Some(NAValue::Int(i64::from(self.key_int))), + VERSION_OPTION => Some(NAValue::Int(i64::from(self.version))), + QUANT_OPTION => if let Some(q) = self.br_ctl.get_quant() { + Some(NAValue::Int(q as i64)) + } else { + Some(NAValue::Int(-1)) + }, + LF_LEVEL_OPTION => if let Some(lev) = self.lf_level { + Some(NAValue::Int(i64::from(lev))) + } else { + Some(NAValue::Int(-1)) + }, + LF_SHARP_OPTION => Some(NAValue::Int(i64::from(self.fenc.loop_params.loop_sharpness))), + LF_SIMPLE_OPTION => Some(NAValue::Bool(self.fenc.loop_params.lf_simple)), + MV_SEARCH_OPTION => Some(NAValue::String(self.me_mode.to_string())), + MV_RANGE_OPTION => Some(NAValue::Int(i64::from(self.me_range))), + MBTREE_DEPTH => Some(NAValue::Int(self.mbt_depth as i64)), + _ => None, + } + } +} + +pub fn get_encoder() -> Box { + Box::new(VP7Encoder::new()) +} + +#[cfg(test)] +mod test { + use nihav_core::codecs::*; + use nihav_core::demuxers::*; + use nihav_core::muxers::*; + use crate::*; + use nihav_commonfmt::*; + use nihav_codec_support::test::enc_video::*; + + fn encode_test(out_name: &'static str, enc_options: &[NAOption], hash: &[u32; 4]) { + let mut dmx_reg = RegisteredDemuxers::new(); + generic_register_all_demuxers(&mut dmx_reg); + let mut dec_reg = RegisteredDecoders::new(); + duck_register_all_decoders(&mut dec_reg); + let mut mux_reg = RegisteredMuxers::new(); + generic_register_all_muxers(&mut mux_reg); + let mut enc_reg = RegisteredEncoders::new(); + duck_register_all_encoders(&mut enc_reg); + + // sample: https://samples.mplayerhq.hu/V-codecs/VP4/ot171_vp40.avi + let dec_config = DecoderTestParams { + demuxer: "avi", + in_name: "assets/Duck/ot171_vp40.avi", + stream_type: StreamType::Video, + limit: Some(9), + dmx_reg, dec_reg, + }; + let enc_config = EncoderTestParams { + muxer: "avi", + enc_name: "vp7", + out_name, + mux_reg, enc_reg, + }; + let dst_vinfo = NAVideoInfo { + width: 0, + height: 0, + format: YUV420_FORMAT, + flipped: false, + bits: 12, + }; + let enc_params = EncodeParameters { + format: NACodecTypeInfo::Video(dst_vinfo), + quality: 0, + bitrate: 50000, + tb_num: 0, + tb_den: 0, + flags: 0, + }; + //test_encoding_to_file(&dec_config, &enc_config, enc_params, enc_options); + test_encoding_md5(&dec_config, &enc_config, enc_params, enc_options, + hash); + } + #[test] + fn test_vp7_encoder() { + let enc_options = &[ + NAOption { name: super::QUANT_OPTION, value: NAValue::Int(42) }, + ]; + encode_test("vp7-q42.avi", enc_options, &[0xa5079e5b, 0x33dd8a63, 0xfc189e21, 0xee08332b]); + } + #[test] + fn test_vp7_encoder_noloop() { + let enc_options = &[ + NAOption { name: super::QUANT_OPTION, value: NAValue::Int(42) }, + NAOption { name: super::LF_LEVEL_OPTION, value: NAValue::Int(0) }, + ]; + encode_test("vp7-noloop.avi", enc_options, &[0xc7d41732, 0x09b03059, 0x8550921c, 0xa99d4c29]); + } + #[test] + fn test_vp7_encoder_mbtree() { + let enc_options = &[ + NAOption { name: super::QUANT_OPTION, value: NAValue::Int(24) }, + NAOption { name: super::MBTREE_DEPTH, value: NAValue::Int(10) }, + ]; + encode_test("vp7-mbt.avi", enc_options, &[0xd0d90d31, 0x0253275d, 0xbe502d3c, 0xacf2b6e7]); + } + #[test] + fn test_vp7_encoder_ratectl() { + let enc_options = &[ + NAOption { name: super::QUANT_OPTION, value: NAValue::Int(-1) }, + ]; + encode_test("vp7-br.avi", enc_options, &[0x47dcd4da, 0x04b06feb, 0x386163c1, 0x54899da3]); + } +}