#![forbid(unsafe_code)]
use crate::{Demux, Mux};
use mediaway_common::{Bytes, CodecKind, Packet, Rational, StreamInfo};
use ogg_core::{Demuxer as CoreDemuxer, Muxer as CoreMuxer};
pub type Error = ogg_core::Error;
#[derive(Debug)]
pub struct Muxer {
inner: CoreMuxer,
output: Vec<u8>,
}
impl Muxer {
#[must_use]
pub const fn new(serial: u32) -> Self {
Self {
inner: CoreMuxer::new(serial),
output: Vec::new(),
}
}
pub fn push_packet(&mut self, packet: &Packet) -> Result<(), Error> {
self.inner.push_packet(
&packet.payload,
packet.pts,
packet.is_discard,
&mut self.output,
)
}
pub const fn flush(&self) {}
pub fn poll_bytes(&mut self, out: &mut Vec<u8>) -> usize {
let n = self.output.len();
out.extend_from_slice(&self.output);
self.output.clear();
n
}
}
#[allow(clippy::use_self)]
impl Mux for Muxer {
type Error = Error;
fn push_packet(&mut self, packet: &Packet) -> Result<(), Self::Error> {
Muxer::push_packet(self, packet)
}
fn flush(&mut self) {
Muxer::flush(self);
}
fn poll_bytes(&mut self, out: &mut Vec<u8>) -> usize {
Muxer::poll_bytes(self, out)
}
}
#[derive(Debug, Default)]
pub struct Demuxer {
inner: CoreDemuxer,
streams: Vec<StreamInfo>,
}
impl Demuxer {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push_bytes(&mut self, chunk: &[u8]) {
self.inner.push_bytes(chunk);
}
#[must_use]
pub fn streams(&self) -> &[StreamInfo] {
&self.streams
}
pub fn poll_packet(&mut self) -> Option<Packet> {
loop {
let p = self.inner.poll_packet().ok().flatten()?;
if self.streams.is_empty() {
let Some(info) = identify(&p.data) else {
continue;
};
self.streams.push(info);
continue; }
let stream = &self.streams[0];
return Some(Packet {
stream_id: stream.id(),
pts: p.granule_position,
dts: p.granule_position,
duration: 0, is_keyframe: true,
is_discard: false,
payload: p.data,
});
}
}
}
#[allow(clippy::use_self)]
impl Demux for Demuxer {
fn push_bytes(&mut self, chunk: &[u8]) {
Demuxer::push_bytes(self, chunk);
}
fn streams(&self) -> &[StreamInfo] {
Demuxer::streams(self)
}
fn poll_packet(&mut self) -> Option<Packet> {
Demuxer::poll_packet(self)
}
}
const OPUS_HEAD_MAGIC: &[u8] = b"OpusHead";
const VORBIS_ID_MAGIC: &[u8] = b"\x01vorbis";
fn identify(packet: &[u8]) -> Option<StreamInfo> {
if packet.len() >= 19 && packet.starts_with(OPUS_HEAD_MAGIC) {
let channels = u16::from(packet[9]);
return Some(StreamInfo::Audio {
id: 0,
codec: CodecKind::Opus,
time_base: Rational::new(1, 48_000),
extra_data: Bytes::copy_from_slice(packet),
sample_rate: 48_000,
channels,
});
}
if packet.len() >= 30 && packet.starts_with(VORBIS_ID_MAGIC) {
let channels = u16::from(packet[11]);
let sample_rate = u32::from_le_bytes([packet[12], packet[13], packet[14], packet[15]]);
if sample_rate == 0 {
return None;
}
return Some(StreamInfo::Audio {
id: 0,
codec: CodecKind::Vorbis,
time_base: Rational::new(1, sample_rate),
extra_data: Bytes::copy_from_slice(packet),
sample_rate,
channels,
});
}
None
}
#[cfg(test)]
#[path = "ogg_tests.rs"]
mod tests;