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>
122 log_info(
"session_participant_create: START - config=%p", config);
125 log_error(
"session_participant_create: config is NULL");
126 SET_ERRNO(ERROR_INVALID_PARAM,
"session_participant_create: NULL config");
132 log_info(
"session_participant_create: allocated p=%p", p);
135 if (config->address) {
141 p->
port = config->port > 0 ? config->port : OPT_PORT_INT_DEFAULT;
144 log_info(
"session_participant_create: address=%s, port=%d", p->
address, p->
port);
146 if (config->password) {
150 if (config->server_key) {
160 p->
socket = INVALID_SOCKET_VALUE;
171 log_info(
"session_participant_create: DONE - returning p=%p (initialized=%d, connected=%d)", p, p->
initialized,
209 if (p->
socket != INVALID_SOCKET_VALUE) {
211 p->
socket = INVALID_SOCKET_VALUE;
230static socket_t connect_to_server(
const char *address,
int port) {
231 struct addrinfo hints, *result = NULL, *rp = NULL;
234 memset(&hints, 0,
sizeof(hints));
235 hints.ai_family = AF_INET;
236 hints.ai_socktype = SOCK_STREAM;
237 hints.ai_protocol = IPPROTO_TCP;
241 int s = getaddrinfo(address, port_str, &hints, &result);
243 SET_ERRNO(ERROR_NETWORK,
"getaddrinfo failed: %s", gai_strerror(s));
244 return INVALID_SOCKET_VALUE;
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) {
254 if (connect(sock, rp->ai_addr, (
int)rp->ai_addrlen) == 0) {
259 sock = INVALID_SOCKET_VALUE;
262 freeaddrinfo(result);
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;
273 log_debug(
"session_participant_connect: enter - p=%p", p);
276 log_debug(
"session_participant_connect: invalid param or not initialized");
277 return SET_ERRNO(ERROR_INVALID_PARAM,
"session_participant_connect: invalid participant");
280 log_debug(
"session_participant_connect: p->initialized=%d, p->connected=%d", p->
initialized, p->
connected);
283 log_debug(
"session_participant_connect: already connected, returning OK");
288 log_debug(
"session_participant_connect: calling connect_to_server(%s, %d)", p->
address, p->
port);
290 log_debug(
"session_participant_connect: connect_to_server returned socket=%d (INVALID=%d)", p->
socket,
291 INVALID_SOCKET_VALUE);
293 if (p->
socket == INVALID_SOCKET_VALUE) {
294 log_error(
"Failed to connect to server at %s:%d", p->
address, p->
port);
296 p->
callbacks.on_error(p, ERROR_NETWORK_CONNECT,
"Failed to connect to server", p->
user_data);
301 log_debug(
"session_participant_connect: setting p->connected=true");
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);
313 log_debug(
"session_participant_connect: returning ASCIICHAT_OK");
335 if (p->
socket != INVALID_SOCKET_VALUE) {
337 p->
socket = INVALID_SOCKET_VALUE;
365 return INVALID_SOCKET_VALUE;
376 return SET_ERRNO(ERROR_INVALID_PARAM,
"session_participant_start_video: invalid participant");
380 return SET_ERRNO(ERROR_INVALID_STATE,
"session_participant_start_video: not connected");
388 return SET_ERRNO(ERROR_INVALID_STATE,
"session_participant_start_video: video not enabled");
420 return SET_ERRNO(ERROR_INVALID_PARAM,
"session_participant_start_audio: invalid participant");
424 return SET_ERRNO(ERROR_INVALID_STATE,
"session_participant_start_audio: not connected");
432 return SET_ERRNO(ERROR_INVALID_STATE,
"session_participant_start_audio: audio not enabled");
468 return SET_ERRNO(ERROR_INVALID_PARAM,
"session_participant_get_settings: invalid parameter");
471 memcpy(settings, &p->
settings,
sizeof(session_settings_t));
477 return SET_ERRNO(ERROR_INVALID_PARAM,
"session_participant_request_settings: invalid parameter");
481 return SET_ERRNO(ERROR_INVALID_STATE,
"session_participant_request_settings: not connected");
486 return SET_ERRNO(ERROR_NOT_SUPPORTED,
"session_participant_request_settings: not implemented yet");
500static void *participant_video_capture_thread(
void *arg) {
503 SET_ERRNO(ERROR_INVALID_PARAM,
"participant_video_capture_thread: invalid participant or video_capture context");
507 log_info(
"Video capture thread started");
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);
533 log_info(
"Video capture thread stopped");
544static void *participant_audio_capture_thread(
void *arg) {
547 SET_ERRNO(ERROR_INVALID_PARAM,
548 "participant_audio_capture_thread: invalid participant, audio_capture, or opus_encoder");
552 log_info(
"Audio capture thread started");
554 float sample_buffer[960];
555 uint8_t opus_buffer[1000];
560 if (samples_read <= 0) {
569 uint16_t frame_sizes[1] = {(uint16_t)opus_len};
570 asciichat_error_t err =
572 if (err != ASCIICHAT_OK) {
573 log_warn_every(5 * US_PER_SEC_INT,
"Failed to send audio packet: %d", err);
578 log_info(
"Audio capture thread stopped");
588 return SET_ERRNO(ERROR_INVALID_PARAM,
"session_participant_start_video_capture: invalid participant");
592 return SET_ERRNO(ERROR_INVALID_STATE,
"session_participant_start_video_capture: not connected");
596 return SET_ERRNO(ERROR_INVALID_STATE,
"session_participant_start_video_capture: video not enabled");
605 session_capture_config_t config = {
606 .type = MEDIA_SOURCE_WEBCAM,
609 .resize_for_network =
true,
613 return SET_ERRNO(ERROR_INVALID_STATE,
"Failed to create video capture context");
620 log_error(
"Failed to spawn video capture thread");
622 return SET_ERRNO(ERROR_THREAD,
"Failed to spawn video capture thread");
625 log_info(
"Video capture started");
644 log_info(
"Video capture stopped");
649 return SET_ERRNO(ERROR_INVALID_PARAM,
"session_participant_start_audio_capture: invalid participant");
653 return SET_ERRNO(ERROR_INVALID_STATE,
"session_participant_start_audio_capture: not connected");
657 return SET_ERRNO(ERROR_INVALID_STATE,
"session_participant_start_audio_capture: audio not enabled");
668 return SET_ERRNO(ERROR_INVALID_STATE,
"Failed to create audio capture context");
674 if (err != ASCIICHAT_OK) {
675 return SET_ERRNO(err,
"Failed to start audio duplex");
683 return SET_ERRNO(ERROR_INVALID_STATE,
"Failed to create Opus encoder");
690 log_error(
"Failed to spawn audio capture thread");
692 return SET_ERRNO(ERROR_THREAD,
"Failed to spawn audio capture thread");
695 log_info(
"Audio capture started");
719 log_info(
"Audio capture stopped");
728 return SET_ERRNO(ERROR_INVALID_PARAM,
"Participant is NULL or not initialized");
731 log_info(
"session_participant_set_transport: Setting transport=%p (was=%p)", transport, p->
transport);
736 log_info(
"WebRTC transport now active for participant");
738 log_info(
"WebRTC transport cleared, reverting to socket");
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)
void opus_codec_destroy(opus_codec_t *codec)
size_t opus_codec_encode(opus_codec_t *codec, const float *samples, int num_samples, uint8_t *out_data, size_t out_size)
asciichat_error_t send_image_frame_packet(socket_t sockfd, const void *image_data, uint16_t width, uint16_t height, uint8_t format)
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)
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 session_settings_init(session_settings_t *settings)
Internal session participant structure.
session_settings_t settings
Current session settings.
bool connected
Currently connected.
struct acip_transport * transport
Alternative transport (WebRTC, WebSocket, etc.) - NULL if using socket only.
asciichat_thread_t video_capture_thread
Video capture thread handle.
bool audio_active
Audio streaming active.
opus_codec_t * opus_encoder
Opus encoder for audio compression.
bool encryption_enabled
Encryption enabled.
char password[BUFFER_SIZE_SMALL]
Password (if any)
socket_t socket
Connection socket.
bool enable_audio
Audio enabled.
char address[BUFFER_SIZE_SMALL]
Server address.
asciichat_thread_t audio_capture_thread
Audio capture thread handle.
char server_key[BUFFER_SIZE_MEDIUM]
Server key for verification (if any)
session_capture_ctx_t * video_capture
Video capture context (for webcam/file media)
bool video_active
Video streaming active.
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.
session_participant_callbacks_t callbacks
Event callbacks.
void * user_data
User data for callbacks.
session_audio_ctx_t * audio_capture
Audio capture context (for microphone)
uint32_t client_id
Assigned client ID.
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
void image_destroy(image_t *p)