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)]
30pub struct SessionMedia {
31 pub audio: MediaCodecs,
32 pub video: MediaCodecs,
33 pub read_codec: Option<CodecOffer>,
36 pub active_audio: Option<CodecOffer>,
39}
40
41impl SessionMedia {
42 pub(crate) fn update_from_entry(&mut self, entry: &LogEntry) {
43 if let Some(Block::CodecNegotiation {
44 media,
45 comparisons,
46 matched,
47 ..
48 }) = &entry.block
49 {
50 let side = match media {
51 CodecMedia::Video => &mut self.video,
52 _ => &mut self.audio,
53 };
54 for (offered, _local) in comparisons {
55 side.offer(offered);
56 }
57 if let Some(last) = matched.last() {
58 side.negotiated = Some(last.clone());
59 }
60 }
61
62 if let MessageKind::Media { detail } = &entry.message_kind {
64 if let Some(codec) = parse_original_read_codec(detail) {
65 self.read_codec = Some(codec);
66 }
67 if let Some(codec) = parse_set_codec(detail) {
68 self.active_audio = Some(codec);
69 }
70 }
71 }
72}
73
74fn parse_original_read_codec(msg: &str) -> Option<CodecOffer> {
76 let rest = msg.split_once("Original read codec set to ")?.1;
77 let (name, payload) = rest.trim().split_once(':')?;
78 if name.is_empty() {
79 return None;
80 }
81 Some(CodecOffer {
82 name: name.to_string(),
83 payload_type: payload.parse().ok()?,
84 clock_rate: None,
85 ptime: None,
86 bitrate: None,
87 channels: None,
88 })
89}
90
91fn parse_set_codec(msg: &str) -> Option<CodecOffer> {
93 let rest = msg.strip_prefix("Set Codec ")?;
94 let (_channel, rest) = rest.split_once(' ')?;
95 let mut fields = rest.split(' ');
96 let (name, rate) = fields.next()?.split_once('/')?;
97 if name.is_empty() {
98 return None;
99 }
100 let ptime = fields.next()?.parse().ok()?;
101 let mut codec = CodecOffer {
103 name: name.to_string(),
104 payload_type: 0,
105 clock_rate: rate.parse().ok(),
106 ptime: Some(ptime),
107 bitrate: None,
108 channels: None,
109 };
110 let tail: Vec<&str> = fields.collect();
111 for pair in tail.windows(2) {
112 match pair {
113 [value, "bits"] => codec.bitrate = value.parse().ok(),
114 [value, "channels"] => codec.channels = value.parse().ok(),
115 _ => {}
116 }
117 }
118 Some(codec)
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn original_read_codec_carries_only_name_and_payload() {
127 let c =
128 parse_original_read_codec("sofia/softphone/1213 Original read codec set to opus:116")
129 .expect("parsed");
130 assert_eq!(c.name, "opus");
131 assert_eq!(c.payload_type, 116);
132 assert_eq!(c.clock_rate, None);
133 assert_eq!(c.ptime, None);
134 }
135
136 #[test]
137 fn set_codec_carries_rate_ptime_bitrate_and_channels() {
138 let c = parse_set_codec(
139 "Set Codec sofia/softphone/1213 opus/16000 20 ms 320 samples 0 bits 1 channels",
140 )
141 .expect("parsed");
142 assert_eq!(c.name, "opus");
143 assert_eq!(c.clock_rate, Some(16000));
144 assert_eq!(c.ptime, Some(20));
145 assert_eq!(c.bitrate, Some(0));
146 assert_eq!(c.channels, Some(1));
147 }
148
149 #[test]
150 fn set_codec_reads_a_bitrate_that_is_not_zero() {
151 let c = parse_set_codec(
152 "Set Codec sofia/softphone/1213 PCMU/8000 20 ms 160 samples 64000 bits 1 channels",
153 )
154 .expect("parsed");
155 assert_eq!(c.bitrate, Some(64000));
156 }
157
158 #[test]
159 fn unrelated_media_lines_are_not_codecs() {
160 assert!(parse_set_codec("Set telephone-event payload to 101@48000").is_none());
161 assert!(parse_original_read_codec("Activating RTCP PORT 4001").is_none());
162 }
163}