ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
participant.c
Go to the documentation of this file.
1
16#include <ascii-chat/session/participant.h>
17#include <ascii-chat/common.h>
18#include <ascii-chat/common/buffer_sizes.h>
19#include <ascii-chat/options/options.h>
20#include <ascii-chat/asciichat_errno.h>
21#include <ascii-chat/platform/socket.h>
22#include <ascii-chat/platform/thread.h>
23#include <ascii-chat/platform/network.h>
24#include <ascii-chat/log/logging.h>
25#include <ascii-chat/session/capture.h>
26#include <ascii-chat/session/audio.h>
27#include <ascii-chat/network/packet.h>
28#include <ascii-chat/util/time.h>
29#include <ascii-chat/audio/opus_codec.h>
30
31#include <string.h>
32#include <stdio.h>
33
34/* ============================================================================
35 * Session Participant Context Structure
36 * ============================================================================ */
37
43typedef struct session_participant {
45 char address[BUFFER_SIZE_SMALL];
46
48 int port;
49
52
54 char password[BUFFER_SIZE_SMALL];
55
57 char server_key[BUFFER_SIZE_MEDIUM];
58
61
64
66 session_participant_callbacks_t callbacks;
67
69 void *user_data;
70
73
75 struct acip_transport *transport;
76
79
81 uint32_t client_id;
82
85
88
90 session_settings_t settings;
91
93 session_capture_ctx_t *video_capture;
94
96 session_audio_ctx_t *audio_capture;
97
99 asciichat_thread_t video_capture_thread;
100
102 asciichat_thread_t audio_capture_thread;
103
106
109
111 opus_codec_t *opus_encoder;
112
116
117/* ============================================================================
118 * Session Participant Lifecycle Functions
119 * ============================================================================ */
120
121session_participant_t *session_participant_create(const session_participant_config_t *config) {
122 log_info("session_participant_create: START - config=%p", config);
123
124 if (!config) {
125 log_error("session_participant_create: config is NULL");
126 SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_create: NULL config");
127 return NULL;
128 }
129
130 // Allocate participant
132 log_info("session_participant_create: allocated p=%p", p);
133
134 // Copy configuration
135 if (config->address) {
136 SAFE_STRNCPY(p->address, config->address, sizeof(p->address));
137 } else {
138 SAFE_STRNCPY(p->address, "127.0.0.1", sizeof(p->address));
139 }
140
141 p->port = config->port > 0 ? config->port : OPT_PORT_INT_DEFAULT;
142 p->encryption_enabled = config->encryption_enabled;
143
144 log_info("session_participant_create: address=%s, port=%d", p->address, p->port);
145
146 if (config->password) {
147 SAFE_STRNCPY(p->password, config->password, sizeof(p->password));
148 }
149
150 if (config->server_key) {
151 SAFE_STRNCPY(p->server_key, config->server_key, sizeof(p->server_key));
152 }
153
154 p->enable_audio = config->enable_audio;
155 p->enable_video = config->enable_video;
156 p->callbacks = config->callbacks;
157 p->user_data = config->user_data;
158
159 // Initialize socket to invalid
160 p->socket = INVALID_SOCKET_VALUE;
161 p->transport = NULL; // No alternative transport initially
162 p->connected = false;
163 p->client_id = 0;
164 p->video_active = false;
165 p->audio_active = false;
166
167 // Initialize settings
169
170 p->initialized = true;
171 log_info("session_participant_create: DONE - returning p=%p (initialized=%d, connected=%d)", p, p->initialized,
172 p->connected);
173 return p;
174}
175
177 if (!p) {
178 return;
179 }
180
181 // Disconnect if connected
182 if (p->connected) {
184 }
185
186 // Stop capture threads if running
187 if (p->video_capture_running) {
189 }
190 if (p->audio_capture_running) {
192 }
193
194 // Clean up media capture contexts
195 if (p->video_capture) {
197 p->video_capture = NULL;
198 }
199 if (p->audio_capture) {
201 p->audio_capture = NULL;
202 }
203 if (p->opus_encoder) {
205 p->opus_encoder = NULL;
206 }
207
208 // Close socket if open
209 if (p->socket != INVALID_SOCKET_VALUE) {
210 socket_close(p->socket);
211 p->socket = INVALID_SOCKET_VALUE;
212 }
213
214 // Clear sensitive data
215 memset(p->password, 0, sizeof(p->password));
216 memset(p->server_key, 0, sizeof(p->server_key));
217
218 p->initialized = false;
219 SAFE_FREE(p);
220}
221
222/* ============================================================================
223 * Session Participant Connection Functions
224 * ============================================================================ */
225
230static socket_t connect_to_server(const char *address, int port) {
231 struct addrinfo hints, *result = NULL, *rp = NULL;
232 char port_str[16];
233
234 memset(&hints, 0, sizeof(hints));
235 hints.ai_family = AF_INET; // IPv4
236 hints.ai_socktype = SOCK_STREAM;
237 hints.ai_protocol = IPPROTO_TCP;
238
239 safe_snprintf(port_str, sizeof(port_str), "%d", port);
240
241 int s = getaddrinfo(address, port_str, &hints, &result);
242 if (s != 0) {
243 SET_ERRNO(ERROR_NETWORK, "getaddrinfo failed: %s", gai_strerror(s));
244 return INVALID_SOCKET_VALUE;
245 }
246
247 socket_t sock = INVALID_SOCKET_VALUE;
248 for (rp = result; rp != NULL; rp = rp->ai_next) {
249 sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
250 if (sock == INVALID_SOCKET_VALUE) {
251 continue;
252 }
253
254 if (connect(sock, rp->ai_addr, (int)rp->ai_addrlen) == 0) {
255 break; // Success
256 }
257
258 socket_close(sock);
259 sock = INVALID_SOCKET_VALUE;
260 }
261
262 freeaddrinfo(result);
263
264 if (sock == INVALID_SOCKET_VALUE) {
265 SET_ERRNO_SYS(ERROR_NETWORK_CONNECT, "Failed to connect to %s:%d", address, port);
266 return INVALID_SOCKET_VALUE;
267 }
268
269 return sock;
270}
271
273 log_debug("session_participant_connect: enter - p=%p", p);
274
275 if (!p || !p->initialized) {
276 log_debug("session_participant_connect: invalid param or not initialized");
277 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_connect: invalid participant");
278 }
279
280 log_debug("session_participant_connect: p->initialized=%d, p->connected=%d", p->initialized, p->connected);
281
282 if (p->connected) {
283 log_debug("session_participant_connect: already connected, returning OK");
284 return ASCIICHAT_OK; // Already connected
285 }
286
287 // Create TCP connection to server
288 log_debug("session_participant_connect: calling connect_to_server(%s, %d)", p->address, p->port);
289 p->socket = connect_to_server(p->address, p->port);
290 log_debug("session_participant_connect: connect_to_server returned socket=%d (INVALID=%d)", p->socket,
291 INVALID_SOCKET_VALUE);
292
293 if (p->socket == INVALID_SOCKET_VALUE) {
294 log_error("Failed to connect to server at %s:%d", p->address, p->port);
295 if (p->callbacks.on_error) {
296 p->callbacks.on_error(p, ERROR_NETWORK_CONNECT, "Failed to connect to server", p->user_data);
297 }
298 return GET_ERRNO();
299 }
300
301 log_debug("session_participant_connect: setting p->connected=true");
302 p->connected = true;
303 p->client_id = 0; // Will be assigned by server
304
305 log_info("Connected to server at %s:%d (socket=%d)", p->address, p->port, p->socket);
306 log_debug("session_participant_connect: p->connected=%d after setting", p->connected);
307
308 // Invoke callback
309 if (p->callbacks.on_connected) {
310 p->callbacks.on_connected(p, p->client_id, p->user_data);
311 }
312
313 log_debug("session_participant_connect: returning ASCIICHAT_OK");
314 return ASCIICHAT_OK;
315}
316
318 if (!p || !p->initialized) {
319 return;
320 }
321
322 if (!p->connected) {
323 return;
324 }
325
326 // Stop media streams
327 if (p->video_active) {
329 }
330 if (p->audio_active) {
332 }
333
334 // Close socket
335 if (p->socket != INVALID_SOCKET_VALUE) {
336 socket_close(p->socket);
337 p->socket = INVALID_SOCKET_VALUE;
338 }
339
340 p->connected = false;
341 p->client_id = 0;
342
343 // Invoke callback
344 if (p->callbacks.on_disconnected) {
345 p->callbacks.on_disconnected(p, p->user_data);
346 }
347}
348
350 if (!p || !p->initialized) {
351 return false;
352 }
353 return p->connected;
354}
355
357 if (!p || !p->initialized || !p->connected) {
358 return 0;
359 }
360 return p->client_id;
361}
362
364 if (!p || !p->initialized) {
365 return INVALID_SOCKET_VALUE;
366 }
367 return p->socket;
368}
369
370/* ============================================================================
371 * Session Participant Media Control Functions
372 * ============================================================================ */
373
375 if (!p || !p->initialized) {
376 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_start_video: invalid participant");
377 }
378
379 if (!p->connected) {
380 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_video: not connected");
381 }
382
383 if (p->video_active) {
384 return ASCIICHAT_OK; // Already active
385 }
386
387 if (!p->enable_video) {
388 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_video: video not enabled");
389 }
390
391 // TODO: Start webcam capture thread using session_capture_ctx_t
392
393 p->video_active = true;
394 return ASCIICHAT_OK;
395}
396
398 if (!p || !p->initialized) {
399 return;
400 }
401
402 if (!p->video_active) {
403 return;
404 }
405
406 // TODO: Stop webcam capture thread
407
408 p->video_active = false;
409}
410
412 if (!p || !p->initialized) {
413 return false;
414 }
415 return p->video_active;
416}
417
419 if (!p || !p->initialized) {
420 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_start_audio: invalid participant");
421 }
422
423 if (!p->connected) {
424 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_audio: not connected");
425 }
426
427 if (p->audio_active) {
428 return ASCIICHAT_OK; // Already active
429 }
430
431 if (!p->enable_audio) {
432 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_audio: audio not enabled");
433 }
434
435 // TODO: Start audio using session_audio_ctx_t
436
437 p->audio_active = true;
438 return ASCIICHAT_OK;
439}
440
442 if (!p || !p->initialized) {
443 return;
444 }
445
446 if (!p->audio_active) {
447 return;
448 }
449
450 // TODO: Stop audio
451
452 p->audio_active = false;
453}
454
456 if (!p || !p->initialized) {
457 return false;
458 }
459 return p->audio_active;
460}
461
462/* ============================================================================
463 * Session Participant Settings Functions
464 * ============================================================================ */
465
466asciichat_error_t session_participant_get_settings(session_participant_t *p, session_settings_t *settings) {
467 if (!p || !p->initialized || !settings) {
468 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_get_settings: invalid parameter");
469 }
470
471 memcpy(settings, &p->settings, sizeof(session_settings_t));
472 return ASCIICHAT_OK;
473}
474
475asciichat_error_t session_participant_request_settings(session_participant_t *p, const session_settings_t *settings) {
476 if (!p || !p->initialized || !settings) {
477 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_request_settings: invalid parameter");
478 }
479
480 if (!p->connected) {
481 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_request_settings: not connected");
482 }
483
484 // TODO: Send settings update request to server
485
486 return SET_ERRNO(ERROR_NOT_SUPPORTED, "session_participant_request_settings: not implemented yet");
487}
488
489/* ============================================================================
490 * Session Participant Media Capture Threads
491 * ============================================================================ */
492
500static void *participant_video_capture_thread(void *arg) {
502 if (!p || !p->video_capture) {
503 SET_ERRNO(ERROR_INVALID_PARAM, "participant_video_capture_thread: invalid participant or video_capture context");
504 return NULL;
505 }
506
507 log_info("Video capture thread started");
508
509 while (p->video_capture_running && p->connected) {
510 // Capture frame from media source (webcam, file, test pattern)
511 image_t *raw_frame = session_capture_read_frame(p->video_capture);
512 if (!raw_frame) {
514 continue;
515 }
516
517 // Process for network transmission (resize to bandwidth-optimal dimensions)
518 image_t *processed = session_capture_process_for_transmission(p->video_capture, raw_frame);
519 if (processed) {
520 // Send to host via IMAGE_FRAME packet
521 asciichat_error_t err = send_image_frame_packet(p->socket, (const void *)processed->pixels,
522 (uint16_t)processed->w, (uint16_t)processed->h, 0);
523 if (err != ASCIICHAT_OK) {
524 log_warn_every(5 * US_PER_SEC_INT, "Failed to send video frame: %d", err);
525 }
526 image_destroy(processed);
527 }
528
529 // Sleep to maintain target frame rate (adaptive based on source)
531 }
532
533 log_info("Video capture thread stopped");
534 return NULL;
535}
536
544static void *participant_audio_capture_thread(void *arg) {
546 if (!p || !p->audio_capture || !p->opus_encoder) {
547 SET_ERRNO(ERROR_INVALID_PARAM,
548 "participant_audio_capture_thread: invalid participant, audio_capture, or opus_encoder");
549 return NULL;
550 }
551
552 log_info("Audio capture thread started");
553
554 float sample_buffer[960]; // 20ms @ 48kHz
555 uint8_t opus_buffer[1000];
556
557 while (p->audio_capture_running && p->connected) {
558 // Read microphone samples (20ms chunk)
559 size_t samples_read = session_audio_read_captured(p->audio_capture, sample_buffer, 960);
560 if (samples_read <= 0) {
562 continue;
563 }
564
565 // Encode to Opus (lossy compression for bandwidth efficiency)
566 size_t opus_len =
567 opus_codec_encode(p->opus_encoder, sample_buffer, (int)samples_read, opus_buffer, sizeof(opus_buffer));
568 if (opus_len > 0) {
569 uint16_t frame_sizes[1] = {(uint16_t)opus_len};
570 asciichat_error_t err =
571 av_send_audio_opus_batch(p->socket, opus_buffer, opus_len, frame_sizes, 48000, 20, 1, NULL);
572 if (err != ASCIICHAT_OK) {
573 log_warn_every(5 * US_PER_SEC_INT, "Failed to send audio packet: %d", err);
574 }
575 }
576 }
577
578 log_info("Audio capture thread stopped");
579 return NULL;
580}
581
582/* ============================================================================
583 * Session Participant Media Capture Public API
584 * ============================================================================ */
585
587 if (!p || !p->initialized) {
588 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_start_video_capture: invalid participant");
589 }
590
591 if (!p->connected) {
592 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_video_capture: not connected");
593 }
594
595 if (!p->enable_video) {
596 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_video_capture: video not enabled");
597 }
598
599 if (p->video_capture_running) {
600 return ASCIICHAT_OK; // Already running
601 }
602
603 // Create video capture context if not already created
604 if (!p->video_capture) {
605 session_capture_config_t config = {
606 .type = MEDIA_SOURCE_WEBCAM,
607 .path = "0", // Default device
608 .target_fps = 60,
609 .resize_for_network = true, // Optimize for bandwidth
610 };
612 if (!p->video_capture) {
613 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to create video capture context");
614 }
615 }
616
617 // Spawn video capture thread (media source is ready in ctx)
618 p->video_capture_running = true;
619 if (asciichat_thread_create(&p->video_capture_thread, participant_video_capture_thread, p) != 0) {
620 log_error("Failed to spawn video capture thread");
621 p->video_capture_running = false;
622 return SET_ERRNO(ERROR_THREAD, "Failed to spawn video capture thread");
623 }
624
625 log_info("Video capture started");
626 return ASCIICHAT_OK;
627}
628
630 if (!p || !p->initialized) {
631 return;
632 }
633
634 if (!p->video_capture_running) {
635 return;
636 }
637
638 // Signal thread to stop
639 p->video_capture_running = false;
640
641 // Wait for thread to complete
643
644 log_info("Video capture stopped");
645}
646
648 if (!p || !p->initialized) {
649 return SET_ERRNO(ERROR_INVALID_PARAM, "session_participant_start_audio_capture: invalid participant");
650 }
651
652 if (!p->connected) {
653 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_audio_capture: not connected");
654 }
655
656 if (!p->enable_audio) {
657 return SET_ERRNO(ERROR_INVALID_STATE, "session_participant_start_audio_capture: audio not enabled");
658 }
659
660 if (p->audio_capture_running) {
661 return ASCIICHAT_OK; // Already running
662 }
663
664 // Create audio capture context if not already created
665 if (!p->audio_capture) {
666 p->audio_capture = session_audio_create(false); // false = participant mode (no mixing)
667 if (!p->audio_capture) {
668 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to create audio capture context");
669 }
670 }
671
672 // Start audio capture and playback
673 asciichat_error_t err = session_audio_start_duplex(p->audio_capture);
674 if (err != ASCIICHAT_OK) {
675 return SET_ERRNO(err, "Failed to start audio duplex");
676 }
677
678 // Create Opus encoder for audio compression (48kHz, VOIP mode, 24 kbps)
679 if (!p->opus_encoder) {
680 p->opus_encoder = opus_codec_create_encoder(OPUS_APPLICATION_VOIP, 48000, 24000);
681 if (!p->opus_encoder) {
683 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to create Opus encoder");
684 }
685 }
686
687 // Spawn audio capture thread
688 p->audio_capture_running = true;
689 if (asciichat_thread_create(&p->audio_capture_thread, participant_audio_capture_thread, p) != 0) {
690 log_error("Failed to spawn audio capture thread");
691 p->audio_capture_running = false;
692 return SET_ERRNO(ERROR_THREAD, "Failed to spawn audio capture thread");
693 }
694
695 log_info("Audio capture started");
696 return ASCIICHAT_OK;
697}
698
700 if (!p || !p->initialized) {
701 return;
702 }
703
704 if (!p->audio_capture_running) {
705 return;
706 }
707
708 // Signal thread to stop
709 p->audio_capture_running = false;
710
711 // Wait for thread to complete
713
714 // Stop audio streams
715 if (p->audio_capture) {
717 }
718
719 log_info("Audio capture stopped");
720}
721
722/* ============================================================================
723 * Session Participant Transport Functions (WebRTC Integration)
724 * ============================================================================ */
725
726asciichat_error_t session_participant_set_transport(session_participant_t *p, acip_transport_t *transport) {
727 if (!p || !p->initialized) {
728 return SET_ERRNO(ERROR_INVALID_PARAM, "Participant is NULL or not initialized");
729 }
730
731 log_info("session_participant_set_transport: Setting transport=%p (was=%p)", transport, p->transport);
732
733 p->transport = transport;
734
735 if (transport) {
736 log_info("WebRTC transport now active for participant");
737 } else {
738 log_info("WebRTC transport cleared, reverting to socket");
739 }
740
741 return ASCIICHAT_OK;
742}
743
745 if (!p || !p->initialized) {
746 return NULL;
747 }
748
749 return p->transport;
750}
751
753 if (!p) {
754 return false;
755 }
756
757 return p->transport != NULL;
758}
int socket_t
size_t session_audio_read_captured(session_audio_ctx_t *ctx, float *buffer, size_t num_samples)
void session_audio_destroy(session_audio_ctx_t *ctx)
asciichat_error_t session_audio_start_duplex(session_audio_ctx_t *ctx)
void session_audio_stop(session_audio_ctx_t *ctx)
session_audio_ctx_t * session_audio_create(bool is_host)
image_t * session_capture_process_for_transmission(session_capture_ctx_t *ctx, image_t *frame)
image_t * session_capture_read_frame(session_capture_ctx_t *ctx)
session_capture_ctx_t * session_capture_create(const session_capture_config_t *config)
void session_capture_sleep_for_fps(session_capture_ctx_t *ctx)
void session_capture_destroy(session_capture_ctx_t *ctx)
opus_codec_t * opus_codec_create_encoder(opus_application_t application, int sample_rate, int bitrate)
Definition opus_codec.c:18
void opus_codec_destroy(opus_codec_t *codec)
Definition opus_codec.c:215
size_t opus_codec_encode(opus_codec_t *codec, const float *samples, int num_samples, uint8_t *out_data, size_t out_size)
Definition opus_codec.c:97
asciichat_error_t send_image_frame_packet(socket_t sockfd, const void *image_data, uint16_t width, uint16_t height, uint8_t format)
Definition packet.c:1227
asciichat_error_t av_send_audio_opus_batch(socket_t sockfd, const uint8_t *opus_data, size_t opus_size, const uint16_t *frame_sizes, int sample_rate, int frame_duration, int frame_count, crypto_context_t *crypto_ctx)
Definition packet.c:1133
void session_participant_stop_video(session_participant_t *p)
bool session_participant_is_audio_active(session_participant_t *p)
struct session_participant session_participant_t
Internal session participant structure.
asciichat_error_t session_participant_connect(session_participant_t *p)
bool session_participant_is_video_active(session_participant_t *p)
void session_participant_stop_audio(session_participant_t *p)
uint32_t session_participant_get_client_id(session_participant_t *p)
bool session_participant_has_transport(session_participant_t *p)
acip_transport_t * session_participant_get_transport(session_participant_t *p)
asciichat_error_t session_participant_start_video_capture(session_participant_t *p)
asciichat_error_t session_participant_request_settings(session_participant_t *p, const session_settings_t *settings)
asciichat_error_t session_participant_start_video(session_participant_t *p)
void session_participant_stop_video_capture(session_participant_t *p)
void session_participant_destroy(session_participant_t *p)
socket_t session_participant_get_socket(session_participant_t *p)
session_participant_t * session_participant_create(const session_participant_config_t *config)
void session_participant_stop_audio_capture(session_participant_t *p)
asciichat_error_t session_participant_start_audio_capture(session_participant_t *p)
bool session_participant_is_connected(session_participant_t *p)
asciichat_error_t session_participant_set_transport(session_participant_t *p, acip_transport_t *transport)
void session_participant_disconnect(session_participant_t *p)
asciichat_error_t session_participant_get_settings(session_participant_t *p, session_settings_t *settings)
asciichat_error_t session_participant_start_audio(session_participant_t *p)
void platform_sleep_ms(unsigned int ms)
void session_settings_init(session_settings_t *settings)
Definition settings.c:26
Internal session participant structure.
Definition participant.c:43
session_settings_t settings
Current session settings.
Definition participant.c:90
bool connected
Currently connected.
Definition participant.c:78
struct acip_transport * transport
Alternative transport (WebRTC, WebSocket, etc.) - NULL if using socket only.
Definition participant.c:75
asciichat_thread_t video_capture_thread
Video capture thread handle.
Definition participant.c:99
bool audio_active
Audio streaming active.
Definition participant.c:87
opus_codec_t * opus_encoder
Opus encoder for audio compression.
bool encryption_enabled
Encryption enabled.
Definition participant.c:51
char password[BUFFER_SIZE_SMALL]
Password (if any)
Definition participant.c:54
socket_t socket
Connection socket.
Definition participant.c:72
int port
Server port.
Definition participant.c:48
bool enable_audio
Audio enabled.
Definition participant.c:60
char address[BUFFER_SIZE_SMALL]
Server address.
Definition participant.c:45
asciichat_thread_t audio_capture_thread
Audio capture thread handle.
char server_key[BUFFER_SIZE_MEDIUM]
Server key for verification (if any)
Definition participant.c:57
session_capture_ctx_t * video_capture
Video capture context (for webcam/file media)
Definition participant.c:93
bool video_active
Video streaming active.
Definition participant.c:84
bool initialized
Context is initialized.
bool video_capture_running
Video capture thread running flag.
bool audio_capture_running
Audio capture thread running flag.
bool enable_video
Video enabled.
Definition participant.c:63
session_participant_callbacks_t callbacks
Event callbacks.
Definition participant.c:66
void * user_data
User data for callbacks.
Definition participant.c:69
session_audio_ctx_t * audio_capture
Audio capture context (for microphone)
Definition participant.c:96
uint32_t client_id
Assigned client ID.
Definition participant.c:81
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
Definition threading.c:42
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
Definition threading.c:46
void image_destroy(image_t *p)
Definition video/image.c:85