use crate::catalog::hang::CatalogExt;
use crate::container::Frame;
use super::FrameHeader;
pub struct Import {
track: crate::container::Producer<crate::catalog::hang::Container, hang::catalog::VideoConfig>,
catalog: crate::codec::video::Catalog,
}
impl Import {
pub fn new<E: CatalogExt>(
track: moq_net::track::Producer,
reserved: crate::catalog::Reserved<E>,
hint: crate::catalog::VideoHint,
) -> crate::Result<Self> {
let wire = crate::catalog::hang::Container::try_from(&hint)?;
let catalog = crate::codec::video::Catalog::new(hint);
let track = reserved.video(track, wire, None)?;
let mut import = Self { track, catalog };
if let Some(config) = import.catalog.initial_config() {
import.apply_config(config)?;
}
Ok(import)
}
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);
self.apply_config(config)?;
Ok(())
}
fn apply_config(&mut self, config: hang::catalog::VideoConfig) -> crate::Result<()> {
self.catalog.publish(&mut self.track, config)
}
pub fn decode<B: moq_net::IntoBytes>(&mut self, frame: B, pts: Option<moq_net::Timestamp>) -> crate::Result<()> {
if frame.as_ref().is_empty() {
return Err(super::Error::EmptyFrame.into());
}
let header = FrameHeader::parse(frame.as_ref())?;
if let Some((width, height)) = header.dimensions {
self.init(width, height)?;
}
let pts = self.track.timestamp(pts)?;
self.track.write(Frame {
timestamp: pts,
payload: frame.into_bytes(),
keyframe: header.keyframe,
duration: None,
})?;
let demand = self.track.track().is_used();
self.catalog.on_frame(&mut self.track, demand)?;
Ok(())
}
pub fn tick(&mut self) -> crate::Result<()> {
let demand = self.track.track().is_used();
self.catalog.tick(&mut self.track, demand)
}
pub fn idle(&mut self) -> crate::Result<()> {
self.catalog.idle(&mut self.track)
}
pub fn observe_lag(&mut self, lag: std::time::Duration) -> crate::Result<()> {
let demand = self.track.track().is_used();
self.catalog.observe_lag(&mut self.track, demand, lag)
}
pub fn demand(&self) -> moq_net::track::Demand {
self.track.track().demand()
}
pub fn flush(&mut self, timestamp: moq_net::Timestamp, now: std::time::Instant) -> crate::Result<()> {
self.track.flush(timestamp, now)
}
pub fn finish(&mut self) -> crate::Result<()> {
self.track.finish()?;
Ok(())
}
pub fn abort(self, err: moq_net::Error) {
self.track.abort(err);
}
pub fn cut(&mut self, end: Option<moq_net::Timestamp>) -> crate::Result<()> {
self.track.cut(end)?;
Ok(())
}
pub fn discontinuity(&mut self) -> crate::Result<()> {
self.track.discontinuity()
}
pub fn seek(&mut self, sequence: u64) -> crate::Result<()> {
self.track.seek(sequence)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use moq_net::Timestamp;
fn setup() -> (moq_net::track::Producer, crate::catalog::Producer) {
let mut broadcast = moq_net::broadcast::Info::new().produce();
let catalog = crate::catalog::Producer::new(&mut broadcast, crate::catalog::Config::default()).unwrap();
let track = broadcast
.create_track("0.vp8", hang::container::track_info(hang::catalog::PRIORITY.video))
.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.reserve(), Default::default()).unwrap();
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 a_loc_reservation_reaches_the_wire_and_the_catalog() {
let (track, catalog) = setup();
let subscriber = track.subscribe(None);
let reserved = catalog.reserve();
let hint = crate::catalog::VideoHint {
codec: Some(hang::catalog::VideoCodec::VP8),
container: hang::catalog::Container::Loc,
..Default::default()
};
let mut import = super::Import::new(track, reserved, hint).unwrap();
import.initialize(&[]).unwrap();
let config = catalog.snapshot().video.renditions.get("0.vp8").cloned().unwrap();
assert_eq!(config.container, hang::catalog::Container::Loc);
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 mut media = crate::container::Consumer::new(
subscriber,
crate::catalog::hang::Container::Loc(crate::container::Kind::Data),
);
let frame = tokio::time::timeout(std::time::Duration::from_secs(1), media.read())
.await
.unwrap()
.unwrap()
.unwrap();
assert_eq!(frame.payload, keyframe);
assert_eq!(frame.timestamp, Timestamp::from_micros(0).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.reserve(), Default::default()).unwrap();
let interframe = Bytes::from_static(&[0x31, 0x00, 0x00, 0xaa, 0xbb]);
assert!(
import
.decode(&interframe, Some(Timestamp::from_micros(0).unwrap()))
.is_err()
);
}
}