#![forbid(unsafe_code)]
use crate::Demux;
use flv_core::{Demuxer as CoreDemuxer, Muxer as CoreMuxer, Tag, TagType};
use mediaway_common::{Bytes, CodecKind, Packet, Rational, StreamInfo};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Tag(#[from] flv_core::Error),
#[error("FLV mux has no tag encoding for codec {0:?}")]
UnsupportedCodec(CodecKind),
#[error("push_packet: no track registered for stream_id {0}")]
UnregisteredStream(u32),
}
const VIDEO_STREAM_ID: u32 = 0;
const AUDIO_STREAM_ID: u32 = 1;
const MS_TIME_BASE: Rational = Rational::new(1, 1_000);
#[derive(Debug)]
struct MuxTrack {
codec: CodecKind,
extra_data: Bytes,
header_written: bool,
}
#[derive(Debug, Default)]
pub struct Muxer {
inner: CoreMuxer,
video: Option<MuxTrack>,
audio: Option<MuxTrack>,
}
impl Muxer {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn write_header(&mut self, has_audio: bool, has_video: bool, out: &mut Vec<u8>) {
self.inner.write_header(has_audio, has_video, out);
}
pub fn add_track(&mut self, stream: &StreamInfo) -> Result<(), Error> {
match stream {
StreamInfo::Video {
codec, extra_data, ..
} => {
if *codec != CodecKind::H264 {
return Err(Error::UnsupportedCodec(*codec));
}
self.video = Some(MuxTrack {
codec: *codec,
extra_data: extra_data.clone(), header_written: false,
});
}
StreamInfo::Audio {
codec, extra_data, ..
} => {
if !matches!(codec, CodecKind::Aac | CodecKind::Mp3) {
return Err(Error::UnsupportedCodec(*codec));
}
self.audio = Some(MuxTrack {
codec: *codec,
extra_data: extra_data.clone(), header_written: false,
});
}
other => return Err(Error::UnsupportedCodec(other.codec())),
}
Ok(())
}
pub fn push_packet(&mut self, packet: &Packet, out: &mut Vec<u8>) -> Result<(), Error> {
match packet.stream_id {
VIDEO_STREAM_ID => self.push_video(packet, out),
AUDIO_STREAM_ID => self.push_audio(packet, out),
other => Err(Error::UnregisteredStream(other)),
}
}
fn push_video(&mut self, packet: &Packet, out: &mut Vec<u8>) -> Result<(), Error> {
let Some(track) = self.video.as_ref() else {
return Err(Error::UnregisteredStream(packet.stream_id));
};
if !track.header_written {
let data = avc_seq_header_data(&track.extra_data);
self.inner.write_tag(
&Tag {
tag_type: TagType::Video,
timestamp_ms: clamp_timestamp(packet.dts),
data,
},
out,
)?;
if let Some(track) = self.video.as_mut() {
track.header_written = true;
}
}
self.inner.write_tag(
&Tag {
tag_type: TagType::Video,
timestamp_ms: clamp_timestamp(packet.dts),
data: avc_nalu_data(packet),
},
out,
)?;
Ok(())
}
fn push_audio(&mut self, packet: &Packet, out: &mut Vec<u8>) -> Result<(), Error> {
let Some(track) = self.audio.as_ref() else {
return Err(Error::UnregisteredStream(packet.stream_id));
};
match track.codec {
CodecKind::Aac => {
if !track.header_written {
let data = aac_seq_header_data(&track.extra_data);
self.inner.write_tag(
&Tag {
tag_type: TagType::Audio,
timestamp_ms: clamp_timestamp(packet.dts),
data,
},
out,
)?;
if let Some(track) = self.audio.as_mut() {
track.header_written = true;
}
}
self.inner.write_tag(
&Tag {
tag_type: TagType::Audio,
timestamp_ms: clamp_timestamp(packet.dts),
data: aac_raw_data(&packet.payload),
},
out,
)?;
}
CodecKind::Mp3 => {
self.inner.write_tag(
&Tag {
tag_type: TagType::Audio,
timestamp_ms: clamp_timestamp(packet.dts),
data: mp3_data(&packet.payload),
},
out,
)?;
}
other => return Err(Error::UnsupportedCodec(other)),
}
Ok(())
}
pub fn write_tag(&self, tag: &Tag, out: &mut Vec<u8>) -> Result<(), Error> {
self.inner.write_tag(tag, out)?;
Ok(())
}
}
fn avc_seq_header_data(avcc: &Bytes) -> Bytes {
let mut data = Vec::with_capacity(5 + avcc.len());
data.push(0x17); data.push(0); data.extend_from_slice(&[0, 0, 0]); data.extend_from_slice(avcc);
Bytes::from(data)
}
#[allow(
clippy::cast_possible_truncation,
reason = "only the low 24 bits of composition_time are ever written, matching the field width"
)]
fn avc_nalu_data(packet: &Packet) -> Bytes {
let frame_type: u8 = if packet.is_keyframe { 1 } else { 2 };
let mut data = Vec::with_capacity(5 + packet.payload.len());
data.push((frame_type << 4) | 7);
data.push(1); let ct = composition_time(packet).to_be_bytes();
data.extend_from_slice(&ct[1..4]);
data.extend_from_slice(&packet.payload);
Bytes::from(data)
}
fn composition_time(packet: &Packet) -> i32 {
let diff = packet.pts.saturating_sub(packet.dts);
i32::try_from(diff).unwrap_or_else(|_| {
if diff.is_positive() {
i32::MAX
} else {
i32::MIN
}
})
}
fn clamp_timestamp(ts: i64) -> u32 {
u32::try_from(ts).unwrap_or(0)
}
fn aac_seq_header_data(asc: &Bytes) -> Bytes {
let mut data = Vec::with_capacity(2 + asc.len());
data.push(0xAF); data.push(0); data.extend_from_slice(asc);
Bytes::from(data)
}
fn aac_raw_data(raw: &Bytes) -> Bytes {
let mut data = Vec::with_capacity(2 + raw.len());
data.push(0xAF);
data.push(1); data.extend_from_slice(raw);
Bytes::from(data)
}
fn mp3_data(frame: &Bytes) -> Bytes {
let mut data = Vec::with_capacity(1 + frame.len());
data.push(0x2F); data.extend_from_slice(frame);
Bytes::from(data)
}
#[derive(Debug, Default)]
pub struct Demuxer {
inner: CoreDemuxer,
streams: Vec<StreamInfo>,
video_extra: Option<Bytes>,
audio_extra: Option<Bytes>,
}
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 tag = self.inner.poll_tag().ok().flatten()?;
match tag.tag_type {
TagType::Video => {
if let Some(p) = self.handle_video(&tag) {
return Some(p);
}
}
TagType::Audio => {
if let Some(p) = self.handle_audio(&tag) {
return Some(p);
}
}
_ => {}
}
}
}
fn handle_video(&mut self, tag: &Tag) -> Option<Packet> {
let (&first, rest) = tag.data.split_first()?;
let frame_type = first >> 4;
let codec_id = first & 0x0F;
if codec_id != 7 {
return None; }
if rest.len() < 4 {
return None;
}
let avc_packet_type = rest[0];
let composition_time_ms = i32::from_be_bytes([0, rest[1], rest[2], rest[3]]) << 8 >> 8;
let nalus = &rest[4..];
match avc_packet_type {
0 => {
self.video_extra = Some(Bytes::copy_from_slice(nalus));
self.sync_video_stream();
None
}
1 => {
self.sync_video_stream();
let dts = i64::from(tag.timestamp_ms);
Some(Packet {
stream_id: VIDEO_STREAM_ID,
pts: dts + i64::from(composition_time_ms),
dts,
duration: 0,
is_keyframe: frame_type == 1,
is_discard: false,
payload: Bytes::copy_from_slice(nalus),
})
}
_ => None, }
}
fn handle_audio(&mut self, tag: &Tag) -> Option<Packet> {
let (&first, rest) = tag.data.split_first()?;
let sound_format = first >> 4;
match sound_format {
2 => {
self.audio_extra.get_or_insert_with(Bytes::new);
self.sync_audio_stream(CodecKind::Mp3);
Some(Packet {
stream_id: AUDIO_STREAM_ID,
pts: i64::from(tag.timestamp_ms),
dts: i64::from(tag.timestamp_ms),
duration: 0,
is_keyframe: true,
is_discard: false,
payload: Bytes::copy_from_slice(rest),
})
}
10 => {
let (&aac_packet_type, aac) = rest.split_first()?;
match aac_packet_type {
0 => {
self.audio_extra = Some(Bytes::copy_from_slice(aac));
self.sync_audio_stream(CodecKind::Aac);
None
}
1 => {
self.sync_audio_stream(CodecKind::Aac);
Some(Packet {
stream_id: AUDIO_STREAM_ID,
pts: i64::from(tag.timestamp_ms),
dts: i64::from(tag.timestamp_ms),
duration: 0,
is_keyframe: true,
is_discard: false,
payload: Bytes::copy_from_slice(aac),
})
}
_ => None,
}
}
_ => None, }
}
fn sync_video_stream(&mut self) {
if self.streams.iter().any(|s| s.id() == VIDEO_STREAM_ID) {
return;
}
self.streams.push(StreamInfo::Video {
id: VIDEO_STREAM_ID,
codec: CodecKind::H264,
time_base: MS_TIME_BASE,
geometry: mediaway_common::VideoGeometry {
width: 0,
height: 0,
},
extra_data: self.video_extra.clone().unwrap_or_default(),
});
}
fn sync_audio_stream(&mut self, codec: CodecKind) {
if self.streams.iter().any(|s| s.id() == AUDIO_STREAM_ID) {
return;
}
self.streams.push(StreamInfo::Audio {
id: AUDIO_STREAM_ID,
codec,
time_base: MS_TIME_BASE,
extra_data: self.audio_extra.clone().unwrap_or_default(),
sample_rate: 0,
channels: 0,
});
}
}
#[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)
}
}
#[cfg(test)]
#[path = "flv_tests.rs"]
mod tests;