#![allow(dead_code)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::too_many_arguments)]
use bytes::{Bytes, BytesMut};
use oximedia_container::Packet;
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug)]
pub struct LiveSegmentGenerator {
representation_id: String,
timescale: u32,
target_duration: Duration,
current_buffer: BytesMut,
current_start_time: u64,
current_duration: u64,
next_segment_number: u64,
init_segment: Option<Bytes>,
codec: CodecInfo,
alignment: SegmentAlignment,
pending_packets: Vec<Packet>,
}
#[derive(Debug, Clone)]
pub struct CodecInfo {
pub codec: String,
pub mime_type: String,
pub init_data: Option<Bytes>,
pub is_video: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SegmentAlignment {
None,
Time,
SapAligned,
}
#[derive(Debug, Clone)]
pub struct GeneratedSegment {
pub number: u64,
pub data: Bytes,
pub start_time: u64,
pub duration: u64,
pub has_keyframe: bool,
pub size: usize,
}
impl LiveSegmentGenerator {
#[must_use]
pub fn new(
representation_id: impl Into<String>,
timescale: u32,
target_duration: Duration,
codec: CodecInfo,
) -> Self {
Self {
representation_id: representation_id.into(),
timescale,
target_duration,
current_buffer: BytesMut::new(),
current_start_time: 0,
current_duration: 0,
next_segment_number: 1,
init_segment: None,
codec,
alignment: SegmentAlignment::Time,
pending_packets: Vec::new(),
}
}
pub fn set_alignment(&mut self, alignment: SegmentAlignment) {
self.alignment = alignment;
}
pub fn generate_init_segment(&mut self, extra_data: Option<&[u8]>) -> Bytes {
let mut init = BytesMut::new();
init.extend_from_slice(b"\x00\x00\x00\x18");
init.extend_from_slice(b"ftyp");
init.extend_from_slice(b"iso5");
init.extend_from_slice(b"\x00\x00\x00\x00");
init.extend_from_slice(b"iso5dash");
let moov_data = self.create_moov_box(extra_data);
init.extend_from_slice(&moov_data);
let bytes = init.freeze();
self.init_segment = Some(bytes.clone());
bytes
}
#[must_use]
pub fn init_segment(&self) -> Option<&Bytes> {
self.init_segment.as_ref()
}
pub fn add_packet(&mut self, packet: Packet) -> Option<GeneratedSegment> {
let pts_rescaled = packet
.timestamp
.rescale(oximedia_core::Rational::new(1, self.timescale as i64));
let pts = pts_rescaled.pts as u64;
if self.should_finalize_segment(&packet, pts) {
let segment = self.finalize_current_segment();
self.current_start_time = pts;
self.current_duration = 0;
self.current_buffer.extend_from_slice(&packet.data);
self.current_duration += self.estimate_packet_duration(&packet);
self.pending_packets.push(packet);
return Some(segment);
}
self.current_buffer.extend_from_slice(&packet.data);
self.current_duration += self.estimate_packet_duration(&packet);
self.pending_packets.push(packet);
None
}
pub fn finalize_segment(&mut self) -> Option<GeneratedSegment> {
if self.current_buffer.is_empty() {
return None;
}
Some(self.finalize_current_segment())
}
#[must_use]
pub const fn current_segment_number(&self) -> u64 {
self.next_segment_number
}
#[must_use]
pub fn representation_id(&self) -> &str {
&self.representation_id
}
#[must_use]
pub const fn timescale(&self) -> u32 {
self.timescale
}
#[must_use]
pub const fn target_duration(&self) -> Duration {
self.target_duration
}
#[must_use]
pub fn codec(&self) -> &CodecInfo {
&self.codec
}
fn should_finalize_segment(&self, packet: &Packet, pts: u64) -> bool {
if self.current_buffer.is_empty() {
return false;
}
let target_duration_units = self.duration_to_units(self.target_duration);
if self.current_duration >= target_duration_units {
if self.codec.is_video && self.alignment == SegmentAlignment::SapAligned {
return packet
.flags
.contains(oximedia_container::PacketFlags::KEYFRAME);
}
return true;
}
if pts < self.current_start_time {
return true;
}
false
}
fn finalize_current_segment(&mut self) -> GeneratedSegment {
let number = self.next_segment_number;
self.next_segment_number += 1;
let segment_data = self.create_segment_boxes();
let has_keyframe = self
.pending_packets
.iter()
.any(|p| p.flags.contains(oximedia_container::PacketFlags::KEYFRAME));
let segment = GeneratedSegment {
number,
data: segment_data.clone(),
start_time: self.current_start_time,
duration: self.current_duration,
has_keyframe,
size: segment_data.len(),
};
self.current_buffer.clear();
self.pending_packets.clear();
self.current_duration = 0;
segment
}
fn create_segment_boxes(&self) -> Bytes {
let mut segment = BytesMut::new();
let moof_data = self.create_moof_box();
segment.extend_from_slice(&moof_data);
let mdat_size = 8 + self.current_buffer.len();
segment.extend_from_slice(&(mdat_size as u32).to_be_bytes());
segment.extend_from_slice(b"mdat");
segment.extend_from_slice(&self.current_buffer);
segment.freeze()
}
fn create_moof_box(&self) -> Vec<u8> {
let mut moof = Vec::new();
let moof_header = vec![
0x00, 0x00, 0x00, 0x28, b'm', b'o', b'o', b'f', 0x00, 0x00, 0x00, 0x10, b'm', b'f', b'h', b'd', 0x00, 0x00, 0x00, 0x00,
];
moof.extend_from_slice(&moof_header);
moof.extend_from_slice(&(self.next_segment_number as u32).to_be_bytes());
moof
}
fn create_moov_box(&self, _extra_data: Option<&[u8]>) -> Vec<u8> {
let mut moov = Vec::new();
let moov_header = vec![
0x00, 0x00, 0x00, 0x40, b'm', b'o', b'o', b'v', 0x00, 0x00, 0x00, 0x20, b'm', b'v', b'h', b'd', 0x00, 0x00, 0x00,
0x00, ];
moov.extend_from_slice(&moov_header);
moov.extend_from_slice(&self.timescale.to_be_bytes());
moov.extend_from_slice(&[0, 0, 0, 0]);
moov
}
fn estimate_packet_duration(&self, _packet: &Packet) -> u64 {
self.duration_to_units(self.target_duration) / 60 }
fn duration_to_units(&self, duration: Duration) -> u64 {
(duration.as_secs_f64() * f64::from(self.timescale)) as u64
}
}
#[derive(Debug)]
pub struct MultiRepresentationGenerator {
generators: HashMap<String, LiveSegmentGenerator>,
alignment: AlignmentCoordinator,
}
impl MultiRepresentationGenerator {
#[must_use]
pub fn new() -> Self {
Self {
generators: HashMap::new(),
alignment: AlignmentCoordinator::new(),
}
}
pub fn add_representation(&mut self, generator: LiveSegmentGenerator) {
let id = generator.representation_id().to_string();
self.generators.insert(id, generator);
}
pub fn add_packet(
&mut self,
representation_id: &str,
packet: Packet,
) -> Option<GeneratedSegment> {
let generator = self.generators.get_mut(representation_id)?;
let segment = generator.add_packet(packet)?;
self.alignment
.register_segment(representation_id, segment.number, segment.start_time);
Some(segment)
}
pub fn finalize_all(&mut self) -> Vec<(String, GeneratedSegment)> {
let mut segments = Vec::new();
for (id, generator) in &mut self.generators {
if let Some(segment) = generator.finalize_segment() {
segments.push((id.clone(), segment));
}
}
segments
}
#[must_use]
pub fn generator(&self, representation_id: &str) -> Option<&LiveSegmentGenerator> {
self.generators.get(representation_id)
}
pub fn generator_mut(&mut self, representation_id: &str) -> Option<&mut LiveSegmentGenerator> {
self.generators.get_mut(representation_id)
}
pub fn representation_ids(&self) -> Vec<&str> {
self.generators.keys().map(String::as_str).collect()
}
#[must_use]
pub fn are_aligned(&self, segment_number: u64) -> bool {
self.alignment.are_aligned(segment_number)
}
}
impl Default for MultiRepresentationGenerator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
struct AlignmentCoordinator {
segments: HashMap<String, Vec<SegmentTiming>>,
}
#[derive(Debug, Clone, Copy)]
struct SegmentTiming {
number: u64,
start_time: u64,
}
impl AlignmentCoordinator {
fn new() -> Self {
Self {
segments: HashMap::new(),
}
}
fn register_segment(&mut self, representation_id: &str, number: u64, start_time: u64) {
let timing = SegmentTiming { number, start_time };
self.segments
.entry(representation_id.to_string())
.or_default()
.push(timing);
}
fn are_aligned(&self, segment_number: u64) -> bool {
if self.segments.len() <= 1 {
return true;
}
let mut start_times = Vec::new();
for timings in self.segments.values() {
if let Some(timing) = timings.iter().find(|t| t.number == segment_number) {
start_times.push(timing.start_time);
} else {
return false; }
}
if start_times.is_empty() {
return false;
}
let first = start_times[0];
start_times.iter().all(|&t| t == first)
}
}
impl GeneratedSegment {
#[must_use]
pub fn duration_secs(&self, timescale: u32) -> f64 {
self.duration as f64 / timescale as f64
}
#[must_use]
pub fn start_time_secs(&self, timescale: u32) -> f64 {
self.start_time as f64 / timescale as f64
}
}
impl CodecInfo {
#[must_use]
pub fn h264(profile: u8, level: u8) -> Self {
let codec = format!("avc1.{profile:02x}{level:02x}");
Self {
codec,
mime_type: "video/mp4".to_string(),
init_data: None,
is_video: true,
}
}
#[must_use]
pub fn h265() -> Self {
Self {
codec: "hvc1.1.6.L93.B0".to_string(),
mime_type: "video/mp4".to_string(),
init_data: None,
is_video: true,
}
}
#[must_use]
pub fn aac() -> Self {
Self {
codec: "mp4a.40.2".to_string(),
mime_type: "audio/mp4".to_string(),
init_data: None,
is_video: false,
}
}
#[must_use]
pub fn opus() -> Self {
Self {
codec: "opus".to_string(),
mime_type: "audio/webm".to_string(),
init_data: None,
is_video: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use oximedia_container::PacketFlags;
use oximedia_core::{Rational, Timestamp};
fn create_test_packet(pts: u64, is_keyframe: bool) -> Packet {
let flags = if is_keyframe {
PacketFlags::KEYFRAME
} else {
PacketFlags::empty()
};
Packet::new(
0,
Bytes::from(vec![0u8; 1024]),
Timestamp::new(pts as i64, Rational::new(1, 90000)),
flags,
)
}
#[test]
fn test_segment_generator_creation() {
let codec = CodecInfo::h264(0x4d, 0x40);
let gen = LiveSegmentGenerator::new("720p", 90000, Duration::from_secs(2), codec);
assert_eq!(gen.representation_id(), "720p");
assert_eq!(gen.timescale(), 90000);
assert_eq!(gen.current_segment_number(), 1);
}
#[test]
fn test_init_segment_generation() {
let codec = CodecInfo::h264(0x4d, 0x40);
let mut gen = LiveSegmentGenerator::new("720p", 90000, Duration::from_secs(2), codec);
let init = gen.generate_init_segment(None);
assert!(!init.is_empty());
assert!(gen.init_segment().is_some());
}
#[test]
fn test_add_packet() {
let codec = CodecInfo::h264(0x4d, 0x40);
let mut gen = LiveSegmentGenerator::new("720p", 90000, Duration::from_secs(2), codec);
let packet = create_test_packet(0, true);
let result = gen.add_packet(packet);
assert!(result.is_none());
}
#[test]
fn test_codec_info() {
let h264 = CodecInfo::h264(0x4d, 0x40);
assert_eq!(h264.codec, "avc1.4d40");
assert!(h264.is_video);
let aac = CodecInfo::aac();
assert_eq!(aac.codec, "mp4a.40.2");
assert!(!aac.is_video);
}
#[test]
fn test_multi_representation_generator() {
let mut multi = MultiRepresentationGenerator::new();
let codec = CodecInfo::h264(0x4d, 0x40);
let gen = LiveSegmentGenerator::new("720p", 90000, Duration::from_secs(2), codec);
multi.add_representation(gen);
assert!(multi.generator("720p").is_some());
assert!(multi.generator("1080p").is_none());
}
}