#![forbid(unsafe_code)]
use crate::{Demux, Mux};
use mediaway_common::{Bytes, CodecKind, Packet, Rational, StreamInfo};
use ogg_core::Packet as CorePacket;
use ogg_core::{Demuxer as CoreDemuxer, Muxer as CoreMuxer};
use std::collections::VecDeque;
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>,
page: Vec<(CorePacket, i64)>,
ready: VecDeque<Packet>,
}
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 {
if let Some(p) = self.ready.pop_front() {
return Some(p);
}
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; }
if !matches!(self.streams[0].codec(), CodecKind::Opus) {
return Some(Packet {
stream_id: self.streams[0].id(),
pts: p.granule_position,
dts: p.granule_position,
duration: 0, is_keyframe: true,
is_discard: false,
payload: p.data,
});
}
let duration = opus_packet_duration(&p.data);
if !self.page.is_empty()
&& (p.granule_position != self.page[0].0.granule_position
|| p.page_count != self.page[0].0.page_count)
{
self.finalize_page();
}
self.page.push((p, duration));
let page_len = u32::try_from(self.page.len()).unwrap_or(u32::MAX);
if page_len == self.page[0].0.page_count {
self.finalize_page();
}
}
}
fn finalize_page(&mut self) {
let stream_id = self.streams[0].id();
let granule = self.page[0].0.granule_position;
let mut suffix = 0i64;
for (p, duration) in self.page.iter().rev() {
let pts = granule - suffix;
self.ready.push_front(Packet {
stream_id,
pts,
dts: pts,
duration: u64::try_from(*duration).unwrap_or(0),
is_keyframe: true,
is_discard: false,
payload: p.data.clone(),
});
suffix += *duration;
}
self.page.clear();
}
}
#[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)
}
}
fn opus_packet_duration(packet: &[u8]) -> i64 {
let Some(&toc) = packet.first() else {
return 0;
};
let config = (toc >> 3) & 0x1F;
let frame: i64 = match config {
0..=11 => i64::from(480 * [1, 2, 4, 6][usize::from(config & 3)]),
12..=15 => i64::from(480 << (config & 1)),
_ => i64::from(120 << (config & 3)), };
let frames = match toc & 0x3 {
0 => 1,
1 => 2,
2 => {
let Some(&l) = packet.get(1) else {
return 0;
};
if l == 0 { 1 } else { 2 }
}
_ => {
let Some(&b1) = packet.get(1) else {
return 0;
};
let m = i64::from((b1 >> 2) & 0x3F).max(1);
if b1 & 1 == 1 {
let mut pos = 2usize;
let mut present = 0i64;
for _ in 0..m - 1 {
let Some(&l) = packet.get(pos) else {
break;
};
pos += 1;
let len = if l >= 252 {
let Some(&l2) = packet.get(pos) else {
break;
};
pos += 1;
i64::from(l2) * 4 + i64::from(l)
} else {
i64::from(l)
};
if len > 0 {
present += 1;
}
}
present + 1
} else {
m
}
}
};
frames * frame
}
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;