use bytes::Bytes;
use crate::catalog::hang::CatalogExt;
use crate::container::Frame;
use crate::container::jitter::Jitter;
use super::FrameHeader;
pub struct Import<E: CatalogExt = ()> {
track: crate::container::Producer<crate::catalog::hang::Container>,
rendition: crate::catalog::VideoTrack<E>,
config: Option<hang::catalog::VideoConfig>,
jitter: Jitter,
}
impl<E: CatalogExt> Import<E> {
pub fn new(track: moq_net::TrackProducer, catalog: crate::catalog::Producer<E>) -> Self {
let rendition = catalog.video_track(track.name());
Self {
track: catalog.media_producer(track, crate::catalog::hang::Container::Legacy),
rendition,
config: None,
jitter: Jitter::new(),
}
}
pub fn initialize(&mut self, buf: &[u8]) -> crate::Result<()> {
if !buf.is_empty() {
self.decode(buf, None)?;
}
Ok(())
}
fn init(&mut self, width: u16, height: u16) -> crate::Result<()> {
let mut config = hang::catalog::VideoConfig::new(hang::catalog::VideoCodec::VP8);
config.coded_width = Some(width as u32);
config.coded_height = Some(height as u32);
config.container = hang::catalog::Container::Legacy;
if self.config.as_ref() == Some(&config) {
return Ok(());
}
tracing::debug!(name = ?self.track.name(), ?config, "starting track");
self.rendition.set(config.clone());
self.config = Some(config);
Ok(())
}
pub fn decode(&mut self, frame: &[u8], pts: Option<crate::container::Timestamp>) -> crate::Result<()> {
if frame.is_empty() {
return Err(super::Error::EmptyFrame.into());
}
let payload = Bytes::copy_from_slice(frame);
let header = FrameHeader::parse(&payload)?;
if let Some((width, height)) = header.dimensions {
self.init(width, height)?;
}
let pts = self.rendition.timestamp(pts)?;
self.track.write(Frame {
timestamp: pts,
payload,
keyframe: header.keyframe,
duration: None,
})?;
if let Some(jitter) = self.jitter.observe(pts) {
self.rendition
.update(|c| c.jitter = moq_net::Time::try_from(jitter).ok());
}
Ok(())
}
pub fn demand(&self) -> moq_net::TrackDemand {
self.track.track().demand()
}
pub fn finish(&mut self) -> crate::Result<()> {
self.track.finish()?;
Ok(())
}
pub fn seek(&mut self, sequence: u64) -> crate::Result<()> {
self.track.seek(sequence)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use crate::container::Timestamp;
fn setup() -> (moq_net::TrackProducer, crate::catalog::Producer) {
let mut broadcast = moq_net::Broadcast::new().produce();
let catalog = crate::catalog::Producer::new(&mut broadcast).unwrap();
let track = broadcast.create_track(moq_net::Track::new("0.vp8")).unwrap();
(track, catalog)
}
#[tokio::test(start_paused = true)]
async fn imports_keyframe_then_interframe() {
let (track, catalog) = setup();
let mut import = super::Import::new(track, catalog.clone());
import.initialize(&[]).unwrap();
assert!(catalog.snapshot().video.renditions.is_empty());
let keyframe = Bytes::from_static(&[0x10, 0x00, 0x00, 0x9d, 0x01, 0x2a, 0x40, 0x01, 0xf0, 0x00]);
import
.decode(&keyframe, Some(Timestamp::from_micros(0).unwrap()))
.unwrap();
let snapshot = catalog.snapshot();
let config = snapshot.video.renditions.get("0.vp8").unwrap();
assert_eq!(config.codec, hang::catalog::VideoCodec::VP8);
assert_eq!(config.coded_width, Some(320));
assert_eq!(config.coded_height, Some(240));
let interframe = Bytes::from_static(&[0x31, 0x00, 0x00, 0xaa, 0xbb]);
import
.decode(&interframe, Some(Timestamp::from_micros(33_000).unwrap()))
.unwrap();
import.finish().unwrap();
}
#[tokio::test(start_paused = true)]
async fn rejects_interframe_first() {
let (track, catalog) = setup();
let mut import = super::Import::new(track, catalog);
let interframe = Bytes::from_static(&[0x31, 0x00, 0x00, 0xaa, 0xbb]);
assert!(
import
.decode(&interframe, Some(Timestamp::from_micros(0).unwrap()))
.is_err()
);
}
}