use std::fs::File;
use std::io::BufReader;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::{Duration, Instant};
use rtc::media::Sample;
use rtc::media::io::ivf_reader::IVFReader;
use rtc::media::io::ogg_reader::OggReader;
use rtc::media_stream::MediaStreamTrack;
use rtc::peer_connection::configuration::media_engine::{MIME_TYPE_OPUS, MIME_TYPE_VP8};
use rtc::rtp_transceiver::PayloadType;
use rtc::rtp_transceiver::rtp_sender::{
RTCRtpCodec, RTCRtpCodingParameters, RTCRtpEncodingParameters, RtpCodecKind,
};
use webrtc::media_stream::Track;
use webrtc::media_stream::track_local::TrackLocal;
use webrtc::media_stream::track_local::static_sample::TrackLocalStaticSample;
use webrtc::media_stream::track_remote::{TrackRemote, TrackRemoteEvent};
use webrtc::peer_connection::{
MediaEngine, PeerConnection, PeerConnectionBuilder, PeerConnectionEventHandler,
RTCIceGatheringState, RTCPeerConnectionState,
};
use webrtc::runtime::channel;
mod common;
use common::{block_on, interval, runtime, timeout};
struct OffererHandler {
gather_complete_tx: webrtc::runtime::Sender<()>,
connected_tx: webrtc::runtime::Sender<()>,
}
#[async_trait::async_trait]
impl PeerConnectionEventHandler for OffererHandler {
async fn on_ice_gathering_state_change(&self, state: RTCIceGatheringState) {
if state == RTCIceGatheringState::Complete {
let _ = self.gather_complete_tx.try_send(());
}
}
async fn on_connection_state_change(&self, state: RTCPeerConnectionState) {
if state == RTCPeerConnectionState::Connected {
let _ = self.connected_tx.try_send(());
}
}
}
struct AnswererHandler {
gather_complete_tx: webrtc::runtime::Sender<()>,
connected_tx: webrtc::runtime::Sender<()>,
video_packets_rx_count: Arc<AtomicU32>,
audio_packets_rx_count: Arc<AtomicU32>,
runtime: Arc<dyn webrtc::runtime::Runtime>,
}
#[async_trait::async_trait]
impl PeerConnectionEventHandler for AnswererHandler {
async fn on_ice_gathering_state_change(&self, state: RTCIceGatheringState) {
if state == RTCIceGatheringState::Complete {
let _ = self.gather_complete_tx.try_send(());
}
}
async fn on_connection_state_change(&self, state: RTCPeerConnectionState) {
if state == RTCPeerConnectionState::Connected {
let _ = self.connected_tx.try_send(());
}
}
async fn on_track(&self, track: Arc<dyn TrackRemote>) {
let video_count = self.video_packets_rx_count.clone();
let audio_count = self.audio_packets_rx_count.clone();
let kind = track.kind().await;
self.runtime.spawn(Box::pin(async move {
while let Some(evt) = track.poll().await {
if let TrackRemoteEvent::OnRtpPacket(packet) = evt {
match kind {
RtpCodecKind::Video => {
video_count.fetch_add(1, Ordering::SeqCst);
}
RtpCodecKind::Audio => {
audio_count.fetch_add(1, Ordering::SeqCst);
}
_ => {}
}
if packet.payload.is_empty() {
break;
}
}
}
}));
}
}
async fn stream_video(
video_file_name: &str,
video_track: Arc<TrackLocalStaticSample>,
payload_type: PayloadType,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let file = File::open(video_file_name)?;
let reader = BufReader::new(file);
let (mut ivf, header) = IVFReader::new(reader)?;
let ssrc = *video_track.ssrcs().await.first().ok_or("no video ssrc")?;
let sleep_time = Duration::from_millis(
((1000 * header.timebase_numerator) / header.timebase_denominator) as u64,
);
let mut ticker = interval(sleep_time);
for _ in 0..50 {
let frame = match ivf.parse_next_frame() {
Ok((frame, _)) => frame,
Err(_) => break,
};
video_track
.sample_writer(ssrc, payload_type)
.write_sample(&Sample {
data: frame.freeze(),
duration: Duration::from_secs(1),
..Default::default()
})
.await?;
let _ = ticker.tick().await;
}
Ok(())
}
async fn stream_audio(
audio_file_name: &str,
audio_track: Arc<TrackLocalStaticSample>,
payload_type: PayloadType,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let file = File::open(audio_file_name)?;
let reader = BufReader::new(file);
let (mut ogg, _) = OggReader::new(reader, true)?;
let ssrc = *audio_track.ssrcs().await.first().ok_or("no audio ssrc")?;
let mut ticker = interval(Duration::from_millis(20));
let mut last_granule: u64 = 0;
for _ in 0..50 {
let (page_data, page_header) = match ogg.parse_next_page() {
Ok(tup) => tup,
Err(_) => break,
};
let sample_count = page_header.granule_position.saturating_sub(last_granule);
last_granule = page_header.granule_position;
let sample_duration = Duration::from_millis(sample_count * 1000 / 48000);
audio_track
.sample_writer(ssrc, payload_type)
.write_sample(&Sample {
data: page_data.freeze(),
duration: sample_duration,
..Default::default()
})
.await?;
let _ = ticker.tick().await;
}
Ok(())
}
#[test]
fn test_play_from_disk_streaming() {
block_on(async {
let runtime = runtime();
let video_file = "rtc/examples/test-data/output_vp8.ivf";
let audio_file = "rtc/examples/test-data/output.ogg";
let mut offerer_media = MediaEngine::default();
offerer_media.register_default_codecs().unwrap();
let mut answerer_media = MediaEngine::default();
answerer_media.register_default_codecs().unwrap();
let video_ssrc = rand::random::<u32>();
let video_track = Arc::new(
TrackLocalStaticSample::new(MediaStreamTrack::new(
"video-stream".to_owned(),
"video-track".to_owned(),
"video-label".to_owned(),
RtpCodecKind::Video,
vec![RTCRtpEncodingParameters {
rtp_coding_parameters: RTCRtpCodingParameters {
ssrc: Some(video_ssrc),
..Default::default()
},
codec: RTCRtpCodec {
mime_type: MIME_TYPE_VP8.to_owned(),
clock_rate: 90000,
channels: 0,
sdp_fmtp_line: String::new(),
rtcp_feedback: vec![],
},
..Default::default()
}],
))
.unwrap(),
);
let audio_ssrc = rand::random::<u32>();
let audio_track = Arc::new(
TrackLocalStaticSample::new(MediaStreamTrack::new(
"audio-stream".to_owned(),
"audio-track".to_owned(),
"audio-label".to_owned(),
RtpCodecKind::Audio,
vec![RTCRtpEncodingParameters {
rtp_coding_parameters: RTCRtpCodingParameters {
ssrc: Some(audio_ssrc),
..Default::default()
},
codec: RTCRtpCodec {
mime_type: MIME_TYPE_OPUS.to_owned(),
clock_rate: 48000,
channels: 2,
sdp_fmtp_line: String::new(),
rtcp_feedback: vec![],
},
..Default::default()
}],
))
.unwrap(),
);
let (off_gather_tx, mut off_gather_rx) = channel(1);
let (off_conn_tx, mut off_conn_rx) = channel(1);
let offerer = PeerConnectionBuilder::new()
.with_media_engine(offerer_media)
.with_handler(Arc::new(OffererHandler {
gather_complete_tx: off_gather_tx,
connected_tx: off_conn_tx,
}))
.with_runtime(runtime.clone())
.with_udp_addrs(vec!["127.0.0.1:0".to_owned()])
.build()
.await
.unwrap();
let offerer = Arc::new(offerer);
let video_sender = offerer
.add_track(Arc::clone(&video_track) as Arc<dyn TrackLocal>)
.await
.unwrap();
let audio_sender = offerer
.add_track(Arc::clone(&audio_track) as Arc<dyn TrackLocal>)
.await
.unwrap();
let video_rx_count = Arc::new(AtomicU32::new(0));
let audio_rx_count = Arc::new(AtomicU32::new(0));
let (ans_gather_tx, mut ans_gather_rx) = channel(1);
let (ans_conn_tx, mut ans_conn_rx) = channel(1);
let answerer = PeerConnectionBuilder::new()
.with_media_engine(answerer_media)
.with_handler(Arc::new(AnswererHandler {
gather_complete_tx: ans_gather_tx,
connected_tx: ans_conn_tx,
video_packets_rx_count: video_rx_count.clone(),
audio_packets_rx_count: audio_rx_count.clone(),
runtime: runtime.clone(),
}))
.with_runtime(runtime.clone())
.with_udp_addrs(vec!["127.0.0.1:0".to_owned()])
.build()
.await
.unwrap();
let answerer = Arc::new(answerer);
let offer = offerer.create_offer(None).await.unwrap();
offerer.set_local_description(offer).await.unwrap();
timeout(Duration::from_secs(5), off_gather_rx.recv())
.await
.unwrap();
let offer_sdp = offerer.local_description().await.unwrap();
answerer.set_remote_description(offer_sdp).await.unwrap();
let answer = answerer.create_answer(None).await.unwrap();
answerer.set_local_description(answer).await.unwrap();
timeout(Duration::from_secs(5), ans_gather_rx.recv())
.await
.unwrap();
let answer_sdp = answerer.local_description().await.unwrap();
offerer.set_remote_description(answer_sdp).await.unwrap();
timeout(Duration::from_secs(5), off_conn_rx.recv())
.await
.unwrap();
timeout(Duration::from_secs(5), ans_conn_rx.recv())
.await
.unwrap();
let video_pt = video_sender
.get_parameters()
.await
.unwrap()
.rtp_parameters
.codecs
.first()
.unwrap()
.payload_type;
let audio_pt = audio_sender
.get_parameters()
.await
.unwrap()
.rtp_parameters
.codecs
.first()
.unwrap()
.payload_type;
let video_track_clone = video_track.clone();
let audio_track_clone = audio_track.clone();
runtime.spawn(Box::pin(async move {
let _ = stream_video(video_file, video_track_clone, video_pt).await;
}));
runtime.spawn(Box::pin(async move {
let _ = stream_audio(audio_file, audio_track_clone, audio_pt).await;
}));
let mut check_interval = interval(Duration::from_millis(100));
let start_time = Instant::now();
loop {
let v_cnt = video_rx_count.load(Ordering::SeqCst);
let a_cnt = audio_rx_count.load(Ordering::SeqCst);
if v_cnt >= 30 && a_cnt >= 30 {
break;
}
if start_time.elapsed() > Duration::from_secs(10) {
panic!("Timeout: only received {v_cnt} video and {a_cnt} audio packets");
}
let _ = check_interval.tick().await;
}
offerer.close().await.unwrap();
answerer.close().await.unwrap();
});
}