use crate::error::NetResult;
use bytes::Bytes;
use parking_lot::RwLock;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
pub mod ancillary;
pub mod audio;
pub mod ptp;
pub mod rtp;
pub mod sdp;
pub mod timing;
pub mod video;
pub use ancillary::{AncillaryConfig, AncillaryData, AncillaryPacket};
pub use audio::{AudioConfig, AudioFormat, AudioPacket, AudioSampleRate};
pub use ptp::{PtpClock, PtpTimestamp};
pub use rtp::{RtpHeader, RtpPacket, RtpSession};
pub use sdp::{MediaType, SdpSession};
pub use timing::{FrameRate, ScanType, TimingMode, TransmissionMode};
pub use video::{PixelFormat, VideoConfig, VideoPacket};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamType {
Video,
Audio,
Ancillary,
}
#[derive(Debug, Clone)]
pub struct St2110Config {
pub stream_type: StreamType,
pub destination: IpAddr,
pub port: u16,
pub source_ip: IpAddr,
pub ssrc: u32,
pub timing_mode: TimingMode,
pub transmission_mode: TransmissionMode,
pub enable_ptp: bool,
pub ptp_domain: u8,
}
impl Default for St2110Config {
fn default() -> Self {
Self {
stream_type: StreamType::Video,
destination: IpAddr::V4(Ipv4Addr::new(239, 0, 0, 1)),
port: 5004,
source_ip: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
ssrc: rand::random(),
timing_mode: TimingMode::Wide,
transmission_mode: TransmissionMode::Gapped,
enable_ptp: true,
ptp_domain: ptp::PTP_DOMAIN_DEFAULT,
}
}
}
#[derive(Debug)]
pub struct St2110Source {
config: St2110Config,
rtp_session: Arc<RwLock<RtpSession>>,
ptp_clock: Option<Arc<RwLock<PtpClock>>>,
video_config: Option<VideoConfig>,
audio_config: Option<AudioConfig>,
ancillary_config: Option<AncillaryConfig>,
}
impl St2110Source {
#[must_use]
pub fn new_video(video_config: VideoConfig) -> Self {
let config = St2110Config {
stream_type: StreamType::Video,
..Default::default()
};
let clock_rate = 90000; let rtp_session = Arc::new(RwLock::new(RtpSession::new(config.ssrc, clock_rate)));
let ptp_clock = if config.enable_ptp {
let clock_id = ptp::ClockIdentity::new([0; 8]); let port_id = ptp::PortIdentity::new(clock_id, 1);
Some(Arc::new(RwLock::new(PtpClock::new(
port_id,
config.ptp_domain,
))))
} else {
None
};
Self {
config,
rtp_session,
ptp_clock,
video_config: Some(video_config),
audio_config: None,
ancillary_config: None,
}
}
#[must_use]
pub fn new_audio(audio_config: AudioConfig) -> Self {
let config = St2110Config {
stream_type: StreamType::Audio,
..Default::default()
};
let clock_rate = audio_config.sample_rate as u32;
let rtp_session = Arc::new(RwLock::new(RtpSession::new(config.ssrc, clock_rate)));
let ptp_clock = if config.enable_ptp {
let clock_id = ptp::ClockIdentity::new([0; 8]);
let port_id = ptp::PortIdentity::new(clock_id, 1);
Some(Arc::new(RwLock::new(PtpClock::new(
port_id,
config.ptp_domain,
))))
} else {
None
};
Self {
config,
rtp_session,
ptp_clock,
video_config: None,
audio_config: Some(audio_config),
ancillary_config: None,
}
}
#[must_use]
pub fn new_ancillary(ancillary_config: AncillaryConfig) -> Self {
let config = St2110Config {
stream_type: StreamType::Ancillary,
..Default::default()
};
let clock_rate = 90000; let rtp_session = Arc::new(RwLock::new(RtpSession::new(config.ssrc, clock_rate)));
let ptp_clock = if config.enable_ptp {
let clock_id = ptp::ClockIdentity::new([0; 8]);
let port_id = ptp::PortIdentity::new(clock_id, 1);
Some(Arc::new(RwLock::new(PtpClock::new(
port_id,
config.ptp_domain,
))))
} else {
None
};
Self {
config,
rtp_session,
ptp_clock,
video_config: None,
audio_config: None,
ancillary_config: Some(ancillary_config),
}
}
#[must_use]
pub const fn config(&self) -> &St2110Config {
&self.config
}
#[must_use]
pub const fn video_config(&self) -> Option<&VideoConfig> {
self.video_config.as_ref()
}
#[must_use]
pub const fn audio_config(&self) -> Option<&AudioConfig> {
self.audio_config.as_ref()
}
#[must_use]
pub const fn ancillary_config(&self) -> Option<&AncillaryConfig> {
self.ancillary_config.as_ref()
}
#[must_use]
pub fn rtp_session(&self) -> Arc<RwLock<RtpSession>> {
Arc::clone(&self.rtp_session)
}
#[must_use]
pub fn ptp_clock(&self) -> Option<Arc<RwLock<PtpClock>>> {
self.ptp_clock.as_ref().map(Arc::clone)
}
#[must_use]
pub fn generate_sdp(&self) -> String {
let mut session = SdpSession::new("SMPTE ST 2110 Stream", self.config.source_ip);
match self.config.stream_type {
StreamType::Video => {
if let Some(video_cfg) = &self.video_config {
session.add_video_media(self.config.destination, self.config.port, video_cfg);
}
}
StreamType::Audio => {
if let Some(audio_cfg) = &self.audio_config {
session.add_audio_media(self.config.destination, self.config.port, audio_cfg);
}
}
StreamType::Ancillary => {
if let Some(anc_cfg) = &self.ancillary_config {
session.add_ancillary_media(self.config.destination, self.config.port, anc_cfg);
}
}
}
session.to_string()
}
#[must_use]
pub fn destination(&self) -> SocketAddr {
SocketAddr::new(self.config.destination, self.config.port)
}
}
#[derive(Debug)]
pub struct St2110Sink {
config: St2110Config,
rtp_session: Arc<RwLock<RtpSession>>,
ptp_clock: Option<Arc<RwLock<PtpClock>>>,
video_decoder: Option<video::VideoDecoder>,
audio_decoder: Option<audio::AudioDecoder>,
}
impl St2110Sink {
#[must_use]
pub fn new_video(video_config: VideoConfig) -> Self {
let config = St2110Config {
stream_type: StreamType::Video,
..Default::default()
};
let clock_rate = 90000;
let rtp_session = Arc::new(RwLock::new(RtpSession::new(config.ssrc, clock_rate)));
let ptp_clock = if config.enable_ptp {
let clock_id = ptp::ClockIdentity::new([0; 8]);
let port_id = ptp::PortIdentity::new(clock_id, 1);
Some(Arc::new(RwLock::new(PtpClock::new(
port_id,
config.ptp_domain,
))))
} else {
None
};
Self {
config,
rtp_session,
ptp_clock,
video_decoder: Some(video::VideoDecoder::new(video_config)),
audio_decoder: None,
}
}
#[must_use]
pub fn new_audio(audio_config: AudioConfig) -> Self {
let config = St2110Config {
stream_type: StreamType::Audio,
..Default::default()
};
let clock_rate = audio_config.sample_rate as u32;
let rtp_session = Arc::new(RwLock::new(RtpSession::new(config.ssrc, clock_rate)));
let ptp_clock = if config.enable_ptp {
let clock_id = ptp::ClockIdentity::new([0; 8]);
let port_id = ptp::PortIdentity::new(clock_id, 1);
Some(Arc::new(RwLock::new(PtpClock::new(
port_id,
config.ptp_domain,
))))
} else {
None
};
Self {
config,
rtp_session,
ptp_clock,
video_decoder: None,
audio_decoder: Some(audio::AudioDecoder::new(audio_config)),
}
}
pub fn process_packet(&mut self, data: Bytes) -> NetResult<()> {
let packet = RtpPacket::parse(data)?;
self.rtp_session.write().process_packet(&packet);
match self.config.stream_type {
StreamType::Video => {
if let Some(decoder) = &mut self.video_decoder {
decoder.process_rtp_packet(&packet)?;
}
}
StreamType::Audio => {
if let Some(decoder) = &mut self.audio_decoder {
decoder.process_rtp_packet(&packet)?;
}
}
StreamType::Ancillary => {
}
}
Ok(())
}
#[must_use]
pub const fn config(&self) -> &St2110Config {
&self.config
}
#[must_use]
pub fn is_ptp_synced(&self) -> bool {
self.ptp_clock
.as_ref()
.map_or(false, |clock| clock.read().is_synchronized())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_st2110_config_default() {
let config = St2110Config::default();
assert_eq!(config.stream_type, StreamType::Video);
assert_eq!(config.port, 5004);
assert_eq!(config.timing_mode, TimingMode::Wide);
}
#[test]
fn test_video_source_creation() {
let video_config = VideoConfig {
width: 1920,
height: 1080,
frame_rate: FrameRate::FPS_25,
pixel_format: PixelFormat::YCbCr422_10bit,
..Default::default()
};
let source = St2110Source::new_video(video_config);
assert_eq!(source.config().stream_type, StreamType::Video);
assert!(source.video_config().is_some());
}
#[test]
fn test_audio_source_creation() {
let audio_config = AudioConfig {
sample_rate: AudioSampleRate::Rate48kHz,
bit_depth: 24,
channels: 2,
..Default::default()
};
let source = St2110Source::new_audio(audio_config);
assert_eq!(source.config().stream_type, StreamType::Audio);
assert!(source.audio_config().is_some());
}
#[test]
fn test_sdp_generation() {
let video_config = VideoConfig {
width: 1920,
height: 1080,
frame_rate: FrameRate::FPS_25,
pixel_format: PixelFormat::YCbCr422_10bit,
..Default::default()
};
let source = St2110Source::new_video(video_config);
let sdp = source.generate_sdp();
assert!(sdp.contains("v=0"));
assert!(sdp.contains("m=video"));
}
}