From 345add5d6a5ba075231c46592d83bbca8d36c803 Mon Sep 17 00:00:00 2001 From: Kostya Shishkov Date: Sat, 4 Apr 2026 14:16:14 +0200 Subject: [PATCH] QT YUV2 and YUV4 encoders --- nihav-allstuff/src/lib.rs | 1 + nihav-qt/Cargo.toml | 10 +- nihav-qt/src/codecs/mod.rs | 19 +++- nihav-qt/src/codecs/rawvidenc.rs | 161 +++++++++++++++++++++++++++++++ nihav-qt/src/lib.rs | 2 + 5 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 nihav-qt/src/codecs/rawvidenc.rs diff --git a/nihav-allstuff/src/lib.rs b/nihav-allstuff/src/lib.rs index 6f5ff69..371632b 100644 --- a/nihav-allstuff/src/lib.rs +++ b/nihav-allstuff/src/lib.rs @@ -86,6 +86,7 @@ pub fn nihav_register_all_encoders(re: &mut RegisteredEncoders) { duck_register_all_encoders(re); llaudio_register_all_encoders(re); ms_register_all_encoders(re); + qt_register_all_encoders(re); rad_register_all_encoders(re); realmedia_register_all_encoders(re); } diff --git a/nihav-qt/Cargo.toml b/nihav-qt/Cargo.toml index 722357b..c80a168 100644 --- a/nihav-qt/Cargo.toml +++ b/nihav-qt/Cargo.toml @@ -16,7 +16,7 @@ features = ["blockdsp", "fft", "qmf", "qt_pal"] nihav_commonfmt = { path = "../nihav-commonfmt", default-features=false, features = ["all_demuxers"] } [features] -default = ["all_decoders", "all_demuxers"] +default = ["all_decoders", "all_demuxers", "all_encoders"] all_decoders = ["all_video_decoders", "all_audio_decoders"] decoders = [] @@ -40,3 +40,11 @@ decoder_alac = ["decoders"] all_demuxers = ["demuxer_warhol"] demuxers = [] demuxer_warhol = ["demuxers"] + +all_encoders = ["all_video_encoders", "all_audio_encoders"] +encoders = [] + +all_video_encoders = ["encoder_rawvid"] +encoder_rawvid = ["encoders"] + +all_audio_encoders = [] diff --git a/nihav-qt/src/codecs/mod.rs b/nihav-qt/src/codecs/mod.rs index e771f21..b75a4c0 100644 --- a/nihav-qt/src/codecs/mod.rs +++ b/nihav-qt/src/codecs/mod.rs @@ -99,9 +99,26 @@ const QT_CODECS: &[DecoderInfo] = &[ DecoderInfo { name: "qdesign-music2", get_decoder: qdm2::get_decoder }, ]; -/// Registers all available codecs provided by this crate. +/// Registers all available decoders provided by this crate. pub fn qt_register_all_decoders(rd: &mut RegisteredDecoders) { for decoder in QT_CODECS.iter() { rd.add_decoder(*decoder); } } + +#[cfg(feature="encoder_rawvid")] +mod rawvidenc; + +const QT_ENCODERS: &[EncoderInfo] = &[ +#[cfg(feature="encoder_rawvid")] + EncoderInfo { name: "qt-yuv2", get_encoder: rawvidenc::get_encoder_yuv2 }, +#[cfg(feature="encoder_rawvid")] + EncoderInfo { name: "qt-yuv4", get_encoder: rawvidenc::get_encoder_yuv4 }, +]; + +/// Registers all available encoders provided by this crate. +pub fn qt_register_all_encoders(re: &mut RegisteredEncoders) { + for encoder in QT_ENCODERS.iter() { + re.add_encoder(*encoder); + } +} diff --git a/nihav-qt/src/codecs/rawvidenc.rs b/nihav-qt/src/codecs/rawvidenc.rs new file mode 100644 index 0000000..bae7a82 --- /dev/null +++ b/nihav-qt/src/codecs/rawvidenc.rs @@ -0,0 +1,161 @@ +use nihav_core::codecs::*; +use std::str::FromStr; + +#[derive(Clone,Copy,Debug,PartialEq)] +enum RawType { + YUV2, + YUV4, +} + +struct RawEncoder { + stream: Option, + pkt: Option, + vinfo: NAVideoInfo, + rtype: RawType, +} + +impl RawEncoder { + fn new(rtype: RawType) -> Self { + Self { + stream: None, + pkt: None, + vinfo: NAVideoInfo::new(0, 0, false, YUV420_FORMAT), + rtype, + } + } +} + +impl NAEncoder for RawEncoder { + fn negotiate_format(&self, encinfo: &EncodeParameters) -> EncoderResult { + match encinfo.format { + NACodecTypeInfo::None => { + let fmt = match self.rtype { + RawType::YUV2 => NAPixelFormaton::from_str("yuv422p").unwrap(), + RawType::YUV4 => YUV420_FORMAT, + }; + Ok(EncodeParameters { + format: NACodecTypeInfo::Video(NAVideoInfo::new(0, 0, false, fmt)), + ..Default::default() + }) + }, + NACodecTypeInfo::Video(_) => { + let fmt = match self.rtype { + RawType::YUV2 => NAPixelFormaton::from_str("yuv422p").unwrap(), + RawType::YUV4 => YUV420_FORMAT, + }; + let mut info = *encinfo; + if let NACodecTypeInfo::Video(ref mut vinfo) = info.format { + vinfo.format = fmt; + vinfo.width = (vinfo.width + 1) & !1; + if self.rtype == RawType::YUV4 { + vinfo.height = (vinfo.height + 1) & !1; + } + } else { + return Err(EncoderError::FormatError); + } + Ok(info) + }, + NACodecTypeInfo::Audio(_) => Err(EncoderError::FormatError), + } + } + fn get_capabilities(&self) -> u64 { ENC_CAPS_CBR } + 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) => { + self.vinfo = vinfo; + let (name, fmt) = match self.rtype { + RawType::YUV2 => ("qt-yuv2", NAPixelFormaton::from_str("yuv422p").unwrap()), + RawType::YUV4 => ("qt-yuv4", YUV420_FORMAT), + }; + if vinfo.format != fmt || (vinfo.width & 1) != 0 || (name == "qt-yuv4" && (vinfo.height & 1) != 0) { + return Err(EncoderError::FormatError); + } + let info = NACodecInfo::new(name, encinfo.format, 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.stream = Some(stream.clone()); + Ok(stream) + } + } + } + fn encode(&mut self, frm: &NAFrame) -> EncoderResult<()> { + let buf = frm.get_buffer(); + let mut dbuf; + if let Some(vinfo) = buf.get_video_info() { + if vinfo != self.vinfo { + println!("Input format differs from the initial one"); + return Err(EncoderError::FormatError); + } + } + if let NABufferType::Video(ref vbuf) = buf { + let vinfo = vbuf.get_info(); + let src = vbuf.get_data(); + let yoff = vbuf.get_offset(0); + let uoff = vbuf.get_offset(1); + let voff = vbuf.get_offset(2); + let ystride = vbuf.get_stride(0); + let ustride = vbuf.get_stride(1); + let vstride = vbuf.get_stride(2); + match self.rtype { + RawType::YUV2 => { + dbuf = Vec::with_capacity(vinfo.width * vinfo.height * 2); + for (yline, (uline, vline)) in src[yoff..].chunks(ystride) + .zip(src[uoff..].chunks(ustride).zip(src[voff..].chunks(vstride))) + .take(vinfo.height) { + for (y2, (&u, &v)) in yline.chunks_exact(2).zip(uline.iter().zip(vline.iter())) { + dbuf.push(y2[0]); + dbuf.push(u ^ 0x80); + dbuf.push(y2[1]); + dbuf.push(v ^ 0x80); + } + } + }, + RawType::YUV4 => { + dbuf = Vec::with_capacity(vinfo.width * vinfo.height * 3 / 2); + for (ylines, (uline, vline)) in src[yoff..].chunks(ystride * 2) + .zip(src[uoff..].chunks(ustride).zip(src[voff..].chunks(vstride))) + .take(vinfo.height) { + let (yline0, yline1) = ylines.split_at(ystride); + for ((y0, y1), (&u, &v)) in yline0.chunks_exact(2).zip(yline1.chunks_exact(2)) + .zip(uline.iter().zip(vline.iter())) { + dbuf.push(u ^ 0x80); + dbuf.push(v ^ 0x80); + dbuf.push(y0[0]); + dbuf.push(y0[1]); + dbuf.push(y1[0]); + dbuf.push(y1[1]); + } + } + }, + } + } else { + return Err(EncoderError::FormatError); + } + self.pkt = Some(NAPacket::new(self.stream.clone().unwrap(), frm.ts, true, dbuf)); + Ok(()) + } + fn get_packet(&mut self) -> EncoderResult> { + let mut npkt = None; + std::mem::swap(&mut self.pkt, &mut npkt); + Ok(npkt) + } + fn flush(&mut self) -> EncoderResult<()> { + Ok(()) + } +} + +impl NAOptionHandler for RawEncoder { + fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } + fn set_options(&mut self, _options: &[NAOption]) { } + fn query_option_value(&self, _name: &str) -> Option { None } +} + +pub fn get_encoder_yuv2() -> Box { + Box::new(RawEncoder::new(RawType::YUV2)) +} +pub fn get_encoder_yuv4() -> Box { + Box::new(RawEncoder::new(RawType::YUV4)) +} diff --git a/nihav-qt/src/lib.rs b/nihav-qt/src/lib.rs index 3869a58..5396a3e 100644 --- a/nihav-qt/src/lib.rs +++ b/nihav-qt/src/lib.rs @@ -8,6 +8,8 @@ extern crate nihav_codec_support; mod codecs; #[cfg(feature="decoders")] pub use crate::codecs::qt_register_all_decoders; +#[cfg(feature="encoders")] +pub use crate::codecs::qt_register_all_encoders; #[cfg(feature="demuxers")] mod demuxers; -- 2.39.5