use crate::{EncodedPacket, NetworkStreamer, Result, StreamConfig, StreamError, StreamerStats};
use std::net::UdpSocket;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
const PACKET_MAGIC: u32 = 0x5A4C_5644;
const UDP_PAYLOAD_MAX: usize = 1454;
const HEADER_SIZE: usize = 18;
pub struct RawUdpStreamer {
socket: Option<UdpSocket>,
target_addr: String,
packets_sent: Arc<AtomicU64>,
packets_dropped: Arc<AtomicU64>,
}
impl RawUdpStreamer {
pub fn new() -> Self {
Self {
socket: None,
target_addr: String::new(),
packets_sent: Arc::new(AtomicU64::new(0)),
packets_dropped: Arc::new(AtomicU64::new(0)),
}
}
fn build_header(pts_micros: u64, is_keyframe: bool, payload_len: u16, frag_idx: u16) -> [u8; HEADER_SIZE] {
let mut h = [0u8; HEADER_SIZE];
h[0..4].copy_from_slice(&PACKET_MAGIC.to_be_bytes());
h[4..12].copy_from_slice(&pts_micros.to_be_bytes());
h[12..14].copy_from_slice(&if is_keyframe { 0x0001u16 } else { 0x0000u16 }.to_be_bytes());
h[14..16].copy_from_slice(&payload_len.to_be_bytes());
h[16..18].copy_from_slice(&frag_idx.to_be_bytes());
h
}
}
impl NetworkStreamer for RawUdpStreamer {
fn backend_name(&self) -> &'static str {
"raw-udp"
}
async fn connect(&mut self, cfg: StreamConfig) -> Result<()> {
self.target_addr = cfg.target_addr.clone();
let socket = UdpSocket::bind("0.0.0.0:0")
.map_err(|e| StreamError::NetworkError(format!("Bind UDP: {}", e)))?;
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let fd = socket.as_raw_fd();
let buf_size: libc::c_int = 128 * 1024;
unsafe {
libc::setsockopt(
fd,
libc::SOL_SOCKET,
libc::SO_SNDBUF,
&buf_size as *const _ as *const libc::c_void,
std::mem::size_of::<libc::c_int>() as libc::socklen_t,
);
}
}
socket.connect(&self.target_addr)
.map_err(|e| StreamError::NetworkError(
format!("Connect UDP vers {}: {}", self.target_addr, e)
))?;
socket.set_nonblocking(true)
.map_err(|e| StreamError::NetworkError(format!("NonBlocking: {}", e)))?;
self.socket = Some(socket);
tracing::info!("[raw-udp] Connecté vers {} (MTU payload: {}B)", self.target_addr, UDP_PAYLOAD_MAX);
Ok(())
}
async fn send_packet(&mut self, packet: EncodedPacket) -> Result<()> {
let socket = match &self.socket {
Some(s) => s,
None => return Ok(()), };
let data = &packet.data;
let total = data.len();
let mut frag_idx: u16 = 0;
let mut offset = 0;
while offset < total {
let chunk_end = (offset + UDP_PAYLOAD_MAX).min(total);
let chunk = &data[offset..chunk_end];
let payload_len = chunk.len() as u16;
let header = Self::build_header(
packet.pts_micros,
packet.is_keyframe && frag_idx == 0, payload_len,
frag_idx,
);
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
let fd = socket.as_raw_fd();
let iov = [
libc::iovec {
iov_base: header.as_ptr() as *mut libc::c_void,
iov_len: header.len(),
},
libc::iovec {
iov_base: chunk.as_ptr() as *mut libc::c_void,
iov_len: chunk.len(),
},
];
let ret = unsafe {
libc::writev(fd, iov.as_ptr(), 2)
};
if ret >= 0 {
self.packets_sent.fetch_add(1, Ordering::Relaxed);
} else {
let err = std::io::Error::last_os_error();
if err.kind() == std::io::ErrorKind::WouldBlock {
self.packets_dropped.fetch_add(1, Ordering::Relaxed);
tracing::warn!("[raw-udp] Buffer noyau plein, fragment {} droppé. Abandon des fragments restants de cette frame.", frag_idx);
break;
} else {
return Err(StreamError::NetworkError(format!("writev: {}", err)));
}
}
}
offset = chunk_end;
frag_idx = frag_idx.saturating_add(1);
}
Ok(())
}
fn stats(&self) -> StreamerStats {
StreamerStats {
packets_sent: self.packets_sent.load(Ordering::Relaxed),
packets_dropped: self.packets_dropped.load(Ordering::Relaxed),
estimated_rtt_ms: None, }
}
async fn disconnect(&mut self) -> Result<()> {
self.socket = None;
tracing::info!("[raw-udp] Déconnecté");
Ok(())
}
}