use std::sync::Arc;
use bytes::Bytes;
use hang::catalog::VideoConfig;
use moq_mux::container::Container as _;
use tokio::sync::Semaphore;
use crate::Error;
use crate::catalog::Resolved;
use crate::feed::{Feed, Item};
const MAX_CONCURRENT_FETCHES: usize = 4;
#[derive(Clone)]
pub(crate) struct Retire(tokio::sync::watch::Receiver<bool>);
impl Retire {
pub(crate) fn channel() -> (tokio::sync::watch::Sender<bool>, Self) {
let (sender, receiver) = tokio::sync::watch::channel(false);
(sender, Self(receiver))
}
async fn fired(&mut self) {
let _ = self.0.wait_for(|retired| *retired).await;
}
}
#[derive(Clone)]
pub(crate) struct Rung {
pub info: Resolved,
pub source: moq_net::track::Consumer,
pub feed: Feed,
pub broadcast: moq_net::broadcast::Consumer,
pub config: VideoConfig,
pub encoder: moq_video::encode::Kind,
pub decoder: moq_video::decode::Kind,
pub resize: moq_video::resize::Config,
pub active: crate::active::Producer,
pub retire: Retire,
}
impl Rung {
async fn pipeline(&self) -> Result<Pipeline, Error> {
Pipeline::new(self).await
}
fn container(&self) -> Result<moq_mux::catalog::hang::Container, Error> {
Ok(moq_mux::catalog::hang::Container::try_from(&self.config)?)
}
async fn encode(&self, color: Option<moq_video::Color>) -> Result<moq_video::encode::Sink, Error> {
let framerate = self.info.framerate.unwrap_or(moq_video::Rate::new(30, 1).unwrap());
let mut config = moq_video::encode::Config::new(self.info.size.width, self.info.size.height, framerate);
config.bitrate = Some(self.info.bitrate);
config.kind = self.encoder.clone();
config.color = color;
config.gop = moq_video::encode::Gop::keyframe_every(std::time::Duration::from_secs(8), framerate);
Ok(moq_video::encode::Sink::open(&config).await?)
}
}
enum Ended {
Open,
Closed,
}
pub(crate) async fn serve(rung: Rung, request: moq_net::track::Request) -> Result<(), Error> {
let dynamic = request.dynamic();
let info = hang::container::track_info(hang::catalog::PRIORITY.video);
let mut producer = request.accept(info);
let (finished, mut finishing) = tokio::sync::watch::channel(false);
let live = async {
let result = live(&rung, &mut producer).await;
let _ = finished.send(true);
result
};
let (live, fetches) = tokio::join!(live, fetches(&rung, dynamic, &mut finishing));
let result = match (live, fetches) {
(Ok(Ended::Open), Ok(())) => producer.finish().map_err(Into::into),
(live, fetches) => live.map(|_| ()).and(fetches),
};
if result.is_err() {
let _ = producer.abort(moq_net::Error::Cancel);
}
result
}
async fn live(rung: &Rung, producer: &mut moq_net::track::Producer) -> Result<Ended, Error> {
let demand = producer.demand();
let mut retire = rung.retire.clone();
let mut retiring = false;
loop {
if retiring {
return Ok(Ended::Open);
}
tokio::select! {
used = demand.used() => if used.is_err() {
return Ok(Ended::Closed);
},
err = rung.broadcast.closed() => {
producer.clone().abort(err)?;
return Ok(Ended::Closed);
}
() = retire.fired() => {
retiring = true;
continue;
}
}
let active = rung.active.attach(&rung.info);
let mut listener = rung.feed.listen();
let mut encoder: Option<moq_video::encode::Sink> = None;
let mut pending_cut = false;
let mut current: Option<moq_net::group::Producer> = None;
'session: loop {
let item = tokio::select! {
item = listener.recv() => item,
_ = demand.unused() => {
if let Some(output) = current.take() {
output.abort(moq_net::Error::Cancel)?;
}
break 'session;
}
() = retire.fired(), if !retiring => {
retiring = true;
if current.is_none() {
break 'session;
}
continue;
}
};
match item {
Some(Item::Group(sequence)) => {
if retiring {
if let Some(output) = current.take() {
output.abort(moq_net::Error::Cancel)?;
}
break 'session;
}
if let Some(encoder) = &mut encoder {
encoder.flush().await?;
}
if let Some(output) = current.take() {
output.abort(moq_net::Error::Cancel)?;
}
match &mut encoder {
Some(encoder) => encoder.cut().await?,
None => pending_cut = true,
}
let info = moq_net::group::Info { sequence };
current = match producer.create_group(info) {
Ok(output) => Some(output),
Err(moq_net::Error::Duplicate) => None,
Err(err) => return Err(err.into()),
};
}
Some(Item::Frame(frame)) => {
let Some(output) = &mut current else { continue };
let frame: Arc<moq_video::Frame> = match frame.size() == rung.info.size {
true => frame,
false => Arc::new(frame.resize(rung.info.size, &rung.resize)?),
};
let encoder = match &mut encoder {
Some(encoder) => encoder,
None => {
let mut opened = rung.encode(frame.surface.color()).await?;
if std::mem::take(&mut pending_cut) {
opened.cut().await?;
}
encoder.insert(opened)
}
};
write(output, &active, encoder.encode(frame).await?)?;
}
Some(Item::End) => {
if let Some(mut output) = current.take() {
if let Some(encoder) = &mut encoder {
write(&mut output, &active, encoder.flush().await?)?;
}
output.finish()?;
}
if retiring {
break 'session;
}
}
Some(Item::Lagged) => {
if let Some(output) = current.take() {
output.abort(moq_net::Error::Cancel)?;
}
if retiring {
break 'session;
}
}
Some(Item::Finished) => {
if let Some(output) = current.take() {
output.abort(moq_net::Error::Cancel)?;
}
return Ok(Ended::Open);
}
None => {
if let Some(output) = current.take() {
let _ = output.abort(moq_net::Error::Cancel);
}
producer.clone().abort(moq_net::Error::Cancel)?;
return Ok(Ended::Closed);
}
}
}
}
}
async fn fetches(
rung: &Rung,
dynamic: moq_net::track::Dynamic,
finishing: &mut tokio::sync::watch::Receiver<bool>,
) -> Result<(), Error> {
let limit = Arc::new(Semaphore::new(MAX_CONCURRENT_FETCHES));
let mut tasks = tokio::task::JoinSet::new();
let mut retire = rung.retire.clone();
let mut retired = false;
loop {
while tasks.try_join_next().is_some() {}
let permit = tokio::select! {
biased;
() = retire.fired() => {
retired = true;
break;
},
_ = finishing.wait_for(|finished| *finished) => break,
permit = limit.clone().acquire_owned() => permit.expect("the semaphore stays open"),
};
let request = tokio::select! {
biased;
() = retire.fired() => {
retired = true;
break;
},
_ = finishing.wait_for(|finished| *finished) => break,
request = dynamic.requested_group() => match request {
Ok(request) => request,
Err(_) => break,
},
};
spawn_fetch(&mut tasks, rung.clone(), request, permit);
}
if retired {
let mut queued = Vec::new();
loop {
tokio::select! {
biased;
request = dynamic.requested_group() => match request {
Ok(request) => queued.push(request),
Err(_) => break,
},
() = std::future::ready(()) => break,
}
}
drop(dynamic);
for request in queued {
let permit = limit.clone().acquire_owned().await.expect("the semaphore stays open");
spawn_fetch(&mut tasks, rung.clone(), request, permit);
}
while let Some(result) = tasks.join_next().await {
if let Err(err) = result {
tracing::warn!(%err, "transcode fetch task panicked");
}
}
} else {
tasks.shutdown().await;
}
Ok(())
}
fn spawn_fetch(
tasks: &mut tokio::task::JoinSet<()>,
rung: Rung,
request: moq_net::group::Request,
permit: tokio::sync::OwnedSemaphorePermit,
) {
tasks.spawn(async move {
let _permit = permit;
let sequence = request.sequence();
if let Err(err) = fetch(rung, request).await {
tracing::warn!(%err, sequence, "transcode fetch failed");
}
});
}
async fn fetch(rung: Rung, request: moq_net::group::Request) -> Result<(), Error> {
let options = moq_net::group::Fetch::default().with_priority(request.priority());
let mut source = match rung.source.fetch_group(request.sequence(), options).await {
Ok(source) => source,
Err(err) => {
request.reject(err.clone());
return Err(err.into());
}
};
let (pipeline, container) = match rung.pipeline().await.and_then(|p| rung.container().map(|c| (p, c))) {
Ok(built) => built,
Err(err) => {
request.reject(moq_net::Error::Cancel);
return Err(err);
}
};
let output = match request.accept(None) {
Ok(output) => output,
Err(err) => return Err(err.into()),
};
let active = rung.active.attach(&rung.info);
transcode_group(pipeline, &container, &mut source, output, &active).await?;
Ok(())
}
async fn transcode_group(
pipeline: Pipeline,
container: &moq_mux::catalog::hang::Container,
source: &mut moq_net::group::Consumer,
mut output: moq_net::group::Producer,
active: &crate::active::Guard,
) -> Result<(), Error> {
match transcode_group_inner(pipeline, container, source, &mut output, active).await {
Ok(()) => {
output.finish()?;
Ok(())
}
Err(err) => {
let _ = output.abort(moq_net::Error::Cancel);
Err(err)
}
}
}
async fn transcode_group_inner(
mut pipeline: Pipeline,
container: &moq_mux::catalog::hang::Container,
source: &mut moq_net::group::Consumer,
output: &mut moq_net::group::Producer,
active: &crate::active::Guard,
) -> Result<(), Error> {
let mut first = true;
while let Some(frames) = container.read(source).await? {
for frame in frames {
let timestamp = frame.timestamp;
let keyframe = frame.keyframe || first;
first = false;
write(
output,
active,
pipeline.process(frame.payload, timestamp, keyframe).await?,
)?;
}
}
write(output, active, pipeline.finish().await?)?;
Ok(())
}
fn write(
output: &mut moq_net::group::Producer,
active: &crate::active::Guard,
encoded: Vec<moq_video::encode::Encoded>,
) -> Result<(), Error> {
let mut frames = 0;
let mut bytes = 0;
let result: Result<(), Error> = (|| {
for encoded in encoded {
let size = encoded.payload.len() as u64;
let frame = hang::container::Frame {
timestamp: encoded.timestamp,
payload: encoded.payload,
};
frame.write_to(output)?;
frames += 1;
bytes += size;
}
Ok(())
})();
active.produced(frames, bytes);
result
}
struct Pipeline {
decoder: moq_video::decode::Sink,
encoder: Option<moq_video::encode::Sink>,
pending_cut: bool,
rung: Rung,
size: moq_video::Size,
}
impl Pipeline {
async fn new(rung: &Rung) -> Result<Self, Error> {
let mut decode = moq_video::decode::Config::new();
decode.kind = rung.decoder.clone();
decode.output = rung.resize.output;
decode.scale_hint = scale_hint(rung.info.size, rung.resize.output);
let decoder = moq_video::decode::Sink::open(&rung.config, &decode).await?;
Ok(Self {
decoder,
encoder: None,
pending_cut: false,
rung: rung.clone(),
size: rung.info.size,
})
}
async fn process(
&mut self,
payload: Bytes,
timestamp: moq_net::Timestamp,
keyframe: bool,
) -> Result<Vec<moq_video::encode::Encoded>, Error> {
if keyframe {
match &mut self.encoder {
Some(encoder) => encoder.cut().await?,
None => self.pending_cut = true,
}
}
let mut encoded = Vec::new();
for raw in self.decoder.decode(payload, timestamp, keyframe).await? {
encoded.extend(self.encode_frame(raw).await?);
}
Ok(encoded)
}
async fn encode_frame(&mut self, raw: moq_video::Frame) -> Result<Vec<moq_video::encode::Encoded>, Error> {
let raw = match raw.size() == self.size {
true => raw,
false => raw.resize(self.size, &self.rung.resize)?,
};
if self.encoder.is_none() {
let mut opened = self.rung.encode(raw.surface.color()).await?;
if std::mem::take(&mut self.pending_cut) {
opened.cut().await?;
}
self.encoder = Some(opened);
}
let encoder = self.encoder.as_mut().expect("just opened");
Ok(encoder.encode(raw).await?)
}
async fn finish(mut self) -> Result<Vec<moq_video::encode::Encoded>, Error> {
let mut encoded = Vec::new();
for raw in self.decoder.flush().await? {
encoded.extend(self.encode_frame(raw).await?);
}
if let Some(encoder) = self.encoder {
encoded.extend(encoder.finish().await?);
}
Ok(encoded)
}
}
fn scale_hint(size: moq_video::Size, output: moq_video::Output) -> Option<moq_video::Size> {
(output != moq_video::Output::Cpu).then_some(size)
}
#[cfg(test)]
mod tests {
use super::*;
use moq_video::Output;
#[test]
fn forced_cpu_skips_the_decoder_scaler() {
let size = moq_video::Size::new(160, 120);
assert_eq!(scale_hint(size, Output::Cpu), None);
assert_eq!(scale_hint(size, Output::Native), Some(size));
}
#[test]
fn write_banks_the_frames_that_reached_the_group() {
let rung = Resolved {
name: "video/120p".to_string(),
height: 120,
size: moq_video::Size::new(160, 120),
bitrate: moq_net::bandwidth::Rate::from_bps(100_000),
framerate: Some(moq_video::Rate::new(30, 1).unwrap()),
};
let active = crate::active::Producer::default();
active.declare(std::slice::from_ref(&rung));
let mut cursor = active.consume();
let rendition = cursor.try_next().expect("ladder").rendition;
let broadcast = moq_net::broadcast::Info::default().produce();
let track = broadcast
.create_track("video/120p", hang::container::track_info(hang::catalog::PRIORITY.video))
.unwrap();
let mut group = track.create_group(moq_net::group::Info { sequence: 0 }).unwrap();
let guard = active.attach(&rung);
let good = moq_video::encode::Encoded::new(
Bytes::from_static(b"hello"),
moq_net::Timestamp::from_micros(0).unwrap(),
);
let bad = moq_video::encode::Encoded::new(
Bytes::from_static(b"world"),
moq_net::Timestamp::from_secs(1 << 60).unwrap(),
);
assert!(write(&mut group, &guard, vec![good, bad]).is_err());
assert_eq!(rendition.frames(), 1);
assert_eq!(rendition.bytes(), 5);
assert!(cursor.try_next().expect("edge").encoding);
}
}