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