Skip to content
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ set(LINUX_SOURCES
src/vaapi_decoder.c
src/audio_capture.c
src/audio_playback.c
src/audio_capture_pulse.c
src/audio_playback_pulse.c
src/audio_capture_dummy.c
src/audio_playback_dummy.c
src/input.c
src/discovery.c
src/service.c
Expand Down Expand Up @@ -219,6 +223,11 @@ if(UNIX AND NOT APPLE)
target_include_directories(rootstream PRIVATE ${ALSA_INCLUDE_DIRS})
endif()

if(PULSEAUDIO_FOUND)
target_link_libraries(rootstream PRIVATE ${PULSEAUDIO_LIBRARIES})
target_include_directories(rootstream PRIVATE ${PULSEAUDIO_INCLUDE_DIRS})
endif()

if(NOT HEADLESS AND GTK3_FOUND)
target_link_libraries(rootstream PRIVATE ${GTK3_LIBRARIES})
target_include_directories(rootstream PRIVATE ${GTK3_INCLUDE_DIRS})
Expand Down Expand Up @@ -262,6 +271,8 @@ if(UNIX AND NOT APPLE)
src/input.c
src/opus_codec.c
src/audio_playback.c
src/audio_playback_pulse.c
src/audio_playback_dummy.c
src/latency.c
${PLATFORM_SOURCES}
)
Expand All @@ -280,6 +291,11 @@ if(UNIX AND NOT APPLE)
m pthread
)

if(PULSEAUDIO_FOUND)
target_link_libraries(rstr-player PRIVATE ${PULSEAUDIO_LIBRARIES})
target_include_directories(rstr-player PRIVATE ${PULSEAUDIO_INCLUDE_DIRS})
endif()

if(Opus_FOUND)
target_link_libraries(rstr-player PRIVATE Opus::opus)
else()
Expand Down
65 changes: 65 additions & 0 deletions include/rootstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,29 @@ typedef struct {
char filename[256]; /* Output filename */
} recording_ctx_t;

/* ============================================================================
* AUDIO BACKEND ABSTRACTION - Multi-fallback support
* ============================================================================ */

/* Forward declaration */
typedef struct rootstream_ctx rootstream_ctx_t;

typedef struct {
const char *name;
int (*init_fn)(rootstream_ctx_t *ctx);
int (*capture_fn)(rootstream_ctx_t *ctx, int16_t *samples, size_t *num_samples);
void (*cleanup_fn)(rootstream_ctx_t *ctx);
bool (*is_available_fn)(void);
} audio_capture_backend_t;

typedef struct {
const char *name;
int (*init_fn)(rootstream_ctx_t *ctx);
int (*playback_fn)(rootstream_ctx_t *ctx, int16_t *samples, size_t num_samples);
void (*cleanup_fn)(rootstream_ctx_t *ctx);
bool (*is_available_fn)(void);
} audio_playback_backend_t;

/* ============================================================================
* MAIN CONTEXT - Application state
* ============================================================================ */
Expand All @@ -398,6 +421,10 @@ typedef struct rootstream_ctx {
/* Audio (client) */
audio_playback_ctx_t audio_playback;

/* Audio backends (Phase 3) */
const audio_capture_backend_t *audio_capture_backend;
const audio_playback_backend_t *audio_playback_backend;

/* Network */
rs_socket_t sock_fd; /* UDP socket */
uint16_t port; /* Listening port */
Expand Down Expand Up @@ -554,6 +581,7 @@ int rootstream_opus_get_frame_size(void);
int rootstream_opus_get_sample_rate(void);
int rootstream_opus_get_channels(void);

/* Audio capture/playback - backward compatibility */
int audio_capture_init(rootstream_ctx_t *ctx);
int audio_capture_frame(rootstream_ctx_t *ctx, int16_t *samples,
size_t *num_samples);
Expand All @@ -564,6 +592,43 @@ int audio_playback_write(rootstream_ctx_t *ctx, int16_t *samples,
size_t num_samples);
void audio_playback_cleanup(rootstream_ctx_t *ctx);

/* Audio backends - ALSA */
bool audio_capture_alsa_available(void);
int audio_capture_init_alsa(rootstream_ctx_t *ctx);
int audio_capture_frame_alsa(rootstream_ctx_t *ctx, int16_t *samples,
size_t *num_samples);
void audio_capture_cleanup_alsa(rootstream_ctx_t *ctx);

bool audio_playback_alsa_available(void);
int audio_playback_init_alsa(rootstream_ctx_t *ctx);
int audio_playback_write_alsa(rootstream_ctx_t *ctx, int16_t *samples,
size_t num_samples);
void audio_playback_cleanup_alsa(rootstream_ctx_t *ctx);

/* Audio backends - PulseAudio */
bool audio_capture_pulse_available(void);
int audio_capture_init_pulse(rootstream_ctx_t *ctx);
int audio_capture_frame_pulse(rootstream_ctx_t *ctx, int16_t *samples,
size_t *num_samples);
void audio_capture_cleanup_pulse(rootstream_ctx_t *ctx);

bool audio_playback_pulse_available(void);
int audio_playback_init_pulse(rootstream_ctx_t *ctx);
int audio_playback_write_pulse(rootstream_ctx_t *ctx, int16_t *samples,
size_t num_samples);
void audio_playback_cleanup_pulse(rootstream_ctx_t *ctx);

/* Audio backends - Dummy (silent/discard) */
int audio_capture_init_dummy(rootstream_ctx_t *ctx);
int audio_capture_frame_dummy(rootstream_ctx_t *ctx, int16_t *samples,
size_t *num_samples);
void audio_capture_cleanup_dummy(rootstream_ctx_t *ctx);

int audio_playback_init_dummy(rootstream_ctx_t *ctx);
int audio_playback_write_dummy(rootstream_ctx_t *ctx, int16_t *samples,
size_t num_samples);
void audio_playback_cleanup_dummy(rootstream_ctx_t *ctx);

/* --- Recording (Phase 7) --- */
int recording_init(rootstream_ctx_t *ctx, const char *filename);
int recording_write_frame(rootstream_ctx_t *ctx, const uint8_t *data,
Expand Down
43 changes: 35 additions & 8 deletions src/audio_capture.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,36 @@ typedef struct {
int channels;
int frame_size;
bool initialized;
} audio_capture_ctx_t;
} alsa_capture_ctx_t;

/*
* Check if ALSA is available
*/
bool audio_capture_alsa_available(void) {
/* Try to open and immediately close a test handle */
snd_pcm_t *handle = NULL;
int err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);
if (err >= 0 && handle) {
snd_pcm_close(handle);
return true;
}
return false;
}

/*
* Initialize ALSA audio capture
*
* @param ctx RootStream context
* @return 0 on success, -1 on error
*/
int audio_capture_init(rootstream_ctx_t *ctx) {
int audio_capture_init_alsa(rootstream_ctx_t *ctx) {
if (!ctx) {
fprintf(stderr, "ERROR: Invalid context for audio capture\n");
return -1;
}

/* Allocate capture context */
audio_capture_ctx_t *capture = calloc(1, sizeof(audio_capture_ctx_t));
alsa_capture_ctx_t *capture = calloc(1, sizeof(alsa_capture_ctx_t));
if (!capture) {
fprintf(stderr, "ERROR: Cannot allocate audio capture context\n");
return -1;
Expand Down Expand Up @@ -167,13 +181,13 @@ int audio_capture_init(rootstream_ctx_t *ctx) {
* @param num_samples Output sample count
* @return 0 on success, -1 on error
*/
int audio_capture_frame(rootstream_ctx_t *ctx, int16_t *samples,
size_t *num_samples) {
int audio_capture_frame_alsa(rootstream_ctx_t *ctx, int16_t *samples,
size_t *num_samples) {
if (!ctx || !samples || !num_samples) {
return -1;
}

audio_capture_ctx_t *capture = (audio_capture_ctx_t*)(intptr_t)ctx->uinput_mouse_fd;
alsa_capture_ctx_t *capture = (alsa_capture_ctx_t*)(intptr_t)ctx->uinput_mouse_fd;
if (!capture || !capture->initialized) {
return -1;
}
Expand Down Expand Up @@ -218,12 +232,12 @@ int audio_capture_frame(rootstream_ctx_t *ctx, int16_t *samples,
/*
* Cleanup audio capture
*/
void audio_capture_cleanup(rootstream_ctx_t *ctx) {
void audio_capture_cleanup_alsa(rootstream_ctx_t *ctx) {
if (!ctx || !ctx->uinput_mouse_fd) {
return;
}

audio_capture_ctx_t *capture = (audio_capture_ctx_t*)(intptr_t)ctx->uinput_mouse_fd;
alsa_capture_ctx_t *capture = (alsa_capture_ctx_t*)(intptr_t)ctx->uinput_mouse_fd;

if (capture->handle) {
snd_pcm_drain(capture->handle);
Expand All @@ -235,3 +249,16 @@ void audio_capture_cleanup(rootstream_ctx_t *ctx) {

printf("✓ Audio capture cleanup complete\n");
}

/* Backward compatibility wrappers */
int audio_capture_init(rootstream_ctx_t *ctx) {
return audio_capture_init_alsa(ctx);
}

int audio_capture_frame(rootstream_ctx_t *ctx, int16_t *samples, size_t *num_samples) {
return audio_capture_frame_alsa(ctx, samples, num_samples);
}

void audio_capture_cleanup(rootstream_ctx_t *ctx) {
audio_capture_cleanup_alsa(ctx);
}
69 changes: 69 additions & 0 deletions src/audio_capture_dummy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* audio_capture_dummy.c - Dummy audio capture (silent)
*
* Always available fallback that generates silence.
* Allows video-only streaming when audio hardware is unavailable.
*
* Parameters:
* - 48000 Hz sample rate
* - 2 channels (stereo)
* - 240 samples per frame (5ms at 48kHz)
*/

#include "../include/rootstream.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SAMPLE_RATE 48000
#define CHANNELS 2
#define FRAME_SIZE 240 /* 5ms at 48kHz */

/*
* Initialize dummy audio capture
*
* @param ctx RootStream context
* @return 0 on success (always succeeds)
*/
int audio_capture_init_dummy(rootstream_ctx_t *ctx) {
if (!ctx) {
return -1;
}

printf("✓ Dummy audio capture ready (silent): %d Hz, %d channels, %d samples/frame\n",
SAMPLE_RATE, CHANNELS, FRAME_SIZE);

return 0;
}

/*
* Capture one audio frame (returns silence)
*
* @param ctx RootStream context
* @param samples Output PCM samples (interleaved stereo, 16-bit)
* @param num_samples Output sample count
* @return 0 on success (always succeeds)
*/
int audio_capture_frame_dummy(rootstream_ctx_t *ctx, int16_t *samples,
size_t *num_samples) {
if (!ctx || !samples || !num_samples) {
return -1;
}

/* Fill with silence (zeros) */
memset(samples, 0, FRAME_SIZE * CHANNELS * sizeof(int16_t));
*num_samples = FRAME_SIZE;

return 0;
}

/*
* Cleanup dummy audio capture
*/
void audio_capture_cleanup_dummy(rootstream_ctx_t *ctx) {
if (!ctx) {
return;
}

printf("✓ Dummy audio capture cleanup complete\n");
}
Loading
Loading