ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
src/common/session/audio.c
Go to the documentation of this file.
1
12
13#include "session/audio.h"
14#include <ascii-chat/common.h>
19
20#include <string.h>
21
22/* ============================================================================
23 * Session Audio Context Structure
24 * ============================================================================ */
25
33#define SESSION_AUDIO_MAX_SOURCES MAX_SESSION_PARTICIPANTS
34
48
83
84/* ============================================================================
85 * Session Audio Lifecycle Functions
86 * ============================================================================ */
87
89 // Allocate context
91
92 ctx->is_host = is_host;
93 ctx->running = false;
94
95 // Initialize underlying audio context
97 if (result != ASCIICHAT_OK) {
98 log_error("Failed to initialize audio context: %d", result);
99 SAFE_FREE(ctx);
100 return NULL;
101 }
102
103 // Initialize mixing resources for host
104 if (is_host) {
105 // Allocate mix buffer (enough for one audio buffer worth)
106 ctx->mix_buffer_size = AUDIO_BUFFER_SIZE * 4; // Extra space for safety
107 ctx->mix_buffer = SAFE_CALLOC(ctx->mix_buffer_size, sizeof(float), float *);
108
109 // Initialize source array
110 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
111 ctx->sources[i].source_id = 0;
112 ctx->sources[i].active = false;
113 ctx->sources[i].buffer = NULL;
114 }
115 ctx->source_count = 0;
116 }
117
118 ctx->initialized = true;
119 return ctx;
120}
121
123 if (!ctx) {
124 return;
125 }
126
127 // Stop audio if running
128 if (ctx->running) {
130 }
131
132 // Cleanup host-specific resources
133 if (ctx->is_host) {
134 // Destroy source buffers
135 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
136 if (ctx->sources[i].buffer) {
138 ctx->sources[i].buffer = NULL;
139 }
140 }
141
142 // Free mix buffer
143 if (ctx->mix_buffer) {
144 SAFE_FREE(ctx->mix_buffer);
145 }
146 }
147
148 // Destroy underlying audio context
151
152 ctx->initialized = false;
153 SAFE_FREE(ctx);
154}
155
156/* ============================================================================
157 * Session Audio Control Functions
158 * ============================================================================ */
159
161 if (!ctx || !ctx->initialized) {
162 return SET_ERRNO(ERROR_INVALID_PARAM, "invalid context");
163 }
164
165 // Use full duplex for proper echo cancellation
166 return session_audio_start_duplex(ctx);
167}
168
170 if (!ctx || !ctx->initialized) {
171 return SET_ERRNO(ERROR_INVALID_PARAM, "invalid context");
172 }
173
174 // Use full duplex for proper echo cancellation
175 return session_audio_start_duplex(ctx);
176}
177
179 if (!ctx || !ctx->initialized) {
180 return SET_ERRNO(ERROR_INVALID_PARAM, "invalid context");
181 }
182
183 if (ctx->running) {
184 return ASCIICHAT_OK; // Already running
185 }
186
187 if (!ctx->pipeline) {
191 if (!ctx->pipeline)
192 return SET_ERRNO(ERROR_AUDIO, "Cannot initialize session audio processing");
194 }
196 if (result == ASCIICHAT_OK) {
197 ctx->running = true;
198 }
199
200 return result;
201}
202
204 if (!ctx || !ctx->initialized || !ctx->running) {
205 return;
206 }
207
208 (void)audio_stop_duplex(&ctx->audio_ctx);
209 ctx->running = false;
210}
211
213 if (!ctx || !ctx->initialized) {
214 return false;
215 }
216 return ctx->running;
217}
218
219/* ============================================================================
220 * Session Audio I/O Functions
221 * ============================================================================ */
222
223size_t session_audio_read_captured(session_audio_ctx_t *ctx, float *buffer, size_t num_samples) {
224 if (!ctx || !ctx->initialized || !buffer || num_samples == 0) {
225 return 0;
226 }
227
228 return audio_ring_buffer_read(ctx->audio_ctx.capture_buffer, buffer, num_samples);
229}
230
231asciichat_error_t session_audio_write_playback(session_audio_ctx_t *ctx, const float *buffer, size_t num_samples) {
232 if (!ctx || !ctx->initialized || !buffer) {
233 return SET_ERRNO(ERROR_INVALID_PARAM, "invalid parameter");
234 }
235
236 return audio_write_samples(&ctx->audio_ctx, buffer, (int)num_samples);
237}
238
239/* ============================================================================
240 * Session Audio Host-Only Functions (Mixing)
241 * ============================================================================ */
242
244 if (!ctx || !ctx->initialized) {
245 return SET_ERRNO(ERROR_INVALID_PARAM, "invalid context");
246 }
247
248 if (!ctx->is_host) {
249 return SET_ERRNO(ERROR_INVALID_STATE, "not a host context");
250 }
251
252 // Check if source already exists
253 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
254 if (ctx->sources[i].active && ctx->sources[i].source_id == source_id) {
255 return ASCIICHAT_OK; // Already registered
256 }
257 }
258
259 // Find empty slot
260 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
261 if (!ctx->sources[i].active) {
262 ctx->sources[i].source_id = source_id;
263 ctx->sources[i].active = true;
265 if (!ctx->sources[i].buffer) {
266 ctx->sources[i].active = false;
267 return SET_ERRNO(ERROR_MEMORY, "Failed to create audio buffer for source");
268 }
269 ctx->source_count++;
270 return ASCIICHAT_OK;
271 }
272 }
273
274 return SET_ERRNO(ERROR_RESOURCE_EXHAUSTED, "Maximum audio sources reached");
275}
276
278 if (!ctx || !ctx->initialized || !ctx->is_host) {
279 return;
280 }
281
282 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
283 if (ctx->sources[i].active && ctx->sources[i].source_id == source_id) {
284 if (ctx->sources[i].buffer) {
286 ctx->sources[i].buffer = NULL;
287 }
288 ctx->sources[i].active = false;
289 ctx->sources[i].source_id = 0;
290 ctx->source_count--;
291 return;
292 }
293 }
294}
295
297 size_t num_samples) {
298 if (!ctx || !ctx->initialized || !samples) {
299 return SET_ERRNO(ERROR_INVALID_PARAM, "invalid parameter");
300 }
301
302 if (!ctx->is_host) {
303 return SET_ERRNO(ERROR_INVALID_STATE, "not a host context");
304 }
305
306 // Find the source
307 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
308 if (ctx->sources[i].active && ctx->sources[i].source_id == source_id) {
309 if (ctx->sources[i].buffer) {
310 return audio_ring_buffer_write(ctx->sources[i].buffer, samples, (int)num_samples);
311 }
312 return SET_ERRNO(ERROR_INVALID_STATE, "Source buffer not initialized");
313 }
314 }
315
316 return SET_ERRNO(ERROR_NOT_FOUND, "Audio source not found: %u", source_id);
317}
318
319size_t session_audio_mix_excluding(session_audio_ctx_t *ctx, uint32_t exclude_id, float *output, size_t num_samples) {
320 if (!ctx || !ctx->initialized || !output || num_samples == 0) {
321 return 0;
322 }
323
324 if (!ctx->is_host) {
325 return 0;
326 }
327
328 // Initialize output to silence
329 memset(output, 0, num_samples * sizeof(float));
330
331 // Ensure we have a mix buffer
332 if (!ctx->mix_buffer || num_samples > ctx->mix_buffer_size) {
333 return 0;
334 }
335
336 int sources_mixed = 0;
337
338 // Mix all active sources except the excluded one
339 for (int i = 0; i < SESSION_AUDIO_MAX_SOURCES; i++) {
340 if (!ctx->sources[i].active || ctx->sources[i].source_id == exclude_id) {
341 continue;
342 }
343
344 if (!ctx->sources[i].buffer) {
345 continue;
346 }
347
348 // Read samples from this source
349 size_t samples_read = audio_ring_buffer_read(ctx->sources[i].buffer, ctx->mix_buffer, num_samples);
350
351 if (samples_read == 0) {
352 continue;
353 }
354
355 // Add to output mix
356 for (size_t j = 0; j < samples_read; j++) {
357 output[j] += ctx->mix_buffer[j];
358 }
359
360 sources_mixed++;
361 }
362
363 // Apply simple clipping prevention if multiple sources were mixed
364 if (sources_mixed > 1) {
365 float scale = 1.0f / (float)sources_mixed;
366 for (size_t i = 0; i < num_samples; i++) {
367 output[i] *= scale;
368 // Soft clipping
369 if (output[i] > 1.0f) {
370 output[i] = 1.0f;
371 } else if (output[i] < -1.0f) {
372 output[i] = -1.0f;
373 }
374 }
375 }
376
377 return num_samples;
378}
379
381 if (ctx && !ctx->running) {
382 audio_set_pipeline(&ctx->audio_ctx, NULL);
384 ctx->pipeline = NULL;
386 }
387}
388
390 return ctx ? &ctx->audio_ctx : NULL;
391}
Synchronized per-thread pending error stacks and value snapshots. SET_ERRNO pushes,...
#define SET_ERRNO(code,...)
#define AUDIO_BUFFER_SIZE
#define AUDIO_CHANNELS
Definition audio/limits.h:5
client_audio_pipeline_t * client_audio_pipeline_create(const client_audio_pipeline_config_t *config)
Create and initialize a client audio pipeline.
client_audio_pipeline_config_t client_audio_pipeline_config_for_source(bool has_media_audio)
void client_audio_pipeline_destroy(client_audio_pipeline_t *pipeline)
Destroy a client audio pipeline.
Unified client-side audio processing pipeline.
⚙️ Common definitions, error codes, macros, and types shared throughout the application
audio_visualization_source_t source
Definition fft.c:24
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)
Read audio samples from ring buffer.
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)
Write audio samples to ring buffer.
void audio_ring_buffer_destroy(audio_ring_buffer_t *rb)
Destroy an audio ring buffer.
asciichat_error_t audio_init(audio_context_t *ctx)
Initialize audio context and PortAudio.
asciichat_error_t audio_start_duplex(audio_context_t *ctx)
Start full-duplex audio (simultaneous capture and playback).
void audio_destroy(audio_context_t *ctx)
Destroy audio context and clean up resources.
asciichat_error_t audio_stop_duplex(audio_context_t *ctx)
Stop full-duplex audio.
asciichat_error_t audio_write_samples(audio_context_t *ctx, const float *buffer, int num_samples)
Write audio samples to playback buffer.
void audio_set_pipeline(audio_context_t *ctx, void *pipeline)
Set audio pipeline for echo cancellation.
unsigned int uint32_t
Definition common.h:62
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_INVALID_STATE
@ ERROR_RESOURCE_EXHAUSTED
@ ERROR_NOT_FOUND
@ ERROR_AUDIO
Definition error_codes.h:62
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
bool media_source_has_audio(media_source_t *source)
Check if media source has audio stream.
Definition source.c:801
struct audio_ring_buffer audio_ring_buffer_t
Audio ring buffer for real-time audio streaming.
struct session_audio_ctx session_audio_ctx_t
Opaque session audio context handle.
asciichat_error_t session_audio_write_playback(session_audio_ctx_t *ctx, const float *buffer, size_t num_samples)
Write audio samples for playback.
size_t session_audio_read_captured(session_audio_ctx_t *ctx, float *buffer, size_t num_samples)
Read captured audio samples.
asciichat_error_t session_audio_start_capture(session_audio_ctx_t *ctx)
Start audio capture (microphone input).
void session_audio_destroy(session_audio_ctx_t *ctx)
Destroy session audio context and free resources.
asciichat_error_t session_audio_start_playback(session_audio_ctx_t *ctx)
Start audio playback (speaker output).
bool session_audio_is_running(session_audio_ctx_t *ctx)
Check if audio is currently running.
asciichat_error_t session_audio_write_source(session_audio_ctx_t *ctx, uint32_t source_id, const float *samples, size_t num_samples)
Write audio samples from a specific source (host only).
asciichat_error_t session_audio_start_duplex(session_audio_ctx_t *ctx)
Start full-duplex audio (simultaneous capture and playback).
void session_audio_stop(session_audio_ctx_t *ctx)
Stop all audio streams.
asciichat_error_t session_audio_add_source(session_audio_ctx_t *ctx, uint32_t source_id)
Add an audio source for mixing (host only).
void session_audio_remove_source(session_audio_ctx_t *ctx, uint32_t source_id)
Remove an audio source from mixing (host only).
session_audio_ctx_t * session_audio_create(bool is_host)
Create a new session audio context.
size_t session_audio_mix_excluding(session_audio_ctx_t *ctx, uint32_t exclude_id, float *output, size_t num_samples)
Mix all sources except one and return the result (host only).
🔊 Audio Capture and Playback Interface for ascii-chat
audio_ring_buffer_t * audio_ring_buffer_create_format(unsigned channels, bool capture)
🎬 Unified media source abstraction for webcam, files, and stdin
void * session_audio_get_context(session_audio_ctx_t *ctx)
void session_audio_set_capture_source(session_audio_ctx_t *ctx, void *source)
#define SESSION_AUDIO_MAX_SOURCES
One participant's audio input in the host mixer.
🔊 Session-level audio coordination wrapper
🔤 String Manipulation and Shell Escaping Utilities
When code needs to work with audio device info, it keeps index, max_input_channels,...
void * capture_media_source
Borrowed local media audio; configure before starting duplex.
audio_ring_buffer_t * capture_buffer
OLD: Worker → Network (will become processed_capture_rb).
Pipeline configuration parameters.
Client audio pipeline state.
Session-owned audio capture, playback, and host-mixing state.
int source_count
Number of active sources (host only).
size_t mix_buffer_size
Size of mix buffer in samples.
bool initialized
Context is fully initialized.
bool is_host
True if this is a host context (has mixing capabilities).
audio_context_t audio_ctx
Underlying audio context from lib/audio.
bool running
Audio streams are currently running.
session_audio_source_t sources[MAX_SESSION_PARTICIPANTS]
Audio sources for mixing (host only).
client_audio_pipeline_t * pipeline
float * mix_buffer
Temporary buffer for mixing operations.
One participant's audio input in the host mixer.