#![forbid(unsafe_code)]
#![allow(clippy::cast_possible_truncation)]
use super::writer::OggPage;
const FLAG_CONTINUATION: u8 = 0x01;
const FLAG_BOS: u8 = 0x02;
const FLAG_EOS: u8 = 0x04;
#[derive(Debug)]
pub struct OggStreamWriter {
serial: u32,
sequence: u32,
last_granule: u64,
buffer: Vec<u8>,
}
impl OggStreamWriter {
#[must_use]
pub const fn new(serial: u32) -> Self {
Self {
serial,
sequence: 0,
last_granule: 0,
buffer: Vec::new(),
}
}
#[must_use]
pub const fn serial(&self) -> u32 {
self.serial
}
#[must_use]
pub const fn sequence(&self) -> u32 {
self.sequence
}
#[must_use]
pub const fn last_granule(&self) -> u64 {
self.last_granule
}
#[must_use]
pub fn build_page(
&mut self,
data: &[u8],
continuation: bool,
eos: bool,
complete: bool,
) -> OggPage {
self.build_page_with_granule(data, continuation, eos, complete, 0)
}
#[must_use]
pub fn build_page_with_granule(
&mut self,
data: &[u8],
continuation: bool,
eos: bool,
complete: bool,
granule: u64,
) -> OggPage {
let mut page = OggPage::new(self.serial, self.sequence);
let mut flags = 0u8;
if continuation {
flags |= FLAG_CONTINUATION;
}
if self.sequence == 0 && !continuation {
flags |= FLAG_BOS;
}
if eos {
flags |= FLAG_EOS;
}
page.flags = flags;
page.granule_position = if complete && granule != u64::MAX {
granule
} else {
u64::MAX
};
page.segments = build_segment_table(data.len(), complete);
page.data = data.to_vec();
self.sequence += 1;
if complete && granule != u64::MAX {
self.last_granule = granule;
}
page
}
#[must_use]
pub fn build_bos_page(&mut self, data: &[u8]) -> OggPage {
let mut page = OggPage::new(self.serial, self.sequence);
page.flags = FLAG_BOS;
page.granule_position = 0;
page.segments = build_segment_table(data.len(), true);
page.data = data.to_vec();
self.sequence += 1;
page
}
#[must_use]
pub fn build_eos_page(&mut self, granule: u64) -> OggPage {
let mut page = OggPage::new(self.serial, self.sequence);
page.flags = FLAG_EOS;
page.granule_position = granule;
page.segments = vec![0]; page.data = Vec::new();
self.sequence += 1;
self.last_granule = granule;
page
}
pub fn append_to_buffer(&mut self, data: &[u8]) {
self.buffer.extend_from_slice(data);
}
#[must_use]
pub fn take_buffer(&mut self) -> Vec<u8> {
std::mem::take(&mut self.buffer)
}
#[must_use]
pub fn has_buffered_data(&self) -> bool {
!self.buffer.is_empty()
}
pub fn reset(&mut self) {
self.sequence = 0;
self.last_granule = 0;
self.buffer.clear();
}
}
#[must_use]
fn build_segment_table(data_size: usize, complete: bool) -> Vec<u8> {
let mut segments = Vec::new();
let mut remaining = data_size;
while remaining >= 255 {
segments.push(255);
remaining -= 255;
}
if complete {
segments.push(remaining as u8);
} else if remaining > 0 {
segments.push(remaining as u8);
}
segments
}
#[must_use]
#[allow(dead_code)]
pub fn calculate_segment_count(data_size: usize) -> usize {
if data_size == 0 {
1 } else {
data_size.div_ceil(255)
}
}
#[must_use]
#[allow(dead_code)]
pub fn split_packet_for_pages(data: &[u8]) -> Vec<Vec<u8>> {
const MAX_PAGE_DATA: usize = 255 * 255;
let mut chunks = Vec::new();
let mut offset = 0;
while offset < data.len() {
let chunk_size = (data.len() - offset).min(MAX_PAGE_DATA);
chunks.push(data[offset..offset + chunk_size].to_vec());
offset += chunk_size;
}
if chunks.is_empty() {
chunks.push(Vec::new());
}
chunks
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stream_writer_new() {
let writer = OggStreamWriter::new(0x1234);
assert_eq!(writer.serial(), 0x1234);
assert_eq!(writer.sequence(), 0);
assert_eq!(writer.last_granule(), 0);
}
#[test]
fn test_build_segment_table_small() {
let segments = build_segment_table(100, true);
assert_eq!(segments, vec![100]);
}
#[test]
fn test_build_segment_table_exact() {
let segments = build_segment_table(255, true);
assert_eq!(segments, vec![255, 0]);
}
#[test]
fn test_build_segment_table_large() {
let segments = build_segment_table(600, true);
assert_eq!(segments, vec![255, 255, 90]);
}
#[test]
fn test_build_segment_table_empty() {
let segments = build_segment_table(0, true);
assert_eq!(segments, vec![0]);
}
#[test]
fn test_build_page() {
let mut writer = OggStreamWriter::new(1);
let page = writer.build_page(&[1, 2, 3, 4, 5], false, false, true);
assert_eq!(page.serial_number, 1);
assert_eq!(page.page_sequence, 0);
assert_eq!(page.flags & FLAG_BOS, FLAG_BOS);
assert_eq!(page.segments, vec![5]);
assert_eq!(page.data, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_build_page_sequence() {
let mut writer = OggStreamWriter::new(1);
let page1 = writer.build_page(&[1, 2], false, false, true);
assert_eq!(page1.page_sequence, 0);
let page2 = writer.build_page(&[3, 4], false, false, true);
assert_eq!(page2.page_sequence, 1);
let page3 = writer.build_page(&[5, 6], false, false, true);
assert_eq!(page3.page_sequence, 2);
}
#[test]
fn test_build_page_with_granule() {
let mut writer = OggStreamWriter::new(1);
let page = writer.build_page_with_granule(&[1, 2, 3], false, false, true, 48000);
assert_eq!(page.granule_position, 48000);
assert_eq!(writer.last_granule(), 48000);
}
#[test]
fn test_build_bos_page() {
let mut writer = OggStreamWriter::new(1);
let page = writer.build_bos_page(&[1, 2, 3]);
assert_eq!(page.flags, FLAG_BOS);
assert_eq!(page.granule_position, 0);
}
#[test]
fn test_build_eos_page() {
let mut writer = OggStreamWriter::new(1);
writer.sequence = 5;
let page = writer.build_eos_page(100000);
assert_eq!(page.flags, FLAG_EOS);
assert_eq!(page.granule_position, 100000);
assert_eq!(page.page_sequence, 5);
}
#[test]
fn test_buffer_operations() {
let mut writer = OggStreamWriter::new(1);
assert!(!writer.has_buffered_data());
writer.append_to_buffer(&[1, 2, 3]);
assert!(writer.has_buffered_data());
let data = writer.take_buffer();
assert_eq!(data, vec![1, 2, 3]);
assert!(!writer.has_buffered_data());
}
#[test]
fn test_reset() {
let mut writer = OggStreamWriter::new(1);
writer.sequence = 10;
writer.last_granule = 48000;
writer.append_to_buffer(&[1, 2, 3]);
writer.reset();
assert_eq!(writer.sequence(), 0);
assert_eq!(writer.last_granule(), 0);
assert!(!writer.has_buffered_data());
}
#[test]
fn test_calculate_segment_count() {
assert_eq!(calculate_segment_count(0), 1);
assert_eq!(calculate_segment_count(100), 1);
assert_eq!(calculate_segment_count(255), 1);
assert_eq!(calculate_segment_count(256), 2);
assert_eq!(calculate_segment_count(510), 2);
assert_eq!(calculate_segment_count(511), 3);
}
#[test]
fn test_split_packet_for_pages() {
let chunks = split_packet_for_pages(&[1, 2, 3]);
assert_eq!(chunks.len(), 1);
assert_eq!(chunks[0], vec![1, 2, 3]);
let chunks = split_packet_for_pages(&[]);
assert_eq!(chunks.len(), 1);
assert!(chunks[0].is_empty());
}
#[test]
fn test_continuation_flag() {
let mut writer = OggStreamWriter::new(1);
let page1 = writer.build_page(&[1, 2], false, false, true);
assert_eq!(page1.flags & FLAG_CONTINUATION, 0);
let page2 = writer.build_page(&[3, 4], true, false, true);
assert_eq!(page2.flags & FLAG_CONTINUATION, FLAG_CONTINUATION);
}
#[test]
fn test_granule_not_set_for_incomplete() {
let mut writer = OggStreamWriter::new(1);
let page = writer.build_page_with_granule(&[1, 2, 3], false, false, false, 48000);
assert_eq!(page.granule_position, u64::MAX);
}
}