use std::io::Write;
use super::header::{GRANULE_RATE, OpusHead, OpusTags};
use super::page::{HeaderType, MAX_SEGMENTS, lacing_values, write_page};
use crate::{Error, Result};
const DEFAULT_PAGE_TARGET: usize = 4096;
pub struct OggOpusWriter<W: Write> {
sink: Option<W>,
serial: u32,
sequence: u32,
page_target: usize,
segments: Vec<u8>,
payload: Vec<u8>,
granule: i64,
page_granule: Option<i64>,
continued: bool,
finished: bool,
scratch: Vec<u8>,
}
impl<W: Write> std::fmt::Debug for OggOpusWriter<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OggOpusWriter")
.field("serial", &format_args!("{:#010x}", self.serial))
.field("pages_written", &self.sequence)
.field("granule", &self.granule)
.field("pending_bytes", &self.payload.len())
.field("finished", &self.finished)
.finish_non_exhaustive()
}
}
impl<W: Write> OggOpusWriter<W> {
pub fn new(sink: W, head: OpusHead) -> Result<Self> {
Self::with_tags(sink, head, OpusTags::new())
}
pub fn with_tags(sink: W, head: OpusHead, tags: OpusTags) -> Result<Self> {
let serial = derive_serial(&head, &tags);
Self::with_serial(sink, head, tags, serial)
}
pub fn with_serial(sink: W, head: OpusHead, tags: OpusTags, serial: u32) -> Result<Self> {
let mut w = OggOpusWriter {
sink: Some(sink),
serial,
sequence: 0,
page_target: DEFAULT_PAGE_TARGET,
segments: Vec::with_capacity(MAX_SEGMENTS),
payload: Vec::with_capacity(DEFAULT_PAGE_TARGET + 255),
granule: 0,
page_granule: None,
continued: false,
finished: false,
scratch: Vec::new(),
};
w.push_packet_bytes(&head.to_packet())?;
w.flush_page(HeaderType::BOS, 0)?;
w.push_packet_bytes(&tags.to_packet())?;
w.flush_page(0, 0)?;
Ok(w)
}
pub fn serial(&self) -> u32 {
self.serial
}
pub fn granule(&self) -> i64 {
self.granule
}
pub fn set_page_target(&mut self, bytes: usize) {
self.page_target = bytes.clamp(1, super::page::MAX_PAGE_PAYLOAD);
}
pub fn write_packet(&mut self, packet: &[u8]) -> Result<()> {
let samples_48k = crate::packet::samples_48k(packet)? as u32;
self.write_packet_with_duration(packet, samples_48k)
}
pub fn write_packet_with_duration(&mut self, packet: &[u8], samples_48k: u32) -> Result<()> {
if self.finished {
return Err(Error::InvalidArgument("writer has already been finished"));
}
if packet.is_empty() {
return Err(Error::InvalidArgument("Opus packets cannot be empty"));
}
if samples_48k > 120 * GRANULE_RATE / 1000 {
return Err(Error::InvalidArgument(
"an Opus packet cannot exceed 120 ms (5760 samples at 48 kHz)",
));
}
if self.payload.len() >= self.page_target || self.segments.len() == MAX_SEGMENTS {
let g = self.page_granule_or_none();
self.flush_page(0, g)?;
}
self.push_packet_bytes(packet)?;
self.granule += i64::from(samples_48k);
self.page_granule = Some(self.granule);
Ok(())
}
pub fn get_ref(&self) -> Option<&W> {
self.sink.as_ref()
}
pub fn get_mut(&mut self) -> Option<&mut W> {
self.sink.as_mut()
}
pub fn finish(mut self) -> Result<W> {
self.finish_in_place()?;
self.sink
.take()
.ok_or(Error::Internal("ogg writer sink taken twice"))
}
fn finish_in_place(&mut self) -> Result<()> {
if self.finished || self.sink.is_none() {
return Ok(());
}
self.finished = true;
if self.segments.is_empty() {
self.segments.push(0);
}
let g = self.page_granule_or_none();
self.flush_page(HeaderType::EOS, g)?;
if let Some(sink) = self.sink.as_mut() {
sink.flush()?;
}
Ok(())
}
fn page_granule_or_none(&self) -> i64 {
self.page_granule.unwrap_or(-1)
}
fn push_packet_bytes(&mut self, packet: &[u8]) -> Result<()> {
let mut off = 0usize;
for lace in lacing_values(packet.len()) {
if self.segments.len() == MAX_SEGMENTS {
let g = self.page_granule_or_none();
self.flush_page(0, g)?;
self.continued = true;
}
self.segments.push(lace);
self.payload
.extend_from_slice(&packet[off..off + lace as usize]);
off += lace as usize;
}
Ok(())
}
fn flush_page(&mut self, flags: u8, granule: i64) -> Result<()> {
if self.segments.is_empty() {
return Ok(());
}
let header_type = flags
| if self.continued {
HeaderType::CONTINUED
} else {
0
};
self.scratch.clear();
write_page(
header_type,
granule,
self.serial,
self.sequence,
&self.segments,
&self.payload,
&mut self.scratch,
);
match self.sink.as_mut() {
Some(sink) => sink.write_all(&self.scratch)?,
None => return Err(Error::Internal("ogg writer used after finish")),
}
self.sequence += 1;
self.segments.clear();
self.payload.clear();
self.page_granule = None;
self.continued = false;
Ok(())
}
}
fn derive_serial(head: &OpusHead, tags: &OpusTags) -> u32 {
let mut h: u32 = 0x811c_9dc5;
for b in head.to_packet().iter().chain(tags.to_packet().iter()) {
h ^= u32::from(*b);
h = h.wrapping_mul(0x0100_0193);
}
if h == 0 { 1 } else { h }
}
impl<W: Write> Drop for OggOpusWriter<W> {
fn drop(&mut self) {
let _ = self.finish_in_place();
}
}