ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
Audio Processing

🔊 Audio capture, playback, and sample processing More...

Files

file  audio.c
 ðŸ”Š Client audio management: capture thread, sample processing, and playback coordination
 
file  audio.h
 ascii-chat Client Audio Processing Management Interface
 

Functions

void audio_process_received_samples (const float *samples, int num_samples)
 Process received audio samples from server.
 
int audio_client_init (void)
 Initialize audio subsystem.
 
int audio_start_thread (void)
 Start audio capture thread.
 
void audio_stop_thread (void)
 Stop audio capture thread.
 
bool audio_thread_exited (void)
 Check if audio capture thread has exited.
 
void audio_cleanup (void)
 Cleanup audio subsystem.
 
client_audio_pipeline_taudio_get_pipeline (void)
 Get the audio pipeline (for advanced usage)
 
int audio_decode_opus (const uint8_t *opus_data, size_t opus_len, float *output, int max_samples)
 Decode Opus packet using the audio pipeline.
 

Detailed Description

🔊 Audio capture, playback, and sample processing

Audio Processing

Overview

The client audio subsystem manages PortAudio initialization, audio capture from microphone, transmission to server, and playback of received audio samples with jitter buffering.

Implementation: src/client/audio.c, src/client/audio.h

Configuration

Audio Parameters:

  • Sample rate: 44100 Hz
  • Channels: 1 (mono)
  • Format: 32-bit float
  • Batch size: 256 samples (~5.8ms @ 44.1kHz)
  • Ring buffer: 8192 samples (~185ms jitter buffer)

Audio Capture Thread

static void *audio_capture_thread_func(void *arg)
{
(void)arg;
float capture_buffer[AUDIO_BATCH_SIZE];
// Capture audio samples from microphone
int samples_captured = audio_capture_samples(capture_buffer, AUDIO_BATCH_SIZE);
if (samples_captured > 0) {
// Send to server
send_packet_to_server(PACKET_TYPE_AUDIO, capture_buffer,
samples_captured * sizeof(float),
}
// Small sleep to avoid busy loop
platform_sleep_usec(1000); // 1ms
}
atomic_store(&g_audio_thread_exited, true);
return NULL;
}
bool should_exit()
Check if client should exit.
bool server_connection_is_active()
Check if server connection is currently active.
uint32_t server_connection_get_client_id()
Get client ID assigned by server.
@ PACKET_TYPE_AUDIO
Single audio packet (legacy)
Definition packet.h:291
void platform_sleep_usec(unsigned int usec)
High-precision sleep function with microsecond precision.

Audio Playback

Jitter Buffering

void audio_process_received_samples(const float *samples, int num_samples)
{
// Write to ring buffer for jitter compensation
audio_ringbuffer_write(&g_playback_ringbuffer, samples, num_samples);
// PortAudio callback reads from ring buffer
}
static int audio_playback_callback(const void *input, void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
(void)input; (void)timeInfo; (void)statusFlags; (void)userData;
float *out = (float *)output;
// Read from ring buffer
int samples_read = audio_ringbuffer_read(&g_playback_ringbuffer, out, frameCount);
// Fill remaining with silence if underrun
if (samples_read < (int)frameCount) {
memset(&out[samples_read], 0, (frameCount - samples_read) * sizeof(float));
}
return paContinue;
}
void audio_process_received_samples(const float *samples, int num_samples)
Process received audio samples from server.
See also
src/client/audio.c
src/client/audio.h
lib/audio.h
Client Overview

Function Documentation

◆ audio_cleanup()

void audio_cleanup ( )

#include <audio.c>

Cleanup audio subsystem.

Cleanup audio subsystem

Stops audio threads and cleans up PortAudio resources. Called during client shutdown.

Definition at line 899 of file src/client/audio.c.

899 {
900 if (!GET_OPTION(audio_enabled)) {
901 return;
902 }
903
904 // Stop capture thread first (stops producing packets)
906
907 // Stop async sender thread (drains queue and exits)
908 audio_sender_cleanup();
909
910 // CRITICAL: Stop audio stream BEFORE destroying pipeline to prevent race condition
911 // PortAudio may invoke the callback one more time after we request stop.
912 // We need to clear the pipeline pointer first so the callback can't access freed memory.
913 if (g_audio_context.initialized) {
914 audio_stop_duplex(&g_audio_context);
915 }
916
917 // Clear the pipeline pointer from audio context BEFORE destroying pipeline
918 // This prevents any lingering PortAudio callbacks from trying to access freed memory
919 audio_set_pipeline(&g_audio_context, NULL);
920
921 // CRITICAL: Sleep to allow CoreAudio threads to finish executing callbacks
922 // On macOS, CoreAudio's internal threads may continue running after Pa_StopStream() returns.
923 // The duplex_callback may still be in-flight on other threads. Even after we set the pipeline
924 // pointer to NULL, a CoreAudio thread may have already cached the pointer before the assignment.
925 // This sleep ensures all in-flight callbacks have fully completed before we destroy the pipeline.
926 // 500ms is sufficient on macOS for CoreAudio's internal thread pool to completely wind down.
927 platform_sleep_usec(500000); // 500ms - macOS CoreAudio needs time to shut down all threads
928
929 // Destroy audio pipeline (handles Opus, AEC, etc.)
930 if (g_audio_pipeline) {
931 client_audio_pipeline_destroy(g_audio_pipeline);
932 g_audio_pipeline = NULL;
933 log_info("Audio pipeline destroyed");
934 }
935
936 // Close WAV dumpers
937 if (g_wav_capture_raw) {
938 wav_writer_close(g_wav_capture_raw);
939 g_wav_capture_raw = NULL;
940 log_info("Closed audio capture raw dump");
941 }
942 if (g_wav_capture_processed) {
943 wav_writer_close(g_wav_capture_processed);
944 g_wav_capture_processed = NULL;
945 log_info("Closed audio capture processed dump");
946 }
947 if (g_wav_playback_received) {
948 wav_writer_close(g_wav_playback_received);
949 g_wav_playback_received = NULL;
950 log_info("Closed audio playback received dump");
951 }
952
953 // Finally destroy the audio context
954 if (g_audio_context.initialized) {
955 audio_destroy(&g_audio_context);
956 }
957}
void client_audio_pipeline_destroy(client_audio_pipeline_t *pipeline)
Destroy a client audio pipeline.
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.
void audio_set_pipeline(audio_context_t *ctx, void *pipeline)
Set audio pipeline for echo cancellation.
void audio_stop_thread()
Stop audio capture thread.
#define log_info(...)
Log an INFO message.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
Definition options.h:644
bool initialized
True if context has been initialized.
void wav_writer_close(wav_writer_t *writer)
Close WAV file and finalize header.
Definition wav_writer.c:99

References audio_destroy(), audio_set_pipeline(), audio_stop_duplex(), audio_stop_thread(), client_audio_pipeline_destroy(), GET_OPTION, audio_context_t::initialized, log_info, platform_sleep_usec(), and wav_writer_close().

◆ audio_client_init()

int audio_client_init ( )

#include <audio.c>

Initialize audio subsystem.

Initialize audio subsystem

Sets up PortAudio context, creates the audio pipeline, and starts audio playback/capture if audio is enabled.

Returns
0 on success, negative on error
0 on success, negative on error

Definition at line 702 of file src/client/audio.c.

702 {
703 if (!GET_OPTION(audio_enabled)) {
704 return 0; // Audio disabled - not an error
705 }
706
707 // Initialize WAV dumper for received audio if debugging enabled
708 if (wav_dump_enabled()) {
709 g_wav_playback_received = wav_writer_open("/tmp/audio_playback_received.wav", AUDIO_SAMPLE_RATE, 1);
710 if (g_wav_playback_received) {
711 log_info("Audio debugging enabled: dumping received audio to /tmp/audio_playback_received.wav");
712 }
713 }
714
715 // Initialize PortAudio context using library function
716 if (audio_init(&g_audio_context) != ASCIICHAT_OK) {
717 log_error("Failed to initialize audio system");
718 // Clean up WAV writer if it was opened
719 if (g_wav_playback_received) {
720 wav_writer_close(g_wav_playback_received);
721 g_wav_playback_received = NULL;
722 }
723 return -1;
724 }
725
726 // Create unified audio pipeline (handles AEC, AGC, noise suppression, Opus)
728 pipeline_config.opus_bitrate = 128000; // 128 kbps AUDIO mode for music quality
729
730 // Use FLAGS_MINIMAL but enable echo cancellation and jitter buffer
731 // Noise suppression, AGC, VAD destroy music/non-voice audio, so keep them disabled
732 // But AEC removes echo without destroying audio quality
733 // Jitter buffer helps synchronize the AEC echo reference
734 pipeline_config.flags.echo_cancel = true; // ENABLE: removes echo
735 pipeline_config.flags.jitter_buffer = true; // ENABLE: needed for AEC sync
736 pipeline_config.flags.noise_suppress = false; // DISABLED: destroys audio
737 pipeline_config.flags.agc = false; // DISABLED: destroys audio
738 pipeline_config.flags.vad = false; // DISABLED: destroys audio
739 pipeline_config.flags.compressor = false; // DISABLED: minimal processing
740 pipeline_config.flags.noise_gate = false; // DISABLED: minimal processing
741 pipeline_config.flags.highpass = false; // DISABLED: minimal processing
742 pipeline_config.flags.lowpass = false; // DISABLED: minimal processing
743
744 // Set jitter buffer margin for smooth playback without excessive delay
745 // 100ms is conservative - AEC3 will adapt to actual network delay automatically
746 // We don't tune this; let the system adapt to its actual conditions
747 pipeline_config.jitter_margin_ms = 100;
748
749 g_audio_pipeline = client_audio_pipeline_create(&pipeline_config);
750 if (!g_audio_pipeline) {
751 log_error("Failed to create audio pipeline");
752 audio_destroy(&g_audio_context);
753 // Clean up WAV writer if it was opened
754 if (g_wav_playback_received) {
755 wav_writer_close(g_wav_playback_received);
756 g_wav_playback_received = NULL;
757 }
758 return -1;
759 }
760
761 log_info("Audio pipeline created: %d Hz sample rate, %d bps bitrate", pipeline_config.sample_rate,
762 pipeline_config.opus_bitrate);
763
764 // Associate pipeline with audio context for echo cancellation
765 // The audio output callback will feed playback samples directly to AEC3 from the speaker output,
766 // ensuring proper timing synchronization (not from the decode path 50-100ms earlier)
767 audio_set_pipeline(&g_audio_context, (void *)g_audio_pipeline);
768
769 // Start full-duplex audio (simultaneous capture + playback for perfect AEC3 timing)
770 if (audio_start_duplex(&g_audio_context) != ASCIICHAT_OK) {
771 log_error("Failed to start full-duplex audio");
772 client_audio_pipeline_destroy(g_audio_pipeline);
773 g_audio_pipeline = NULL;
774 audio_destroy(&g_audio_context);
775 // Clean up WAV writer if it was opened
776 if (g_wav_playback_received) {
777 wav_writer_close(g_wav_playback_received);
778 g_wav_playback_received = NULL;
779 }
780 return -1;
781 }
782
783 // Initialize async audio sender (decouples capture from network I/O)
784 audio_sender_init();
785
786 return 0;
787}
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_default_config(void)
Get default configuration.
#define AUDIO_SAMPLE_RATE
Audio sample rate (48kHz professional quality, Opus-compatible)
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)
@ ASCIICHAT_OK
Definition error_codes.h:48
#define log_error(...)
Log an ERROR message.
Pipeline configuration parameters.
client_audio_pipeline_flags_t flags
bool wav_dump_enabled(void)
Check if audio dumping is enabled via environment.
Definition wav_writer.c:123
wav_writer_t * wav_writer_open(const char *filepath, int sample_rate, int channels)
Open WAV file for writing.
Definition wav_writer.c:39

References client_audio_pipeline_flags_t::agc, ASCIICHAT_OK, audio_destroy(), audio_init(), AUDIO_SAMPLE_RATE, audio_set_pipeline(), audio_start_duplex(), client_audio_pipeline_create(), client_audio_pipeline_default_config(), client_audio_pipeline_destroy(), client_audio_pipeline_flags_t::compressor, client_audio_pipeline_flags_t::echo_cancel, client_audio_pipeline_config_t::flags, GET_OPTION, client_audio_pipeline_flags_t::highpass, client_audio_pipeline_flags_t::jitter_buffer, client_audio_pipeline_config_t::jitter_margin_ms, log_error, log_info, client_audio_pipeline_flags_t::lowpass, client_audio_pipeline_flags_t::noise_gate, client_audio_pipeline_flags_t::noise_suppress, client_audio_pipeline_config_t::opus_bitrate, client_audio_pipeline_config_t::sample_rate, client_audio_pipeline_flags_t::vad, wav_dump_enabled(), wav_writer_close(), and wav_writer_open().

◆ audio_decode_opus()

int audio_decode_opus ( const uint8_t opus_data,
size_t  opus_len,
float *  output,
int  max_samples 
)

#include <audio.c>

Decode Opus packet using the audio pipeline.

Parameters
opus_dataOpus packet data
opus_lenOpus packet length
outputOutput buffer for decoded samples
max_samplesMaximum samples output buffer can hold
Returns
Number of decoded samples, or negative on error

Definition at line 979 of file src/client/audio.c.

979 {
980 if (!g_audio_pipeline || !output || max_samples <= 0) {
981 return -1;
982 }
983
984 return client_audio_pipeline_playback(g_audio_pipeline, opus_data, (int)opus_len, output, max_samples);
985}
int client_audio_pipeline_playback(client_audio_pipeline_t *pipeline, const uint8_t *opus_in, int opus_len, float *output, int num_samples)
Decode Opus packet and process for playback.

References client_audio_pipeline_playback().

◆ audio_get_pipeline()

client_audio_pipeline_t * audio_get_pipeline ( void  )

#include <audio.c>

Get the audio pipeline (for advanced usage)

Returns
Pointer to the audio pipeline, or NULL if not initialized

Definition at line 965 of file src/client/audio.c.

965 {
966 return g_audio_pipeline;
967}

◆ audio_process_received_samples()

void audio_process_received_samples ( const float *  samples,
int  num_samples 
)

#include <audio.c>

Process received audio samples from server.

Process received audio samples from server

Uses the audio pipeline for processing:

  1. Input validation and size checking
  2. Feed samples to pipeline (applies soft clipping)
  3. Feed echo reference for AEC
  4. Submit processed samples to PortAudio playback queue
Parameters
samplesRaw audio sample data from server
num_samplesNumber of samples in the buffer
samplesAudio sample data from server
num_samplesNumber of samples in buffer

Definition at line 368 of file src/client/audio.c.

368 {
369 // Validate parameters
370 if (!samples || num_samples <= 0) {
371 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid audio samples: samples=%p, num_samples=%d", (void *)samples, num_samples);
372 return;
373 }
374
375 if (!GET_OPTION(audio_enabled)) {
376 log_warn_every(1000000, "Received audio samples but audio is disabled");
377 return;
378 }
379
380 // Allow both single packets and batched packets
381 if (num_samples > AUDIO_BATCH_SAMPLES) {
382 log_warn("Audio packet too large: %d samples (max %d)", num_samples, AUDIO_BATCH_SAMPLES);
383 return;
384 }
385
386 // Calculate RMS energy of received samples
387 float sum_squares = 0.0f;
388 for (int i = 0; i < num_samples; i++) {
389 sum_squares += samples[i] * samples[i];
390 }
391 float received_rms = sqrtf(sum_squares / num_samples);
392
393 // DUMP: Received audio from server (before playback processing)
394 if (g_wav_playback_received) {
395 wav_writer_write(g_wav_playback_received, samples, num_samples);
396 }
397
398 // Track samples for analysis
399 if (GET_OPTION(audio_analysis_enabled)) {
400 for (int i = 0; i < num_samples; i++) {
402 }
403 }
404
405 // Copy samples to playback buffer (no processing needed - mixer already handled clipping)
406 float audio_buffer[AUDIO_BATCH_SAMPLES];
407 memcpy(audio_buffer, samples, (size_t)num_samples * sizeof(float));
408
409 // DEBUG: Log what we're writing to playback buffer (with first 4 samples to verify audio integrity)
410 static int recv_count = 0;
411 recv_count++;
412 if (recv_count <= 10 || recv_count % 50 == 0) {
413 float peak = 0.0f;
414 for (int i = 0; i < num_samples; i++) {
415 float abs_val = fabsf(samples[i]);
416 if (abs_val > peak)
417 peak = abs_val;
418 }
419 log_info("CLIENT AUDIO RECV #%d: %d samples, RMS=%.6f, Peak=%.6f, first4=[%.4f,%.4f,%.4f,%.4f]", recv_count,
420 num_samples, received_rms, peak, num_samples > 0 ? samples[0] : 0.0f, num_samples > 1 ? samples[1] : 0.0f,
421 num_samples > 2 ? samples[2] : 0.0f, num_samples > 3 ? samples[3] : 0.0f);
422 }
423
424 // Submit to playback system (goes to jitter buffer and speakers)
425 // NOTE: AEC3's AnalyzeRender is called in output_callback() when audio actually plays,
426 // NOT here. The jitter buffer adds 50-100ms delay, so calling AnalyzeRender here
427 // would give AEC3 the wrong timing and break echo cancellation.
428 audio_write_samples(&g_audio_context, audio_buffer, num_samples);
429
430 // Log latency after writing to playback buffer
431 if (g_audio_context.playback_buffer) {
432 size_t buffer_samples = audio_ring_buffer_available_read(g_audio_context.playback_buffer);
433 float buffer_latency_ms = (float)buffer_samples / 48.0f;
434 log_debug_every(500000, "LATENCY: Client playback buffer after recv: %.1fms (%zu samples)", buffer_latency_ms,
435 buffer_samples);
436 }
437
438#ifdef DEBUG_AUDIO
439 log_debug("Processed %d received audio samples", num_samples);
440#endif
441}
void audio_analysis_track_received_sample(float sample)
Track received audio sample.
Definition analysis.c:275
size_t audio_ring_buffer_available_read(audio_ring_buffer_t *rb)
Get number of samples available for reading.
asciichat_error_t audio_write_samples(audio_context_t *ctx, const float *buffer, int num_samples)
Write audio samples to playback buffer.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
#define log_debug(...)
Log a DEBUG message.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
#define AUDIO_BATCH_SAMPLES
Total samples in audio batch (8192 samples)
Definition packet.h:214
audio_ring_buffer_t * playback_buffer
Ring buffer for decoded audio from network.
int wav_writer_write(wav_writer_t *writer, const float *samples, int num_samples)
Write audio samples to WAV file.
Definition wav_writer.c:85

References audio_analysis_track_received_sample(), AUDIO_BATCH_SAMPLES, audio_ring_buffer_available_read(), audio_write_samples(), ERROR_INVALID_PARAM, GET_OPTION, log_debug, log_debug_every, log_info, log_warn, log_warn_every, audio_context_t::playback_buffer, SET_ERRNO, and wav_writer_write().

◆ audio_start_thread()

int audio_start_thread ( )

#include <audio.c>

Start audio capture thread.

Start audio capture thread

Creates and starts the audio capture thread. Also sends stream start notification to server.

Returns
0 on success, negative on error
0 on success, negative on error

Definition at line 799 of file src/client/audio.c.

799 {
800 log_info("audio_start_thread called: audio_enabled=%d", GET_OPTION(audio_enabled));
801
802 if (!GET_OPTION(audio_enabled)) {
803 log_info("Audio is disabled, skipping audio capture thread creation");
804 return 0; // Audio disabled - not an error
805 }
806
807 // Check if thread is already running (not just created flag)
808 if (g_audio_capture_thread_created && !atomic_load(&g_audio_capture_thread_exited)) {
809 log_warn("Audio capture thread already running");
810 return 0;
811 }
812
813 // If thread exited, allow recreation
814 if (g_audio_capture_thread_created && atomic_load(&g_audio_capture_thread_exited)) {
815 log_info("Previous audio capture thread exited, recreating");
816 // Use timeout to prevent indefinite blocking
817 int join_result = asciichat_thread_join_timeout(&g_audio_capture_thread, NULL, 5000);
818 if (join_result != 0) {
819 log_warn("Audio capture thread join timed out after 5s - thread may be deadlocked, "
820 "forcing thread handle reset (stuck thread resources will not be cleaned up)");
821 // Thread is stuck - we can't safely reuse the handle, but we can reset our tracking
822 // This is a resource leak of the stuck thread but continuing is safer than hanging
823 }
824 g_audio_capture_thread_created = false;
825 }
826
827 // Notify server we're starting to send audio BEFORE spawning thread
828 // IMPORTANT: Must send STREAM_START before thread starts sending packets to avoid protocol violation
830 log_error("Failed to send audio stream start packet");
831 return -1; // Don't start thread if we can't notify server
832 }
833
834 // Start audio capture thread
835 atomic_store(&g_audio_capture_thread_exited, false);
836 if (thread_pool_spawn(g_client_worker_pool, audio_capture_thread_func, NULL, 4, "audio_capture") != ASCIICHAT_OK) {
837 log_error("Failed to spawn audio capture thread in worker pool");
838 LOG_ERRNO_IF_SET("Audio capture thread creation failed");
839 return -1;
840 }
841
842 g_audio_capture_thread_created = true;
843
844 return 0;
845}
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
thread_pool_t * g_client_worker_pool
Global client worker thread pool.
int threaded_send_stream_start_packet(uint32_t stream_type)
Thread-safe stream start packet transmission.
#define STREAM_TYPE_AUDIO
Audio stream.
Definition packet.h:830
int asciichat_thread_join_timeout(asciichat_thread_t *thread, void **retval, uint32_t timeout_ms)
Wait for a thread to complete with timeout.
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread in the pool.
Definition thread_pool.c:65

References ASCIICHAT_OK, asciichat_thread_join_timeout(), g_client_worker_pool, GET_OPTION, LOG_ERRNO_IF_SET, log_error, log_info, log_warn, STREAM_TYPE_AUDIO, thread_pool_spawn(), and threaded_send_stream_start_packet().

Referenced by protocol_start_connection().

◆ audio_stop_thread()

void audio_stop_thread ( )

#include <audio.c>

Stop audio capture thread.

Stop audio capture thread

Gracefully stops the audio capture thread and cleans up resources. Safe to call multiple times.

Definition at line 855 of file src/client/audio.c.

855 {
856 if (!THREAD_IS_CREATED(g_audio_capture_thread_created)) {
857 return;
858 }
859
860 // Note: We don't call signal_exit() here because that's for global shutdown only
861 // The audio capture thread checks server_connection_is_active() to detect connection loss
862
863 // Wait for thread to exit gracefully
864 int wait_count = 0;
865 while (wait_count < 20 && !atomic_load(&g_audio_capture_thread_exited)) {
866 platform_sleep_usec(100000); // 100ms
867 wait_count++;
868 }
869
870 if (!atomic_load(&g_audio_capture_thread_exited)) {
871 log_warn("Audio capture thread not responding - will be joined by thread pool");
872 }
873
874 // Thread will be joined by thread_pool_stop_all() in protocol_stop_connection()
875 g_audio_capture_thread_created = false;
876
877 log_info("Audio capture thread stopped");
878}
#define THREAD_IS_CREATED(created_flag)
Definition util/thread.h:62

References log_info, log_warn, platform_sleep_usec(), and THREAD_IS_CREATED.

Referenced by audio_cleanup(), and protocol_stop_connection().

◆ audio_thread_exited()

bool audio_thread_exited ( )

#include <audio.c>

Check if audio capture thread has exited.

Check if audio capture thread has exited

Returns
true if thread has exited, false otherwise
true if thread exited, false otherwise

Definition at line 887 of file src/client/audio.c.

887 {
888 return atomic_load(&g_audio_capture_thread_exited);
889}