From: Kostya Shishkov Date: Sat, 30 May 2020 14:16:14 +0000 (+0200) Subject: introduce interface and support code for encoders X-Git-Url: https://git.nihav.org/?p=nihav.git;a=commitdiff_plain;h=0b257d9fde8ee0cc24e15a63544b100a0b6da52d introduce interface and support code for encoders --- diff --git a/nihav-codec-support/src/test/enc_video.rs b/nihav-codec-support/src/test/enc_video.rs new file mode 100644 index 0000000..1240e83 --- /dev/null +++ b/nihav-codec-support/src/test/enc_video.rs @@ -0,0 +1,131 @@ +use std::fs::File; +use nihav_core::frame::*; +use nihav_core::codecs::*; +use nihav_core::demuxers::*; +use nihav_core::muxers::*; +use nihav_core::scale::*; +use nihav_core::soundcvt::*; + +pub struct DecoderTestParams { + pub demuxer: &'static str, + pub in_name: &'static str, + pub limit: Option, + pub stream_type: StreamType, + pub dmx_reg: RegisteredDemuxers, + pub dec_reg: RegisteredDecoders, +} + +pub struct EncoderTestParams { + pub muxer: &'static str, + pub enc_name: &'static str, + pub out_name: &'static str, + pub mux_reg: RegisteredMuxers, + pub enc_reg: RegisteredEncoders, +} + +pub fn test_encoding_to_file(dec_config: &DecoderTestParams, enc_config: &EncoderTestParams, mut enc_params: EncodeParameters) { + let dmx_f = dec_config.dmx_reg.find_demuxer(dec_config.demuxer).unwrap(); + let mut file = File::open(dec_config.in_name).unwrap(); + let mut fr = FileReader::new_read(&mut file); + let mut br = ByteReader::new(&mut fr); + let mut dmx = create_demuxer(dmx_f, &mut br).unwrap(); + + let in_stream = dmx.get_streams().find(|str| str.get_media_type() == dec_config.stream_type).unwrap(); + let in_stream_id = in_stream.id; + let decfunc = dec_config.dec_reg.find_decoder(in_stream.get_info().get_name()).unwrap(); + let mut dec = (decfunc)(); + let mut dsupp = Box::new(NADecoderSupport::new()); + dec.init(&mut dsupp, in_stream.get_info()).unwrap(); + + let mut out_sm = StreamManager::new(); + enc_params.tb_num = in_stream.tb_num; + enc_params.tb_den = in_stream.tb_den; + + if let (NACodecTypeInfo::Video(ref mut vinfo), Some(ref_vinfo)) = (&mut enc_params.format, in_stream.get_info().get_properties().get_video_info()) { + if vinfo.width == 0 { + vinfo.width = ref_vinfo.width; + vinfo.height = ref_vinfo.height; + } + } + let mut dst_chmap = NAChannelMap::new(); + if let (NACodecTypeInfo::Audio(ref mut ainfo), Some(ref_ainfo)) = (&mut enc_params.format, in_stream.get_info().get_properties().get_audio_info()) { + if ainfo.sample_rate == 0 { + ainfo.sample_rate = ref_ainfo.sample_rate; + } + if ainfo.channels == 0 { + ainfo.channels = ref_ainfo.channels; + } + match ainfo.channels { + 1 => { + dst_chmap.add_channel(NAChannelType::C); + }, + 2 => { + dst_chmap.add_channel(NAChannelType::L); + dst_chmap.add_channel(NAChannelType::R); + }, + _ => panic!("cannot guess channel map"), + } + } + + let encfunc = enc_config.enc_reg.find_encoder(enc_config.enc_name).unwrap(); + let mut encoder = (encfunc)(); + let out_str = encoder.init(0, enc_params).unwrap(); + out_sm.add_stream(NAStream::clone(&out_str)); + + let mux_f = enc_config.mux_reg.find_muxer(enc_config.muxer).unwrap(); + let out_name = "assets/test_out/".to_owned() + enc_config.out_name; + let file = File::create(&out_name).unwrap(); + let mut fw = FileWriter::new_write(file); + let mut bw = ByteWriter::new(&mut fw); + let mut mux = create_muxer(mux_f, out_sm, &mut bw).unwrap(); + + let (mut ifmt, dst_vinfo) = if let NACodecTypeInfo::Video(vinfo) = enc_params.format { + (ScaleInfo { fmt: vinfo.format, width: vinfo.width, height: vinfo.height }, + vinfo) + } else { + (ScaleInfo { fmt: YUV420_FORMAT, width: 2, height: 2 }, + NAVideoInfo { width: 2, height: 2, format: YUV420_FORMAT, flipped: false }) + }; + let ofmt = ifmt; + let mut scaler = NAScale::new(ifmt, ofmt).unwrap(); + let mut cvt_buf = alloc_video_buffer(dst_vinfo, 2).unwrap(); + loop { + let pktres = dmx.get_frame(); + if let Err(e) = pktres { + if e == DemuxerError::EOF { break; } + panic!("decoding error"); + } + let pkt = pktres.unwrap(); + if pkt.get_stream().id != in_stream_id { continue; } + let frm = dec.decode(&mut dsupp, &pkt).unwrap(); + let buf = frm.get_buffer(); + let cfrm = if let NACodecTypeInfo::Video(_) = enc_params.format { + let cur_ifmt = get_scale_fmt_from_pic(&buf); + if cur_ifmt != ifmt { + ifmt = cur_ifmt; + scaler = NAScale::new(ifmt, ofmt).unwrap(); + } + scaler.convert(&buf, &mut cvt_buf).unwrap(); + NAFrame::new(frm.get_time_information(), frm.frame_type, frm.key, frm.get_info(), cvt_buf.clone()) + } else if let NACodecTypeInfo::Audio(ref dst_ainfo) = enc_params.format { + let cvt_buf = convert_audio_frame(&buf, dst_ainfo, &dst_chmap).unwrap(); + NAFrame::new(frm.get_time_information(), frm.frame_type, frm.key, frm.get_info(), cvt_buf) + } else { + panic!("unexpected format"); + }; + encoder.encode(&cfrm).unwrap(); + while let Ok(Some(pkt)) = encoder.get_packet() { + mux.mux_frame(pkt).unwrap(); + } + if let Some(maxts) = dec_config.limit { + if frm.get_pts().unwrap_or(0) >= maxts { + break; + } + } + } + encoder.flush().unwrap(); + while let Ok(Some(pkt)) = encoder.get_packet() { + mux.mux_frame(pkt).unwrap(); + } + mux.end().unwrap(); +} diff --git a/nihav-codec-support/src/test/mod.rs b/nihav-codec-support/src/test/mod.rs index 367be24..2d2cda7 100644 --- a/nihav-codec-support/src/test/mod.rs +++ b/nihav-codec-support/src/test/mod.rs @@ -2,6 +2,7 @@ //! //! This module provides functions that may be used in internal test to check that decoders produce output and that they produce expected output. pub mod dec_video; +pub mod enc_video; pub mod wavwriter; mod md5; // for internal checksums only diff --git a/nihav-core/src/codecs/mod.rs b/nihav-core/src/codecs/mod.rs index 3418944..5470325 100644 --- a/nihav-core/src/codecs/mod.rs +++ b/nihav-core/src/codecs/mod.rs @@ -129,3 +129,177 @@ impl RegisteredDecoders { self.decs.iter() } } + +/// A list specifying general encoding errors. +#[derive(Debug,Clone,Copy,PartialEq)] +#[allow(dead_code)] +pub enum EncoderError { + /// No frame was provided. + NoFrame, + /// Allocation failed. + AllocError, + /// Operation requires repeating. + TryAgain, + /// Input format is not supported by codec. + FormatError, + /// Invalid input parameters were provided. + InvalidParameters, + /// Feature is not implemented. + NotImplemented, + /// Some bug in encoder. It should not happen yet it might. + Bug, +} + +/// A specialised `Result` type for decoding operations. +pub type EncoderResult = Result; + +impl From for EncoderError { + fn from(_: ByteIOError) -> Self { EncoderError::Bug } +} + +/// Encoding parameter flag to force constant bitrate mode. +pub const ENC_MODE_CBR: u64 = 1 << 0; +/// Encoding parameter flag to force constant framerate mode. +pub const ENC_MODE_CFR: u64 = 1 << 1; + +/// Encoding parameters. +#[derive(Clone,Copy,PartialEq)] +pub struct EncodeParameters { + /// Input format. + pub format: NACodecTypeInfo, + /// Time base numerator. Ignored for audio. + pub tb_num: u32, + /// Time base denominator. Ignored for audio. + pub tb_den: u32, + /// Bitrate in kilobits per second. + pub bitrate: u32, + /// A collection of various boolean encoder settings like CBR mode. + /// + /// See `ENC_MODE_*` constants for available options. + pub flags: u64, + /// Encoding quality. + pub quality: u8, +} + +impl Default for EncodeParameters { + fn default() -> EncodeParameters { + EncodeParameters { + format: NACodecTypeInfo::None, + tb_num: 0, + tb_den: 0, + bitrate: 0, + flags: 0, + quality: 0, + } + } +} + +/// Encoder trait. +/// +/// Overall encoding is more complex than decoding. +/// There are at least two issues that should be addressed: input format and the need for lookahead. +/// +/// Some formats (like MPEG-1 ones) have fixed picture dimensions and framerate, or sampling rate. +/// Some formats accept only pictures with dimensions being multiple of eight or sixteen. +/// Some audio formats work only with monaural sound. +/// In order to account for all this user first needs to check whether encoder can handle provided input format as is or some conversion is required. +/// That is why `NAEncoder` has [`negotiate_format`] function that performs such check and returns what encoder can handle. +/// +/// Additionally, encoders for complex formats often need several frames lookahead to encode data efficiently, actual frame encoding may take place only when some frames are accumulated. +/// That is why encoder has two functions, one for queueing frames for encoding and one for obtaining encoded packets when they are available. +/// In result encoder should first queue a frame for encoding with [`encode`] and then retrieve zero or more encoded packets with [`get_packet`] in a loop. +/// +/// Overall encoding loop should look like this: +/// ```ignore +/// let encoder = ...; // create encoder +/// let enc_params = encoder.negotiate_format(input_enc_params)?; // negotiate format +/// let output_stream = encoder.init(stream_no, enc_params)?; +/// while let Some(frame) = queue.get_frame() { +/// // convert to the format encoder expects if required +/// encoder.encode(frame)?; +/// while let Some(enc_pkt) = encoder.get_packet()? { +/// // send encoded packet to a muxer for example +/// } +/// } +/// // retrieve the rest of encoded packets +/// encoder.flush()?; +/// while let Ok(enc_pkt) = encoder.get_packet()? { +/// // send encoded packet to a muxer for example +/// } +/// ``` +/// +/// [`negotiate_format`]: ./trait.NAEncoder.html#tymethod.negotiate_format +/// [`encode`]: ./trait.NAEncoder.html#tymethod.encode +/// [`get_packet`]: ./trait.NAEncoder.html#tymethod.get_packet +pub trait NAEncoder { + /// Tries to negotiate input format. + /// + /// This function takes input encoding parameters and returns adjusted encoding parameters if input ones make sense. + /// If input parameters are empty then the default parameters are returned. + /// + /// # Example + /// ```ignore + /// let enc_params = [ EncodeParameters {...}, ..., EncodeParameters::default() ]; + /// let mut target_params = EncodeParameters::default(); + /// for params in enc_params.iter() { + /// if let Ok(dparams) = encoder.negotiate_format(params) { + /// target_params = dparams; + /// break; + /// } + /// } + /// // since negotiate_format(EncodeParameters::default()) will return a valid format, target_params should be valid here + /// let stream = encoder.init(stream_id, target_params)?; + /// // convert input into format defined in target_params, feed to the encoder, ... + /// ``` + fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult; + /// Initialises the encoder. + fn init(&mut self, stream_id: u32, encinfo: EncodeParameters) -> EncoderResult; + /// Takes a single frame for encoding. + fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()>; + /// Returns encoded packet if available. + fn get_packet(&mut self) -> EncoderResult>; + /// Tells encoder to encode all data it currently has. + fn flush(&mut self) -> EncoderResult<()>; +} + +/// Encoder information used during creating an encoder for requested codec. +#[derive(Clone,Copy)] +pub struct EncoderInfo { + /// Short encoder name. + pub name: &'static str, + /// The function that creates an encoder instance. + pub get_encoder: fn () -> Box, +} + +/// Structure for registering known encoders. +/// +/// It is supposed to be filled using `register_all_codecs()` from some encoders crate and then it can be used to create encoders for the requested codecs. +#[derive(Default)] +pub struct RegisteredEncoders { + encs: Vec, +} + +impl RegisteredEncoders { + /// Constructs a new instance of `RegisteredEncoders`. + pub fn new() -> Self { + Self { encs: Vec::new() } + } + /// Adds another encoder to the registry. + pub fn add_encoder(&mut self, enc: EncoderInfo) { + self.encs.push(enc); + } + /// Searches for the encoder for the provided name and returns a function for creating it on success. + pub fn find_encoder(&self, name: &str) -> Option Box> { + for &enc in self.encs.iter() { + if enc.name == name { + return Some(enc.get_encoder); + } + } + None + } + /// Provides an iterator over currently registered encoders. + pub fn iter(&self) -> std::slice::Iter { + self.encs.iter() + } +} +