ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
sdp.h File Reference

SDP (Session Description Protocol) for WebRTC audio/video negotiation. More...

Go to the source code of this file.

Data Structures

struct  opus_config_t
 Opus codec parameters for ascii-chat. More...
 
struct  terminal_format_params_t
 Terminal rendering format parameters. More...
 
struct  terminal_capability_t
 Supported terminal capability (for offer/answer) More...
 
struct  sdp_session_t
 SDP media session (simplified for WebRTC) More...
 

Enumerations

enum  acip_codec_t { ACIP_CODEC_TRUECOLOR = 96 , ACIP_CODEC_256COLOR = 97 , ACIP_CODEC_16COLOR = 98 , ACIP_CODEC_MONO = 99 }
 Terminal rendering capability payload types. More...
 

Functions

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, uint32_t video_codec_caps, uint32_t audio_codec_caps, sdp_session_t *offer_out)
 Generate SDP offer (client side)
 
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, uint32_t server_video_codec_caps, uint32_t server_audio_codec_caps, sdp_session_t *answer_out)
 Generate SDP answer (server side)
 
asciichat_error_t sdp_parse (const char *sdp_string, sdp_session_t *session)
 Parse SDP offer or answer.
 
asciichat_error_t sdp_get_selected_video_codec (const sdp_session_t *answer, acip_codec_t *selected_codec, terminal_format_params_t *selected_format)
 Extract selected video codec from SDP answer.
 
asciichat_error_t sdp_detect_terminal_capabilities (terminal_capability_t *capabilities, size_t capability_count, size_t *detected_count)
 Detect client terminal capabilities at startup.
 
void sdp_session_destroy (sdp_session_t *session)
 Free SDP session resources.
 
const char * sdp_codec_name (acip_codec_t codec)
 Get human-readable codec name.
 
const char * sdp_renderer_name (int renderer_type)
 Get human-readable renderer name.
 

Detailed Description

SDP (Session Description Protocol) for WebRTC audio/video negotiation.

Handles SDP offer/answer generation and parsing for WebRTC connections. Includes:

  • Opus audio codec negotiation (48kHz, mono, 24kbps)
  • Terminal capability negotiation via custom ACIP video "codecs"
  • Format parameters for resolution, renderer, charset, compression

SDP Offer/Answer Flow

Client (Joiner) generates OFFER:

  • Lists supported audio codecs (Opus)
  • Lists supported terminal rendering modes in preference order
  • Server selects best mutually-supported mode

Server (Creator) generates ANSWER:

  • Selects single audio codec (Opus)
  • Selects best terminal rendering mode
  • Server enforces its rendering constraints

Terminal Capability "Codecs"

These are RTP payload types that represent terminal rendering modes:

  • PT 96: ACIP-TC (Truecolor, 24-bit RGB)
  • PT 97: ACIP-256 (256-color xterm)
  • PT 98: ACIP-16 (16-color ANSI)
  • PT 99: ACIP-MONO (Monochrome, ASCII only)
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
January 2026

Definition in file sdp.h.

Enumeration Type Documentation

◆ acip_codec_t

Terminal rendering capability payload types.

These are custom "codecs" that represent terminal rendering modes. Used in SDP to negotiate which color mode both peers can support.

Enumerator
ACIP_CODEC_TRUECOLOR 

24-bit RGB (truecolor)

ACIP_CODEC_256COLOR 

256-color (xterm palette)

ACIP_CODEC_16COLOR 

16-color (ANSI standard)

ACIP_CODEC_MONO 

Monochrome (ASCII only)

Definition at line 77 of file sdp.h.

77 {
81 ACIP_CODEC_MONO = 99,
acip_codec_t
Terminal rendering capability payload types.
Definition sdp.h:77
@ ACIP_CODEC_16COLOR
16-color (ANSI standard)
Definition sdp.h:80
@ ACIP_CODEC_MONO
Monochrome (ASCII only)
Definition sdp.h:81
@ ACIP_CODEC_256COLOR
256-color (xterm palette)
Definition sdp.h:79
@ ACIP_CODEC_TRUECOLOR
24-bit RGB (truecolor)
Definition sdp.h:78

Function Documentation

◆ sdp_codec_name()

const char * sdp_codec_name ( acip_codec_t  codec)

Get human-readable codec name.

Parameters
codecCodec type
Returns
Static string (e.g., "ACIP-TC", "ACIP-256")

Definition at line 24 of file sdp.c.

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

References ACIP_CODEC_16COLOR, ACIP_CODEC_256COLOR, ACIP_CODEC_MONO, and ACIP_CODEC_TRUECOLOR.

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 
)

Detect client terminal capabilities at startup.

Auto-detects from environment and terminal:

  1. COLORTERM env var (truecolor/24bit)
  2. Terminal color query (terminfo/XTGETTCAP)
  3. UTF-8 support (LANG, test character)
  4. CSI REP support (escape sequence test)
  5. Terminal size (TIOCGWINSZ)
Parameters
capabilitiesDetected capabilities (output)
capability_countMaximum capabilities to detect
detected_countActual number detected (output)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 691 of file sdp.c.

692 {
693 if (!capabilities || capability_count == 0 || !detected_count) {
694 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid capability detection parameters");
695 }
696
697 *detected_count = 0;
698 memset(capabilities, 0, capability_count * sizeof(terminal_capability_t));
699
700 // Detect terminal color level using platform terminal module
701 const char *colorterm = SAFE_GETENV("COLORTERM");
702 const char *term = SAFE_GETENV("TERM");
704
705 if (colorterm && (strcmp(colorterm, "truecolor") == 0 || strcmp(colorterm, "24bit") == 0)) {
706 color_level = TERM_COLOR_TRUECOLOR;
707 } else if (term) {
708 if (strstr(term, "256color") || strstr(term, "256")) {
709 color_level = TERM_COLOR_256;
710 } else if (strstr(term, "color") || strcmp(term, "xterm") == 0) {
711 color_level = TERM_COLOR_16;
712 }
713 }
714
715 // Detect UTF-8 support from LANG environment variable
716 const char *lang = SAFE_GETENV("LANG");
717 bool has_utf8 = (lang && (strstr(lang, "UTF-8") || strstr(lang, "utf8")));
718
719 // CSI REP support correlates with UTF-8
720 bool has_csi_rep = has_utf8;
721
722 // Get terminal size from platform module
723 uint16_t term_width = 80; // default
724 uint16_t term_height = 24; // default
725
726 terminal_size_t term_size;
727 asciichat_error_t term_err = terminal_get_size(&term_size);
728 if (term_err == ASCIICHAT_OK) {
729 term_width = (uint16_t)term_size.cols;
730 term_height = (uint16_t)term_size.rows;
731 } else {
732 log_debug("SDP: Using default terminal size %ux%u", term_width, term_height);
733 }
734
735 // Fill capabilities array based on detected color level (preference order)
736 size_t idx = 0;
737
738 if (color_level >= TERM_COLOR_TRUECOLOR && idx < capability_count) {
739 capabilities[idx].codec = ACIP_CODEC_TRUECOLOR;
740 capabilities[idx].format.width = term_width;
741 capabilities[idx].format.height = term_height;
742 capabilities[idx].format.renderer = RENDERER_BLOCK;
743 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
744 capabilities[idx].format.compression = COMPRESSION_RLE;
745 capabilities[idx].format.csi_rep_support = has_csi_rep;
746 idx++;
747 }
748
749 if (color_level >= TERM_COLOR_256 && idx < capability_count) {
750 capabilities[idx].codec = ACIP_CODEC_256COLOR;
751 capabilities[idx].format.width = term_width;
752 capabilities[idx].format.height = term_height;
753 capabilities[idx].format.renderer = RENDERER_BLOCK;
754 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
755 capabilities[idx].format.compression = COMPRESSION_RLE;
756 capabilities[idx].format.csi_rep_support = has_csi_rep;
757 idx++;
758 }
759
760 if (color_level >= TERM_COLOR_16 && idx < capability_count) {
761 capabilities[idx].codec = ACIP_CODEC_16COLOR;
762 capabilities[idx].format.width = term_width;
763 capabilities[idx].format.height = term_height;
764 capabilities[idx].format.renderer = RENDERER_BLOCK;
765 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
766 capabilities[idx].format.compression = COMPRESSION_NONE;
767 capabilities[idx].format.csi_rep_support = has_csi_rep;
768 idx++;
769 }
770
771 // Monochrome always supported
772 if (idx < capability_count) {
773 capabilities[idx].codec = ACIP_CODEC_MONO;
774 capabilities[idx].format.width = term_width;
775 capabilities[idx].format.height = term_height;
776 capabilities[idx].format.renderer = RENDERER_BLOCK;
777 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
778 capabilities[idx].format.compression = COMPRESSION_NONE;
779 capabilities[idx].format.csi_rep_support = has_csi_rep;
780 idx++;
781 }
782
783 *detected_count = idx;
784
785 log_debug("SDP: Detected %zu terminal capabilities (colors=%d, utf8=%s, csi_rep=%s, size=%ux%u)", *detected_count,
786 color_level, has_utf8 ? "yes" : "no", has_csi_rep ? "yes" : "no", term_width, term_height);
787
788 return ASCIICHAT_OK;
789}
unsigned short uint16_t
Definition common.h:57
#define SAFE_GETENV(name)
Definition common.h:434
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
asciichat_error_t terminal_get_size(terminal_size_t *size)
Get terminal size.
Supported terminal capability (for offer/answer)
Definition sdp.h:100
terminal_format_params_t format
Format parameters.
Definition sdp.h:102
acip_codec_t codec
Rendering capability type.
Definition sdp.h:101
enum terminal_format_params_t::@17 charset
bool csi_rep_support
CSI REP (repeat) support.
Definition sdp.h:93
enum terminal_format_params_t::@18 compression
enum terminal_format_params_t::@16 renderer
uint16_t height
Terminal height in characters.
Definition sdp.h:89
uint16_t width
Terminal width in characters.
Definition sdp.h:88
Terminal size structure.
Definition terminal.h:163
int cols
Number of columns (width) in terminal.
Definition terminal.h:165
int rows
Number of rows (height) in terminal.
Definition terminal.h:164
terminal_color_mode_t
Terminal color support levels.
Definition terminal.h:578
@ TERM_COLOR_NONE
No color support (monochrome terminal)
Definition terminal.h:582
@ TERM_COLOR_16
16-color support (standard ANSI colors)
Definition terminal.h:584
@ TERM_COLOR_256
256-color support (extended ANSI palette)
Definition terminal.h:586
@ TERM_COLOR_TRUECOLOR
24-bit truecolor support (RGB colors)
Definition terminal.h:588

References ACIP_CODEC_16COLOR, ACIP_CODEC_256COLOR, ACIP_CODEC_MONO, ACIP_CODEC_TRUECOLOR, ASCIICHAT_OK, terminal_format_params_t::charset, terminal_capability_t::codec, terminal_size_t::cols, terminal_format_params_t::compression, terminal_format_params_t::csi_rep_support, ERROR_INVALID_PARAM, terminal_capability_t::format, terminal_format_params_t::height, log_debug, terminal_format_params_t::renderer, terminal_size_t::rows, SAFE_GETENV, SET_ERRNO, TERM_COLOR_16, TERM_COLOR_256, TERM_COLOR_NONE, TERM_COLOR_TRUECOLOR, terminal_get_size(), and terminal_format_params_t::width.

◆ 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,
uint32_t  server_video_codec_caps,
uint32_t  server_audio_codec_caps,
sdp_session_t *  answer_out 
)

Generate SDP answer (server side)

Creates an SDP answer by selecting best mutually-supported mode from offer. Server enforces its rendering constraints and codec capabilities. Performs codec capability negotiation (intersection of client and server capabilities).

Parameters
offerReceived SDP offer from client (includes client's codec capabilities)
server_capabilitiesArray of server-supported terminal capabilities
server_capability_countNumber of server capabilities
audio_configOpus codec configuration
server_formatServer's rendering constraints
server_video_codec_capsBitmask of server's supported video codecs (VIDEO_CODEC_CAP_*)
server_audio_codec_capsBitmask of server's supported audio codecs (AUDIO_CODEC_CAP_*)
answer_outGenerated SDP answer (output)
Returns
ASCIICHAT_OK on success, error code on failure
Note
Caller must free answer_out using sdp_session_destroy()

Definition at line 183 of file sdp.c.

186 {
187 if (!offer || !server_capabilities || server_capability_count == 0 || !audio_config || !answer_out) {
188 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid SDP answer parameters");
189 }
190
191 memset(answer_out, 0, sizeof(sdp_session_t));
192
193 // Copy session ID from offer, increment version
194 SAFE_STRNCPY(answer_out->session_id, offer->session_id, sizeof(answer_out->session_id));
195
196 time_t now = time(NULL);
197 safe_snprintf(answer_out->session_version, sizeof(answer_out->session_version), "%ld", now);
198
199 // Answer audio section: accept Opus with codec capability negotiation
200 answer_out->has_audio = offer->has_audio;
201 if (answer_out->has_audio) {
202 memcpy(&answer_out->audio_config, audio_config, sizeof(opus_config_t));
203 // Negotiate audio codec capabilities: intersection of client and server
204 answer_out->codec_capabilities_audio = offer->codec_capabilities_audio & server_audio_codec_caps;
205 log_debug("SDP: Audio codec negotiation - client=0x%x server=0x%x negotiated=0x%x", offer->codec_capabilities_audio,
206 server_audio_codec_caps, answer_out->codec_capabilities_audio);
207 }
208
209 // Answer video section: find best mutually-supported codec with capability negotiation
210 answer_out->has_video = offer->has_video;
211
212 if (answer_out->has_video && offer->video_codecs && offer->video_codec_count > 0) {
213 // Negotiate video codec capabilities: intersection of client and server
214 answer_out->codec_capabilities_video = offer->codec_capabilities_video & server_video_codec_caps;
215 log_debug("SDP: Video codec negotiation - client=0x%x server=0x%x negotiated=0x%x", offer->codec_capabilities_video,
216 server_video_codec_caps, answer_out->codec_capabilities_video);
217
218 // Find best terminal capability codec: iterate through server preferences and find first match in offer
219 int selected_index = -1;
220
221 for (size_t s = 0; s < server_capability_count; s++) {
222 for (size_t o = 0; o < offer->video_codec_count; o++) {
223 if (server_capabilities[s].codec == offer->video_codecs[o].codec) {
224 selected_index = (int)s;
225 break; // Found match, use server's preference order
226 }
227 }
228 if (selected_index >= 0) {
229 break;
230 }
231 }
232
233 // Allocate and fill selected codec
235 answer_out->video_codec_count = 1;
236
237 // Use server's capability with any server_format overrides
238 if (selected_index >= 0) {
239 memcpy(answer_out->video_codecs, &server_capabilities[selected_index], sizeof(terminal_capability_t));
240 } else {
241 // Fallback to monochrome
242 memcpy(answer_out->video_codecs, &server_capabilities[0], sizeof(terminal_capability_t));
243 }
244
245 // Apply server format constraints if provided
246 if (server_format) {
247 if (server_format->width > 0) {
248 answer_out->video_codecs[0].format.width = server_format->width;
249 }
250 if (server_format->height > 0) {
251 answer_out->video_codecs[0].format.height = server_format->height;
252 }
253 if (server_format->renderer != RENDERER_BLOCK) {
254 answer_out->video_codecs[0].format.renderer = server_format->renderer;
255 }
256 if (server_format->compression != COMPRESSION_NONE) {
257 answer_out->video_codecs[0].format.compression = server_format->compression;
258 }
259 }
260
261 memcpy(&answer_out->video_format, &answer_out->video_codecs[0].format, sizeof(terminal_format_params_t));
262 }
263
264 // Build complete SDP answer
265 char *sdp = answer_out->sdp_string;
266 size_t remaining = sizeof(answer_out->sdp_string);
267 int written = 0;
268
269 // Session-level attributes
270 written = safe_snprintf(sdp, remaining, "v=0\r\n");
271 sdp += written;
272 remaining -= written;
273
274 written = safe_snprintf(sdp, remaining, "o=ascii-chat %s %s IN IP4 0.0.0.0\r\n", answer_out->session_id,
275 answer_out->session_version);
276 sdp += written;
277 remaining -= written;
278
279 written = safe_snprintf(sdp, remaining, "s=-\r\n");
280 sdp += written;
281 remaining -= written;
282
283 written = safe_snprintf(sdp, remaining, "t=0 0\r\n");
284 sdp += written;
285 remaining -= written;
286
287 // Audio media section (if present in offer)
288 if (answer_out->has_audio) {
289 written = safe_snprintf(sdp, remaining, "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n");
290 sdp += written;
291 remaining -= written;
292
293 written = safe_snprintf(sdp, remaining, "a=rtpmap:111 opus/48000/2\r\n");
294 sdp += written;
295 remaining -= written;
296
297 written = safe_snprintf(sdp, remaining, "a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1\r\n");
298 sdp += written;
299 remaining -= written;
300
301 // Negotiated audio codec capabilities (custom attribute)
302 written =
303 safe_snprintf(sdp, remaining, "a=codec-capabilities-audio:0x%x\r\n", answer_out->codec_capabilities_audio);
304 sdp += written;
305 remaining -= written;
306 }
307
308 // Video media section (if present in offer)
309 if (answer_out->has_video && answer_out->video_codecs && answer_out->video_codec_count > 0) {
310 written = safe_snprintf(sdp, remaining, "m=video 9 UDP/TLS/RTP/SAVPF 96\r\n");
311 sdp += written;
312 remaining -= written;
313
314 const char *codec_name = sdp_codec_name(answer_out->video_codecs[0].codec);
315 written = safe_snprintf(sdp, remaining, "a=rtpmap:96 %s/90000\r\n", codec_name);
316 sdp += written;
317 remaining -= written;
318
319 // Format parameters
320 const terminal_format_params_t *cap_format = &answer_out->video_codecs[0].format;
321 const char *renderer_name = sdp_renderer_name(cap_format->renderer);
322 const char *charset_name = (cap_format->charset == CHARSET_UTF8) ? "utf8" : "ascii";
323 const char *compression_name = "none";
324 if (cap_format->compression == COMPRESSION_RLE) {
325 compression_name = "rle";
326 } else if (cap_format->compression == COMPRESSION_ZSTD) {
327 compression_name = "zstd";
328 }
329
330 written = safe_snprintf(sdp, remaining,
331 "a=fmtp:96 width=%u;height=%u;renderer=%s;charset=%s;compression=%s;csi_rep=%d\r\n",
332 cap_format->width, cap_format->height, renderer_name, charset_name, compression_name,
333 cap_format->csi_rep_support ? 1 : 0);
334 sdp += written;
335 remaining -= written;
336
337 // Negotiated video codec capabilities (custom attribute)
338 written =
339 safe_snprintf(sdp, remaining, "a=codec-capabilities-video:0x%x\r\n", answer_out->codec_capabilities_video);
340 sdp += written;
341 remaining -= written;
342 }
343
344 answer_out->sdp_length = strlen(answer_out->sdp_string);
345
346 log_debug("SDP: Generated answer with codec %s", sdp_codec_name(answer_out->video_codecs[0].codec));
347
348 return ASCIICHAT_OK;
349}
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_MALLOC(size, cast)
Definition common.h:264
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
const char * sdp_codec_name(acip_codec_t codec)
Get human-readable codec name.
Definition sdp.c:24
const char * sdp_renderer_name(int renderer_type)
Get human-readable renderer name.
Definition sdp.c:39
Opus codec parameters for ascii-chat.
Definition sdp.h:58
SDP media session (simplified for WebRTC)
Definition sdp.h:115
terminal_format_params_t video_format
Default format parameters.
Definition sdp.h:129
uint32_t codec_capabilities_audio
AUDIO_CODEC_CAP_* bitmask (which audio codecs peer supports)
Definition sdp.h:123
size_t video_codec_count
Number of supported capabilities.
Definition sdp.h:128
char session_version[16]
Session version (timestamp)
Definition sdp.h:118
size_t sdp_length
Length of SDP string (excluding null)
Definition sdp.h:134
bool has_audio
Audio media included.
Definition sdp.h:121
char session_id[32]
Session identifier.
Definition sdp.h:117
terminal_capability_t * video_codecs
Array of supported capabilities.
Definition sdp.h:127
uint32_t codec_capabilities_video
VIDEO_CODEC_CAP_* bitmask (which video codecs peer supports)
Definition sdp.h:130
opus_config_t audio_config
Opus codec configuration.
Definition sdp.h:122
bool has_video
Video media included.
Definition sdp.h:126
char sdp_string[4096]
Complete SDP as null-terminated string.
Definition sdp.h:133
Terminal rendering format parameters.
Definition sdp.h:87

References ASCIICHAT_OK, sdp_session_t::audio_config, terminal_format_params_t::charset, terminal_capability_t::codec, sdp_session_t::codec_capabilities_audio, sdp_session_t::codec_capabilities_video, terminal_format_params_t::compression, terminal_format_params_t::csi_rep_support, ERROR_INVALID_PARAM, terminal_capability_t::format, sdp_session_t::has_audio, sdp_session_t::has_video, terminal_format_params_t::height, log_debug, terminal_format_params_t::renderer, SAFE_MALLOC, safe_snprintf(), SAFE_STRNCPY, sdp_codec_name(), sdp_session_t::sdp_length, sdp_renderer_name(), sdp_session_t::sdp_string, sdp_session_t::session_id, sdp_session_t::session_version, SET_ERRNO, sdp_session_t::video_codec_count, sdp_session_t::video_codecs, sdp_session_t::video_format, and terminal_format_params_t::width.

◆ 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,
uint32_t  video_codec_caps,
uint32_t  audio_codec_caps,
sdp_session_t *  offer_out 
)

Generate SDP offer (client side)

Creates an SDP offer with:

  • Opus audio codec with audio codec capabilities (AUDIO_CODEC_CAP_*)
  • Terminal capabilities in client preference order with video codec capabilities (VIDEO_CODEC_CAP_*)
Parameters
capabilitiesArray of supported terminal capabilities
capability_countNumber of capabilities
audio_configOpus codec configuration
formatFormat parameters for video section
video_codec_capsBitmask of supported video codecs (VIDEO_CODEC_CAP_*)
audio_codec_capsBitmask of supported audio codecs (AUDIO_CODEC_CAP_*)
offer_outGenerated SDP offer (output)
Returns
ASCIICHAT_OK on success, error code on failure
Note
Caller must free offer_out using sdp_session_destroy()

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 (terminal capabilities)
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 // Store codec capabilities (for SDP negotiation)
76 offer_out->codec_capabilities_video = video_codec_caps;
77 offer_out->codec_capabilities_audio = audio_codec_caps;
78
79 offer_out->has_audio = true;
80 memcpy(&offer_out->audio_config, audio_config, sizeof(opus_config_t));
81
82 offer_out->has_video = true;
83 if (format) {
84 memcpy(&offer_out->video_format, format, sizeof(terminal_format_params_t));
85 }
86
87 // Build complete SDP offer
88 char *sdp = offer_out->sdp_string;
89 size_t remaining = sizeof(offer_out->sdp_string);
90 int written = 0;
91
92 // Session-level attributes
93 written = safe_snprintf(sdp, remaining, "v=0\r\n");
94 sdp += written;
95 remaining -= written;
96
97 written = safe_snprintf(sdp, remaining, "o=ascii-chat %s %s IN IP4 0.0.0.0\r\n", offer_out->session_id,
98 offer_out->session_version);
99 sdp += written;
100 remaining -= written;
101
102 written = safe_snprintf(sdp, remaining, "s=-\r\n");
103 sdp += written;
104 remaining -= written;
105
106 written = safe_snprintf(sdp, remaining, "t=0 0\r\n");
107 sdp += written;
108 remaining -= written;
109
110 // Audio media section
111 written = safe_snprintf(sdp, remaining, "m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n");
112 sdp += written;
113 remaining -= written;
114
115 written = safe_snprintf(sdp, remaining, "a=rtpmap:111 opus/48000/2\r\n");
116 sdp += written;
117 remaining -= written;
118
119 written = safe_snprintf(sdp, remaining, "a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1\r\n");
120 sdp += written;
121 remaining -= written;
122
123 // Audio codec capabilities (custom attribute)
124 written = safe_snprintf(sdp, remaining, "a=codec-capabilities-audio:0x%x\r\n", audio_codec_caps);
125 sdp += written;
126 remaining -= written;
127
128 // Video media section with terminal capabilities
129 char codec_list[128] = "96";
130 size_t codec_pos = strlen(codec_list);
131 for (size_t i = 1; i < capability_count && codec_pos < sizeof(codec_list) - 1; i++) {
132 codec_pos += safe_snprintf(codec_list + codec_pos, sizeof(codec_list) - codec_pos, " %d", 96 + (int)i);
133 }
134
135 written = safe_snprintf(sdp, remaining, "m=video 9 UDP/TLS/RTP/SAVPF %s\r\n", codec_list);
136 sdp += written;
137 remaining -= written;
138
139 // Add rtpmap and fmtp for each capability
140 for (size_t i = 0; i < capability_count; i++) {
141 int pt = 96 + (int)i;
142 const char *codec_name = sdp_codec_name(capabilities[i].codec);
143
144 written = safe_snprintf(sdp, remaining, "a=rtpmap:%d %s/90000\r\n", pt, codec_name);
145 sdp += written;
146 remaining -= written;
147
148 // Format parameters
149 const terminal_format_params_t *cap_format = &capabilities[i].format;
150 const char *renderer_name = sdp_renderer_name(cap_format->renderer);
151 const char *charset_name = (cap_format->charset == CHARSET_UTF8) ? "utf8" : "ascii";
152 const char *compression_name = "none";
153 if (cap_format->compression == COMPRESSION_RLE) {
154 compression_name = "rle";
155 } else if (cap_format->compression == COMPRESSION_ZSTD) {
156 compression_name = "zstd";
157 }
158
159 written = safe_snprintf(sdp, remaining,
160 "a=fmtp:%d width=%u;height=%u;renderer=%s;charset=%s;compression=%s;csi_rep=%d\r\n", pt,
161 cap_format->width, cap_format->height, renderer_name, charset_name, compression_name,
162 cap_format->csi_rep_support ? 1 : 0);
163 sdp += written;
164 remaining -= written;
165 }
166
167 // Video codec capabilities (custom attribute)
168 written = safe_snprintf(sdp, remaining, "a=codec-capabilities-video:0x%x\r\n", video_codec_caps);
169 sdp += written;
170 remaining -= written;
171
172 offer_out->sdp_length = strlen(offer_out->sdp_string);
173
174 log_debug("SDP: Generated offer with %zu video codecs and Opus audio", capability_count);
175
176 return ASCIICHAT_OK;
177}

References ASCIICHAT_OK, sdp_session_t::audio_config, terminal_format_params_t::charset, sdp_session_t::codec_capabilities_audio, sdp_session_t::codec_capabilities_video, terminal_format_params_t::compression, terminal_format_params_t::csi_rep_support, ERROR_INVALID_PARAM, terminal_capability_t::format, sdp_session_t::has_audio, sdp_session_t::has_video, terminal_format_params_t::height, log_debug, terminal_format_params_t::renderer, SAFE_MALLOC, safe_snprintf(), sdp_codec_name(), sdp_session_t::sdp_length, sdp_renderer_name(), sdp_session_t::sdp_string, sdp_session_t::session_id, sdp_session_t::session_version, SET_ERRNO, sdp_session_t::video_codec_count, sdp_session_t::video_codecs, sdp_session_t::video_format, and terminal_format_params_t::width.

◆ 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 
)

Extract selected video codec from SDP answer.

Determines which terminal rendering capability the peer selected.

Parameters
answerSDP answer from peer
selected_codecSelected codec (output)
selected_formatFormat parameters (output)
Returns
ASCIICHAT_OK if valid selection found, error otherwise

Definition at line 664 of file sdp.c.

665 {
666 if (!answer || !selected_codec || !selected_format) {
667 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for codec selection");
668 }
669
670 if (!answer->has_video || !answer->video_codecs || answer->video_codec_count == 0) {
671 return SET_ERRNO(ERROR_NOT_FOUND, "No video codec in answer");
672 }
673
674 // In SDP answer, server selects only ONE codec (server's preference)
675 // The first codec in the answer is the selected one
676 terminal_capability_t *selected_cap = &answer->video_codecs[0];
677
678 *selected_codec = selected_cap->codec;
679 memcpy(selected_format, &selected_cap->format, sizeof(terminal_format_params_t));
680
681 log_debug("SDP: Selected video codec %s with resolution %ux%u", sdp_codec_name(selected_cap->codec),
682 selected_cap->format.width, selected_cap->format.height);
683
684 return ASCIICHAT_OK;
685}
@ ERROR_NOT_FOUND

References ASCIICHAT_OK, terminal_capability_t::codec, ERROR_INVALID_PARAM, ERROR_NOT_FOUND, terminal_capability_t::format, sdp_session_t::has_video, terminal_format_params_t::height, log_debug, sdp_codec_name(), SET_ERRNO, sdp_session_t::video_codec_count, sdp_session_t::video_codecs, and terminal_format_params_t::width.

◆ sdp_parse()

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

Parse SDP offer or answer.

Parses an SDP string and extracts audio/video configurations.

Parameters
sdp_stringSDP string (null-terminated)
sessionParsed session (output)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 479 of file sdp.c.

479 {
480 if (!sdp_string || !session) {
481 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid SDP parse parameters");
482 }
483
484 memset(session, 0, sizeof(sdp_session_t));
485
486 // Copy original SDP string for reference
487 SAFE_STRNCPY(session->sdp_string, sdp_string, sizeof(session->sdp_string));
488 session->sdp_length = strlen(sdp_string);
489
490 // Parse line by line
491 char line_buffer[512];
492 SAFE_STRNCPY(line_buffer, sdp_string, sizeof(line_buffer));
493
494 char *saveptr = NULL;
495 char *line = platform_strtok_r(line_buffer, "\r\n", &saveptr);
496
497 int in_audio_section = 0;
498 int in_video_section = 0;
499 size_t video_codec_index = 0;
500
501 while (line) {
502 // Parse line format: "key=value"
503 char *equals = strchr(line, '=');
504 if (!equals) {
505 line = platform_strtok_r(NULL, "\r\n", &saveptr);
506 continue;
507 }
508
509 char key = line[0];
510 const char *value = equals + 1;
511
512 switch (key) {
513 case 'v':
514 // v=0 (version)
515 break;
516
517 case 'o':
518 // o=username session_id session_version ... session_id ...
519 // Parse session ID (second field)
520 {
521 char o_copy[256];
522 SAFE_STRNCPY(o_copy, value, sizeof(o_copy));
523 char *o_saveptr = NULL;
524 platform_strtok_r(o_copy, " ", &o_saveptr); // username
525 char *session_id_str = platform_strtok_r(NULL, " ", &o_saveptr);
526 if (session_id_str) {
527 SAFE_STRNCPY(session->session_id, session_id_str, sizeof(session->session_id));
528 }
529 }
530 break;
531
532 case 's':
533 // s=session_name (typically "-" or empty)
534 break;
535
536 case 'm':
537 // m=audio 9 UDP/TLS/RTP/SAVPF ...
538 // m=video 9 UDP/TLS/RTP/SAVPF ...
539 in_audio_section = 0;
540 in_video_section = 0;
541
542 if (strstr(value, "audio")) {
543 in_audio_section = 1;
544 session->has_audio = true;
545 } else if (strstr(value, "video")) {
546 in_video_section = 1;
547 session->has_video = true;
548 video_codec_index = 0;
549 }
550 break;
551
552 case 'a':
553 // a=rtpmap:111 opus/48000/2
554 // a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1
555 // a=rtpmap:96 ACIP-TC/90000
556 // a=fmtp:96 ...
557 // a=codec-capabilities-video:0x...
558 // a=codec-capabilities-audio:0x...
559
560 if (strstr(value, "codec-capabilities-video:")) {
561 // Parse video codec capabilities: "codec-capabilities-video:0xHH"
562 const char *cap_str = value + strlen("codec-capabilities-video:");
563 session->codec_capabilities_video = (uint32_t)strtol(cap_str, NULL, 16);
564 log_debug("SDP: Parsed video codec capabilities: 0x%x", session->codec_capabilities_video);
565 } else if (strstr(value, "codec-capabilities-audio:")) {
566 // Parse audio codec capabilities: "codec-capabilities-audio:0xHH"
567 const char *cap_str = value + strlen("codec-capabilities-audio:");
568 session->codec_capabilities_audio = (uint32_t)strtol(cap_str, NULL, 16);
569 log_debug("SDP: Parsed audio codec capabilities: 0x%x", session->codec_capabilities_audio);
570 } else if (strstr(value, "rtpmap:")) {
571 const char *rtpmap = value + 7; // Skip "rtpmap:"
572
573 // Parse: PT codec/rate[/channels]
574 char *endptr = NULL;
575 int pt = (int)strtol(rtpmap, &endptr, 10);
576 char codec_name[32] = {0};
577 if (endptr && *endptr == ' ') {
578 SAFE_STRNCPY(codec_name, endptr + 1, sizeof(codec_name));
579 }
580
581 if (in_audio_section && pt == 111 && strstr(codec_name, "opus")) {
582 // Opus audio
583 session->audio_config.sample_rate = 48000;
584 session->audio_config.channels = 2;
585 } else if (in_video_section && video_codec_index < 4) {
586 // Terminal capability codec
587 if (!session->video_codecs) {
589 }
590
591 terminal_capability_t *cap = &session->video_codecs[video_codec_index];
592 memset(cap, 0, sizeof(terminal_capability_t));
593
594 if (pt == 96 && strstr(codec_name, "ACIP-TC")) {
596 video_codec_index++;
597 session->video_codec_count++;
598 } else if (pt == 97 && strstr(codec_name, "ACIP-256")) {
600 video_codec_index++;
601 session->video_codec_count++;
602 } else if (pt == 98 && strstr(codec_name, "ACIP-16")) {
604 video_codec_index++;
605 session->video_codec_count++;
606 } else if (pt == 99 && strstr(codec_name, "ACIP-MONO")) {
607 cap->codec = ACIP_CODEC_MONO;
608 video_codec_index++;
609 session->video_codec_count++;
610 }
611 }
612 } else if (strstr(value, "fmtp:")) {
613 // Parse format parameters
614 // a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1
615 // a=fmtp:96 width=80;height=24;renderer=block;charset=utf8;compression=rle;csi_rep=1
616
617 const char *fmtp = value + 5; // Skip "fmtp:"
618
619 if (in_audio_section) {
620 // Parse Opus parameters
621 if (strstr(fmtp, "useinbandfec=1")) {
622 session->audio_config.fec_enabled = true;
623 }
624 if (strstr(fmtp, "usedtx=1")) {
625 session->audio_config.dtx_enabled = true;
626 }
627 // Default values for Opus
628 session->audio_config.bitrate = 24000;
629 session->audio_config.frame_duration = 20;
630 } else if (in_video_section && video_codec_index > 0) {
631 // Parse terminal format parameters
632 terminal_capability_t *cap = &session->video_codecs[video_codec_index - 1];
633
634 // Skip PT number in fmtp string (format: "96 width=...")
635 const char *params = fmtp;
636 while (*params && *params != ' ') {
637 params++;
638 }
639 if (*params == ' ') {
640 params++; // Skip space after PT
641 }
642
643 // Parse with PCRE2 regex (atomic extraction)
644 sdp_parse_fmtp_video_pcre2(params, cap);
645 }
646 }
647 break;
648
649 default:
650 // Ignore unknown SDP attributes
651 break;
652 }
653
654 line = platform_strtok_r(NULL, "\r\n", &saveptr);
655 }
656
657 return ASCIICHAT_OK;
658}
char ** line_buffer
Circular buffer for context_before.
Definition grep.c:89
unsigned int uint32_t
Definition common.h:58
char * platform_strtok_r(char *str, const char *delim, char **saveptr)
Thread-safe string tokenization (strtok_r replacement)
bool dtx_enabled
Discontinuous Transmission (silence suppression)
Definition sdp.h:63
bool fec_enabled
Forward Error Correction for lossy networks.
Definition sdp.h:64
uint32_t sample_rate
48000 Hz (Opus native rate)
Definition sdp.h:59
uint32_t bitrate
24000 bps (good quality for speech)
Definition sdp.h:61
uint16_t frame_duration
20 ms (balance latency/efficiency)
Definition sdp.h:62
uint8_t channels
1 (mono for voice chat)
Definition sdp.h:60

References ACIP_CODEC_16COLOR, ACIP_CODEC_256COLOR, ACIP_CODEC_MONO, ACIP_CODEC_TRUECOLOR, ASCIICHAT_OK, sdp_session_t::audio_config, opus_config_t::bitrate, opus_config_t::channels, terminal_capability_t::codec, sdp_session_t::codec_capabilities_audio, sdp_session_t::codec_capabilities_video, opus_config_t::dtx_enabled, ERROR_INVALID_PARAM, opus_config_t::fec_enabled, opus_config_t::frame_duration, sdp_session_t::has_audio, sdp_session_t::has_video, line_buffer, log_debug, platform_strtok_r(), SAFE_MALLOC, SAFE_STRNCPY, opus_config_t::sample_rate, sdp_session_t::sdp_length, sdp_session_t::sdp_string, sdp_session_t::session_id, SET_ERRNO, sdp_session_t::video_codec_count, and sdp_session_t::video_codecs.

◆ sdp_renderer_name()

const char * sdp_renderer_name ( int  renderer_type)

Get human-readable renderer name.

Returns
Static string (e.g., "block", "halfblock")

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)

Free SDP session resources.

Parameters
sessionSession to free

Definition at line 795 of file sdp.c.

795 {
796 if (!session) {
797 return;
798 }
799
800 // Free allocated video codec array
801 if (session->video_codecs) {
802 SAFE_FREE(session->video_codecs);
803 session->video_codec_count = 0;
804 }
805
806 // Zero out the session structure
807 memset(session, 0, sizeof(sdp_session_t));
808}
#define SAFE_FREE(ptr)
Definition common.h:376

References SAFE_FREE, sdp_session_t::video_codec_count, and sdp_session_t::video_codecs.