Skip to content

PHASE 3: Enable multi-fallback audio pipeline (PulseAudio + backend integration)#35

Merged
infinityabundance merged 3 commits intomainfrom
copilot/multi-fallback-audio-pipeline
Feb 13, 2026
Merged

PHASE 3: Enable multi-fallback audio pipeline (PulseAudio + backend integration)#35
infinityabundance merged 3 commits intomainfrom
copilot/multi-fallback-audio-pipeline

Conversation

Copy link
Contributor

Copilot AI commented Feb 13, 2026

Summary

Audio backend infrastructure existed but was incomplete: PulseAudio wasn't detected/compiled, and network.c bypassed the backend system by calling ALSA directly. This prevented graceful degradation on systems without ALSA.

Details

  • Bug fix
  • New feature
  • Performance improvement
  • Documentation / tooling

What changed?

CMakeLists.txt (8 lines)

  • Added PulseAudio detection via pkg_check_modules(PULSEAUDIO libpulse-simple libpulse)
  • Set HAVE_PULSEAUDIO compile definition when available
  • Added PulseAudio status to build summary

src/network.c (9 lines)

  • Fixed audio playback to use backend approach: ctx->audio_playback_backend->playback_fn() instead of audio_playback_write()
  • Added NULL check with one-time warning for missing backends
// Before: Direct ALSA call
audio_playback_write(ctx, pcm_buffer, pcm_samples);

// After: Backend approach with NULL safety
if (ctx->audio_playback_backend && ctx->audio_playback_backend->playback_fn) {
    ctx->audio_playback_backend->playback_fn(ctx, pcm_buffer, pcm_samples);
} else {
    static bool warned = false;
    if (!warned) {
        fprintf(stderr, "WARNING: No audio playback backend available\n");
        warned = true;
    }
}

Rationale

Enables three-tier fallback (ALSA → PulseAudio → Dummy/Silent) for robust audio across environments:

  • ALSA: Low latency (~5ms) primary path
  • PulseAudio: Fallback for modern distros where ALSA may be unavailable
  • Dummy: Always-works fallback for headless/container systems

Ensures video streaming continues even when audio hardware is missing, aligning with RootStream's reliability goals.

Testing

  • Built successfully (make)
  • PulseAudio detected in CMake output: PulseAudio: 1
  • All backend functions linked in binary (verified with nm)
  • All 3 unit tests pass (crypto, encoding, packet)
  • Tested on:
    • Distro: Ubuntu 24.04
    • Kernel: 6.8.0
    • GPU & driver: N/A (build/link verification only)

Notes

  • Latency impact: None. PulseAudio only used as fallback when ALSA unavailable
  • All audio backend implementations (6 files) already existed - this PR only wires them up
  • Follow-up: Runtime testing on PulseAudio-only and headless systems
Original prompt

PHASE 3: Multi-Fallback Audio Pipeline with Graceful Degradation

Current State

  • src/audio_capture.c - ALSA audio capture (exists but incomplete)
  • src/audio_playback.c - ALSA audio playback (exists but incomplete)
  • src/opus_codec.c - Opus encoding/decoding (fully implemented)
  • Missing: Audio integration into main streaming loops
  • Missing: PulseAudio fallback backends
  • Missing: Dummy/silent audio fallback
  • Missing: Robust error handling for audio failures

Problem

In service_run_host() and service_run_client():

  • Audio capture/playback functions exist but are not consistently called in main loops
  • If ALSA initialization fails, audio silently fails (or whole app crashes)
  • No fallback to PulseAudio, which is more robust on modern systems
  • Audio failures should NOT stop video streaming
  • On headless/container systems with no audio device, app should continue

Solution: Three-Tier Audio with Main Loop Integration

Tier 1: ALSA (Primary)

  • File: src/audio_capture.c, src/audio_playback.c (already exist)
  • Status: ⚠️ Needs minimal fixes + main loop integration
  • Performance: Low latency (~5ms)
  • Availability: Most Linux systems

Tier 2: PulseAudio (Fallback)

  • File: src/audio_capture_pulse.c (NEW), src/audio_playback_pulse.c (NEW)
  • Method: PulseAudio daemon via libpulse
  • Performance: Higher latency (~30-50ms) but acceptable
  • Availability: Modern distributions (Ubuntu, Fedora, Arch)

Tier 3: Dummy/Silent Audio (Always Works)

  • File: src/audio_capture_dummy.c (NEW), src/audio_playback_dummy.c (NEW)
  • Method: Generate zeros (silent audio)
  • Performance: Instant
  • Availability: Always (no dependencies)

Implementation

File 1: src/audio_capture_pulse.c - PulseAudio Audio Capture

/*
 * audio_capture_pulse.c - PulseAudio audio capture
 * 
 * Fallback for systems where ALSA is unavailable or misconfigured.
 * Uses libpulse simple API for straightforward recording.
 */

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

#ifdef HAVE_PULSE
#include <pulse/simple.h>
#include <pulse/error.h>

typedef struct {
    pa_simple *s;
    int sample_rate;
    int channels;
    int frame_size;
} pulse_capture_ctx_t;

static char last_error[256] = {0};

int audio_capture_init_pulse(rootstream_ctx_t *ctx) {
    pulse_capture_ctx_t *pulse = calloc(1, sizeof(pulse_capture_ctx_t));
    if (!pulse) {
        snprintf(last_error, sizeof(last_error), "Memory allocation failed");
        return -1;
    }

    pulse->sample_rate = 48000;
    pulse->channels = 2;
    pulse->frame_size = 240;  /* 5ms at 48kHz */

    pa_sample_spec ss = {
        .format = PA_SAMPLE_S16LE,
        .rate = pulse->sample_rate,
        .channels = pulse->channels
    };

    int error;
    pulse->s = pa_simple_new(NULL, NULL, PA_STREAM_RECORD, NULL,
                            "RootStream", &ss, NULL, &error);
    if (!pulse->s) {
        snprintf(last_error, sizeof(last_error), "PulseAudio error: %s",
                pa_strerror(error));
        free(pulse);
        return -1;
    }

    ctx->audio_capture_priv = pulse;
    printf("✓ PulseAudio audio capture initialized (48kHz, stereo)\n");
    return 0;
}

int audio_capture_frame_pulse(rootstream_ctx_t *ctx, int16_t *samples,
                             size_t *num_samples) {
    if (!ctx || !samples || !num_samples) return -1;
    
    pulse_capture_ctx_t *pulse = (pulse_capture_ctx_t *)ctx->audio_capture_priv;
    if (!pulse || !pulse->s) return -1;

    size_t bytes = pulse->frame_size * pulse->channels * sizeof(int16_t);
    int error;
    
    if (pa_simple_read(pulse->s, samples, bytes, &error) < 0) {
        snprintf(last_error, sizeof(last_error), "PulseAudio read error: %s",
                pa_strerror(error));
        return -1;
    }

    *num_samples = pulse->frame_size * pulse->channels;
    return 0;
}

void audio_capture_cleanup_pulse(rootstream_ctx_t *ctx) {
    if (!ctx || !ctx->audio_capture_priv) return;
    
    pulse_capture_ctx_t *pulse = (pulse_capture_ctx_t *)ctx->audio_capture_priv;
    if (pulse->s) pa_simple_free(pulse->s);
    free(pulse);
    ctx->audio_capture_priv = NULL;
}

bool audio_capture_pulse_available(void) {
    /* Try to create a dummy connection */
    pa_sample_spec ss = {
        .format = PA_SAMPLE_S16LE,
        .rate = 48000,
        .channels = 2
    };
    int error;
    pa_simple *s = pa_simple_new(NULL, NULL, PA_STREAM_RECORD, NULL,
                                "test", &ss, NULL, &error);
    if (s) {
        pa_simple_free(s);
        return true;
    }
    return false;
}

#else

/* Stub for NO_PULSE builds */
int audio_capture_init_pulse(rootstream_ctx_t *ctx) {
    (void)ctx;
    snprintf(last_error, sizeof(last_error), "PulseAudio not compiled in");
    return -1;
}

int audio_capture_frame_pulse(rootstream_ctx_t *ctx, int16_t *samples,
  ...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

Copilot AI and others added 2 commits February 13, 2026 01:34
Co-authored-by: infinityabundance <255699974+infinityabundance@users.noreply.github.com>
Co-authored-by: infinityabundance <255699974+infinityabundance@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement multi-fallback audio pipeline with graceful degradation PHASE 3: Enable multi-fallback audio pipeline (PulseAudio + backend integration) Feb 13, 2026
@infinityabundance infinityabundance marked this pull request as ready for review February 13, 2026 01:42
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@infinityabundance infinityabundance merged commit 9feb420 into main Feb 13, 2026
5 of 9 checks passed
@infinityabundance infinityabundance deleted the copilot/multi-fallback-audio-pipeline branch February 19, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants