]> git.nihav.org Git - nihav.git/commitdiff
add RealMedia and RealAudio muxers
authorKostya Shishkov <kostya.shishkov@gmail.com>
Wed, 1 Mar 2023 18:36:38 +0000 (19:36 +0100)
committerKostya Shishkov <kostya.shishkov@gmail.com>
Wed, 1 Mar 2023 18:36:38 +0000 (19:36 +0100)
nihav-allstuff/src/lib.rs
nihav-realmedia/Cargo.toml
nihav-realmedia/src/lib.rs
nihav-realmedia/src/muxers/mod.rs [new file with mode: 0644]
nihav-realmedia/src/muxers/rmvb/audiostream.rs [new file with mode: 0644]
nihav-realmedia/src/muxers/rmvb/mod.rs [new file with mode: 0644]
nihav-realmedia/src/muxers/rmvb/videostream.rs [new file with mode: 0644]

index f655b953d1d5bbd7c29952b14941f43b1a244ffd..fc60450bf8bcd1a198f6ee6f3adc56effa69e36b 100644 (file)
@@ -78,6 +78,7 @@ pub fn nihav_register_all_muxers(rm: &mut RegisteredMuxers) {
     flash_register_all_muxers(rm);
     generic_register_all_muxers(rm);
     llaudio_register_all_muxers(rm);
+    realmedia_register_all_muxers(rm);
 }
 
 #[cfg(test)]
index dc3c0d68e81b05362bf3f18f46426f4e4be5154b..84a07e5482a97d29d9f920a1ca56e11570d2c017 100644 (file)
@@ -12,10 +12,13 @@ path = "../nihav-codec-support"
 features = ["h263", "mdct", "blockdsp"]
 
 [features]
-default = ["all_decoders", "all_demuxers"]
+default = ["all_decoders", "all_demuxers", "all_muxers"]
 demuxers = []
 all_demuxers = ["demuxer_real"]
 demuxer_real = ["demuxers"]
+muxers = []
+all_muxers = ["muxer_real"]
+muxer_real = ["muxers"]
 
 all_decoders = ["all_video_decoders", "all_audio_decoders"]
 decoders = []
index bcc66fddc11053554a535a3f3a4681a45d57c618..801299258138a272cfb6ed06af37d6d26419f976 100644 (file)
@@ -25,3 +25,8 @@ pub use crate::codecs::realmedia_register_all_decoders;
 mod demuxers;
 #[cfg(feature="demuxers")]
 pub use crate::demuxers::realmedia_register_all_demuxers;
+
+#[cfg(feature="muxers")]
+mod muxers;
+#[cfg(feature="muxers")]
+pub use crate::muxers::realmedia_register_all_muxers;
diff --git a/nihav-realmedia/src/muxers/mod.rs b/nihav-realmedia/src/muxers/mod.rs
new file mode 100644 (file)
index 0000000..e0594c9
--- /dev/null
@@ -0,0 +1,26 @@
+use nihav_core::muxers::*;
+
+#[cfg(debug_assertions)]
+macro_rules! validate {
+    ($a:expr) => { if !$a { println!("check failed at {}:{}", file!(), line!()); return Err(MuxerError::InvalidData); } };
+}
+#[cfg(not(debug_assertions))]
+macro_rules! validate {
+    ($a:expr) => { if !$a { return Err(MuxerError::InvalidData); } };
+}
+
+#[cfg(feature="muxer_real")]
+mod rmvb;
+
+const MUXERS: &[&dyn MuxerCreator] = &[
+#[cfg(feature="muxer_real")]
+    &rmvb::RealMediaMuxerCreator {},
+#[cfg(feature="muxer_real")]
+    &rmvb::RealAudioMuxerCreator {},
+];
+
+pub fn realmedia_register_all_muxers(rm: &mut RegisteredMuxers) {
+    for muxer in MUXERS.iter() {
+        rm.add_muxer(*muxer);
+    }
+}
diff --git a/nihav-realmedia/src/muxers/rmvb/audiostream.rs b/nihav-realmedia/src/muxers/rmvb/audiostream.rs
new file mode 100644 (file)
index 0000000..fcb3d9c
--- /dev/null
@@ -0,0 +1,693 @@
+use nihav_core::frame::*;
+use nihav_core::muxers::*;
+use super::RMStreamWriter;
+
+// fourcc, codec name, interleaver, version
+static AUDIO_CODEC_REGISTRY: &[(&[u8;4], &str, &[u8;4], u8)] = &[
+    (b"lpcJ", "ra14.4", b"Int0", 3),
+    (b"28_8", "ra28.8", b"Int4", 4),
+    (b"cook", "cook",   b"genr", 5),
+    (b"dnet", "ac3",    b"Int0", 4),
+    (b"sipr", "sipro",  b"sipr", 4),
+    (b"atrc", "atrac3", b"genr", 5),
+    (b"LSD:", "ralf",   b"Int0", 6),
+    (b"raac", "aac",    b"vbrs", 5),
+    (b"racp", "aac",    b"vbrf", 5),
+];
+
+struct InterleaveParams {
+    block_size:     usize,
+    factor:         usize,
+    frames_per_blk: usize,
+}
+
+trait Interleaver {
+    fn get_flavor(&self) -> usize;
+    fn get_block_size(&self) -> usize;
+    fn get_factor(&self) -> usize;
+    fn get_frames_per_block(&self) -> usize;
+    fn add_packet(&mut self, src: &[u8]) -> bool;
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)>;
+    fn flush(&mut self);
+
+    fn get_frame_size(&self) -> usize { self.get_block_size() / self.get_frames_per_block() }
+}
+
+struct NoInterleaver {
+    frame_size: usize,
+    pkt:        Option<Vec<u8>>,
+}
+impl Interleaver for NoInterleaver {
+    fn get_flavor(&self) -> usize { 0 }
+    fn get_block_size(&self) -> usize { self.frame_size }
+    fn get_factor(&self) -> usize { 1 }
+    fn get_frames_per_block(&self) -> usize { 1 }
+    fn add_packet(&mut self, src: &[u8]) -> bool {
+        if self.pkt.is_none() {
+            self.pkt = Some(src.to_vec());
+            true
+        } else {
+            false
+        }
+    }
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)> {
+        let mut ret = None;
+        std::mem::swap(&mut self.pkt, &mut ret);
+        if let Some(pkt) = ret {
+            Some((pkt, true))
+        } else {
+            None
+        }
+    }
+    fn flush(&mut self) {}
+}
+
+struct Int4Interleaver {
+    flavor:     usize,
+    factor:     usize,
+    frame_size: usize,
+    block_size: usize,
+    fpb:        usize,
+    cur_frame:  usize,
+    rd_block:   usize,
+    buf:        Vec<u8>,
+    map:        Vec<usize>,
+}
+impl Int4Interleaver {
+    fn new(frame_size: usize) -> MuxerResult<Self> {
+        let params = RA_28_8_INTERLEAVE_PARAMS;
+        for (flavor, entry) in params.iter().enumerate() {
+            if entry.block_size / entry.frames_per_blk == frame_size {
+                let full_size = entry.frames_per_blk * entry.factor;
+                let mut map = vec![0; full_size];
+                for i in 0..full_size {
+                    let fval = i * entry.factor;
+                    let mapped = (fval % full_size) + fval / full_size;
+                    map[mapped] = i;
+                }
+
+                return Ok(Self {
+                        flavor,
+                        frame_size,
+                        map,
+                        factor:     entry.factor,
+                        block_size: entry.block_size,
+                        fpb:        entry.frames_per_blk,
+                        cur_frame:  0,
+                        rd_block:   0,
+                        buf:        vec![0; entry.block_size * entry.factor],
+                    });
+            }
+        }
+        Err(MuxerError::UnsupportedFormat)
+    }
+}
+impl Interleaver for Int4Interleaver {
+    fn get_flavor(&self) -> usize { self.flavor }
+    fn get_block_size(&self) -> usize { self.block_size }
+    fn get_factor(&self) -> usize { self.factor }
+    fn get_frames_per_block(&self) -> usize { self.fpb }
+    fn add_packet(&mut self, src: &[u8]) -> bool {
+        if self.cur_frame == self.factor * self.fpb {
+            return false;
+        }
+        let pos = self.map[self.cur_frame];
+        self.buf[pos * self.frame_size..][..self.frame_size].copy_from_slice(&src);
+        self.cur_frame += 1;
+        true
+    }
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)> {
+        if self.cur_frame == self.factor * self.fpb {
+            let first = self.rd_block == 0;
+            let src = &self.buf[self.rd_block * self.block_size..][..self.block_size];
+            self.rd_block += 1;
+            if self.rd_block == self.factor {
+                self.rd_block = 0;
+                self.cur_frame = 0;
+            }
+            Some((src.to_vec(), first))
+        } else {
+            None
+        }
+    }
+    fn flush(&mut self) {
+        if self.cur_frame != 0 {
+            self.cur_frame = self.factor * self.fpb;
+        }
+    }
+}
+
+struct GenericInterleaver {
+    flavor:     usize,
+    factor:     usize,
+    frame_size: usize,
+    block_size: usize,
+    fpb:        usize,
+    cur_frame:  usize,
+    rd_block:   usize,
+    buf:        Vec<u8>,
+    map:        Vec<usize>,
+}
+impl GenericInterleaver {
+    fn new(frame_size: usize, fcc: [u8; 4]) -> MuxerResult<Self> {
+        let params = match &fcc {
+                b"atrc" => ATRAC_INTERLEAVE_PARAMS,
+                b"cook" => COOK_INTERLEAVE_PARAMS,
+                b"sipr" => SIPRO_INTERLEAVE_PARAMS,
+                _ => return Err(MuxerError::UnsupportedFormat),
+            };
+        for (flavor, entry) in params.iter().enumerate() {
+            if entry.block_size / entry.frames_per_blk == frame_size {
+                let full_size = entry.frames_per_blk * entry.factor;
+                let mut map = vec![0; full_size];
+
+                let mut frm = 0;
+                let mut blk = 0;
+                let mut even = true;
+                for dst in map.iter_mut() {
+                    let mapped = blk * entry.frames_per_blk + frm;
+                    blk += 2;
+                    if blk >= entry.factor {
+                        if even {
+                            blk = 1;
+                        } else {
+                            blk = 0;
+                            frm += 1;
+                        }
+                        even = !even;
+                    }
+                    *dst = mapped;
+                }
+
+                return Ok(Self {
+                        flavor,
+                        frame_size,
+                        map,
+                        factor:     entry.factor,
+                        block_size: entry.block_size,
+                        fpb:        entry.frames_per_blk,
+                        cur_frame:  0,
+                        rd_block:   0,
+                        buf:        vec![0; entry.block_size * entry.factor],
+                    });
+            }
+        }
+        Err(MuxerError::UnsupportedFormat)
+    }
+}
+impl Interleaver for GenericInterleaver {
+    fn get_flavor(&self) -> usize { self.flavor }
+    fn get_block_size(&self) -> usize { self.block_size }
+    fn get_factor(&self) -> usize { self.factor }
+    fn get_frames_per_block(&self) -> usize { self.fpb }
+    fn add_packet(&mut self, src: &[u8]) -> bool {
+        if self.cur_frame == self.factor * self.fpb {
+            return false;
+        }
+        let pos = self.map[self.cur_frame];
+        self.buf[pos * self.frame_size..][..self.frame_size].copy_from_slice(&src);
+        self.cur_frame += 1;
+        true
+    }
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)> {
+        if self.cur_frame == self.factor * self.fpb {
+            let first = self.rd_block == 0;
+            let src = &self.buf[self.rd_block * self.block_size..][..self.block_size];
+            self.rd_block += 1;
+            if self.rd_block == self.factor {
+                self.rd_block = 0;
+                self.cur_frame = 0;
+            }
+            Some((src.to_vec(), first))
+        } else {
+            None
+        }
+    }
+    fn flush(&mut self) {
+        if self.cur_frame != 0 {
+            self.cur_frame = self.factor * self.fpb;
+        }
+    }
+}
+
+struct SiproInterleaver {
+    block_size: usize,
+    factor:     usize,
+    flavor:     usize,
+    buf:        Vec<u8>,
+    wr_pos:     usize,
+    rd_pos:     usize,
+}
+impl SiproInterleaver {
+    fn new(mut frame_size: usize) -> MuxerResult<Self> {
+        if frame_size == 0 {
+            return Err(MuxerError::UnsupportedFormat);
+        }
+        while frame_size < 200 {
+            frame_size <<= 1;
+        }
+        for (flavor, entry) in SIPRO_INTERLEAVE_PARAMS.iter().enumerate() {
+            if entry.block_size == frame_size {
+                return Ok(Self {
+                    block_size: entry.block_size,
+                    factor:     entry.factor,
+                    flavor,
+                    buf:        vec![0; entry.block_size * entry.factor],
+                    wr_pos:     0,
+                    rd_pos:     0,
+                });
+            }
+        }
+        Err(MuxerError::UnsupportedFormat)
+    }
+}
+impl Interleaver for SiproInterleaver {
+    fn get_flavor(&self) -> usize { self.flavor }
+    fn get_block_size(&self) -> usize { self.block_size }
+    fn get_factor(&self) -> usize { self.factor }
+    fn get_frames_per_block(&self) -> usize { 1 }
+    fn add_packet(&mut self, src: &[u8]) -> bool {
+        if self.wr_pos < self.factor {
+            self.buf[self.wr_pos * self.block_size..][..self.block_size].copy_from_slice(src);
+            self.wr_pos += 1;
+            true
+        } else {
+            false
+        }
+    }
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)> {
+        if self.wr_pos == self.factor {
+            let first = self.rd_pos == 0;
+            if self.rd_pos == 0 {
+                sipro_scramble(&mut self.buf, self.factor, self.block_size);
+            }
+            let ret = self.buf[self.rd_pos * self.block_size..][..self.block_size].to_vec();
+            self.rd_pos += 1;
+            if self.rd_pos == self.factor {
+                self.rd_pos = 0;
+                self.wr_pos = 0;
+            }
+            Some((ret, first))
+        } else {
+            None
+        }
+    }
+    fn flush(&mut self) {
+        if self.wr_pos != self.factor {
+            self.wr_pos = self.factor;
+        }
+    }
+}
+
+fn sipro_scramble(buf: &mut [u8], factor: usize, fsize: usize) {
+    let stride = factor * fsize * 2 / 96;
+    for &swap_pair in SIPRO_SWAPS.iter() {
+        let mut sidx = usize::from(swap_pair[0]) * stride;
+        let mut didx = usize::from(swap_pair[1]) * stride;
+        for _ in 0..stride {
+            let in0 = buf[sidx >> 1];
+            let in1 = buf[didx >> 1];
+            let nib0 = (in0 >> ((sidx & 1) * 4)) & 0xF;
+            let nib1 = (in1 >> ((didx & 1) * 4)) & 0xF;
+
+            buf[didx >> 1] = (nib0 << (4 * (didx & 1))) | (in1 & (0xF << (4 * (!didx & 1))));
+            buf[sidx >> 1] = (nib1 << (4 * (sidx & 1))) | (in0 & (0xF << (4 * (!sidx & 1))));
+
+            sidx += 1;
+            didx += 1;
+        }
+    }
+}
+
+struct AACInterleaver {
+    frame_size: usize,
+    data:       Vec<u8>,
+    sizes:      Vec<usize>,
+    full:       bool,
+}
+impl AACInterleaver {
+    fn new(frame_size: usize) -> Self {
+        Self {
+            frame_size,
+            data:   Vec::with_capacity(frame_size * 3 / 2),
+            sizes:  Vec::with_capacity((frame_size / 128).max(8)),
+            full:   false,
+        }
+    }
+}
+impl Interleaver for AACInterleaver {
+    fn get_flavor(&self) -> usize { 0 }
+    fn get_block_size(&self) -> usize { self.frame_size }
+    fn get_factor(&self) -> usize { 1 }
+    fn get_frames_per_block(&self) -> usize { 1 }
+    fn add_packet(&mut self, src: &[u8]) -> bool {
+        if !self.full {
+            self.data.extend_from_slice(src);
+            self.sizes.push(src.len());
+            if self.data.len() >= self.frame_size {
+                self.full = true;
+            }
+            true
+        } else {
+            false
+        }
+    }
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)> {
+        if self.full {
+            let mut dst = Vec::with_capacity(self.frame_size);
+            let mut gw = GrowableMemoryWriter::new_write(&mut dst);
+            let mut bw = ByteWriter::new(&mut gw);
+            bw.write_u16be((self.sizes.len() * 16) as u16).unwrap();
+            for &pkt_size in self.sizes.iter() {
+                bw.write_u16be(pkt_size as u16).unwrap();
+            }
+            bw.write_buf(&self.data).unwrap();
+
+            self.data.clear();
+            self.sizes.clear();
+            self.full = false;
+
+            Some((dst, true))
+        } else {
+            None
+        }
+    }
+    fn flush(&mut self) {
+        if !self.sizes.is_empty() {
+            self.full = true;
+        }
+    }
+}
+
+struct InterleaveInfo {
+    fcc:            [u8; 4],
+    il_fcc:         [u8; 4],
+    block_size:     usize,
+    frame_size:     usize,
+    factor:         usize,
+    flavor:         usize,
+}
+
+struct AudioStreamWriter {
+    fcc:            [u8; 4],
+    il_fcc:         [u8; 4],
+    is_raw:         bool,
+    version:        u8,
+    header_pos:     u64,
+    interleave:     Box<dyn Interleaver>,
+    data_size:      usize,
+}
+
+impl RMStreamWriter for AudioStreamWriter {
+    fn write_header(&mut self, bw: &mut ByteWriter, astream: &NAStream) -> MuxerResult<()> {
+        self.header_pos = bw.tell();
+        if self.version < 6 {
+            bw.write_buf(b".ra\xFD")?;
+            bw.write_u16be(self.version.into())?;
+        }
+
+        let il_info = InterleaveInfo {
+                fcc:        self.fcc,
+                il_fcc:     self.il_fcc,
+                block_size: self.interleave.get_block_size(),
+                frame_size: self.interleave.get_frame_size(),
+                factor:     self.interleave.get_factor(),
+                flavor:     self.interleave.get_flavor(),
+            };
+
+        match self.version {
+            3 => write_aformat3(bw, astream, &il_info)?,
+            4 => write_aformat4(bw, astream, &il_info)?,
+            5 => write_aformat5(bw, astream, &il_info)?,
+            6 => write_lsd(bw, astream)?,
+            _ => unreachable!(),
+        }
+        Ok(())
+    }
+    fn queue_packet(&mut self, pkt: NAPacket) -> bool {
+        let src = pkt.get_buffer();
+        self.data_size += src.len();
+        let frame_size = self.interleave.get_frame_size();
+        if !self.is_raw {
+            let mut ret = false;
+            for frame in src.chunks(frame_size) {
+                ret = self.interleave.add_packet(frame);
+            }
+            ret
+        } else {
+            self.interleave.add_packet(&src)
+        }
+    }
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)> { self.interleave.get_packet() }
+    fn flush(&mut self) { self.interleave.flush() }
+    fn finish(&mut self, bw: &mut ByteWriter) -> MuxerResult<()> {
+        let cur_pos = bw.tell();
+        match self.version {
+            3 => {
+                bw.seek(SeekFrom::Start(self.header_pos + 18))?;
+                bw.write_u32be(self.data_size as u32)?;
+            },
+            4 | 5 => {
+                bw.seek(SeekFrom::Start(self.header_pos + 12))?;
+                bw.write_u32be(self.data_size/*+header_size*/ as u32)?;
+                bw.seek(SeekFrom::Current(12))?;
+                bw.write_u32be(self.data_size as u32)?;
+            },
+            6 => unimplemented!(),
+            _ => unreachable!(),
+        };
+        bw.seek(SeekFrom::Start(cur_pos))?;
+        Ok(())
+    }
+}
+
+fn write_audio_metadata(bw: &mut ByteWriter) -> MuxerResult<()> {
+    bw.write_byte(0)?; // title_string_length
+    bw.write_byte(0)?; // author_string_length
+    bw.write_byte(0)?; // copyright_string_length
+    bw.write_byte(0)?; // user_string_length
+    Ok(())
+}
+
+fn write_aformat3(bw: &mut ByteWriter, _stream: &NAStream, il_info: &InterleaveInfo) -> MuxerResult<()> {
+    let start = bw.tell();
+    bw.write_u16be(0)?; // header_bytes
+    bw.write_u16be(il_info.flavor as u16)?;
+    bw.write_u32be(il_info.frame_size as u32)?; // granularity
+    bw.write_u32be(0)?; // bytes per minute
+    bw.write_u32be(0)?; // total bytes
+    write_audio_metadata(bw)?;
+    bw.write_byte(0)?; //can_copy
+    bw.write_byte(4)?; // FCC length
+    bw.write_buf(&il_info.fcc)?;
+    let end = bw.tell();
+    bw.seek(SeekFrom::Start(start))?;
+    bw.write_u16be((end - start - 2) as u16)?;
+    bw.seek(SeekFrom::Start(end))?;
+
+    Ok(())
+}
+
+fn write_aformat4(bw: &mut ByteWriter, stream: &NAStream, il_info: &InterleaveInfo) -> MuxerResult<()> {
+    let info = stream.get_info().get_properties().get_audio_info().unwrap();
+
+    bw.write_u16be(0)?;
+    let start = bw.tell();
+    bw.write_buf(b".ra4")?;
+    bw.write_u32be(0)?; // data size
+    bw.write_u16be(4)?; // version
+    bw.write_u16be(0)?; // revision
+    bw.write_u16be(0)?; // header_bytes
+    bw.write_u16be(il_info.flavor as u16)?;
+    bw.write_u32be(il_info.frame_size as u32)?; // granularity
+    bw.write_u32be(0)?; // total bytes
+    bw.write_u32be(0)?; // bytes_per_minute
+    bw.write_u32be(0)?; // bytes_per_minute2
+    bw.write_u16be(il_info.factor as u16)?;
+    bw.write_u16be(il_info.block_size as u16)?;
+    bw.write_u16be(0)?; // user data
+    bw.write_u32be(info.sample_rate)?; // sample rate
+    bw.write_u32be(info.format.bits.into())?; // sample size
+    bw.write_u16be(info.channels.into())?; // num channels
+    bw.write_byte(4)?; // ileave ID len
+    bw.write_buf(&il_info.il_fcc)?;
+    bw.write_byte(4)?; // codec ID len
+    bw.write_buf(&il_info.fcc)?;
+    bw.write_byte(if &il_info.il_fcc == b"Int0" { 0 } else { 1 })?;
+    bw.write_byte(7)?; // can_copy
+    bw.write_byte(0)?; // stream_type
+    write_audio_metadata(bw)?;
+    let end = bw.tell();
+    bw.seek(SeekFrom::Start(start + 12))?;
+    bw.write_u16be((end - start - 4) as u16)?;
+    bw.seek(SeekFrom::Start(end))?;
+
+    Ok(())
+}
+
+fn write_aformat5(bw: &mut ByteWriter, stream: &NAStream, il_info: &InterleaveInfo) -> MuxerResult<()> {
+    let info = stream.get_info().get_properties().get_audio_info().unwrap();
+
+    bw.write_u16be(0)?;
+    let start = bw.tell();
+    bw.write_buf(b".ra5")?;
+    bw.write_u32be(0)?; // data size
+    bw.write_u16be(5)?; // version
+    bw.write_u16be(0)?; // revision
+    bw.write_u16be(0)?; // header_bytes
+    bw.write_u16be(il_info.flavor as u16)?;
+    bw.write_u32be(il_info.block_size as u32)?; // granularity
+    bw.write_u32be(0)?; // total bytes
+    bw.write_u32be(0)?; // bytes_per_minute
+    bw.write_u32be(0)?; // bytes_per_minute2
+    bw.write_u16be(il_info.factor as u16)?;
+    bw.write_u16be(il_info.block_size as u16)?;
+    bw.write_u16be(il_info.frame_size as u16)?;
+    bw.write_u16be(0)?; // user data
+    bw.write_u32be(info.sample_rate)?; // sample rate
+    bw.write_u32be(info.sample_rate)?; // actual sample rate
+    bw.write_u32be(info.format.bits.into())?; // sample size
+    bw.write_u16be(info.channels.into())?; // num channels
+    bw.write_buf(&il_info.il_fcc)?;
+    bw.write_buf(&il_info.fcc)?;
+    bw.write_byte(if &il_info.il_fcc == b"Int0" { 0 } else { 1 })?;
+    bw.write_byte(7)?; // can_copy
+    bw.write_byte(0)?; // stream_type
+    bw.write_byte(0)?; // has_interleave_pattern
+    if let Some(edata) = stream.get_info().get_extradata() {
+        if !matches!(&il_info.fcc, b"raac" | b"racp") {
+            bw.write_u32be(edata.len() as u32)?;
+            bw.write_buf(&edata)?;
+        } else {
+            bw.write_u32be((edata.len() + 1) as u32)?;
+            bw.write_byte(2)?;
+            bw.write_buf(&edata)?;
+        }
+    } else {
+        bw.write_u32be(0)?;
+    }
+    let end = bw.tell();
+    bw.seek(SeekFrom::Start(start + 12))?;
+    bw.write_u16be((end - start - 4) as u16)?;
+    bw.seek(SeekFrom::Start(end))?;
+
+    Ok(())
+}
+
+fn write_lsd(bw: &mut ByteWriter, stream: &NAStream) -> MuxerResult<()> {
+    if let Some(edata) = stream.get_info().get_extradata() {
+        bw.write_buf(&edata)?;
+    }
+    Ok(())
+}
+
+fn create_interleaver(id: [u8; 4], fcc: [u8; 4], ainfo: NAAudioInfo) -> MuxerResult<Box<dyn Interleaver>> {
+    let frame_size = ainfo.block_len;
+    match &id {
+        b"Int0" => Ok(Box::new(NoInterleaver{ frame_size, pkt: None })),
+        b"Int4" => Ok(Box::new(Int4Interleaver::new(frame_size)?)),
+        b"sipr" => Ok(Box::new(SiproInterleaver::new(frame_size)?)),
+        b"genr" => Ok(Box::new(GenericInterleaver::new(frame_size, fcc)?)),
+        b"vbrs" | b"vbrf" => Ok(Box::new(AACInterleaver::new(1024))),
+        _ => unimplemented!(),
+    }
+}
+
+pub fn create_audio_stream(stream: &NAStream) -> MuxerResult<Box<dyn RMStreamWriter>> {
+    let info = stream.get_info();
+    let cname = info.get_name();
+    let mut fourcc = [0u8; 4];
+    let mut ileave = [0u8; 4];
+    let mut version = 0;
+    for &(fcc, name, ileaver, cversion) in AUDIO_CODEC_REGISTRY.iter() {
+        if name == cname {
+            fourcc = *fcc;
+            ileave = *ileaver;
+            version = cversion;
+            break;
+        }
+    }
+    if version > 0 {
+        let ainfo = info.get_properties().get_audio_info().unwrap();
+        Ok(Box::new(AudioStreamWriter {
+            fcc:        fourcc,
+            il_fcc:     ileave,
+            is_raw:     &ileave == b"Int0",
+            version,
+            interleave: create_interleaver(ileave, fourcc, ainfo)?,
+            header_pos: 0,
+            data_size:  0,
+        }))
+    } else {
+        Err(MuxerError::UnsupportedFormat)
+    }
+}
+
+const SIPRO_SWAPS:   [[u8; 2]; 38] = [
+    [  0, 63 ], [  1, 22 ], [  2, 44 ], [  3, 90 ],
+    [  5, 81 ], [  7, 31 ], [  8, 86 ], [  9, 58 ],
+    [ 10, 36 ], [ 12, 68 ], [ 13, 39 ], [ 14, 73 ],
+    [ 15, 53 ], [ 16, 69 ], [ 17, 57 ], [ 19, 88 ],
+    [ 20, 34 ], [ 21, 71 ], [ 24, 46 ], [ 25, 94 ],
+    [ 26, 54 ], [ 28, 75 ], [ 29, 50 ], [ 32, 70 ],
+    [ 33, 92 ], [ 35, 74 ], [ 38, 85 ], [ 40, 56 ],
+    [ 42, 87 ], [ 43, 65 ], [ 45, 59 ], [ 48, 79 ],
+    [ 49, 93 ], [ 51, 89 ], [ 55, 95 ], [ 61, 76 ],
+    [ 67, 83 ], [ 77, 80 ]
+];
+
+const RA_28_8_INTERLEAVE_PARAMS: &[InterleaveParams] = &[
+    InterleaveParams { block_size: 228, factor: 12, frames_per_blk: 6 },
+];
+const SIPRO_INTERLEAVE_PARAMS: &[InterleaveParams] = &[
+    InterleaveParams { block_size: 232, factor: 6, frames_per_blk: 16 },
+    InterleaveParams { block_size: 304, factor: 6, frames_per_blk: 16 },
+    InterleaveParams { block_size: 296, factor: 6, frames_per_blk: 16 },
+    InterleaveParams { block_size: 320, factor: 6, frames_per_blk: 16 }
+];
+const ATRAC_INTERLEAVE_PARAMS: &[InterleaveParams] = &[
+    InterleaveParams { block_size:  768, factor: 20, frames_per_blk: 4 },
+    InterleaveParams { block_size: 1088, factor: 20, frames_per_blk: 4 },
+    InterleaveParams { block_size:  912, factor: 30, frames_per_blk: 3 },
+    InterleaveParams { block_size: 1152, factor: 30, frames_per_blk: 3 },
+    InterleaveParams { block_size: 1272, factor: 30, frames_per_blk: 3 },
+    InterleaveParams { block_size: 1024, factor: 30, frames_per_blk: 2 },
+    InterleaveParams { block_size:  768, factor: 10, frames_per_blk: 1 },
+    InterleaveParams { block_size: 1024, factor: 10, frames_per_blk: 1 }
+];
+const COOK_INTERLEAVE_PARAMS: &[InterleaveParams] = &[
+    InterleaveParams { block_size:  288, factor:  8, frames_per_blk:  9 },
+    InterleaveParams { block_size:  352, factor:  8, frames_per_blk: 11 },
+    InterleaveParams { block_size:  564, factor:  8, frames_per_blk: 12 },
+    InterleaveParams { block_size:  600, factor:  9, frames_per_blk: 10 },
+    InterleaveParams { block_size:  651, factor: 14, frames_per_blk:  7 },
+    InterleaveParams { block_size:  640, factor: 15, frames_per_blk:  5 },
+    InterleaveParams { block_size:  744, factor: 20, frames_per_blk:  4 },
+    InterleaveParams { block_size:  558, factor: 16, frames_per_blk:  6 },
+    InterleaveParams { block_size:  288, factor:  6, frames_per_blk: 12 },
+    InterleaveParams { block_size:  580, factor: 10, frames_per_blk: 10 },
+    InterleaveParams { block_size:  564, factor: 14, frames_per_blk:  6 },
+    InterleaveParams { block_size:  640, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size:  744, factor: 20, frames_per_blk:  4 },
+    InterleaveParams { block_size:  834, factor: 30, frames_per_blk:  3 },
+    InterleaveParams { block_size:  644, factor: 20, frames_per_blk:  4 },
+    InterleaveParams { block_size:  600, factor:  9, frames_per_blk: 10 },
+    InterleaveParams { block_size:  651, factor: 14, frames_per_blk:  7 },
+    InterleaveParams { block_size:  528, factor:  8, frames_per_blk: 11 },
+    InterleaveParams { block_size:  600, factor: 10, frames_per_blk: 10 },
+    InterleaveParams { block_size:  600, factor: 10, frames_per_blk: 10 },
+    InterleaveParams { block_size:  465, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size:  465, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size:  640, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size:  640, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size:  930, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size: 1400, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size:  376, factor:  8, frames_per_blk: 11 },
+    InterleaveParams { block_size:  930, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size: 1400, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size:  640, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size: 1395, factor: 16, frames_per_blk:  5 },
+    InterleaveParams { block_size: 1143, factor: 10, frames_per_blk:  3 },
+    InterleaveParams { block_size: 1064, factor: 10, frames_per_blk:  2 },
+    InterleaveParams { block_size:  778, factor:  1, frames_per_blk:  1 }
+];
diff --git a/nihav-realmedia/src/muxers/rmvb/mod.rs b/nihav-realmedia/src/muxers/rmvb/mod.rs
new file mode 100644 (file)
index 0000000..32fbbb9
--- /dev/null
@@ -0,0 +1,599 @@
+use nihav_core::muxers::*;
+mod audiostream;
+use audiostream::*;
+mod videostream;
+use videostream::*;
+
+trait RMWriterHelper {
+    fn write_chunk(&mut self, id: &[u8], size: u32, version: u16) -> MuxerResult<()>;
+    fn write_string(&mut self, data: &[u8]) -> MuxerResult<()>;
+    fn patch_value(&mut self, val: u32, off: u64) -> MuxerResult<()>;
+}
+
+impl<'a> RMWriterHelper for ByteWriter<'a> {
+    fn write_chunk(&mut self, id: &[u8], size: u32, version: u16) -> MuxerResult<()> {
+        self.write_buf(id)?;
+        self.write_u32be(size)?;
+        self.write_u16be(version)?;
+        Ok(())
+    }
+    fn write_string(&mut self, data: &[u8]) -> MuxerResult<()> {
+        validate!(data.len() < 256);
+        self.write_byte(data.len() as u8)?;
+        self.write_buf(data)?;
+        Ok(())
+    }
+    fn patch_value(&mut self, val: u32, off: u64) -> MuxerResult<()> {
+        let cur_pos = self.tell();
+        self.seek(SeekFrom::Start(off))?;
+        self.write_u32be(val)?;
+        self.seek(SeekFrom::Start(cur_pos))?;
+        Ok(())
+    }
+}
+
+pub trait RMStreamWriter {
+    fn write_header(&mut self, bw: &mut ByteWriter, astream: &NAStream) -> MuxerResult<()>;
+    fn queue_packet(&mut self, pkt: NAPacket) -> bool;
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)>;
+    fn flush(&mut self);
+    fn finish(&mut self, bw: &mut ByteWriter) -> MuxerResult<()>;
+}
+
+#[derive(Clone,Copy)]
+struct IndexEntry {
+    time:       u32,
+    pos:        u64,
+    pkt_no:     u32,
+}
+
+struct RMStream {
+    packetiser:     Box<dyn RMStreamWriter>,
+    stream_id:      u16,
+    mdpr_pos:       u64,
+    npkts:          usize,
+    data_size:      usize,
+    max_pkt_size:   usize,
+    time:           u32,
+    cur_time:       u32,
+    keyframe:       bool,
+    audio:          bool,
+    index:          Vec<IndexEntry>,
+}
+
+impl RMStream {
+    fn new(strno: usize, stream: &NAStream) -> MuxerResult<Self> {
+        let packetiser = match stream.get_media_type() {
+                StreamType::Video => create_video_stream(stream)?,
+                StreamType::Audio => create_audio_stream(stream)?,
+                _ => Box::new(DummyStreamWriter{}),
+            };
+        Ok(Self{
+            packetiser,
+            stream_id:      strno as u16,
+            mdpr_pos:       0,
+            npkts:          0,
+            data_size:      0,
+            max_pkt_size:   0,
+            time:           0,
+            cur_time:       0,
+            keyframe:       false,
+            audio:          false,
+            index:          Vec::new(),
+        })
+    }
+    fn write_mdpr(&mut self, bw: &mut ByteWriter, strm: &NAStream) -> MuxerResult<()> {
+        self.mdpr_pos = bw.tell();
+
+        bw.write_chunk(b"MDPR", 0, 0)?;
+        bw.write_u16be(self.stream_id as u16)?;
+        bw.write_u32be(0)?; //max br
+        bw.write_u32be(0)?; //avg br
+        bw.write_u32be(0)?; //max ps
+        bw.write_u32be(0)?; //avg ps
+        bw.write_u32be(0)?; //num packets
+        bw.write_u32be(0)?; //duration
+        bw.write_u32be(0)?; //preroll
+
+        match strm.get_media_type() {
+            StreamType::Video => {
+                bw.write_string(b"The Video Stream")?;
+                bw.write_string(b"video/x-pn-realvideo")?;
+            },
+            StreamType::Audio => {
+                bw.write_string(b"The Audio Stream")?;
+                bw.write_string(b"audio/x-pn-realaudio")?;
+                self.audio = true;
+            },
+            _ => {
+                bw.write_string(b"some other stream")?;
+                bw.write_string(b"data")?;
+            },
+        };
+        bw.write_u32be(0)?; //extradata size
+        let edata_start = bw.tell();
+        self.packetiser.write_header(bw, strm)?;
+        let edata_end = bw.tell();
+        bw.patch_value((edata_end - edata_start) as u32, edata_start - 4)?;
+
+        patch_size(bw, self.mdpr_pos)?;
+
+        Ok(())
+    }
+    fn write_packet(&mut self, bw: &mut ByteWriter, pkt: NAPacket, pkt_no: &mut u32) -> MuxerResult<()> {
+        if let Some(pts) = pkt.get_pts() {
+            let (tb_num, tb_den) = pkt.get_stream().get_timebase();
+            let ms = NATimeInfo::ts_to_time(pts, 1000, tb_num, tb_den) as u32;
+            self.time = self.time.max(ms);
+            self.cur_time = ms;
+        }
+        self.keyframe = pkt.keyframe;
+        self.packetiser.queue_packet(pkt);
+        self.write_packets(bw, pkt_no)
+    }
+    fn write_packets(&mut self, bw: &mut ByteWriter, pkt_no: &mut u32) -> MuxerResult<()> {
+        while let Some((data, first)) = self.packetiser.get_packet() {
+            validate!(data.len() < 65000);
+            if self.keyframe && first {
+                self.index.push(IndexEntry{ time: self.cur_time, pos: bw.tell(), pkt_no: *pkt_no });
+            }
+            let is_keyframe = self.keyframe && (!self.audio || first);
+            bw.write_u16be(0)?; //version;
+            bw.write_u16be((data.len() + 12) as u16)?;
+            bw.write_u16be(self.stream_id)?;
+            bw.write_u32be(self.cur_time)?;
+            bw.write_byte(0)?; //packet group
+            bw.write_byte(if is_keyframe { 0x2 } else { 0x0 })?;
+            bw.write_buf(&data)?;
+
+            self.npkts += 1;
+            self.data_size += data.len();
+
+            *pkt_no += 1;
+        }
+        Ok(())
+    }
+    fn finish(&mut self, bw: &mut ByteWriter, pkt_no: &mut u32) -> MuxerResult<()> {
+        self.packetiser.flush();
+        self.write_packets(bw, pkt_no)?;
+
+        let pos = bw.tell();
+        bw.seek(SeekFrom::Start(self.mdpr_pos + 12))?;
+        bw.write_u32be(if self.time > 0 { (self.data_size * 1000 / (self.time as usize)) as u32 } else { 0 })?;
+        bw.write_u32be(if self.time > 0 { (self.data_size * 1000 / (self.time as usize)) as u32 } else { 0 })?;
+        bw.write_u32be(self.max_pkt_size as u32)?;
+        bw.write_u32be(if self.npkts > 0 { (self.data_size / self.npkts) as u32 } else { 0 })?;
+        bw.seek(SeekFrom::Current(8))?;
+        bw.write_u32be(self.time)?;
+
+        bw.seek(SeekFrom::Start(pos))?;
+        Ok(())
+    }
+}
+
+struct RMMuxer<'a> {
+    bw:             &'a mut ByteWriter<'a>,
+    streams:        Vec<RMStream>,
+    data_pos:       u64,
+    num_chunks:     u32,
+    cur_packet:     u32,
+}
+
+impl<'a> RMMuxer<'a> {
+    fn new(bw: &'a mut ByteWriter<'a>) -> Self {
+        Self {
+            bw,
+            streams:    Vec::new(),
+            data_pos:   0,
+            num_chunks: 0,
+            cur_packet: 0,
+        }
+    }
+    fn write_index(&mut self) -> MuxerResult<()> {
+        let mut indx_pos = 0x38;
+
+        for stream in self.streams.iter() {
+            let cur_pos = self.bw.tell();
+            self.bw.patch_value(cur_pos as u32, indx_pos)?;
+            indx_pos = cur_pos + 16;
+
+            let idx_size = 10 + 10 + stream.index.len() * 14;
+            self.bw.write_chunk(b"INDX", idx_size as u32, 0)?;
+            self.bw.write_u32be(stream.index.len() as u32)?;
+            self.bw.write_u16be(stream.stream_id)?;
+            self.bw.write_u32be(0)?; // next index position
+            for entry in stream.index.iter() {
+                self.bw.write_u16be(0)?; // version
+                self.bw.write_u32be(entry.time)?;
+                self.bw.write_u32be(entry.pos as u32)?;
+                self.bw.write_u32be(entry.pkt_no)?;
+            }
+
+            self.num_chunks += 1;
+        }
+
+        Ok(())
+    }
+    fn update_prop(&mut self) -> MuxerResult<()> {
+        let mut data_size = 0;
+        let mut npkts = 0;
+        let mut max_pkt_size = 0;
+        let mut time = 0;
+
+        for stream in self.streams.iter() {
+            data_size += stream.data_size;
+            time = time.max(stream.time);
+            npkts += stream.npkts;
+            max_pkt_size = max_pkt_size.max(stream.max_pkt_size);
+        }
+
+        if npkts > 0 && time > 0 {
+            let cur_pos = self.bw.tell();
+
+            let bitrate = (data_size * 1000 / (time as usize)) as u32;
+            self.bw.seek(SeekFrom::Start(28))?;
+            self.bw.write_u32be(bitrate)?;
+            self.bw.write_u32be(bitrate)?;
+            self.bw.write_u32be(max_pkt_size as u32)?;
+            self.bw.write_u32be((data_size / npkts) as u32)?;
+            self.bw.write_u32be(npkts as u32)?;
+            self.bw.write_u32be(time)?;
+
+            self.bw.seek(SeekFrom::Start(cur_pos))?;
+        }
+
+        self.bw.patch_value(self.data_pos as u32, 0x3C)?;
+
+        Ok(())
+    }
+}
+
+fn patch_size(bw: &mut ByteWriter, pos: u64) -> MuxerResult<()> {
+    let end = bw.tell();
+    bw.patch_value((end - pos) as u32, pos + 4)
+}
+
+impl<'a> MuxCore<'a> for RMMuxer<'a> {
+    fn create(&mut self, strmgr: &StreamManager) -> MuxerResult<()> {
+        if strmgr.get_num_streams() == 0 {
+            return Err(MuxerError::InvalidArgument);
+        }
+        if strmgr.get_num_streams() > 100 {
+            return Err(MuxerError::UnsupportedFormat);
+        }
+
+        self.bw.write_chunk(b".RMF", 18, 0)?;
+        self.bw.write_u32be(0)?; // container version
+        self.bw.write_u32be(0)?; // number of chunks
+
+        self.num_chunks = 1;
+        let prop_start = self.bw.tell();
+        self.bw.write_chunk(b"PROP", 0, 0)?;
+        self.bw.write_u32be(0)?; //max br
+        self.bw.write_u32be(0)?; //avg br
+        self.bw.write_u32be(0)?; //max ps
+        self.bw.write_u32be(0)?; //avg ps
+        self.bw.write_u32be(0)?; //num packets
+        self.bw.write_u32be(0)?; //duration
+        self.bw.write_u32be(0)?; //preroll
+        self.bw.write_u32be(0)?; //index offset
+        self.bw.write_u32be(0)?; //data offset
+        self.bw.write_u16be(strmgr.get_num_streams() as u16)?;
+        self.bw.write_u16be(0)?; // flags
+        patch_size(self.bw, prop_start)?;
+
+        self.streams.clear();
+        for (strno, strm) in strmgr.iter().enumerate() {
+            let mut swriter = RMStream::new(strno, &strm)?;
+            swriter.write_mdpr(self.bw, &strm)?;
+            self.streams.push(swriter);
+            self.num_chunks += 1;
+        }
+
+        self.data_pos = self.bw.tell();
+        self.bw.write_chunk(b"DATA", 0, 0)?;
+        self.bw.write_u32be(0)?; //num packets
+        self.bw.write_u32be(0)?; //next data chunk
+        self.num_chunks += 1;
+
+        Ok(())
+    }
+    fn mux_frame(&mut self, _strmgr: &StreamManager, pkt: NAPacket) -> MuxerResult<()> {
+        if self.data_pos == 0 {
+            return Err(MuxerError::NotCreated);
+        }
+        let stream = pkt.get_stream();
+        let str_num = stream.get_num();
+        if str_num > self.streams.len() {
+            return Err(MuxerError::UnsupportedFormat);
+        }
+        self.streams[str_num].write_packet(self.bw, pkt, &mut self.cur_packet)?;
+
+        Ok(())
+    }
+    fn flush(&mut self) -> MuxerResult<()> {
+        Ok(())
+    }
+    fn end(&mut self) -> MuxerResult<()> {
+        if self.data_pos == 0 {
+            return Err(MuxerError::NotCreated);
+        }
+        let mut tot_npkts = 0;
+        for stream in self.streams.iter_mut() {
+            stream.finish(self.bw, &mut self.cur_packet)?;
+            tot_npkts += stream.npkts;
+        }
+
+        let data_size = self.bw.tell() - self.data_pos;
+        self.bw.patch_value(data_size as u32, self.data_pos + 4)?;
+        self.bw.patch_value(tot_npkts as u32, self.data_pos + 10)?;
+
+        self.write_index()?;
+        self.update_prop()?;
+
+        self.bw.patch_value(self.num_chunks, 14)?;
+        Ok(())
+    }
+}
+
+impl<'a> NAOptionHandler for RMMuxer<'a> {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub struct RealMediaMuxerCreator {}
+
+impl MuxerCreator for RealMediaMuxerCreator {
+    fn new_muxer<'a>(&self, bw: &'a mut ByteWriter<'a>) -> Box<dyn MuxCore<'a> + 'a> {
+        Box::new(RMMuxer::new(bw))
+    }
+    fn get_name(&self) -> &'static str { "realmedia" }
+    fn get_capabilities(&self) -> MuxerCapabilities { MuxerCapabilities::Universal }
+}
+
+struct RAMuxer<'a> {
+    bw:             &'a mut ByteWriter<'a>,
+    sw:             Option<Box<dyn RMStreamWriter>>,
+}
+
+impl<'a> RAMuxer<'a> {
+    fn new(bw: &'a mut ByteWriter<'a>) -> Self {
+        Self {
+            bw,
+            sw:     None,
+        }
+    }
+}
+
+impl<'a> MuxCore<'a> for RAMuxer<'a> {
+    fn create(&mut self, strmgr: &StreamManager) -> MuxerResult<()> {
+        if strmgr.get_num_streams() != 1 {
+            return Err(MuxerError::InvalidArgument);
+        }
+        let astream = strmgr.get_stream(0).unwrap();
+        if astream.get_media_type() != StreamType::Audio {
+            return Err(MuxerError::InvalidArgument);
+        }
+        self.sw = Some(create_audio_stream(&astream)?);
+        if let Some(ref mut sw) = self.sw {
+            sw.write_header(self.bw, &astream)?;
+        }
+        Ok(())
+    }
+    fn mux_frame(&mut self, _strmgr: &StreamManager, pkt: NAPacket) -> MuxerResult<()> {
+        if let Some(ref mut sw) = self.sw {
+            sw.queue_packet(pkt);
+            while let Some((data, _)) = sw.get_packet() {
+                self.bw.write_buf(&data)?;
+            }
+            Ok(())
+        } else {
+            Err(MuxerError::NotCreated)
+        }
+    }
+    fn flush(&mut self) -> MuxerResult<()> {
+        Ok(())
+    }
+    fn end(&mut self) -> MuxerResult<()> {
+        if let Some(ref mut sw) = self.sw {
+            sw.finish(&mut self.bw)?;
+        }
+        Ok(())
+    }
+}
+
+impl<'a> NAOptionHandler for RAMuxer<'a> {
+    fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] }
+    fn set_options(&mut self, _options: &[NAOption]) { }
+    fn query_option_value(&self, _name: &str) -> Option<NAValue> { None }
+}
+
+pub struct RealAudioMuxerCreator {}
+
+impl MuxerCreator for RealAudioMuxerCreator {
+    fn new_muxer<'a>(&self, bw: &'a mut ByteWriter<'a>) -> Box<dyn MuxCore<'a> + 'a> {
+        Box::new(RAMuxer::new(bw))
+    }
+    fn get_name(&self) -> &'static str { "realaudio" }
+    fn get_capabilities(&self) -> MuxerCapabilities { MuxerCapabilities::SingleAudio("any") }
+}
+
+#[cfg(test)]
+mod test {
+    use nihav_core::codecs::*;
+    use nihav_core::demuxers::*;
+    use nihav_core::muxers::*;
+    use nihav_codec_support::test::enc_video::*;
+    use crate::*;
+
+    #[test]
+    fn test_rm_muxer() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        realmedia_register_all_demuxers(&mut dmx_reg);
+        // sample from a private collection
+        let dec_config = DecoderTestParams {
+                demuxer:        "realmedia",
+                in_name:        "assets/RV/rv30_weighted_mc.rm",
+                limit:          None,
+                stream_type:    StreamType::None,
+                dmx_reg, dec_reg: RegisteredDecoders::new(),
+            };
+        let mut mux_reg = RegisteredMuxers::new();
+        realmedia_register_all_muxers(&mut mux_reg);
+        /*let enc_config = EncoderTestParams {
+                muxer:      "realmedia",
+                enc_name:   "",
+                out_name:   "muxed.rm",
+                mux_reg, enc_reg: RegisteredEncoders::new(),
+            };
+        test_remuxing(&dec_config, &enc_config);*/
+        test_remuxing_md5(&dec_config, "realmedia", &mux_reg,
+                          [0x26422839, 0xa2d7bdd1, 0xd6ea2a78, 0x1b58033a]);
+    }
+
+    #[test]
+    fn test_ra_muxer_v3() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        realmedia_register_all_demuxers(&mut dmx_reg);
+        //test sample: https://samples.mplayerhq.hu/real/RA/14_4/drummers.14.ra
+        let dec_config = DecoderTestParams {
+                demuxer:        "realaudio",
+                in_name:        "assets/RV/drummers.14.ra",
+                limit:          None,
+                stream_type:    StreamType::None,
+                dmx_reg, dec_reg: RegisteredDecoders::new(),
+            };
+        let mut mux_reg = RegisteredMuxers::new();
+        realmedia_register_all_muxers(&mut mux_reg);
+        /*let enc_config = EncoderTestParams {
+                muxer:      "realaudio",
+                enc_name:   "",
+                out_name:   "v3.ra",
+                mux_reg, enc_reg: RegisteredEncoders::new(),
+            };
+        test_remuxing(&dec_config, &enc_config);*/
+        test_remuxing_md5(&dec_config, "realaudio", &mux_reg,
+                          [0x8101a484, 0xf5d80805, 0x24577596, 0x9b27262f]);
+    }
+    #[test]
+    fn test_ra_muxer_v4() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        realmedia_register_all_demuxers(&mut dmx_reg);
+        //test sample: https://samples.mplayerhq.hu/real//RA/ra_with_comment_field/diemusik.ra
+        let dec_config = DecoderTestParams {
+                demuxer:        "realaudio",
+                in_name:        "assets/RV/diemusik.ra",
+                limit:          None,
+                stream_type:    StreamType::None,
+                dmx_reg, dec_reg: RegisteredDecoders::new(),
+            };
+        let mut mux_reg = RegisteredMuxers::new();
+        realmedia_register_all_muxers(&mut mux_reg);
+        /*let enc_config = EncoderTestParams {
+                muxer:      "realaudio",
+                enc_name:   "",
+                out_name:   "v4.ra",
+                mux_reg, enc_reg: RegisteredEncoders::new(),
+            };
+        test_remuxing(&dec_config, &enc_config);*/
+        test_remuxing_md5(&dec_config, "realaudio", &mux_reg,
+                          [0x33665ec3, 0x69b68ea2, 0x08d4b138, 0x318e305f]);
+    }
+    #[test]
+    fn test_ra_muxer_sipro() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        realmedia_register_all_demuxers(&mut dmx_reg);
+        //test sample: https://samples.mplayerhq.hu/real/AC-sipr/autahi-vox.rm
+        let dec_config = DecoderTestParams {
+                demuxer:        "realmedia",
+                in_name:        "assets/RV/autahi-vox.rm",
+                limit:          None,
+                stream_type:    StreamType::None,
+                dmx_reg, dec_reg: RegisteredDecoders::new(),
+            };
+        let mut mux_reg = RegisteredMuxers::new();
+        realmedia_register_all_muxers(&mut mux_reg);
+        /*let enc_config = EncoderTestParams {
+                muxer:      "realaudio",
+                enc_name:   "",
+                out_name:   "v4-sipro.ra",
+                mux_reg, enc_reg: RegisteredEncoders::new(),
+            };
+        test_remuxing(&dec_config, &enc_config);*/
+        test_remuxing_md5(&dec_config, "realaudio", &mux_reg,
+                          [0x08bd496d, 0x5f35d7ae, 0xe9c93c50, 0x9e803f76]);
+    }
+    #[test]
+    fn test_ra_muxer_v5() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        realmedia_register_all_demuxers(&mut dmx_reg);
+        //test sample: https://samples.mplayerhq.hu/real/AC-cook/cook_5.1/multichannel.rma
+        let dec_config = DecoderTestParams {
+                demuxer:        "realmedia",
+                in_name:        "assets/RV/multichannel.rma",
+                limit:          None,
+                stream_type:    StreamType::None,
+                dmx_reg, dec_reg: RegisteredDecoders::new(),
+            };
+        let mut mux_reg = RegisteredMuxers::new();
+        realmedia_register_all_muxers(&mut mux_reg);
+        /*let enc_config = EncoderTestParams {
+                muxer:      "realaudio",
+                enc_name:   "",
+                out_name:   "v5.ra",
+                mux_reg, enc_reg: RegisteredEncoders::new(),
+            };
+        test_remuxing(&dec_config, &enc_config);*/
+        test_remuxing_md5(&dec_config, "realaudio", &mux_reg,
+                          [0x52f42c49, 0x90ac79a7, 0x275a465f, 0x7a6f3659]);
+    }
+    #[test]
+    fn test_rm_muxer_aac() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        realmedia_register_all_demuxers(&mut dmx_reg);
+        //sample from a private collection
+        let dec_config = DecoderTestParams {
+                demuxer:        "realmedia",
+                in_name:        "assets/RV/rv40_weighted_mc_2.rmvb",
+                limit:          None,
+                stream_type:    StreamType::None,
+                dmx_reg, dec_reg: RegisteredDecoders::new(),
+            };
+        let mut mux_reg = RegisteredMuxers::new();
+        realmedia_register_all_muxers(&mut mux_reg);
+        /*let enc_config = EncoderTestParams {
+                muxer:      "realmedia",
+                enc_name:   "",
+                out_name:   "aac.ram",
+                mux_reg, enc_reg: RegisteredEncoders::new(),
+            };
+        test_remuxing(&dec_config, &enc_config);*/
+        test_remuxing_md5(&dec_config, "realmedia", &mux_reg,
+                          [0xe38b36c0, 0x1aedef10, 0x4e418ac4, 0x4ff57f6c]);
+    }
+    #[test]
+    fn test_rm_muxer_ralf() {
+        let mut dmx_reg = RegisteredDemuxers::new();
+        realmedia_register_all_demuxers(&mut dmx_reg);
+        //sample from a private collection
+        let dec_config = DecoderTestParams {
+                demuxer:        "realmedia",
+                in_name:        "assets/RV/rv40_ralf.rmvb",
+                limit:          None,
+                stream_type:    StreamType::None,
+                dmx_reg, dec_reg: RegisteredDecoders::new(),
+            };
+        let mut mux_reg = RegisteredMuxers::new();
+        realmedia_register_all_muxers(&mut mux_reg);
+        /*let enc_config = EncoderTestParams {
+                muxer:      "realmedia",
+                enc_name:   "",
+                out_name:   "ralf.ram",
+                mux_reg, enc_reg: RegisteredEncoders::new(),
+            };
+        test_remuxing(&dec_config, &enc_config);*/
+        test_remuxing_md5(&dec_config, "realmedia", &mux_reg,
+                          [0xa0c336d1, 0x76221455, 0x75252067, 0x6189d4af]);
+    }
+}
diff --git a/nihav-realmedia/src/muxers/rmvb/videostream.rs b/nihav-realmedia/src/muxers/rmvb/videostream.rs
new file mode 100644 (file)
index 0000000..708874d
--- /dev/null
@@ -0,0 +1,168 @@
+use nihav_core::frame::*;
+use nihav_core::muxers::*;
+use super::RMStreamWriter;
+
+static VIDEO_CODEC_REGISTRY: &[(&[u8;4], &str)] = &[
+    (b"RV10", "realvideo1"),
+    (b"RV20", "realvideo2"),
+    (b"RVTR", "realvideo2"),
+    (b"RV30", "realvideo3"),
+    (b"RV40", "realvideo4"),
+    (b"RV60", "realvideo6"),
+    (b"CLV1", "clearvideo_rm"),
+];
+
+pub struct DummyStreamWriter {}
+impl RMStreamWriter for DummyStreamWriter {
+    fn write_header(&mut self, _bw: &mut ByteWriter, _stream: &NAStream) -> MuxerResult<()> {
+        Ok(())
+    }
+    fn queue_packet(&mut self, _pkt: NAPacket) -> bool {
+        true
+    }
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)> {
+        None
+    }
+    fn flush(&mut self) { }
+    fn finish(&mut self, _bw: &mut ByteWriter) -> MuxerResult<()> {
+        Ok(())
+    }
+}
+
+struct VideoStreamWriter {
+    fcc:        [u8; 4],
+    buf:        Vec<u8>,
+    nslices:    usize,
+    cur_slice:  usize,
+    seq_no:     u8,
+}
+
+impl RMStreamWriter for VideoStreamWriter {
+    fn write_header(&mut self, bw: &mut ByteWriter, vstream: &NAStream) -> MuxerResult<()> {
+        let info = vstream.get_info().get_properties().get_video_info().unwrap();
+        let start = bw.tell();
+
+        bw.write_u32be(0)?; // header size
+        bw.write_buf(b"VIDO")?;
+        bw.write_buf(&self.fcc)?;
+        bw.write_u16be(info.width as u16)?;
+        bw.write_u16be(info.height as u16)?;
+        bw.write_u16be(12)?; // bpp
+        bw.write_u16be(0)?; // aligned width
+        bw.write_u16be(0)?; // aligned height
+        let (tb_num, tb_den) = vstream.get_timebase();
+        if tb_num != 0 && tb_den != 0 {
+            bw.write_u16be((tb_den / tb_num) as u16)?;
+            let mut fps_frac = tb_den % tb_num;
+            let mut div = tb_num;
+            while div >= 0x10000 {
+                fps_frac >>= 1;
+                div      >>= 1;
+            }
+            fps_frac = (fps_frac << 16) / div;
+            bw.write_u16le(fps_frac as u16)?;
+        } else {
+            bw.write_u16be(0)?;
+            bw.write_u16be(0)?;
+        }
+
+        if let Some(edata) = vstream.get_info().get_extradata() {
+            bw.write_buf(&edata)?;
+        }
+        let end = bw.tell();
+        bw.seek(SeekFrom::Start(start))?;
+        bw.write_u32be((end - start) as u32)?;
+        bw.seek(SeekFrom::Start(end))?;
+        Ok(())
+    }
+    fn queue_packet(&mut self, pkt: NAPacket) -> bool {
+        if self.nslices == 0 {
+            let src = pkt.get_buffer();
+            let nslices = usize::from(src[0]) + 1;
+            if src.len() > nslices * 8 + 1 {
+                self.nslices = nslices;
+                self.cur_slice = 0;
+                self.buf.resize(src.len(), 0);
+                self.buf.copy_from_slice(&src);
+            }
+            true
+        } else {
+            false
+        }
+    }
+    fn get_packet(&mut self) -> Option<(Vec<u8>, bool)> {
+        if self.cur_slice < self.nslices {
+            let first = self.cur_slice == 0;
+            let hdr_size = self.nslices * 8 + 1;
+            let cur_off = (read_u32be(&self.buf[self.cur_slice * 8 + 5..]).unwrap_or(0) as usize) + hdr_size;
+            let next_off = if self.cur_slice + 1 < self.nslices {
+                    (read_u32be(&self.buf[self.cur_slice * 8 + 13..]).unwrap_or(0) as usize) + hdr_size
+                } else {
+                    self.buf.len()
+                };
+            let next_off = next_off.max(cur_off);
+            let src = &self.buf[cur_off..next_off];
+            let ret = if self.nslices == 1 {
+                    let mut dst = vec![0; src.len() + 2];
+                    dst[0] = 0x40;
+                    dst[1] = self.seq_no;
+                    dst[2..].copy_from_slice(src);
+                    dst
+                } else {
+                    let mut dst = Vec::with_capacity(src.len() + 11);
+                    let mut gw = GrowableMemoryWriter::new_write(&mut dst);
+                    let mut bw = ByteWriter::new(&mut gw);
+
+                    let hdr = ((self.nslices as u16) << 7) | ((self.cur_slice + 1) as u16);
+                    bw.write_u16be(hdr).unwrap();
+
+                    let full_size = self.buf.len() - hdr_size;
+                    if full_size < (1 << 14) {
+                        bw.write_u16be(0xC000 | (full_size as u16)).unwrap();
+                    } else {
+                        bw.write_u32be(0x80000000 | (full_size as u32)).unwrap();
+                    }
+                    let coff = cur_off - hdr_size;
+                    if coff < (1 << 14) {
+                        bw.write_u16be(0x4000 | (coff as u16)).unwrap();
+                    } else {
+                        bw.write_u32be(coff as u32).unwrap();
+                    }
+                    bw.write_byte(self.seq_no).unwrap();
+                    bw.write_buf(src).unwrap();
+                    dst
+                };
+            self.cur_slice += 1;
+            if self.cur_slice == self.nslices {
+                self.nslices = 0;
+                self.cur_slice = 0;
+                self.seq_no = self.seq_no.wrapping_add(1);
+            }
+            Some((ret, first))
+        } else {
+            None
+        }
+    }
+    fn flush(&mut self) { }
+    fn finish(&mut self, _bw: &mut ByteWriter) -> MuxerResult<()> {
+        Ok(())
+    }
+}
+
+pub fn create_video_stream(stream: &NAStream) -> MuxerResult<Box<dyn RMStreamWriter>> {
+    let info = stream.get_info();
+    let cname = info.get_name();
+
+    for &(fcc, name) in VIDEO_CODEC_REGISTRY.iter() {
+        if name == cname {
+            return Ok(Box::new(VideoStreamWriter {
+                    fcc:        *fcc,
+                    buf:        Vec::new(),
+                    nslices:    0,
+                    cur_slice:  0,
+                    seq_no:     0,
+                }));
+        }
+    }
+    Err(MuxerError::UnsupportedFormat)
+}