/** * Audio Router - Verwaltet Audio-Quellen und -Routing * * USB hat Priorität über Bluetooth * Automatischer Wechsel bei Verbindungsänderungen */ #include #include "audio_router.h" #include "bluetooth/bt_manager.h" #include "usb_audio/usb_audio_host.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" static const char* TAG = "AUDIO_RT"; // State static bool s_initialized = false; static audio_source_t s_active_source = AUDIO_SOURCE_NONE; static bool s_call_active = false; static uint8_t s_volume = 80; static bool s_muted = false; static audio_stats_t s_stats; static SemaphoreHandle_t s_mutex = NULL; // Callbacks static audio_from_headset_callback_t s_input_callback = NULL; static headset_button_callback_t s_button_callback = NULL; static audio_source_change_callback_t s_source_change_callback = NULL; // Forward Declarations static void update_active_source(void); static void bt_audio_data_handler(const uint8_t* data, size_t len); static void bt_button_handler(bt_button_event_t event); static void usb_audio_data_handler(const uint8_t* data, size_t len); static void usb_button_handler(usb_button_event_t event); static void usb_state_handler(usb_audio_state_t state); // Callback von Bluetooth Manager static void bt_audio_data_handler(const uint8_t* data, size_t len) { if (s_active_source != AUDIO_SOURCE_BLUETOOTH || !s_call_active) { return; } s_stats.packets_received++; // An SIP weiterleiten if (s_input_callback) { audio_format_t format = { .sample_rate = 8000, // HFP Standard .channels = 1, .bits_per_sample = 16 }; s_input_callback(data, len, &format); } } static void bt_button_handler(bt_button_event_t event) { headset_button_t button; switch (event) { case BT_BUTTON_ANSWER: button = HEADSET_BUTTON_ANSWER; break; case BT_BUTTON_REJECT: button = HEADSET_BUTTON_REJECT; break; case BT_BUTTON_HANGUP: button = HEADSET_BUTTON_HANGUP; break; case BT_BUTTON_VOLUME_UP: button = HEADSET_BUTTON_VOLUME_UP; break; case BT_BUTTON_VOLUME_DOWN: button = HEADSET_BUTTON_VOLUME_DOWN; break; case BT_BUTTON_MUTE: button = HEADSET_BUTTON_MUTE; break; default: return; } if (s_button_callback) { s_button_callback(button, AUDIO_SOURCE_BLUETOOTH); } } // Callback von USB Audio static void usb_audio_data_handler(const uint8_t* data, size_t len) { if (s_active_source != AUDIO_SOURCE_USB || !s_call_active) { return; } s_stats.packets_received++; // An SIP weiterleiten if (s_input_callback) { audio_format_t format = { .sample_rate = 16000, // USB typisch .channels = 1, .bits_per_sample = 16 }; s_input_callback(data, len, &format); } } static void usb_button_handler(usb_button_event_t event) { headset_button_t button; switch (event) { case USB_BUTTON_ANSWER: button = HEADSET_BUTTON_ANSWER; break; case USB_BUTTON_HANGUP: button = HEADSET_BUTTON_HANGUP; break; case USB_BUTTON_MUTE: button = HEADSET_BUTTON_MUTE; break; case USB_BUTTON_VOLUME_UP: button = HEADSET_BUTTON_VOLUME_UP; break; case USB_BUTTON_VOLUME_DOWN: button = HEADSET_BUTTON_VOLUME_DOWN; break; default: return; } if (s_button_callback) { s_button_callback(button, AUDIO_SOURCE_USB); } } static void usb_state_handler(usb_audio_state_t state) { ESP_LOGI(TAG, "USB Audio State: %d", state); // Bei USB Verbindungsänderung Quelle neu evaluieren update_active_source(); } static void update_active_source(void) { audio_source_t new_source = AUDIO_SOURCE_NONE; xSemaphoreTake(s_mutex, portMAX_DELAY); // USB hat höchste Priorität if (usb_audio_host_is_connected()) { new_source = AUDIO_SOURCE_USB; } // Bluetooth als Fallback else if (bt_manager_is_connected()) { new_source = AUDIO_SOURCE_BLUETOOTH; } if (new_source != s_active_source) { audio_source_t old_source = s_active_source; s_active_source = new_source; const char* sources[] = {"Keine", "USB", "Bluetooth"}; ESP_LOGI(TAG, "Audio-Quelle: %s -> %s", sources[old_source], sources[new_source]); // Bei aktivem Anruf: Streaming anpassen if (s_call_active) { // Altes Audio stoppen if (old_source == AUDIO_SOURCE_USB) { usb_audio_host_stop_stream(); } // Neues Audio starten if (new_source == AUDIO_SOURCE_USB) { usb_audio_host_start_stream(); } } xSemaphoreGive(s_mutex); // Callback aufrufen if (s_source_change_callback) { s_source_change_callback(old_source, new_source); } } else { xSemaphoreGive(s_mutex); } } esp_err_t audio_router_init(void) { if (s_initialized) { return ESP_OK; } ESP_LOGI(TAG, "Initialisiere Audio Router"); s_mutex = xSemaphoreCreateMutex(); if (!s_mutex) { ESP_LOGE(TAG, "Mutex erstellen fehlgeschlagen"); return ESP_ERR_NO_MEM; } // Callbacks bei Audio-Quellen registrieren bt_manager_register_audio_data_callback(bt_audio_data_handler); bt_manager_register_button_callback(bt_button_handler); usb_audio_host_register_data_callback(usb_audio_data_handler); usb_audio_host_register_button_callback(usb_button_handler); usb_audio_host_register_state_callback(usb_state_handler); memset(&s_stats, 0, sizeof(s_stats)); s_initialized = true; // Initial die Quelle bestimmen update_active_source(); ESP_LOGI(TAG, "Audio Router initialisiert"); return ESP_OK; } esp_err_t audio_router_deinit(void) { if (!s_initialized) return ESP_OK; ESP_LOGI(TAG, "Deinitalisiere Audio Router"); if (s_call_active) { audio_router_stop_call(); } if (s_mutex) { vSemaphoreDelete(s_mutex); s_mutex = NULL; } s_initialized = false; return ESP_OK; } audio_source_t audio_router_get_active_source(void) { return s_active_source; } bool audio_router_is_source_available(audio_source_t source) { switch (source) { case AUDIO_SOURCE_USB: return usb_audio_host_is_connected(); case AUDIO_SOURCE_BLUETOOTH: return bt_manager_is_connected(); default: return false; } } esp_err_t audio_router_start_call(void) { if (s_call_active) { return ESP_OK; } ESP_LOGI(TAG, "Starte Audio für Anruf"); xSemaphoreTake(s_mutex, portMAX_DELAY); s_call_active = true; memset(&s_stats, 0, sizeof(s_stats)); // Audio-Streaming auf aktiver Quelle starten if (s_active_source == AUDIO_SOURCE_USB) { usb_audio_host_start_stream(); } else if (s_active_source == AUDIO_SOURCE_BLUETOOTH) { // BT Audio wird automatisch über HFP gestartet // bt_hfp_audio_connect() wird vom SIP-Client aufgerufen } xSemaphoreGive(s_mutex); return ESP_OK; } esp_err_t audio_router_stop_call(void) { if (!s_call_active) { return ESP_OK; } ESP_LOGI(TAG, "Stoppe Audio für Anruf"); xSemaphoreTake(s_mutex, portMAX_DELAY); s_call_active = false; // Audio-Streaming stoppen if (s_active_source == AUDIO_SOURCE_USB) { usb_audio_host_stop_stream(); } xSemaphoreGive(s_mutex); return ESP_OK; } esp_err_t audio_router_send_to_headset(const uint8_t* data, size_t len, const audio_format_t* format) { if (!s_call_active || s_active_source == AUDIO_SOURCE_NONE) { return ESP_ERR_INVALID_STATE; } if (s_muted) { return ESP_OK; // Muted - nichts senden } s_stats.packets_sent++; // TODO: Lautstärke anwenden // TODO: Format-Konvertierung wenn nötig esp_err_t ret = ESP_OK; if (s_active_source == AUDIO_SOURCE_USB) { ret = usb_audio_host_send(data, len); } else if (s_active_source == AUDIO_SOURCE_BLUETOOTH) { ret = bt_manager_send_audio(data, len); } if (ret != ESP_OK) { s_stats.underruns++; } return ret; } void audio_router_register_input_callback(audio_from_headset_callback_t callback) { s_input_callback = callback; } void audio_router_register_button_callback(headset_button_callback_t callback) { s_button_callback = callback; } void audio_router_register_source_change_callback(audio_source_change_callback_t callback) { s_source_change_callback = callback; } esp_err_t audio_router_set_volume(uint8_t volume) { if (volume > 100) volume = 100; s_volume = volume; ESP_LOGI(TAG, "Lautstärke: %d%%", volume); // Lautstärke an aktive Quelle weitergeben if (s_active_source == AUDIO_SOURCE_USB) { usb_audio_host_set_volume(volume); } // BT: Volume wird über HFP AG gehandhabt return ESP_OK; } uint8_t audio_router_get_volume(void) { return s_volume; } esp_err_t audio_router_set_mute(bool mute) { s_muted = mute; ESP_LOGI(TAG, "Mute: %s", mute ? "an" : "aus"); if (s_active_source == AUDIO_SOURCE_USB) { usb_audio_host_set_mute(mute); } return ESP_OK; } bool audio_router_is_muted(void) { return s_muted; } esp_err_t audio_router_get_stats(audio_stats_t* stats) { if (!stats) return ESP_ERR_INVALID_ARG; memcpy(stats, &s_stats, sizeof(audio_stats_t)); return ESP_OK; }