[TSM.ID].[11031972] PXE : 19 Cangkang -> REAL Implementation (for/if/match/tests)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user