diff --git a/include/rootstream.h b/include/rootstream.h index 44995e8..b69ca1d 100644 --- a/include/rootstream.h +++ b/include/rootstream.h @@ -64,6 +64,34 @@ /* RootStream code format: base64(pubkey) + "@" + hostname */ #define ROOTSTREAM_CODE_MAX_LEN 128 +/* Simple fallback selection macro (PHASE 0) + * + * This macro simplifies backend initialization with automatic fallback. + * Reserved for future phases (1-6) when additional fallback backends are added. + * + * Example usage (future): + * int result; + * const char *name; + * TRY_INIT_BACKEND(ctx, primary_init, "Primary", fallback_init, "Fallback", &result, &name); + * if (result) { + * ctx->active_backend.some_name = name; + * } + */ +#define TRY_INIT_BACKEND(ctx, primary_fn, primary_name, fallback_fn, fallback_name, result_ptr, name_ptr) \ + do { \ + if ((primary_fn)(ctx) == 0) { \ + *(name_ptr) = (primary_name); \ + *(result_ptr) = 1; /* Mark as initialized */ \ + } else if ((fallback_fn) != NULL && (fallback_fn)(ctx) == 0) { \ + printf("INFO: Primary failed, using fallback: %s\n", (fallback_name)); \ + *(name_ptr) = (fallback_name); \ + *(result_ptr) = 1; \ + } else { \ + printf("ERROR: Both primary and fallback failed\n"); \ + *(result_ptr) = 0; \ + } \ + } while(0) + /* ============================================================================ * CAPTURE - Multi-backend framebuffer capture * ============================================================================ */ @@ -458,6 +486,23 @@ typedef struct rootstream_ctx { bool is_host; /* Host mode (streamer) */ uint64_t last_video_ts_us; /* Last received video timestamp */ uint64_t last_audio_ts_us; /* Last received audio timestamp */ + + /* Backend tracking (added in PHASE 0) */ + struct { + const char *capture_name; /* Name of active capture backend */ + const char *encoder_name; /* Name of active encoder backend */ + const char *audio_cap_name; /* Name of active audio capture backend */ + const char *audio_play_name; /* Name of active audio playback backend */ + const char *decoder_name; /* Name of active decoder backend */ + const char *display_name; /* Name of active display backend */ + } active_backend; + + /* User preferences for backend override */ + struct { + const char *capture_override; /* User-specified capture backend */ + const char *encoder_override; /* User-specified encoder backend */ + bool verbose; /* Print fallback attempts */ + } backend_prefs; } rootstream_ctx_t; /* Encoder backend abstraction for multi-tier fallback */ diff --git a/src/core.c b/src/core.c index 2ea4efb..8f7d704 100644 --- a/src/core.c +++ b/src/core.c @@ -60,6 +60,20 @@ int rootstream_init(rootstream_ctx_t *ctx) { printf(" %s\n", ctx->keypair.rootstream_code); printf("\n"); + /* Initialize backend tracking (PHASE 0) */ + ctx->active_backend.capture_name = "uninitialized"; + ctx->active_backend.encoder_name = "uninitialized"; + ctx->active_backend.audio_cap_name = "uninitialized"; + ctx->active_backend.audio_play_name = "uninitialized"; + ctx->active_backend.decoder_name = "uninitialized"; + ctx->active_backend.display_name = "uninitialized"; + + ctx->backend_prefs.capture_override = NULL; + ctx->backend_prefs.encoder_override = NULL; + ctx->backend_prefs.verbose = false; + + printf("INFO: Backend infrastructure initialized\n"); + return 0; } diff --git a/src/main.c b/src/main.c index 679f56f..a04067e 100644 --- a/src/main.c +++ b/src/main.c @@ -285,6 +285,7 @@ int main(int argc, char **argv) { {"no-discovery",no_argument, 0, 'n'}, {"latency-log", no_argument, 0, 'l'}, {"latency-interval", required_argument, 0, 'i'}, + {"backend-verbose", no_argument, 0, 0}, {0, 0, 0, 0} }; @@ -298,10 +299,19 @@ int main(int argc, char **argv) { const char *record_file = NULL; bool latency_log = false; uint64_t latency_interval_ms = 1000; + bool backend_verbose = false; int opt; - while ((opt = getopt_long(argc, argv, "hvqLsp:d:b:r:nli:", long_options, NULL)) != -1) { + int option_index = 0; + while ((opt = getopt_long(argc, argv, "hvqLsp:d:b:r:nli:", long_options, &option_index)) != -1) { switch (opt) { + case 0: + /* Long option without short equivalent */ + if (strcmp(long_options[option_index].name, "backend-verbose") == 0) { + backend_verbose = true; + printf("INFO: Backend selection verbose mode enabled\n"); + } + break; case 'h': print_usage(argv[0]); return 0; @@ -367,6 +377,9 @@ int main(int argc, char **argv) { return 1; } + /* Set backend verbose mode if requested */ + ctx.backend_prefs.verbose = backend_verbose; + if (latency_init(&ctx.latency, 240, latency_interval_ms, latency_log) < 0) { fprintf(stderr, "WARNING: Latency logging disabled due to init failure\n"); } diff --git a/src/service.c b/src/service.c index 7e609a7..5b8e6d2 100644 --- a/src/service.c +++ b/src/service.c @@ -184,6 +184,7 @@ int service_run_host(rootstream_ctx_t *ctx) { if (backends[backend_idx].init_fn(ctx) == 0) { printf("✓ Capture backend '%s' initialized successfully\n", backends[backend_idx].name); ctx->capture_backend = &backends[backend_idx]; + ctx->active_backend.capture_name = backends[backend_idx].name; break; } else { printf("WARNING: Capture backend '%s' failed, trying next...\n", backends[backend_idx].name); @@ -268,6 +269,7 @@ int service_run_host(rootstream_ctx_t *ctx) { if (init_result == 0) { printf("✓ Encoder backend '%s' initialized successfully\n", backend->name); ctx->encoder_backend = backend; + ctx->active_backend.encoder_name = backend->name; encoder_initialized = true; /* Warn if using fallback (software or raw) */ @@ -345,6 +347,7 @@ int service_run_host(rootstream_ctx_t *ctx) { if (capture_backends[capture_idx].init_fn(ctx) == 0) { printf("✓ Audio capture backend '%s' initialized\n", capture_backends[capture_idx].name); ctx->audio_capture_backend = &capture_backends[capture_idx]; + ctx->active_backend.audio_cap_name = capture_backends[capture_idx].name; break; } else { printf("WARNING: Audio capture backend '%s' failed, trying next...\n", @@ -355,6 +358,7 @@ int service_run_host(rootstream_ctx_t *ctx) { if (!ctx->audio_capture_backend) { printf("WARNING: All audio capture backends failed, streaming video only\n"); + ctx->active_backend.audio_cap_name = "disabled"; } else { /* Initialize Opus encoder */ printf("INFO: Initializing Opus encoder...\n"); @@ -364,11 +368,13 @@ int service_run_host(rootstream_ctx_t *ctx) { ctx->audio_capture_backend->cleanup_fn(ctx); } ctx->audio_capture_backend = NULL; + ctx->active_backend.audio_cap_name = "disabled"; } } } else { printf("INFO: Audio disabled in settings\n"); ctx->audio_capture_backend = NULL; + ctx->active_backend.audio_cap_name = "disabled"; } /* Announce service */ @@ -393,6 +399,17 @@ int service_run_host(rootstream_ctx_t *ctx) { return -1; } + /* Report active backends (PHASE 0) */ + printf("\n"); + printf("╔════════════════════════════════════════════════╗\n"); + printf("║ RootStream Backend Status ║\n"); + printf("╚════════════════════════════════════════════════╝\n"); + printf("Capture: %s\n", ctx->active_backend.capture_name); + printf("Encoder: %s\n", ctx->active_backend.encoder_name); + printf("Audio Cap: %s\n", ctx->active_backend.audio_cap_name); + printf("Audio Play: %s\n", ctx->active_backend.audio_play_name); + printf("\n"); + /* Main loop */ while (service_running && ctx->running) { uint64_t loop_start_us = get_timestamp_us(); @@ -525,6 +542,17 @@ int service_run_client(rootstream_ctx_t *ctx) { fprintf(stderr, "ERROR: Decoder initialization failed\n"); return -1; } + /* Set decoder backend name based on platform + * NOTE: This is a simple platform check. For Phase 0, we assume: + * - Windows uses Media Foundation decoder + * - Linux/Unix uses VA-API decoder + * Future phases could add runtime detection if needed. + */ + #ifdef _WIN32 + ctx->active_backend.decoder_name = "Media Foundation"; + #else + ctx->active_backend.decoder_name = "VA-API"; + #endif /* Initialize display (SDL2 window) */ if (display_init(ctx, "RootStream Client", 1920, 1080) < 0) { @@ -532,6 +560,7 @@ int service_run_client(rootstream_ctx_t *ctx) { rootstream_decoder_cleanup(ctx); return -1; } + ctx->active_backend.display_name = "SDL2"; /* Initialize audio playback with fallback */ if (ctx->settings.audio_enabled) { @@ -577,6 +606,7 @@ int service_run_client(rootstream_ctx_t *ctx) { if (playback_backends[playback_idx].init_fn(ctx) == 0) { printf("✓ Audio playback backend '%s' initialized\n", playback_backends[playback_idx].name); ctx->audio_playback_backend = &playback_backends[playback_idx]; + ctx->active_backend.audio_play_name = playback_backends[playback_idx].name; break; } else { printf("WARNING: Audio playback backend '%s' failed, trying next...\n", @@ -587,6 +617,7 @@ int service_run_client(rootstream_ctx_t *ctx) { if (!ctx->audio_playback_backend) { printf("WARNING: All audio playback backends failed, watching video only\n"); + ctx->active_backend.audio_play_name = "disabled"; } else { /* Initialize Opus decoder */ printf("INFO: Initializing Opus decoder...\n"); @@ -596,11 +627,13 @@ int service_run_client(rootstream_ctx_t *ctx) { ctx->audio_playback_backend->cleanup_fn(ctx); } ctx->audio_playback_backend = NULL; + ctx->active_backend.audio_play_name = "disabled"; } } } else { printf("INFO: Audio disabled in settings\n"); ctx->audio_playback_backend = NULL; + ctx->active_backend.audio_play_name = "disabled"; } printf("✓ Client initialized - ready to receive video and audio\n"); @@ -609,6 +642,16 @@ int service_run_client(rootstream_ctx_t *ctx) { ctx->latency.report_interval_ms, ctx->latency.capacity); } + /* Report active backends (PHASE 0) */ + printf("\n"); + printf("╔════════════════════════════════════════════════╗\n"); + printf("║ RootStream Client Backend Status ║\n"); + printf("╚════════════════════════════════════════════════╝\n"); + printf("Decoder: %s\n", ctx->active_backend.decoder_name); + printf("Display: %s\n", ctx->active_backend.display_name); + printf("Audio Play: %s\n", ctx->active_backend.audio_play_name); + printf("\n"); + /* Allocate decode buffer */ frame_buffer_t decoded_frame = {0};