use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{Duration, Instant};
use bytes::Bytes;
use crate::emulator::{HEIGHT, WIDTH};
pub struct VideoEncoder {
tx: tokio::sync::mpsc::Sender<EncoderMsg>,
pub demand: moq_net::track::Demand,
force_keyframe: Arc<AtomicBool>,
encode_duration: Arc<AtomicU64>,
_thread: std::thread::JoinHandle<()>,
}
enum EncoderMsg {
Frame {
rgba: Bytes,
ts: hang::container::Timestamp,
},
Discontinuity,
}
impl VideoEncoder {
pub fn spawn(broadcast: moq_net::broadcast::Producer, catalog: moq_mux::catalog::Producer) -> Self {
let (tx, rx) = tokio::sync::mpsc::channel(4);
let producer = moq_video::encode::Producer::new(broadcast, catalog, moq_video::encode::Codec::H264)
.expect("failed to create avc3 producer");
let demand = producer.demand();
let force_keyframe = Arc::new(AtomicBool::new(false));
let encode_duration = Arc::new(AtomicU64::new(0));
let fk = force_keyframe.clone();
let ed = encode_duration.clone();
let thread = std::thread::Builder::new()
.name("video-encoder".into())
.spawn(move || encoder_thread(rx, producer, fk, ed))
.expect("failed to spawn video encoder thread");
Self {
tx,
demand,
force_keyframe,
encode_duration,
_thread: thread,
}
}
pub fn try_frame(&self, rgba: Bytes, ts: hang::container::Timestamp) {
if self.tx.try_send(EncoderMsg::Frame { rgba, ts }).is_err() {
tracing::warn!("video frame dropped: encoder backpressure");
}
}
pub fn discontinuity(&self) {
if self.tx.blocking_send(EncoderMsg::Discontinuity).is_err() {
tracing::warn!("video discontinuity dropped: encoder gone");
}
}
pub fn force_keyframe(&self) {
self.force_keyframe.store(true, Ordering::Release);
}
pub fn encode_duration(&self) -> Duration {
Duration::from_micros(self.encode_duration.load(Ordering::Relaxed))
}
}
fn encoder_thread(
mut rx: tokio::sync::mpsc::Receiver<EncoderMsg>,
mut producer: moq_video::encode::Producer,
force_keyframe: Arc<AtomicBool>,
encode_duration: Arc<AtomicU64>,
) {
let mut encoder: Option<moq_video::encode::Encoder> = None;
while let Some(msg) = rx.blocking_recv() {
let msg = match msg {
EncoderMsg::Frame { rgba, ts } => (rgba, ts),
EncoderMsg::Discontinuity => {
if let Err(e) = producer.discontinuity() {
tracing::warn!(error = %e, "failed to mark the video discontinuity");
}
continue;
}
};
let (rgba, ts) = msg;
let enc = match encoder.as_mut() {
Some(enc) => enc,
None => {
let mut config = moq_video::encode::Config::new(WIDTH, HEIGHT, 60);
config.kind = moq_video::encode::Kind::Software;
match moq_video::encode::Encoder::new(&config) {
Ok(enc) => encoder.insert(enc),
Err(e) => {
tracing::error!(error = %e, "H.264 encoder init failed");
return;
}
}
}
};
if force_keyframe.swap(false, Ordering::AcqRel) {
enc.keyframe();
}
let start = Instant::now();
let surface = match moq_video::Surface::rgba(&rgba, moq_video::Size::new(WIDTH, HEIGHT)) {
Ok(surface) => surface,
Err(e) => {
tracing::error!(error = %e, "RGBA conversion error");
continue;
}
};
match enc.encode(&moq_video::Frame::new(surface, ts)) {
Ok(encoded) => {
if let Err(e) = producer.publish(&encoded) {
tracing::error!(error = %e, "video publish failed; stopping encoder");
return;
}
}
Err(e) => tracing::error!(error = %e, "H.264 encode error"),
}
encode_duration.store(start.elapsed().as_micros() as u64, Ordering::Relaxed);
}
}