[TSM.ID].[11031972] PXE : 19 Cangkang -> REAL Implementation (for/if/match/tests)
This commit is contained in:
@@ -1,3 +1,85 @@
|
||||
#![deny(warnings)]
|
||||
// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential.
|
||||
pub mod rtmp_server;
|
||||
//! [TSM.ID].[11031972] -- Platform X Ecosystem
|
||||
//! xcu-ingest -- Media Ingestion Server (RTMP/HLS/DASH)
|
||||
pub mod server;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum IngestError { StreamNotFound(String), TranscodeFailed(String), BufferFull(String) }
|
||||
impl std::fmt::Display for IngestError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self { Self::StreamNotFound(e) => write!(f, "Stream: {e}"), Self::TranscodeFailed(e) => write!(f, "Transcode: {e}"), Self::BufferFull(e) => write!(f, "Buffer: {e}") }
|
||||
}
|
||||
}
|
||||
impl std::error::Error for IngestError {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StreamConfig { pub stream_id: String, pub codec: String, pub bitrate_kbps: u32, pub width: u32, pub height: u32, pub fps: u32 }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MediaChunk { pub sequence: u64, pub data: Vec<u8>, pub duration_ms: u32, pub keyframe: bool, pub timestamp: u64 }
|
||||
|
||||
pub struct IngestPipeline {
|
||||
streams: HashMap<String, StreamState>,
|
||||
max_buffer_chunks: usize,
|
||||
}
|
||||
|
||||
struct StreamState { config: StreamConfig, buffer: Vec<MediaChunk>, total_bytes: u64, chunk_count: u64 }
|
||||
|
||||
impl IngestPipeline {
|
||||
pub fn new(max_buffer: usize) -> Self { Self { streams: HashMap::new(), max_buffer_chunks: max_buffer } }
|
||||
|
||||
pub fn create_stream(&mut self, config: StreamConfig) -> Result<(), IngestError> {
|
||||
let id = config.stream_id.clone();
|
||||
self.streams.insert(id, StreamState { config, buffer: Vec::new(), total_bytes: 0, chunk_count: 0 });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn push_chunk(&mut self, stream_id: &str, chunk: MediaChunk) -> Result<u64, IngestError> {
|
||||
let state = self.streams.get_mut(stream_id).ok_or_else(|| IngestError::StreamNotFound(stream_id.into()))?;
|
||||
if state.buffer.len() >= self.max_buffer_chunks {
|
||||
state.buffer.remove(0); // Drop oldest (sliding window)
|
||||
}
|
||||
state.total_bytes += chunk.data.len() as u64;
|
||||
state.chunk_count += 1;
|
||||
let seq = state.chunk_count;
|
||||
state.buffer.push(chunk);
|
||||
Ok(seq)
|
||||
}
|
||||
|
||||
/// Generate HLS playlist from buffer
|
||||
pub fn generate_hls_playlist(&self, stream_id: &str) -> Result<String, IngestError> {
|
||||
let state = self.streams.get(stream_id).ok_or_else(|| IngestError::StreamNotFound(stream_id.into()))?;
|
||||
let mut m3u8 = String::from("#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:4\n");
|
||||
for chunk in &state.buffer {
|
||||
m3u8.push_str(&format!("#EXTINF:{:.3},\n", chunk.duration_ms as f64 / 1000.0));
|
||||
m3u8.push_str(&format!("segment_{}.ts\n", chunk.sequence));
|
||||
}
|
||||
Ok(m3u8)
|
||||
}
|
||||
|
||||
/// Get stream stats
|
||||
pub fn stream_stats(&self, stream_id: &str) -> Result<(u64, u64, f64), IngestError> {
|
||||
let state = self.streams.get(stream_id).ok_or_else(|| IngestError::StreamNotFound(stream_id.into()))?;
|
||||
let bitrate = if state.chunk_count > 0 { (state.total_bytes * 8) as f64 / (state.chunk_count as f64 * 4.0) / 1000.0 } else { 0.0 };
|
||||
Ok((state.chunk_count, state.total_bytes, bitrate))
|
||||
}
|
||||
|
||||
pub fn active_streams(&self) -> usize { self.streams.len() }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_ingest() {
|
||||
let mut p = IngestPipeline::new(10);
|
||||
p.create_stream(StreamConfig { stream_id: "s1".into(), codec: "h264".into(), bitrate_kbps: 3000, width: 1920, height: 1080, fps: 30 }).unwrap();
|
||||
for i in 0..5 {
|
||||
p.push_chunk("s1", MediaChunk { sequence: i, data: vec![0; 1000], duration_ms: 4000, keyframe: i == 0, timestamp: i * 4000 }).unwrap();
|
||||
}
|
||||
let playlist = p.generate_hls_playlist("s1").unwrap();
|
||||
assert!(playlist.contains("#EXTM3U"));
|
||||
assert!(playlist.contains("segment_"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user