ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
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...

Macros

#define NETWORK_WEBRTC_SDP_SESSION_ID_BUFFER_SIZE   32
#define NETWORK_WEBRTC_SDP_SESSION_VERSION_BUFFER_SIZE   16
#define NETWORK_WEBRTC_SDP_SDP_STRING_BUFFER_SIZE   4096

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)
 SDP media session (simplified for WebRTC).
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.

Macro Definition Documentation

◆ NETWORK_WEBRTC_SDP_SDP_STRING_BUFFER_SIZE

#define NETWORK_WEBRTC_SDP_SDP_STRING_BUFFER_SIZE   4096

Definition at line 41 of file sdp.h.

◆ NETWORK_WEBRTC_SDP_SESSION_ID_BUFFER_SIZE

#define NETWORK_WEBRTC_SDP_SESSION_ID_BUFFER_SIZE   32

Definition at line 39 of file sdp.h.

◆ NETWORK_WEBRTC_SDP_SESSION_VERSION_BUFFER_SIZE

#define NETWORK_WEBRTC_SDP_SESSION_VERSION_BUFFER_SIZE   16

Definition at line 40 of 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 85 of file sdp.h.

85 {
89 ACIP_CODEC_MONO = 99,
acip_codec_t
Terminal rendering capability payload types.
Definition sdp.h:85
@ ACIP_CODEC_16COLOR
16-color (ANSI standard)
Definition sdp.h:88
@ ACIP_CODEC_MONO
Monochrome (ASCII only).
Definition sdp.h:89
@ ACIP_CODEC_256COLOR
256-color (xterm palette)
Definition sdp.h:87
@ ACIP_CODEC_TRUECOLOR
24-bit RGB (truecolor)
Definition sdp.h:86

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 33 of file sdp.c.

33 {
34 switch (codec) {
36 return "ACIP-TC";
38 return "ACIP-256";
40 return "ACIP-16";
41 case ACIP_CODEC_MONO:
42 return "ACIP-MONO";
43 default:
44 return "UNKNOWN";
45 }
46}

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 698 of file sdp.c.

699 {
700 if (!capabilities || capability_count == 0 || !detected_count) {
701 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid capability detection parameters");
702 }
703
704 *detected_count = 0;
705 memset(capabilities, 0, capability_count * sizeof(terminal_capability_t));
706
707 // Detect terminal color level using platform terminal module
708 const char *colorterm = SAFE_GETENV("COLORTERM");
709 const char *term = SAFE_GETENV("TERM");
710 terminal_color_mode_t color_level = TERM_COLOR_NONE;
711
712 if (colorterm && (strcmp(colorterm, "truecolor") == 0 || strcmp(colorterm, "24bit") == 0)) {
713 color_level = TERM_COLOR_TRUECOLOR;
714 } else if (term) {
715 if (strstr(term, "256color") || strstr(term, "256")) {
716 color_level = TERM_COLOR_256;
717 } else if (strstr(term, "color") || strcmp(term, "xterm") == 0) {
718 color_level = TERM_COLOR_16;
719 }
720 }
721
722 // Detect UTF-8 support from LANG environment variable
723 const char *lang = SAFE_GETENV("LANG");
724 bool has_utf8 = (lang && (strstr(lang, "UTF-8") || strstr(lang, "utf8")));
725
726 // CSI REP support correlates with UTF-8
727 bool has_csi_rep = has_utf8;
728
729 // Get terminal size from platform module
730 uint16_t term_width = 80; // default
731 uint16_t term_height = 24; // default
732
733 terminal_size_t term_size;
734 asciichat_error_t term_err = terminal_get_size(&term_size);
735 if (term_err == ASCIICHAT_OK) {
736 term_width = (uint16_t)term_size.cols;
737 term_height = (uint16_t)term_size.rows;
738 } else {
739 log_debug("SDP: Using default terminal size %ux%u", term_width, term_height);
740 }
741
742 // Fill capabilities array based on detected color level (preference order)
743 size_t idx = 0;
744
745 if (color_level >= TERM_COLOR_TRUECOLOR && idx < capability_count) {
746 capabilities[idx].codec = ACIP_CODEC_TRUECOLOR;
747 capabilities[idx].format.width = term_width;
748 capabilities[idx].format.height = term_height;
749 capabilities[idx].format.renderer = RENDERER_BLOCK;
750 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
751 capabilities[idx].format.compression = COMPRESSION_RLE;
752 capabilities[idx].format.csi_rep_support = has_csi_rep;
753 idx++;
754 }
755
756 if (color_level >= TERM_COLOR_256 && idx < capability_count) {
757 capabilities[idx].codec = ACIP_CODEC_256COLOR;
758 capabilities[idx].format.width = term_width;
759 capabilities[idx].format.height = term_height;
760 capabilities[idx].format.renderer = RENDERER_BLOCK;
761 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
762 capabilities[idx].format.compression = COMPRESSION_RLE;
763 capabilities[idx].format.csi_rep_support = has_csi_rep;
764 idx++;
765 }
766
767 if (color_level >= TERM_COLOR_16 && idx < capability_count) {
768 capabilities[idx].codec = ACIP_CODEC_16COLOR;
769 capabilities[idx].format.width = term_width;
770 capabilities[idx].format.height = term_height;
771 capabilities[idx].format.renderer = RENDERER_BLOCK;
772 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
773 capabilities[idx].format.compression = COMPRESSION_NONE;
774 capabilities[idx].format.csi_rep_support = has_csi_rep;
775 idx++;
776 }
777
778 // Monochrome always supported
779 if (idx < capability_count) {
780 capabilities[idx].codec = ACIP_CODEC_MONO;
781 capabilities[idx].format.width = term_width;
782 capabilities[idx].format.height = term_height;
783 capabilities[idx].format.renderer = RENDERER_BLOCK;
784 capabilities[idx].format.charset = has_utf8 ? CHARSET_UTF8 : CHARSET_ASCII;
785 capabilities[idx].format.compression = COMPRESSION_NONE;
786 capabilities[idx].format.csi_rep_support = has_csi_rep;
787 idx++;
788 }
789
790 *detected_count = idx;
791
792 log_debug("SDP: Detected %zu terminal capabilities (colors=%d, utf8=%s, csi_rep=%s, size=%ux%u)", *detected_count,
793 color_level, has_utf8 ? "yes" : "no", has_csi_rep ? "yes" : "no", term_width, term_height);
794
795 return ASCIICHAT_OK;
796}
#define SET_ERRNO(code,...)
unsigned short uint16_t
Definition common.h:61
#define SAFE_GETENV(name)
Definition common.h:449
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
Supported terminal capability (for offer/answer).
Definition sdp.h:108
terminal_format_params_t format
Format parameters.
Definition sdp.h:110
acip_codec_t codec
Rendering capability type.
Definition sdp.h:109
enum terminal_format_params_t::@033245003045027275006271166040003043002061260227 compression
bool csi_rep_support
CSI REP (repeat) support.
Definition sdp.h:101
enum terminal_format_params_t::@266046313330162021345135012376004106262220314124 renderer
enum terminal_format_params_t::@332210022076263272012344026361040017317251215266 charset
uint16_t height
Terminal height in characters.
Definition sdp.h:97
uint16_t width
Terminal width in characters.
Definition sdp.h:96

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_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, SAFE_GETENV, SET_ERRNO, 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 192 of file sdp.c.

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

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 )

SDP media session (simplified for WebRTC).

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 65 of file sdp.c.

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

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, NETWORK_WEBRTC_SDP_CODEC_LIST_BUFFER_SIZE, 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 671 of file sdp.c.

672 {
673 if (!answer || !selected_codec || !selected_format) {
674 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters for codec selection");
675 }
676
677 if (!answer->has_video || !answer->video_codecs || answer->video_codec_count == 0) {
678 return SET_ERRNO(ERROR_NOT_FOUND, "No video codec in answer");
679 }
680
681 // In SDP answer, server selects only ONE codec (server's preference)
682 // The first codec in the answer is the selected one
683 terminal_capability_t *selected_cap = &answer->video_codecs[0];
684
685 *selected_codec = selected_cap->codec;
686 memcpy(selected_format, &selected_cap->format, sizeof(terminal_format_params_t));
687
688 log_debug("SDP: Selected video codec %s with resolution %ux%u", sdp_codec_name(selected_cap->codec),
689 selected_cap->format.width, selected_cap->format.height);
690
691 return ASCIICHAT_OK;
692}
@ 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 485 of file sdp.c.

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

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, NETWORK_WEBRTC_SDP_CODEC_NAME_BUFFER_SIZE, NETWORK_WEBRTC_SDP_O_COPY_BUFFER_SIZE, SAFE_MALLOC, SAFE_STRNCPY, opus_config_t::sample_rate, sdp_session_t::sdp_length, sdp_session_t::sdp_string, SDP_VIDEO_CODEC_CAPACITY, 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 48 of file sdp.c.

48 {
49 switch (renderer_type) {
50 case RENDERER_BLOCK:
51 return "block";
52 case RENDERER_HALFBLOCK:
53 return "halfblock";
54 case RENDERER_BRAILLE:
55 return "braille";
56 default:
57 return "unknown";
58 }
59}

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 802 of file sdp.c.

802 {
803 if (!session) {
804 return;
805 }
806
807 // Free allocated video codec array
808 if (session->video_codecs) {
809 SAFE_FREE(session->video_codecs);
810 session->video_codec_count = 0;
811 }
812
813 // Zero out the session structure
814 memset(session, 0, sizeof(sdp_session_t));
815}
#define SAFE_FREE(ptr)
Definition common.h:377

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