freeswitch_log_parser/session/
media.rs1use crate::codec::{CodecMedia, CodecOffer};
8use crate::message::MessageKind;
9use crate::stream::{Block, LogEntry};
10
11#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct MediaCodecs {
14 pub negotiated: Option<CodecOffer>,
16 pub offered: Vec<CodecOffer>,
18}
19
20impl MediaCodecs {
21 fn offer(&mut self, codec: &CodecOffer) {
22 if !self.offered.contains(codec) {
23 self.offered.push(codec.clone());
24 }
25 }
26}
27
28#[derive(Debug, Clone, Default, PartialEq, Eq)]
34#[non_exhaustive]
35pub struct CodecImpl {
36 pub name: String,
37 pub payload_type: Option<u8>,
38 pub clock_rate: Option<u32>,
39 pub ptime: Option<u32>,
40 pub bitrate: Option<u32>,
41 pub channels: Option<u8>,
42}
43
44impl CodecImpl {
45 fn named(name: &str) -> Self {
47 CodecImpl {
48 name: name.to_string(),
49 ..CodecImpl::default()
50 }
51 }
52}
53
54#[derive(Debug, Clone, Default, PartialEq, Eq)]
56pub struct SessionMedia {
57 pub audio: MediaCodecs,
58 pub video: MediaCodecs,
59 pub read_codec: Option<CodecImpl>,
61 pub active_audio: Option<CodecImpl>,
64}
65
66impl SessionMedia {
67 pub(crate) fn update_from_entry(&mut self, entry: &LogEntry) {
68 if let Some(Block::CodecNegotiation {
69 media,
70 comparisons,
71 matched,
72 ..
73 }) = &entry.block
74 {
75 let side = match media {
78 CodecMedia::Video => &mut self.video,
79 CodecMedia::Audio => &mut self.audio,
80 };
81 for (offered, _local) in comparisons {
82 side.offer(offered);
83 }
84 if let Some(last) = matched.last() {
85 side.negotiated = Some(last.clone());
86 }
87 }
88
89 if let MessageKind::Media { detail } = &entry.message_kind {
91 if let Some(codec) = parse_original_read_codec(detail) {
92 self.read_codec = Some(codec);
93 }
94 if let Some(codec) = parse_set_codec(detail) {
95 self.active_audio = Some(codec);
96 }
97 }
98 }
99}
100
101fn parse_original_read_codec(msg: &str) -> Option<CodecImpl> {
103 let rest = msg.split_once("Original read codec set to ")?.1;
104 let (name, payload) = rest.trim().split_once(':')?;
105 if name.is_empty() {
106 return None;
107 }
108 Some(CodecImpl {
109 name: name.to_string(),
110 payload_type: Some(payload.parse().ok()?),
111 ..CodecImpl::named(name)
112 })
113}
114
115fn parse_set_codec(msg: &str) -> Option<CodecImpl> {
117 let rest = msg.strip_prefix("Set Codec ")?;
118 let (_channel, rest) = rest.split_once(' ')?;
119 let mut fields = rest.split(' ');
120 let (name, rate) = fields.next()?.split_once('/')?;
121 if name.is_empty() {
122 return None;
123 }
124 let ptime = fields.next()?.parse().ok()?;
125 let mut codec = CodecImpl {
126 clock_rate: rate.parse().ok(),
127 ptime: Some(ptime),
128 ..CodecImpl::named(name)
129 };
130 let tail: Vec<&str> = fields.collect();
131 for pair in tail.windows(2) {
132 match pair {
133 [value, "bits"] => codec.bitrate = value.parse().ok(),
134 [value, "channels"] => codec.channels = value.parse().ok(),
135 _ => {}
136 }
137 }
138 Some(codec)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn original_read_codec_carries_only_name_and_payload() {
147 let c =
148 parse_original_read_codec("sofia/softphone/1213 Original read codec set to opus:116")
149 .expect("parsed");
150 assert_eq!(c.name, "opus");
151 assert_eq!(c.payload_type, Some(116));
152 assert_eq!(c.clock_rate, None);
153 assert_eq!(c.ptime, None);
154 }
155
156 #[test]
157 fn set_codec_carries_rate_ptime_bitrate_and_channels() {
158 let c = parse_set_codec(
159 "Set Codec sofia/softphone/1213 opus/16000 20 ms 320 samples 0 bits 1 channels",
160 )
161 .expect("parsed");
162 assert_eq!(c.name, "opus");
163 assert_eq!(c.clock_rate, Some(16000));
164 assert_eq!(c.ptime, Some(20));
165 assert_eq!(c.bitrate, Some(0));
166 assert_eq!(c.channels, Some(1));
167 assert_eq!(
168 c.payload_type, None,
169 "the line names none, and 0 would claim PCMU"
170 );
171 }
172
173 #[test]
174 fn set_codec_reads_a_bitrate_that_is_not_zero() {
175 let c = parse_set_codec(
176 "Set Codec sofia/softphone/1213 PCMU/8000 20 ms 160 samples 64000 bits 1 channels",
177 )
178 .expect("parsed");
179 assert_eq!(c.bitrate, Some(64000));
180 }
181
182 #[test]
183 fn unrelated_media_lines_are_not_codecs() {
184 assert!(parse_set_codec("Set telephone-event payload to 101@48000").is_none());
185 assert!(parse_original_read_codec("Activating RTCP PORT 4001").is_none());
186 }
187}