ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
sdp.c File Reference

SDP offer/answer generation and parsing. More...

Go to the source code of this file.

Functions

const char * sdp_codec_name (acip_codec_t codec)
 
const char * sdp_renderer_name (int renderer_type)
 
asciichat_error_t sdp_generate_offer (const terminal_capability_t *capabilities, size_t capability_count, const opus_config_t *audio_config, const terminal_format_params_t *format, sdp_session_t *offer_out)
 
asciichat_error_t sdp_generate_answer (const sdp_session_t *offer, const terminal_capability_t *server_capabilities, size_t server_capability_count, const opus_config_t *audio_config, const terminal_format_params_t *server_format, sdp_session_t *answer_out)
 
asciichat_error_t sdp_parse (const char *sdp_string, sdp_session_t *session)
 
asciichat_error_t sdp_get_selected_video_codec (const sdp_session_t *answer, acip_codec_t *selected_codec, terminal_format_params_t *selected_format)
 
asciichat_error_t sdp_detect_terminal_capabilities (terminal_capability_t *capabilities, size_t capability_count, size_t *detected_count)
 
void sdp_session_destroy (sdp_session_t *session)
 

Detailed Description

SDP offer/answer generation and parsing.

Implements SDP generation for audio (Opus) and video (terminal capabilities). Terminal capabilities are negotiated as custom "codecs" in the video media section.

Date
January 2026

Definition in file sdp.c.

Function Documentation

◆ sdp_codec_name()

const char * sdp_codec_name ( acip_codec_t  codec)

Definition at line 24 of file sdp.c.

24 {
25 switch (codec) {
26 case ACIP_CODEC_TRUECOLOR:
27 return "ACIP-TC";
28 case ACIP_CODEC_256COLOR:
29 return "ACIP-256";
30 case ACIP_CODEC_16COLOR:
31 return "ACIP-16";
32 case ACIP_CODEC_MONO:
33 return "ACIP-MONO";
34 default:
35 return "UNKNOWN";
36 }
37}

Referenced by sdp_generate_answer(), sdp_generate_offer(), and sdp_get_selected_video_codec().

◆ sdp_detect_terminal_capabilities()

asciichat_error_t sdp_detect_terminal_capabilities ( terminal_capability_t *  capabilities,
size_t  capability_count,
size_t *  detected_count 
)

Definition at line 639 of file sdp.c.

640 {
641 if (!capabilities || capability_count == 0 || !detected_count) {
642 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid capability detection parameters");
643 }
644
645 *detected_count = 0;
646 memset(capabilities, 0, capability_count * sizeof(terminal_capability_t));
647
648 // Detect terminal color level using platform terminal module
649 const char *colorterm = SAFE_GETENV("COLORTERM");
650 const char *term = SAFE_GETENV("TERM");
651 terminal_color_mode_t color_level = TERM_COLOR_NONE;
652
653 if (colorterm && (strcmp(colorterm, "truecolor") == 0 || strcmp(colorterm, "24bit") == 0)) {
654 color_level = TERM_COLOR_TRUECOLOR;
655 } else if (term) {
656 if (strstr(term, "256color") || strstr(term, "256")) {
657 color_level = TERM_COLOR_256;
658 } else if (strstr(term, "color") || strcmp(term, "xterm") == 0) {
659 color_level = TERM_COLOR_16;
660 }
661 }
662
663 // Detect UTF-8 support from LANG environment variable
664 const char *lang = SAFE_GETENV("LANG");
665 bool has_utf8 = (lang && (strstr(lang, "UTF-8") || strstr(lang, "utf8")));
666
667 // CSI REP support correlates with UTF-8
668 bool has_csi_rep = has_utf8;
669
670 // Get terminal size from platform module
671 uint16_t term_width = 80; // default
672 uint16_t term_height = 24; // default
673
674 terminal_size_t term_size;
675 asciichat_error_t term_err = terminal_get_size(&term_size);
676 if (term_err == ASCIICHAT_OK) {
677 term_width = (uint16_t)term_size.cols;
678 term_height = (uint16_t)term_size.rows;
679 } else {
680 log_debug("SDP: Using default terminal size %ux%u", term_width, term_height);
681 }
682
683 // Fill capabilities array based on detected color level (preference order)
684 size_t idx = 0;
685
686 if (color_level >= TERM_COLOR_TRUECOLOR && idx < capability_count) {
687 capabilities[idx].codec = ACIP_CODEC_TRUECOLOR;
688 capabilities[idx].format.width = term_width;
689 capabilities[idx].format.height = term_height;
690 capabilities[idx].format.renderer = RENDERER_BLOCK;
691 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
692 capabilities[idx].format.compression = COMPRESSION_RLE;
693 capabilities[idx].format.csi_rep_support = has_csi_rep;
694 idx++;
695 }
696
697 if (color_level >= TERM_COLOR_256 && idx < capability_count) {
698 capabilities[idx].codec = ACIP_CODEC_256COLOR;
699 capabilities[idx].format.width = term_width;
700 capabilities[idx].format.height = term_height;
701 capabilities[idx].format.renderer = RENDERER_BLOCK;
702 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
703 capabilities[idx].format.compression = COMPRESSION_RLE;
704 capabilities[idx].format.csi_rep_support = has_csi_rep;
705 idx++;
706 }
707
708 if (color_level >= TERM_COLOR_16 && idx < capability_count) {
709 capabilities[idx].codec = ACIP_CODEC_16COLOR;
710 capabilities[idx].format.width = term_width;
711 capabilities[idx].format.height = term_height;
712 capabilities[idx].format.renderer = RENDERER_BLOCK;
713 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
714 capabilities[idx].format.compression = COMPRESSION_NONE;
715 capabilities[idx].format.csi_rep_support = has_csi_rep;
716 idx++;
717 }
718
719 // Monochrome always supported
720 if (idx < capability_count) {
721 capabilities[idx].codec = ACIP_CODEC_MONO;
722 capabilities[idx].format.width = term_width;
723 capabilities[idx].format.height = term_height;
724 capabilities[idx].format.renderer = RENDERER_BLOCK;
725 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
726 capabilities[idx].format.compression = COMPRESSION_NONE;
727 capabilities[idx].format.csi_rep_support = has_csi_rep;
728 idx++;
729 }
730
731 *detected_count = idx;
732
733 log_debug("SDP: Detected %zu terminal capabilities (colors=%d, utf8=%s, csi_rep=%s, size=%ux%u)", *detected_count,
734 color_level, has_utf8 ? "yes" : "no", has_csi_rep ? "yes" : "no", term_width, term_height);
735
736 return ASCIICHAT_OK;
737}
asciichat_error_t terminal_get_size(terminal_size_t *size)

References terminal_get_size().

◆ sdp_generate_answer()

asciichat_error_t sdp_generate_answer ( const sdp_session_t *  offer,
const terminal_capability_t *  server_capabilities,
size_t  server_capability_count,
const opus_config_t *  audio_config,
const terminal_format_params_t *  server_format,
sdp_session_t *  answer_out 
)

Definition at line 169 of file sdp.c.

171 {
172 if (!offer || !server_capabilities || server_capability_count == 0 || !audio_config || !answer_out) {
173 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid SDP answer parameters");
174 }
175
176 memset(answer_out, 0, sizeof(sdp_session_t));
177
178 // Copy session ID from offer, increment version
179 SAFE_STRNCPY(answer_out->session_id, offer->session_id, sizeof(answer_out->session_id));
180
181 time_t now = time(NULL);
182 safe_snprintf(answer_out->session_version, sizeof(answer_out->session_version), "%ld", now);
183
184 // Answer audio section: accept Opus
185 answer_out->has_audio = offer->has_audio;
186 if (answer_out->has_audio) {
187 memcpy(&answer_out->audio_config, audio_config, sizeof(opus_config_t));
188 }
189
190 // Answer video section: find best mutually-supported codec
191 answer_out->has_video = offer->has_video;
192
193 if (answer_out->has_video && offer->video_codecs && offer->video_codec_count > 0) {
194 // Find best codec: iterate through server preferences and find first match in offer
195 int selected_index = -1;
196
197 for (size_t s = 0; s < server_capability_count; s++) {
198 for (size_t o = 0; o < offer->video_codec_count; o++) {
199 if (server_capabilities[s].codec == offer->video_codecs[o].codec) {
200 selected_index = (int)s;
201 break; // Found match, use server's preference order
202 }
203 }
204 if (selected_index >= 0) {
205 break;
206 }
207 }
208
209 // Allocate and fill selected codec
210 answer_out->video_codecs = SAFE_MALLOC(sizeof(terminal_capability_t), terminal_capability_t *);
211 answer_out->video_codec_count = 1;
212
213 // Use server's capability with any server_format overrides
214 if (selected_index >= 0) {
215 memcpy(answer_out->video_codecs, &server_capabilities[selected_index], sizeof(terminal_capability_t));
216 } else {
217 // Fallback to monochrome
218 memcpy(answer_out->video_codecs, &server_capabilities[0], sizeof(terminal_capability_t));
219 }
220
221 // Apply server format constraints if provided
222 if (server_format) {
223 if (server_format->width > 0) {
224 answer_out->video_codecs[0].format.width = server_format->width;
225 }
226 if (server_format->height > 0) {
227 answer_out->video_codecs[0].format.height = server_format->height;
228 }
229 if (server_format->renderer != RENDERER_BLOCK) {
230 answer_out->video_codecs[0].format.renderer = server_format->renderer;
231 }
232 if (server_format->compression != COMPRESSION_NONE) {
233 answer_out->video_codecs[0].format.compression = server_format->compression;
234 }
235 }
236
237 memcpy(&answer_out->video_format, &answer_out->video_codecs[0].format, sizeof(terminal_format_params_t));
238 }
239
240 // Build complete SDP answer
241 char *sdp = answer_out->sdp_string;
242 size_t remaining = sizeof(answer_out->sdp_string);
243 int written = 0;
244
245 // Session-level attributes
246 written = safe_snprintf(sdp, remaining, "v=0\r\n");
247 sdp += written;
248 remaining -= written;
249
250 written = safe_snprintf(sdp, remaining, "o=ascii-chat %s %s IN IP4 0.0.0.0\r\n", answer_out->session_id,
251 answer_out->session_version);
252 sdp += written;
253 remaining -= written;
254
255 written = safe_snprintf(sdp, remaining, "s=-\r\n");
256 sdp += written;
257 remaining -= written;
258
259 written = safe_snprintf(sdp, remaining, "t=0 0\r\n");
260 sdp += written;
261 remaining -= written;
262
263 // Audio media section (if present in offer)
264 if (answer_out->has_audio) {
265 written = safe_snprintf(sdp, remaining, "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n");
266 sdp += written;
267 remaining -= written;
268
269 written = safe_snprintf(sdp, remaining, "a=rtpmap:111 opus/48000/2\r\n");
270 sdp += written;
271 remaining -= written;
272
273 written = safe_snprintf(sdp, remaining, "a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1\r\n");
274 sdp += written;
275 remaining -= written;
276 }
277
278 // Video media section (if present in offer)
279 if (answer_out->has_video && answer_out->video_codecs && answer_out->video_codec_count > 0) {
280 written = safe_snprintf(sdp, remaining, "m=video 9 UDP/TLS/RTP/SAVPF 96\r\n");
281 sdp += written;
282 remaining -= written;
283
284 const char *codec_name = sdp_codec_name(answer_out->video_codecs[0].codec);
285 written = safe_snprintf(sdp, remaining, "a=rtpmap:96 %s/90000\r\n", codec_name);
286 sdp += written;
287 remaining -= written;
288
289 // Format parameters
290 const terminal_format_params_t *cap_format = &answer_out->video_codecs[0].format;
291 const char *renderer_name = sdp_renderer_name(cap_format->renderer);
292 const char *charset_name = (cap_format->charset == CHARSET_UTF8) ? "utf8" : "ascii";
293 const char *compression_name = "none";
294 if (cap_format->compression == COMPRESSION_RLE) {
295 compression_name = "rle";
296 } else if (cap_format->compression == COMPRESSION_ZSTD) {
297 compression_name = "zstd";
298 }
299
300 written = safe_snprintf(sdp, remaining,
301 "a=fmtp:96 width=%u;height=%u;renderer=%s;charset=%s;compression=%s;csi_rep=%d\r\n",
302 cap_format->width, cap_format->height, renderer_name, charset_name, compression_name,
303 cap_format->csi_rep_support ? 1 : 0);
304 sdp += written;
305 remaining -= written;
306 }
307
308 answer_out->sdp_length = strlen(answer_out->sdp_string);
309
310 log_debug("SDP: Generated answer with codec %s", sdp_codec_name(answer_out->video_codecs[0].codec));
311
312 return ASCIICHAT_OK;
313}
const char * sdp_codec_name(acip_codec_t codec)
Definition sdp.c:24
const char * sdp_renderer_name(int renderer_type)
Definition sdp.c:39
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456

References safe_snprintf(), sdp_codec_name(), and sdp_renderer_name().

◆ sdp_generate_offer()

asciichat_error_t sdp_generate_offer ( const terminal_capability_t *  capabilities,
size_t  capability_count,
const opus_config_t *  audio_config,
const terminal_format_params_t *  format,
sdp_session_t *  offer_out 
)

Definition at line 56 of file sdp.c.

58 {
59 if (!capabilities || capability_count == 0 || !audio_config || !offer_out) {
60 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid SDP offer parameters");
61 }
62
63 memset(offer_out, 0, sizeof(sdp_session_t));
64
65 // Generate session ID and version from current time
66 time_t now = time(NULL);
67 safe_snprintf(offer_out->session_id, sizeof(offer_out->session_id), "%ld", now);
68 safe_snprintf(offer_out->session_version, sizeof(offer_out->session_version), "%ld", now);
69
70 // Allocate and copy video codecs
71 offer_out->video_codecs = SAFE_MALLOC(capability_count * sizeof(terminal_capability_t), terminal_capability_t *);
72 memcpy(offer_out->video_codecs, capabilities, capability_count * sizeof(terminal_capability_t));
73 offer_out->video_codec_count = capability_count;
74
75 offer_out->has_audio = true;
76 memcpy(&offer_out->audio_config, audio_config, sizeof(opus_config_t));
77
78 offer_out->has_video = true;
79 if (format) {
80 memcpy(&offer_out->video_format, format, sizeof(terminal_format_params_t));
81 }
82
83 // Build complete SDP offer
84 char *sdp = offer_out->sdp_string;
85 size_t remaining = sizeof(offer_out->sdp_string);
86 int written = 0;
87
88 // Session-level attributes
89 written = safe_snprintf(sdp, remaining, "v=0\r\n");
90 sdp += written;
91 remaining -= written;
92
93 written = safe_snprintf(sdp, remaining, "o=ascii-chat %s %s IN IP4 0.0.0.0\r\n", offer_out->session_id,
94 offer_out->session_version);
95 sdp += written;
96 remaining -= written;
97
98 written = safe_snprintf(sdp, remaining, "s=-\r\n");
99 sdp += written;
100 remaining -= written;
101
102 written = safe_snprintf(sdp, remaining, "t=0 0\r\n");
103 sdp += written;
104 remaining -= written;
105
106 // Audio media section
107 written = safe_snprintf(sdp, remaining, "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n");
108 sdp += written;
109 remaining -= written;
110
111 written = safe_snprintf(sdp, remaining, "a=rtpmap:111 opus/48000/2\r\n");
112 sdp += written;
113 remaining -= written;
114
115 written = safe_snprintf(sdp, remaining, "a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1\r\n");
116 sdp += written;
117 remaining -= written;
118
119 // Video media section with terminal capabilities
120 char codec_list[128] = "96";
121 size_t codec_pos = strlen(codec_list);
122 for (size_t i = 1; i < capability_count && codec_pos < sizeof(codec_list) - 1; i++) {
123 codec_pos += safe_snprintf(codec_list + codec_pos, sizeof(codec_list) - codec_pos, " %d", 96 + (int)i);
124 }
125
126 written = safe_snprintf(sdp, remaining, "m=video 9 UDP/TLS/RTP/SAVPF %s\r\n", codec_list);
127 sdp += written;
128 remaining -= written;
129
130 // Add rtpmap and fmtp for each capability
131 for (size_t i = 0; i < capability_count; i++) {
132 int pt = 96 + (int)i;
133 const char *codec_name = sdp_codec_name(capabilities[i].codec);
134
135 written = safe_snprintf(sdp, remaining, "a=rtpmap:%d %s/90000\r\n", pt, codec_name);
136 sdp += written;
137 remaining -= written;
138
139 // Format parameters
140 const terminal_format_params_t *cap_format = &capabilities[i].format;
141 const char *renderer_name = sdp_renderer_name(cap_format->renderer);
142 const char *charset_name = (cap_format->charset == CHARSET_UTF8) ? "utf8" : "ascii";
143 const char *compression_name = "none";
144 if (cap_format->compression == COMPRESSION_RLE) {
145 compression_name = "rle";
146 } else if (cap_format->compression == COMPRESSION_ZSTD) {
147 compression_name = "zstd";
148 }
149
150 written = safe_snprintf(sdp, remaining,
151 "a=fmtp:%d width=%u;height=%u;renderer=%s;charset=%s;compression=%s;csi_rep=%d\r\n", pt,
152 cap_format->width, cap_format->height, renderer_name, charset_name, compression_name,
153 cap_format->csi_rep_support ? 1 : 0);
154 sdp += written;
155 remaining -= written;
156 }
157
158 offer_out->sdp_length = strlen(offer_out->sdp_string);
159
160 log_debug("SDP: Generated offer with %zu video codecs and Opus audio", capability_count);
161
162 return ASCIICHAT_OK;
163}

References safe_snprintf(), sdp_codec_name(), and sdp_renderer_name().

◆ sdp_get_selected_video_codec()

asciichat_error_t sdp_get_selected_video_codec ( const sdp_session_t *  answer,
acip_codec_t *  selected_codec,
terminal_format_params_t *  selected_format 
)

Definition at line 612 of file sdp.c.

613 {
614 if (!answer || !selected_codec || !selected_format) {
615 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for codec selection");
616 }
617
618 if (!answer->has_video || !answer->video_codecs || answer->video_codec_count == 0) {
619 return SET_ERRNO(ERROR_NOT_FOUND, "No video codec in answer");
620 }
621
622 // In SDP answer, server selects only ONE codec (server's preference)
623 // The first codec in the answer is the selected one
624 terminal_capability_t *selected_cap = &answer->video_codecs[0];
625
626 *selected_codec = selected_cap->codec;
627 memcpy(selected_format, &selected_cap->format, sizeof(terminal_format_params_t));
628
629 log_debug("SDP: Selected video codec %s with resolution %ux%u", sdp_codec_name(selected_cap->codec),
630 selected_cap->format.width, selected_cap->format.height);
631
632 return ASCIICHAT_OK;
633}

References sdp_codec_name().

◆ sdp_parse()

asciichat_error_t sdp_parse ( const char *  sdp_string,
sdp_session_t *  session 
)

Definition at line 443 of file sdp.c.

443 {
444 if (!sdp_string || !session) {
445 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid SDP parse parameters");
446 }
447
448 memset(session, 0, sizeof(sdp_session_t));
449
450 // Copy original SDP string for reference
451 SAFE_STRNCPY(session->sdp_string, sdp_string, sizeof(session->sdp_string));
452 session->sdp_length = strlen(sdp_string);
453
454 // Parse line by line
455 char line_buffer[512];
456 SAFE_STRNCPY(line_buffer, sdp_string, sizeof(line_buffer));
457
458 char *saveptr = NULL;
459 char *line = platform_strtok_r(line_buffer, "\r\n", &saveptr);
460
461 int in_audio_section = 0;
462 int in_video_section = 0;
463 size_t video_codec_index = 0;
464
465 while (line) {
466 // Parse line format: "key=value"
467 char *equals = strchr(line, '=');
468 if (!equals) {
469 line = platform_strtok_r(NULL, "\r\n", &saveptr);
470 continue;
471 }
472
473 char key = line[0];
474 const char *value = equals + 1;
475
476 switch (key) {
477 case 'v':
478 // v=0 (version)
479 break;
480
481 case 'o':
482 // o=username session_id session_version ... session_id ...
483 // Parse session ID (second field)
484 {
485 char o_copy[256];
486 SAFE_STRNCPY(o_copy, value, sizeof(o_copy));
487 char *o_saveptr = NULL;
488 platform_strtok_r(o_copy, " ", &o_saveptr); // username
489 char *session_id_str = platform_strtok_r(NULL, " ", &o_saveptr);
490 if (session_id_str) {
491 SAFE_STRNCPY(session->session_id, session_id_str, sizeof(session->session_id));
492 }
493 }
494 break;
495
496 case 's':
497 // s=session_name (typically "-" or empty)
498 break;
499
500 case 'm':
501 // m=audio 9 UDP/TLS/RTP/SAVPF ...
502 // m=video 9 UDP/TLS/RTP/SAVPF ...
503 in_audio_section = 0;
504 in_video_section = 0;
505
506 if (strstr(value, "audio")) {
507 in_audio_section = 1;
508 session->has_audio = true;
509 } else if (strstr(value, "video")) {
510 in_video_section = 1;
511 session->has_video = true;
512 video_codec_index = 0;
513 }
514 break;
515
516 case 'a':
517 // a=rtpmap:111 opus/48000/2
518 // a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1
519 // a=rtpmap:96 ACIP-TC/90000
520 // a=fmtp:96 ...
521
522 if (strstr(value, "rtpmap:")) {
523 const char *rtpmap = value + 7; // Skip "rtpmap:"
524
525 // Parse: PT codec/rate[/channels]
526 char *endptr = NULL;
527 int pt = (int)strtol(rtpmap, &endptr, 10);
528 char codec_name[32] = {0};
529 if (endptr && *endptr == ' ') {
530 SAFE_STRNCPY(codec_name, endptr + 1, sizeof(codec_name));
531 }
532
533 if (in_audio_section && pt == 111 && strstr(codec_name, "opus")) {
534 // Opus audio
535 session->audio_config.sample_rate = 48000;
536 session->audio_config.channels = 2;
537 } else if (in_video_section && video_codec_index < 4) {
538 // Terminal capability codec
539 if (!session->video_codecs) {
540 session->video_codecs = SAFE_MALLOC(4 * sizeof(terminal_capability_t), terminal_capability_t *);
541 }
542
543 terminal_capability_t *cap = &session->video_codecs[video_codec_index];
544 memset(cap, 0, sizeof(terminal_capability_t));
545
546 if (pt == 96 && strstr(codec_name, "ACIP-TC")) {
547 cap->codec = ACIP_CODEC_TRUECOLOR;
548 video_codec_index++;
549 session->video_codec_count++;
550 } else if (pt == 97 && strstr(codec_name, "ACIP-256")) {
551 cap->codec = ACIP_CODEC_256COLOR;
552 video_codec_index++;
553 session->video_codec_count++;
554 } else if (pt == 98 && strstr(codec_name, "ACIP-16")) {
555 cap->codec = ACIP_CODEC_16COLOR;
556 video_codec_index++;
557 session->video_codec_count++;
558 } else if (pt == 99 && strstr(codec_name, "ACIP-MONO")) {
559 cap->codec = ACIP_CODEC_MONO;
560 video_codec_index++;
561 session->video_codec_count++;
562 }
563 }
564 } else if (strstr(value, "fmtp:")) {
565 // Parse format parameters
566 // a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1
567 // a=fmtp:96 width=80;height=24;renderer=block;charset=utf8;compression=rle;csi_rep=1
568
569 const char *fmtp = value + 5; // Skip "fmtp:"
570
571 if (in_audio_section) {
572 // Parse Opus parameters
573 if (strstr(fmtp, "useinbandfec=1")) {
574 session->audio_config.fec_enabled = true;
575 }
576 if (strstr(fmtp, "usedtx=1")) {
577 session->audio_config.dtx_enabled = true;
578 }
579 // Default values for Opus
580 session->audio_config.bitrate = 24000;
581 session->audio_config.frame_duration = 20;
582 } else if (in_video_section && video_codec_index > 0) {
583 // Parse terminal format parameters
584 terminal_capability_t *cap = &session->video_codecs[video_codec_index - 1];
585
586 // Skip PT number in fmtp string (format: "96 width=...")
587 const char *params = fmtp;
588 while (*params && *params != ' ') {
589 params++;
590 }
591 if (*params == ' ') {
592 params++; // Skip space after PT
593 }
594
595 // Parse with PCRE2 regex (atomic extraction)
596 sdp_parse_fmtp_video_pcre2(params, cap);
597 }
598 }
599 break;
600 }
601
602 line = platform_strtok_r(NULL, "\r\n", &saveptr);
603 }
604
605 return ASCIICHAT_OK;
606}
char ** line_buffer
Circular buffer for context_before.
Definition grep.c:83

References line_buffer.

◆ sdp_renderer_name()

const char * sdp_renderer_name ( int  renderer_type)

Definition at line 39 of file sdp.c.

39 {
40 switch (renderer_type) {
41 case RENDERER_BLOCK:
42 return "block";
43 case RENDERER_HALFBLOCK:
44 return "halfblock";
45 case RENDERER_BRAILLE:
46 return "braille";
47 default:
48 return "unknown";
49 }
50}

Referenced by sdp_generate_answer(), and sdp_generate_offer().

◆ sdp_session_destroy()

void sdp_session_destroy ( sdp_session_t *  session)

Definition at line 743 of file sdp.c.

743 {
744 if (!session) {
745 return;
746 }
747
748 // Free allocated video codec array
749 if (session->video_codecs) {
750 SAFE_FREE(session->video_codecs);
751 session->video_codec_count = 0;
752 }
753
754 // Zero out the session structure
755 memset(session, 0, sizeof(sdp_session_t));
756}