// Rust RTSP Server // // Copyright (C) 2024 Nirbheek Chauhan // // This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. // If a copy of the MPL was not distributed with this file, You can obtain one at // . // // SPDX-License-Identifier: MPL-2.0 use super::imp::RtspError; use super::imp::RtspProtocol; use super::imp::CAT; use sdp_types::Attribute; use sdp_types::Connection; use std::collections::BTreeSet; use std::net::IpAddr; use url::Url; macro_rules! init_payload_info { ($($t:expr),*,) => { [ $( { let (pt, media, encoding_name, clock_rate, encoding_params) = $t; PayloadInfo { pt, media, encoding_name, clock_rate, encoding_params, } } ),* ] }; } struct PayloadInfo<'a> { pt: u8, media: &'a str, encoding_name: &'a str, clock_rate: u32, encoding_params: Option<&'a str>, } // Copied from gst-plugins-base/gst-libs/gst/rtp/gstrtppayloads.h const STATIC_PAYLOAD_INFO: &[PayloadInfo] = &init_payload_info!( // static audio (0, "audio", "PCMU", 8000, Some("1")), // (1, "audio", "reserved", 0, None), // (2, "audio", "reserved", 0, None), (3, "audio", "GSM", 8000, Some("1")), (4, "audio", "G723", 8000, Some("1")), (5, "audio", "DVI4", 8000, Some("1")), (6, "audio", "DVI4", 16000, Some("1")), (7, "audio", "LPC", 8000, Some("1")), (8, "audio", "PCMA", 8000, Some("1")), (9, "audio", "G722", 8000, Some("1")), (10, "audio", "L16", 44100, Some("2")), (11, "audio", "L16", 44100, Some("1")), (12, "audio", "QCELP", 8000, Some("1")), (13, "audio", "CN", 8000, Some("1")), (14, "audio", "MPA", 90000, None), (15, "audio", "G728", 8000, Some("1")), (16, "audio", "DVI4", 11025, Some("1")), (17, "audio", "DVI4", 22050, Some("1")), (18, "audio", "G729", 8000, Some("1")), // (19, "audio", "reserved", 0, None), // (20, "audio", "unassigned", 0, None), // (21, "audio", "unassigned", 0, None), // (22, "audio", "unassigned", 0, None), // (23, "audio", "unassigned", 0, None), // video and video/audio // (24, "video", "unassigned", 0, None), (25, "video", "CelB", 90000, None), (26, "video", "JPEG", 90000, None), // (27, "video", "unassigned", 0, None), (28, "video", "nv", 90000, None), // (29, "video", "unassigned", 0, None), // (30, "video", "unassigned", 0, None), (31, "video", "H261", 90000, None), (32, "video", "MPV", 90000, None), (33, "video", "MP2T", 90000, None), (34, "video", "H263", 90000, None), // (35-71, "unassigned", 0, 0, None), // (72-76, "reserved", 0, 0, None), // (77-95, "unassigned", 0, 0, None), // (96-127, "dynamic", 0, 0, None), ); // Known media types with dynamic payloads, can only be matched via name const DYNAMIC_PAYLOAD_INFO: &[PayloadInfo] = &init_payload_info!( (0, "application", "parityfec", 0, None), // [RFC3009] (0, "application", "rtx", 0, None), // [RFC4588] (0, "audio", "AMR", 8000, None), // [RFC4867][RFC3267] (0, "audio", "AMR-WB", 16000, None), // [RFC4867][RFC3267] (0, "audio", "DAT12", 0, None), // [RFC3190] (0, "audio", "dsr-es201108", 0, None), // [RFC3557] (0, "audio", "EVRC", 8000, Some("1")), // [RFC4788] (0, "audio", "EVRC0", 8000, Some("1")), // [RFC4788] (0, "audio", "EVRC1", 8000, Some("1")), // [RFC4788] (0, "audio", "EVRCB", 8000, Some("1")), // [RFC4788] (0, "audio", "EVRCB0", 8000, Some("1")), // [RFC4788] (0, "audio", "EVRCB1", 8000, Some("1")), // [RFC4788] (0, "audio", "G7221", 16000, Some("1")), // [RFC3047] (0, "audio", "G726-16", 8000, Some("1")), // [RFC3551][RFC4856] (0, "audio", "G726-24", 8000, Some("1")), // [RFC3551][RFC4856] (0, "audio", "G726-32", 8000, Some("1")), // [RFC3551][RFC4856] (0, "audio", "G726-40", 8000, Some("1")), // [RFC3551][RFC4856] (0, "audio", "G729D", 8000, Some("1")), // [RFC3551][RFC4856] (0, "audio", "G729E", 8000, Some("1")), // [RFC3551][RFC4856] (0, "audio", "GSM-EFR", 8000, Some("1")), // [RFC3551][RFC4856] (0, "audio", "L8", 0, None), // [RFC3551][RFC4856] (0, "audio", "RED", 0, None), // [RFC2198][RFC3555] (0, "audio", "rtx", 0, None), // [RFC4588] (0, "audio", "VDVI", 0, Some("1")), // [RFC3551][RFC4856] (0, "audio", "L20", 0, None), // [RFC3190] (0, "audio", "L24", 0, None), // [RFC3190] (0, "audio", "MP4A-LATM", 0, None), // [RFC3016] (0, "audio", "mpa-robust", 90000, None), // [RFC3119] (0, "audio", "parityfec", 0, None), // [RFC3009] (0, "audio", "SMV", 8000, Some("1")), // [RFC3558] (0, "audio", "SMV0", 8000, Some("1")), // [RFC3558] (0, "audio", "t140c", 0, None), // [RFC4351] (0, "audio", "t38", 0, None), // [RFC4612] (0, "audio", "telephone-event", 0, None), // [RFC4733] (0, "audio", "tone", 0, None), // [RFC4733] (0, "audio", "DVI4", 0, None), // [RFC4856] (0, "audio", "G722", 0, None), // [RFC4856] (0, "audio", "G723", 0, None), // [RFC4856] (0, "audio", "G728", 0, None), // [RFC4856] (0, "audio", "G729", 0, None), // [RFC4856] (0, "audio", "GSM", 0, None), // [RFC4856] (0, "audio", "L16", 0, None), // [RFC4856] (0, "audio", "LPC", 0, None), // [RFC4856] (0, "audio", "PCMA", 0, None), // [RFC4856] (0, "audio", "PCMU", 0, None), // [RFC4856] (0, "text", "parityfec", 0, None), // [RFC3009] (0, "text", "red", 1000, None), // [RFC4102] (0, "text", "rtx", 0, None), // [RFC4588] (0, "text", "t140", 1000, None), // [RFC4103] (0, "video", "BMPEG", 90000, None), // [RFC2343][RFC3555] (0, "video", "BT656", 90000, None), // [RFC2431][RFC3555] (0, "video", "DV", 90000, None), // [RFC3189] (0, "video", "H263-1998", 90000, None), // [RFC2429][RFC3555] (0, "video", "H263-2000", 90000, None), // [RFC2429][RFC3555] (0, "video", "MP1S", 90000, None), // [RFC2250][RFC3555] (0, "video", "MP2P", 90000, None), // [RFC2250][RFC3555] (0, "video", "MP4V-ES", 90000, None), // [RFC3016] (0, "video", "parityfec", 0, None), // [RFC3009] (0, "video", "pointer", 90000, None), // [RFC2862] (0, "video", "raw", 90000, None), // [RFC4175] (0, "video", "rtx", 0, None), // [RFC4588] (0, "video", "SMPTE292M", 0, None), // [RFC3497] (0, "video", "vc1", 90000, None), // [RFC4425] // not in http://www.iana.org/assignments/rtp-parameters (0, "audio", "AC3", 0, None), (0, "audio", "ILBC", 8000, None), (0, "audio", "MPEG4-GENERIC", 0, None), (0, "audio", "SPEEX", 0, None), (0, "audio", "OPUS", 48000, None), (0, "application", "MPEG4-GENERIC", 0, None), (0, "video", "H264", 90000, None), (0, "video", "H265", 90000, None), (0, "video", "MPEG4-GENERIC", 90000, None), (0, "video", "THEORA", 0, None), (0, "video", "VORBIS", 0, None), (0, "video", "X-SV3V-ES", 90000, None), (0, "video", "X-SORENSON-VIDEO", 90000, None), (0, "video", "VP8", 90000, None), (0, "video", "VP9", 90000, None), ); // https://datatracker.ietf.org/doc/html/rfc2326#appendix-C.1.1 pub fn parse_control_path(path: &str, base: &Url) -> Option { match Url::parse(path) { Ok(v) => Some(v), Err(url::ParseError::RelativeUrlWithoutBase) => { if path == "*" { Some(base.clone()) } else { base.join(path).ok() } } Err(_) => None, } } fn parse_rtpmap( rtpmap: &str, pt: u8, media: &str, s: &mut gst::structure::Structure, ) -> Result<(), RtspError> { let Some((_pt, rtpmap)) = rtpmap.split_once(' ') else { return Err(RtspError::Fatal(format!( "Could not parse rtpmap: {rtpmap}" ))); }; let mut iter = rtpmap.split('/'); let Some(encoding_name) = iter.next() else { return Err(RtspError::Fatal(format!( "Could not parse encoding-name from rtpmap: {rtpmap}" ))); }; let encoding_name = encoding_name.to_ascii_uppercase(); s.set("encoding-name", &encoding_name); let Some(v) = iter.next() else { if pt >= 96 { return guess_rtpmap_from_pt(pt, media, s).map_err(|err| { RtspError::Fatal(format!( "Could not get clock-rate from rtpmap {rtpmap}: {}", err )) }); } else { return guess_rtpmap_from_encoding_name(&encoding_name, media, s).map_err(|err| { RtspError::Fatal(format!( "Could not get clock-rate from rtpmap {rtpmap}: {}", err )) }); } }; let Ok(clock_rate) = v.parse::() else { return Err(RtspError::Fatal(format!( "Could not parse clock-rate from rtpmap: {rtpmap}" ))); }; s.set("clock-rate", clock_rate); if let Some(v) = iter.next() { s.set("encoding-params", v); } debug_assert!(iter.next().is_none()); Ok(()) } fn guess_rtpmap_from_encoding_name( encoding_name: &str, media: &str, s: &mut gst::structure::Structure, ) -> Result<(), RtspError> { for info in STATIC_PAYLOAD_INFO .iter() .chain(DYNAMIC_PAYLOAD_INFO.iter()) { if media == info.media && encoding_name == info.encoding_name { s.set("encoding-name", info.encoding_name); if info.clock_rate > 0 { s.set("clock-rate", info.clock_rate); } if let Some(v) = info.encoding_params { s.set("encoding-params", v); }; return Ok(()); } } Err(RtspError::Fatal(format!( "Cannot guess rtpmap: unknown encoding name {encoding_name}" ))) } fn guess_rtpmap_from_pt( pt: u8, media: &str, s: &mut gst::structure::Structure, ) -> Result<(), RtspError> { if pt >= 96 { return Err(RtspError::Fatal(format!( "Unknown dynamic payload type {pt}", ))); } for info in STATIC_PAYLOAD_INFO { if pt == info.pt && media == info.media { s.set("encoding-name", info.encoding_name); if info.clock_rate > 0 { s.set("clock-rate", info.clock_rate); } if let Some(v) = info.encoding_params { s.set("encoding-params", v); }; return Ok(()); } } Err(RtspError::Fatal(format!( "Cannot guess rtpmap: unknown static payload type {pt}" ))) } fn parse_fmtp(fmtp: &str, s: &mut gst::structure::Structure) { // Non-compliant RTSP servers will incorrectly set these here, ignore them let ignore_fields = [ "media", "payload", "clock-rate", "encoding-name", "encoding-params", ]; let encoding_name = s.get::("encoding-name").unwrap(); let Some((_pt, fmtp)) = fmtp.split_once(' ') else { gst::warning!(CAT, "Could not parse fmtp: {fmtp}"); return; }; let iter = fmtp.split(';').map_while(|x| x.split_once('=')); for (k, v) in iter { let k = k.trim().to_ascii_lowercase(); if ignore_fields.contains(&k.as_str()) { continue; } if encoding_name == "H264" && k == "profile-level-id" { let profile_idc = u8::from_str_radix(&v[0..2], 16); let csf_idc = u8::from_str_radix(&v[2..4], 16); let level_idc = u8::from_str_radix(&v[4..6], 16); if let (Ok(p), Ok(c), Ok(l)) = (profile_idc, csf_idc, level_idc) { let sps = &[p, c, l]; let profile = gst_pbutils::codec_utils_h264_get_profile(sps); let level = gst_pbutils::codec_utils_h264_get_level(sps); if let (Ok(profile), Ok(level)) = (profile, level) { s.set("profile", profile); s.set("level", level); continue; } } gst::warning!(CAT, "Failed to parse profile-level-id {v}, ignoring..."); continue; } s.set(k, v); } // Adjust H264 caps for level asymmetry if encoding_name == "H264" { if s.get_optional("level-asymmetry-allowed") != Ok(Some("0")) && s.has_field("level") { s.remove_field("level"); } if s.has_field("level-asymmetry-allowed") { s.remove_field("level-asymmetry-allowed"); }; } } fn parse_framesize(framesize: &str, s: &mut gst::structure::Structure) { let Some((_pt, dim)) = framesize.split_once(' ') else { gst::warning!(CAT, "Could not parse framesize {framesize}, ignoring"); return; }; s.set("a-framesize", dim); } pub fn parse_media_attributes( attrs: &Vec, pt: u8, media: &str, s: &mut gst::structure::Structure, ) -> Result<(), RtspError> { let mut skip_attrs = vec!["control", "range", "ssrc"]; for Attribute { attribute, value } in attrs { let attr = attribute.as_str(); if skip_attrs.contains(&attr) { continue; } let Some(value) = value else { continue; }; if attr.starts_with("x-") { s.set(attr, value); continue; } match attr { "rtpmap" => parse_rtpmap(value, pt, media, s)?, "fmtp" => parse_fmtp(value, s), "framesize" => parse_framesize(value, s), // TODO: extmap, key-mgmt, rid, rtcp-fb, source-filter, ssrc _ => s.set(format!("a-{attribute}"), value), }; skip_attrs.push(attr); } if !skip_attrs.contains(&"rtpmap") { guess_rtpmap_from_pt(pt, media, s)?; } Ok(()) } pub fn parse_connections(conns: &Vec) -> (BTreeSet, bool) { let mut is_ipv4 = true; let mut conn_protocols = BTreeSet::new(); for conn in conns { if conn.nettype != "IN" { continue; } // XXX: For now, assume that all connections use the same addrtype match conn.addrtype.as_str() { "IP4" => is_ipv4 = true, "IP6" => is_ipv4 = false, _ => continue, }; // Strip subnet mask, if any let addr = if let Some((first, _)) = conn.connection_address.split_once('/') { first } else { conn.connection_address.as_str() }; let Ok(addr) = addr.parse::() else { continue; }; // If this is an instance of gst-rtsp-server that only supports // udp-multicast, it will put the multicast address in the media // connections field. if addr.is_multicast() { conn_protocols.insert(RtspProtocol::UdpMulticast); } else { conn_protocols.insert(RtspProtocol::Tcp); conn_protocols.insert(RtspProtocol::Udp); } } (conn_protocols, is_ipv4) }