use std::sync::Arc;
use async_trait::async_trait;
use bytes::Bytes;
use chrono::Utc;
use std::sync::Mutex;
use tokio::sync::{mpsc, Notify};
use tokio::task::JoinHandle;
use rvoip_core::capability::CodecInfo;
use rvoip_core::connection::Direction;
use rvoip_core::error::Result as RvoipResult;
use rvoip_core::ids::StreamId;
use rvoip_core::stream::{MediaFrame, MediaStream, QualitySnapshot, StreamKind};
use crate::api::unified::UnifiedCoordinator;
use crate::SessionId;
use rvoip_media_core::codec::audio::common::AudioCodec;
use rvoip_media_core::codec::audio::g711::G711Codec;
const G711_SAMPLE_RATE: u32 = 8_000;
const G711_FRAME_SAMPLES: usize = 160;
const FRAME_CHANNEL_CAP: usize = 64;
struct SipMediaStreamInner {
stream_id: StreamId,
codec: CodecInfo,
direction: Direction,
frames_in_rx: Mutex<Option<mpsc::Receiver<MediaFrame>>>,
frames_out_tx: mpsc::Sender<MediaFrame>,
pumps: Mutex<Vec<JoinHandle<()>>>,
cancel: Arc<Notify>,
}
pub struct SipMediaStream {
inner: Arc<SipMediaStreamInner>,
}
impl SipMediaStream {
pub async fn new(
coordinator: Arc<UnifiedCoordinator>,
session_id: SessionId,
direction: Direction,
) -> crate::errors::Result<Arc<Self>> {
let stream_id = StreamId::new();
let codec = CodecInfo {
name: "g.711-mu".to_string(),
clock_rate_hz: G711_SAMPLE_RATE,
channels: 1,
fmtp: None,
};
let (frames_in_tx, frames_in_rx) = mpsc::channel::<MediaFrame>(FRAME_CHANNEL_CAP);
let (frames_out_tx, mut frames_out_rx) = mpsc::channel::<MediaFrame>(FRAME_CHANNEL_CAP);
let cancel = Arc::new(Notify::new());
let mut subscriber = coordinator
.subscribe_to_audio(&session_id)
.await
.map_err(|e| crate::errors::SessionError::Other(format!("subscribe_to_audio: {e}")))?;
let stream_id_in = stream_id.clone();
let cancel_in = Arc::clone(&cancel);
let frames_in_tx_pump = frames_in_tx.clone();
let inbound_handle = tokio::spawn(async move {
let mut encoder = match G711Codec::mu_law(G711_SAMPLE_RATE, 1) {
Ok(c) => c,
Err(e) => {
tracing::warn!(target: "rvoip_sip", error = %e, "SipMediaStream: G.711 mu_law encoder init failed");
return;
}
};
loop {
tokio::select! {
_ = cancel_in.notified() => break,
frame = subscriber.receiver.recv() => {
let Some(audio_frame) = frame else { break; };
let encoded = match encoder.encode(&audio_frame) {
Ok(bytes) => bytes,
Err(e) => {
tracing::trace!(target: "rvoip_sip", error = %e, "SipMediaStream: G.711 encode failed");
continue;
}
};
let media_frame = MediaFrame {
stream_id: stream_id_in.clone(),
kind: StreamKind::Audio,
payload: Bytes::from(encoded),
timestamp_rtp: audio_frame.timestamp,
captured_at: Utc::now(),
payload_type: Some(0),
};
if frames_in_tx_pump.send(media_frame).await.is_err() {
break;
}
}
}
}
});
let coordinator_out = Arc::clone(&coordinator);
let session_id_out = session_id.clone();
let cancel_out = Arc::clone(&cancel);
let outbound_handle = tokio::spawn(async move {
let mut decoder = match G711Codec::mu_law(G711_SAMPLE_RATE, 1) {
Ok(c) => c,
Err(e) => {
tracing::warn!(target: "rvoip_sip", error = %e, "SipMediaStream: G.711 mu_law decoder init failed");
return;
}
};
let mut next_timestamp: u32 = 0;
loop {
tokio::select! {
_ = cancel_out.notified() => break,
frame = frames_out_rx.recv() => {
let Some(media_frame) = frame else { break; };
const TELEPHONE_EVENT_PT: u8 = 101;
if media_frame.payload_type == Some(TELEPHONE_EVENT_PT) {
if let Some(digit) = parse_rfc4733_digit(&media_frame.payload) {
if let Err(e) =
coordinator_out.send_dtmf(&session_id_out, digit).await
{
tracing::trace!(target: "rvoip_sip", error = %e, "SipMediaStream: send_dtmf failed");
}
}
continue;
}
let mut audio_frame = match decoder.decode(&media_frame.payload) {
Ok(f) => f,
Err(e) => {
tracing::trace!(target: "rvoip_sip", error = %e, bytes = media_frame.payload.len(), "SipMediaStream: G.711 decode failed; dropping frame");
continue;
}
};
audio_frame.timestamp = if media_frame.timestamp_rtp != 0 {
media_frame.timestamp_rtp
} else {
next_timestamp
};
next_timestamp = audio_frame
.timestamp
.wrapping_add(G711_FRAME_SAMPLES as u32);
if let Err(e) = coordinator_out.send_audio(&session_id_out, audio_frame).await {
tracing::trace!(target: "rvoip_sip", error = %e, "SipMediaStream: send_audio failed");
}
}
}
}
});
drop(frames_in_tx);
Ok(Arc::new(Self {
inner: Arc::new(SipMediaStreamInner {
stream_id,
codec,
direction,
frames_in_rx: Mutex::new(Some(frames_in_rx)),
frames_out_tx,
pumps: Mutex::new(vec![inbound_handle, outbound_handle]),
cancel,
}),
}))
}
}
#[async_trait]
impl MediaStream for SipMediaStream {
fn id(&self) -> StreamId {
self.inner.stream_id.clone()
}
fn kind(&self) -> StreamKind {
StreamKind::Audio
}
fn codec(&self) -> CodecInfo {
self.inner.codec.clone()
}
fn direction(&self) -> Direction {
self.inner.direction
}
fn frames_in(&self) -> mpsc::Receiver<MediaFrame> {
self.inner
.frames_in_rx
.lock()
.ok()
.and_then(|mut g| g.take())
.unwrap_or_else(|| mpsc::channel(1).1)
}
fn frames_out(&self) -> mpsc::Sender<MediaFrame> {
self.inner.frames_out_tx.clone()
}
fn quality_snapshot(&self) -> QualitySnapshot {
QualitySnapshot::default()
}
async fn close(self: Arc<Self>) -> RvoipResult<()> {
self.inner.cancel.notify_waiters();
if let Ok(mut guard) = self.inner.pumps.lock() {
for handle in guard.drain(..) {
handle.abort();
}
}
Ok(())
}
}
fn parse_rfc4733_digit(payload: &[u8]) -> Option<char> {
if payload.len() < 4 {
return None;
}
let event = payload[0];
let duration = u16::from_be_bytes([payload[2], payload[3]]);
if duration != 0 {
return None;
}
match event {
0..=9 => Some((b'0' + event) as char),
10 => Some('*'),
11 => Some('#'),
12 => Some('A'),
13 => Some('B'),
14 => Some('C'),
15 => Some('D'),
_ => None,
}
}
#[cfg(test)]
mod rfc4733_tests {
use super::parse_rfc4733_digit;
#[test]
fn start_packet_returns_digit() {
let packet = [0x05, 0x0A, 0x00, 0x00];
assert_eq!(parse_rfc4733_digit(&packet), Some('5'));
}
#[test]
fn duration_nonzero_returns_none_to_avoid_duplicates() {
let packet = [0x05, 0x0A, 0x00, 0xA0];
assert_eq!(parse_rfc4733_digit(&packet), None);
}
#[test]
fn star_hash_letters_map_correctly() {
assert_eq!(parse_rfc4733_digit(&[10, 0, 0, 0]), Some('*'));
assert_eq!(parse_rfc4733_digit(&[11, 0, 0, 0]), Some('#'));
assert_eq!(parse_rfc4733_digit(&[12, 0, 0, 0]), Some('A'));
assert_eq!(parse_rfc4733_digit(&[15, 0, 0, 0]), Some('D'));
}
#[test]
fn unknown_events_return_none() {
assert_eq!(parse_rfc4733_digit(&[99, 0, 0, 0]), None);
assert_eq!(parse_rfc4733_digit(&[0xFF, 0, 0, 0]), None);
}
#[test]
fn short_payload_returns_none() {
assert_eq!(parse_rfc4733_digit(&[5, 0, 0]), None);
assert_eq!(parse_rfc4733_digit(&[]), None);
}
}