refactor: convert to workspace

This commit is contained in:
2025-01-29 11:48:57 +00:00
parent 20c9d107b7
commit 9045bb93e4
56 changed files with 6215 additions and 1123 deletions

View File

@ -0,0 +1,28 @@
use crate::ingress::{spawn_pipeline, ConnectionInfo};
use crate::overseer::Overseer;
use anyhow::Result;
use log::info;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::runtime::Handle;
pub async fn listen(out_dir: String, path: PathBuf, overseer: Arc<dyn Overseer>) -> Result<()> {
info!("Sending file: {}", path.display());
let info = ConnectionInfo {
ip_addr: "127.0.0.1:6969".to_string(),
endpoint: "file-input".to_owned(),
app_name: "".to_string(),
key: "test".to_string(),
};
let file = std::fs::File::open(path)?;
spawn_pipeline(
Handle::current(),
info,
out_dir.clone(),
overseer.clone(),
Box::new(file),
);
Ok(())
}

View File

@ -0,0 +1,69 @@
use crate::overseer::Overseer;
use crate::pipeline::runner::PipelineRunner;
use log::{error, info};
use serde::{Deserialize, Serialize};
use std::io::Read;
use std::sync::Arc;
use tokio::runtime::Handle;
pub mod file;
#[cfg(feature = "rtmp")]
pub mod rtmp;
#[cfg(feature = "srt")]
pub mod srt;
pub mod tcp;
#[cfg(feature = "test-pattern")]
pub mod test;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConnectionInfo {
/// Endpoint of the ingress
pub endpoint: String,
/// IP address of the connection
pub ip_addr: String,
/// App name, empty unless RTMP ingress
pub app_name: String,
/// Stream key
pub key: String,
}
pub fn spawn_pipeline(
handle: Handle,
info: ConnectionInfo,
out_dir: String,
seer: Arc<dyn Overseer>,
reader: Box<dyn Read + Send>,
) {
info!("New client connected: {}", &info.ip_addr);
let seer = seer.clone();
let out_dir = out_dir.to_string();
std::thread::spawn(move || unsafe {
match PipelineRunner::new(handle, out_dir, seer, info, reader) {
Ok(mut pl) => loop {
match pl.run() {
Ok(c) => {
if !c {
if let Err(e) = pl.flush() {
error!("Pipeline flush failed: {}", e);
}
break;
}
}
Err(e) => {
if let Err(e) = pl.flush() {
error!("Pipeline flush failed: {}", e);
}
error!("Pipeline run failed: {}", e);
break;
}
}
},
Err(e) => {
error!("Failed to create PipelineRunner: {}", e);
}
}
});
}

View File

@ -0,0 +1,238 @@
use crate::ingress::{spawn_pipeline, ConnectionInfo};
use crate::overseer::Overseer;
use anyhow::{bail, Result};
use log::{error, info};
use rml_rtmp::handshake::{Handshake, HandshakeProcessResult, PeerType};
use rml_rtmp::sessions::{
ServerSession, ServerSessionConfig, ServerSessionEvent, ServerSessionResult,
};
use std::collections::VecDeque;
use std::io::{ErrorKind, Read, Write};
use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::runtime::Handle;
use tokio::time::Instant;
#[derive(PartialEq, Eq, Clone, Hash)]
struct RtmpPublishedStream(String, String);
struct RtmpClient {
socket: std::net::TcpStream,
media_buf: Vec<u8>,
session: ServerSession,
msg_queue: VecDeque<ServerSessionResult>,
reader_buf: [u8; 4096],
pub published_stream: Option<RtmpPublishedStream>,
}
impl RtmpClient {
async fn start(mut socket: TcpStream) -> Result<Self> {
let mut hs = Handshake::new(PeerType::Server);
let exchange = hs.generate_outbound_p0_and_p1()?;
socket.write_all(&exchange).await?;
let mut buf = [0; 4096];
loop {
let r = socket.read(&mut buf).await?;
if r == 0 {
bail!("EOF reached while reading");
}
match hs.process_bytes(&buf[..r])? {
HandshakeProcessResult::InProgress { response_bytes } => {
socket.write_all(&response_bytes).await?;
}
HandshakeProcessResult::Completed {
response_bytes,
remaining_bytes,
} => {
socket.write_all(&response_bytes).await?;
let cfg = ServerSessionConfig::new();
let (mut ses, mut res) = ServerSession::new(cfg)?;
let q = ses.handle_input(&remaining_bytes)?;
res.extend(q);
let ret = Self {
socket: socket.into_std()?,
media_buf: vec![],
session: ses,
msg_queue: VecDeque::from(res),
reader_buf: [0; 4096],
published_stream: None,
};
return Ok(ret);
}
}
}
}
/// Read data until we get the publish request
pub fn read_until_publish_request(&mut self, timeout: Duration) -> Result<()> {
let start = Instant::now();
while self.published_stream.is_none() {
if (Instant::now() - start) > timeout {
bail!("Timed out waiting for publish request");
}
self.read_data()?;
}
Ok(())
}
fn read_data(&mut self) -> Result<()> {
let r = match self.socket.read(&mut self.reader_buf) {
Ok(r) => r,
Err(e) => {
return match e.kind() {
ErrorKind::WouldBlock => Ok(()),
ErrorKind::Interrupted => Ok(()),
_ => Err(anyhow::Error::new(e)),
};
}
};
if r == 0 {
bail!("EOF");
}
let mx = self.session.handle_input(&self.reader_buf[..r])?;
if !mx.is_empty() {
self.msg_queue.extend(mx);
self.process_msg_queue()?;
}
Ok(())
}
fn process_msg_queue(&mut self) -> Result<()> {
while let Some(msg) = self.msg_queue.pop_front() {
match msg {
ServerSessionResult::OutboundResponse(data) => {
self.socket.write_all(&data.bytes)?
}
ServerSessionResult::RaisedEvent(ev) => self.handle_event(ev)?,
ServerSessionResult::UnhandleableMessageReceived(m) => {
// treat any non-flv streams as raw media stream in rtmp
self.media_buf.extend(&m.data);
}
}
}
Ok(())
}
fn handle_event(&mut self, event: ServerSessionEvent) -> Result<()> {
match event {
ServerSessionEvent::ClientChunkSizeChanged { new_chunk_size } => {
info!("New client chunk size: {}", new_chunk_size);
}
ServerSessionEvent::ConnectionRequested { request_id, .. } => {
let mx = self.session.accept_request(request_id)?;
self.msg_queue.extend(mx);
}
ServerSessionEvent::ReleaseStreamRequested { .. } => {}
ServerSessionEvent::PublishStreamRequested {
request_id,
app_name,
stream_key,
mode,
} => {
if self.published_stream.is_some() {
let mx =
self.session
.reject_request(request_id, "0", "stream already published")?;
self.msg_queue.extend(mx);
} else {
let mx = self.session.accept_request(request_id)?;
self.msg_queue.extend(mx);
info!(
"Published stream request: {app_name}/{stream_key} [{:?}]",
mode
);
self.published_stream = Some(RtmpPublishedStream(app_name, stream_key));
}
}
ServerSessionEvent::PublishStreamFinished { .. } => {}
ServerSessionEvent::StreamMetadataChanged {
app_name,
stream_key,
metadata,
} => {
info!(
"Metadata configured: {}/{} {:?}",
app_name, stream_key, metadata
);
}
ServerSessionEvent::AudioDataReceived { data, .. } => {
self.media_buf.extend(data);
}
ServerSessionEvent::VideoDataReceived { data, .. } => {
self.media_buf.extend(data);
}
ServerSessionEvent::UnhandleableAmf0Command { .. } => {}
ServerSessionEvent::PlayStreamRequested { request_id, .. } => {
let mx = self
.session
.reject_request(request_id, "0", "playback not supported")?;
self.msg_queue.extend(mx);
}
ServerSessionEvent::PlayStreamFinished { .. } => {}
ServerSessionEvent::AcknowledgementReceived { .. } => {}
ServerSessionEvent::PingResponseReceived { .. } => {}
}
Ok(())
}
}
impl Read for RtmpClient {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
// block this thread until something comes into [media_buf]
while self.media_buf.is_empty() {
if let Err(e) = self.read_data() {
error!("Error reading data: {}", e);
return Ok(0);
};
}
let to_read = buf.len().min(self.media_buf.len());
let drain = self.media_buf.drain(..to_read);
buf[..to_read].copy_from_slice(drain.as_slice());
Ok(to_read)
}
}
pub async fn listen(out_dir: String, addr: String, overseer: Arc<dyn Overseer>) -> Result<()> {
let listener = TcpListener::bind(&addr).await?;
info!("RTMP listening on: {}", &addr);
while let Ok((socket, ip)) = listener.accept().await {
let mut cc = RtmpClient::start(socket).await?;
let addr = addr.clone();
let overseer = overseer.clone();
let out_dir = out_dir.clone();
let handle = Handle::current();
std::thread::Builder::new()
.name("rtmp-client".to_string())
.spawn(move || {
if let Err(e) = cc.read_until_publish_request(Duration::from_secs(10)) {
error!("{}", e);
} else {
let pr = cc.published_stream.as_ref().unwrap();
let info = ConnectionInfo {
ip_addr: ip.to_string(),
endpoint: addr.clone(),
app_name: pr.0.clone(),
key: pr.1.clone(),
};
spawn_pipeline(
handle,
info,
out_dir.clone(),
overseer.clone(),
Box::new(cc),
);
}
})?;
}
Ok(())
}

View File

@ -0,0 +1,66 @@
use crate::ingress::{spawn_pipeline, ConnectionInfo};
use crate::overseer::Overseer;
use anyhow::Result;
use futures_util::stream::FusedStream;
use futures_util::StreamExt;
use log::info;
use srt_tokio::{SrtListener, SrtSocket};
use std::io::Read;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::runtime::Handle;
pub async fn listen(out_dir: String, addr: String, overseer: Arc<dyn Overseer>) -> Result<()> {
let binder: SocketAddr = addr.parse()?;
let (_binding, mut packets) = SrtListener::builder().bind(binder).await?;
info!("SRT listening on: {}", &addr);
while let Some(request) = packets.incoming().next().await {
let socket = request.accept(None).await?;
let info = ConnectionInfo {
endpoint: addr.clone(),
ip_addr: socket.settings().remote.to_string(),
app_name: "".to_string(),
key: socket
.settings()
.stream_id
.as_ref()
.map_or(String::new(), |s| s.to_string()),
};
spawn_pipeline(
Handle::current(),
info,
out_dir.clone(),
overseer.clone(),
Box::new(SrtReader {
handle: Handle::current(),
socket,
buf: Vec::with_capacity(4096),
}),
);
}
Ok(())
}
struct SrtReader {
pub handle: Handle,
pub socket: SrtSocket,
pub buf: Vec<u8>,
}
impl Read for SrtReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let (mut rx, _) = self.socket.split_mut();
while self.buf.len() < buf.len() {
if rx.is_terminated() {
return Ok(0);
}
if let Some((_, data)) = self.handle.block_on(rx.next()) {
self.buf.extend(data.iter().as_slice());
}
}
let drain = self.buf.drain(..buf.len());
buf.copy_from_slice(drain.as_slice());
Ok(buf.len())
}
}

View File

@ -0,0 +1,30 @@
use crate::ingress::{spawn_pipeline, ConnectionInfo};
use crate::overseer::Overseer;
use anyhow::Result;
use log::info;
use std::sync::Arc;
use tokio::net::TcpListener;
use tokio::runtime::Handle;
pub async fn listen(out_dir: String, addr: String, overseer: Arc<dyn Overseer>) -> Result<()> {
let listener = TcpListener::bind(&addr).await?;
info!("TCP listening on: {}", &addr);
while let Ok((socket, ip)) = listener.accept().await {
let info = ConnectionInfo {
ip_addr: ip.to_string(),
endpoint: addr.clone(),
app_name: "".to_string(),
key: "no-key-tcp".to_string(),
};
let socket = socket.into_std()?;
spawn_pipeline(
Handle::current(),
info,
out_dir.clone(),
overseer.clone(),
Box::new(socket),
);
}
Ok(())
}

View File

@ -0,0 +1,187 @@
use crate::ingress::{spawn_pipeline, ConnectionInfo};
use crate::overseer::Overseer;
use anyhow::Result;
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVColorSpace::AVCOL_SPC_RGB;
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVPictureType::AV_PICTURE_TYPE_NONE;
use ffmpeg_rs_raw::ffmpeg_sys_the_third::AVPixelFormat::{AV_PIX_FMT_RGBA, AV_PIX_FMT_YUV420P};
use ffmpeg_rs_raw::ffmpeg_sys_the_third::{
av_frame_alloc, av_frame_free, av_frame_get_buffer, av_packet_free, AV_PROFILE_H264_MAIN,
};
use ffmpeg_rs_raw::{Encoder, Muxer, Scaler};
use fontdue::layout::{CoordinateSystem, Layout, TextStyle};
use fontdue::Font;
use log::info;
use ringbuf::traits::{Observer, Split};
use ringbuf::{HeapCons, HeapRb};
use std::io::Read;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tiny_skia::Pixmap;
use tokio::runtime::Handle;
pub async fn listen(out_dir: String, overseer: Arc<dyn Overseer>) -> Result<()> {
info!("Test pattern enabled");
let info = ConnectionInfo {
endpoint: "test-pattern".to_string(),
ip_addr: "test-pattern".to_string(),
app_name: "".to_string(),
key: "test".to_string(),
};
let src = TestPatternSrc::new()?;
spawn_pipeline(
Handle::current(),
info,
out_dir.clone(),
overseer.clone(),
Box::new(src),
);
Ok(())
}
struct TestPatternSrc {
encoder: Encoder,
scaler: Scaler,
muxer: Muxer,
background: Pixmap,
font: [Font; 1],
frame_no: u64,
start: Instant,
reader: HeapCons<u8>,
}
unsafe impl Send for TestPatternSrc {}
impl TestPatternSrc {
pub fn new() -> Result<Self> {
let scaler = Scaler::new();
let encoder = unsafe {
Encoder::new_with_name("libx264")?
.with_stream_index(0)
.with_framerate(30.0)?
.with_bitrate(1_000_000)
.with_pix_fmt(AV_PIX_FMT_YUV420P)
.with_width(1280)
.with_height(720)
.with_level(51)
.with_profile(AV_PROFILE_H264_MAIN)
.open(None)?
};
let svg_data = include_bytes!("../../test.svg");
let tree = usvg::Tree::from_data(svg_data, &Default::default())?;
let mut pixmap = Pixmap::new(1280, 720).unwrap();
let render_ts = tiny_skia::Transform::from_scale(
pixmap.width() as f32 / tree.size().width(),
pixmap.height() as f32 / tree.size().height(),
);
resvg::render(&tree, render_ts, &mut pixmap.as_mut());
let font = include_bytes!("../../SourceCodePro-Regular.ttf") as &[u8];
let font = Font::from_bytes(font, Default::default()).unwrap();
let buf = HeapRb::new(1024 * 1024);
let (writer, reader) = buf.split();
let muxer = unsafe {
let mut m = Muxer::builder()
.with_output_write(writer, Some("mpegts"))?
.with_stream_encoder(&encoder)?
.build()?;
m.open(None)?;
m
};
Ok(Self {
encoder,
scaler,
muxer,
background: pixmap,
font: [font],
frame_no: 0,
start: Instant::now(),
reader,
})
}
pub unsafe fn next_pkt(&mut self) -> Result<()> {
let stream_time = Duration::from_secs_f64(self.frame_no as f64 / 30.0);
let real_time = Instant::now().duration_since(self.start);
let wait_time = if stream_time > real_time {
stream_time - real_time
} else {
Duration::new(0, 0)
};
if !wait_time.is_zero() {
std::thread::sleep(wait_time);
}
self.frame_no += 1;
let mut src_frame = unsafe {
let src_frame = av_frame_alloc();
(*src_frame).width = 1280;
(*src_frame).height = 720;
(*src_frame).pict_type = AV_PICTURE_TYPE_NONE;
(*src_frame).key_frame = 1;
(*src_frame).colorspace = AVCOL_SPC_RGB;
(*src_frame).format = AV_PIX_FMT_RGBA as _;
(*src_frame).pts = self.frame_no as i64;
(*src_frame).duration = 1;
av_frame_get_buffer(src_frame, 0);
self.background
.data()
.as_ptr()
.copy_to((*src_frame).data[0] as *mut _, 1280 * 720 * 4);
src_frame
};
let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
layout.clear();
layout.append(
&self.font,
&TextStyle::new(&format!("frame={}", self.frame_no), 40.0, 0),
);
for g in layout.glyphs() {
let (metrics, bitmap) = self.font[0].rasterize_config_subpixel(g.key);
for y in 0..metrics.height {
for x in 0..metrics.width {
let dst_x = x + g.x as usize;
let dst_y = y + g.y as usize;
let offset_src = (x + y * metrics.width) * 3;
unsafe {
let offset_dst = 4 * dst_x + dst_y * (*src_frame).linesize[0] as usize;
let pixel_dst = (*src_frame).data[0].add(offset_dst);
*pixel_dst.offset(0) = bitmap[offset_src];
*pixel_dst.offset(1) = bitmap[offset_src + 1];
*pixel_dst.offset(2) = bitmap[offset_src + 2];
}
}
}
}
// scale/encode
let mut frame = self
.scaler
.process_frame(src_frame, 1280, 720, AV_PIX_FMT_YUV420P)?;
for mut pkt in self.encoder.encode_frame(frame)? {
self.muxer.write_packet(pkt)?;
av_packet_free(&mut pkt);
}
av_frame_free(&mut frame);
av_frame_free(&mut src_frame);
Ok(())
}
}
impl Read for TestPatternSrc {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
unsafe {
while self.reader.occupied_len() < buf.len() {
self.next_pkt().map_err(std::io::Error::other)?;
}
}
self.reader.read(buf)
}
}