[TSM.ID].[11031972] PXE : 19 Cangkang -> REAL Implementation (for/if/match/tests)

This commit is contained in:
TSM.ID
2026-05-25 05:05:13 +07:00
parent e0360b3ecd
commit 9e5f7c78a9
19 changed files with 2749 additions and 958 deletions
+112 -2
View File
@@ -1,3 +1,113 @@
#![deny(warnings)]
// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential.
pub mod rtp_parser;
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-media -- Media Framework Core (RTP, codec negotiation, pipeline)
pub mod rtp;
use std::collections::HashMap;
#[derive(Debug)]
pub enum MediaError { UnsupportedCodec(String), PipelineError(String), PayloadTooLarge(String) }
impl std::fmt::Display for MediaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { Self::UnsupportedCodec(e) => write!(f, "Unsupported: {e}"), Self::PipelineError(e) => write!(f, "Pipeline: {e}"), Self::PayloadTooLarge(e) => write!(f, "Too large: {e}") }
}
}
impl std::error::Error for MediaError {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CodecType { H264, H265, VP8, VP9, AV1, Opus, G711 }
impl CodecType {
pub fn payload_type(&self) -> u8 {
match self { Self::H264 => 96, Self::H265 => 97, Self::VP8 => 98, Self::VP9 => 99, Self::AV1 => 100, Self::Opus => 111, Self::G711 => 0 }
}
pub fn clock_rate(&self) -> u32 {
match self { Self::Opus => 48000, Self::G711 => 8000, _ => 90000 }
}
}
#[derive(Debug, Clone)]
pub struct RtpPacket {
pub version: u8, pub payload_type: u8, pub sequence: u16,
pub timestamp: u32, pub ssrc: u32, pub payload: Vec<u8>,
pub marker: bool,
}
impl RtpPacket {
pub fn new(pt: u8, seq: u16, ts: u32, ssrc: u32, payload: Vec<u8>, marker: bool) -> Self {
Self { version: 2, payload_type: pt, sequence: seq, timestamp: ts, ssrc: ssrc, payload, marker }
}
/// Serialize to bytes (simplified RTP header)
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(12 + self.payload.len());
buf.push((self.version << 6) | if self.marker { 0x80 >> 1 } else { 0 });
buf.push(self.payload_type | if self.marker { 0x80 } else { 0 });
buf.extend_from_slice(&self.sequence.to_be_bytes());
buf.extend_from_slice(&self.timestamp.to_be_bytes());
buf.extend_from_slice(&self.ssrc.to_be_bytes());
buf.extend_from_slice(&self.payload);
buf
}
/// Parse from bytes
pub fn from_bytes(data: &[u8]) -> Result<Self, MediaError> {
if data.len() < 12 { return Err(MediaError::PayloadTooLarge("Packet too small".into())); }
let version = (data[0] >> 6) & 0x03;
let marker = (data[1] & 0x80) != 0;
let pt = data[1] & 0x7F;
let seq = u16::from_be_bytes([data[2], data[3]]);
let ts = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
let ssrc = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
Ok(Self { version, payload_type: pt, sequence: seq, timestamp: ts, ssrc, payload: data[12..].to_vec(), marker })
}
}
/// Codec negotiation: find common codecs between offer and answer
pub fn negotiate_codecs(offer: &[CodecType], answer: &[CodecType]) -> Vec<CodecType> {
offer.iter().filter(|c| answer.contains(c)).cloned().collect()
}
/// Jitter buffer: reorder packets by sequence number
pub struct JitterBuffer { buffer: HashMap<u16, RtpPacket>, next_seq: u16, max_size: usize }
impl JitterBuffer {
pub fn new(max_size: usize) -> Self { Self { buffer: HashMap::new(), next_seq: 0, max_size } }
pub fn push(&mut self, pkt: RtpPacket) {
if self.buffer.len() >= self.max_size { self.buffer.remove(&self.next_seq); self.next_seq = self.next_seq.wrapping_add(1); }
self.buffer.insert(pkt.sequence, pkt);
}
pub fn pop_ordered(&mut self) -> Option<RtpPacket> {
let pkt = self.buffer.remove(&self.next_seq)?;
self.next_seq = self.next_seq.wrapping_add(1);
Some(pkt)
}
pub fn len(&self) -> usize { self.buffer.len() }
pub fn is_empty(&self) -> bool { self.buffer.is_empty() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rtp_roundtrip() {
let pkt = RtpPacket::new(96, 42, 1000, 0xDEAD, vec![1, 2, 3], true);
let bytes = pkt.to_bytes();
let parsed = RtpPacket::from_bytes(&bytes).unwrap();
assert_eq!(parsed.sequence, 42);
assert_eq!(parsed.payload, vec![1, 2, 3]);
}
#[test]
fn test_negotiate() {
let offer = vec![CodecType::VP9, CodecType::H264, CodecType::Opus];
let answer = vec![CodecType::H264, CodecType::Opus, CodecType::AV1];
let common = negotiate_codecs(&offer, &answer);
assert_eq!(common, vec![CodecType::H264, CodecType::Opus]);
}
#[test]
fn test_jitter_buffer() {
let mut jb = JitterBuffer::new(10);
jb.push(RtpPacket::new(96, 2, 2000, 1, vec![], false));
jb.push(RtpPacket::new(96, 0, 0, 1, vec![], false));
jb.push(RtpPacket::new(96, 1, 1000, 1, vec![], false));
let p0 = jb.pop_ordered().unwrap();
assert_eq!(p0.sequence, 0);
let p1 = jb.pop_ordered().unwrap();
assert_eq!(p1.sequence, 1);
}
}