/** * USB Audio Host - USB Headset Unterstützung * * Verwendet ESP32-S3 USB OTG im Host-Modus für USB Audio Class Geräte * Nur auf ESP32-S3 verfügbar (USB OTG Hardware) */ #include #include "usb_audio_host.h" #include "esp_log.h" #include "sdkconfig.h" static const char* TAG = "USB_AUDIO"; #if CONFIG_IDF_TARGET_ESP32S3 #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/ringbuf.h" #include "usb/usb_host.h" // USB Audio Class Definitionen #define USB_CLASS_AUDIO 0x01 #define USB_SUBCLASS_AUDIOCONTROL 0x01 #define USB_SUBCLASS_AUDIOSTREAMING 0x02 #define USB_CLASS_HID 0x03 // State static bool s_initialized = false; static bool s_host_installed = false; static usb_audio_state_t s_state = USB_AUDIO_STATE_NOT_CONNECTED; static usb_audio_info_t s_device_info; static usb_host_client_handle_t s_client_handle = NULL; static usb_device_handle_t s_device_handle = NULL; static TaskHandle_t s_usb_task_handle = NULL; // Audio Buffers #define AUDIO_RINGBUF_SIZE (16 * 1024) static RingbufHandle_t s_audio_in_ringbuf = NULL; // Vom USB Mic static RingbufHandle_t s_audio_out_ringbuf = NULL; // Zum USB Speaker // Callbacks static usb_audio_state_callback_t s_state_callback = NULL; static usb_audio_data_callback_t s_data_callback = NULL; static usb_button_callback_t s_button_callback = NULL; static void notify_state_change(usb_audio_state_t new_state) { s_state = new_state; if (s_state_callback) { s_state_callback(new_state); } } static void usb_host_client_event_callback(const usb_host_client_event_msg_t *event_msg, void *arg) { switch (event_msg->event) { case USB_HOST_CLIENT_EVENT_NEW_DEV: ESP_LOGI(TAG, "Neues USB-Gerät gefunden: Adresse %d", event_msg->new_dev.address); // Gerät öffnen if (usb_host_device_open(s_client_handle, event_msg->new_dev.address, &s_device_handle) == ESP_OK) { // Device Descriptor holen const usb_device_desc_t *dev_desc; if (usb_host_get_device_descriptor(s_device_handle, &dev_desc) == ESP_OK) { s_device_info.vendor_id = dev_desc->idVendor; s_device_info.product_id = dev_desc->idProduct; ESP_LOGI(TAG, "USB Gerät: VID=0x%04X PID=0x%04X", dev_desc->idVendor, dev_desc->idProduct); // Configuration Descriptor analysieren const usb_config_desc_t *config_desc; if (usb_host_get_active_config_descriptor(s_device_handle, &config_desc) == ESP_OK) { // Interfaces durchgehen int offset = 0; const usb_intf_desc_t *intf_desc; while ((intf_desc = usb_parse_interface_descriptor( config_desc, offset, 0, &offset)) != NULL) { if (intf_desc->bInterfaceClass == USB_CLASS_AUDIO) { if (intf_desc->bInterfaceSubClass == USB_SUBCLASS_AUDIOSTREAMING) { ESP_LOGI(TAG, "Audio Streaming Interface gefunden"); s_device_info.has_speaker = true; s_device_info.has_microphone = true; } } else if (intf_desc->bInterfaceClass == USB_CLASS_HID) { ESP_LOGI(TAG, "HID Interface gefunden (Tasten)"); s_device_info.has_hid = true; } } if (s_device_info.has_speaker || s_device_info.has_microphone) { notify_state_change(USB_AUDIO_STATE_CONNECTED); // Standard Audio-Format s_device_info.sample_rate = 16000; s_device_info.channels = 1; s_device_info.bit_depth = 16; ESP_LOGI(TAG, "USB Audio Headset erkannt"); } } } } break; case USB_HOST_CLIENT_EVENT_DEV_GONE: ESP_LOGI(TAG, "USB-Gerät entfernt"); if (s_device_handle) { usb_host_device_close(s_client_handle, s_device_handle); s_device_handle = NULL; } memset(&s_device_info, 0, sizeof(s_device_info)); notify_state_change(USB_AUDIO_STATE_NOT_CONNECTED); break; default: break; } } static void usb_host_task(void *arg) { ESP_LOGI(TAG, "USB Host Task gestartet"); while (s_initialized) { // USB Host Events verarbeiten usb_host_lib_handle_events(pdMS_TO_TICKS(100), NULL); // Client Events verarbeiten if (s_client_handle) { usb_host_client_handle_events(s_client_handle, pdMS_TO_TICKS(100)); } // Wenn verbunden: Audio-Daten verarbeiten if (s_state == USB_AUDIO_STATE_STREAMING && s_audio_in_ringbuf) { // Audio vom USB lesen und Callback aufrufen size_t item_size; uint8_t* data = xRingbufferReceive(s_audio_in_ringbuf, &item_size, 0); if (data && item_size > 0) { if (s_data_callback) { s_data_callback(data, item_size); } vRingbufferReturnItem(s_audio_in_ringbuf, data); } } vTaskDelay(pdMS_TO_TICKS(10)); } ESP_LOGI(TAG, "USB Host Task beendet"); vTaskDelete(NULL); } esp_err_t usb_audio_host_init(void) { if (s_initialized) { return ESP_OK; } ESP_LOGI(TAG, "Initialisiere USB Audio Host"); // Audio Buffers erstellen s_audio_in_ringbuf = xRingbufferCreate(AUDIO_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF); s_audio_out_ringbuf = xRingbufferCreate(AUDIO_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF); if (!s_audio_in_ringbuf || !s_audio_out_ringbuf) { ESP_LOGE(TAG, "Ringbuffer erstellen fehlgeschlagen"); return ESP_ERR_NO_MEM; } // USB Host Library installieren usb_host_config_t host_config = { .skip_phy_setup = false, .intr_flags = ESP_INTR_FLAG_LEVEL1, }; esp_err_t ret = usb_host_install(&host_config); if (ret != ESP_OK) { ESP_LOGE(TAG, "USB Host install failed: %s", esp_err_to_name(ret)); return ret; } s_host_installed = true; // Client registrieren usb_host_client_config_t client_config = { .is_synchronous = false, .max_num_event_msg = 5, .async = { .client_event_callback = usb_host_client_event_callback, .callback_arg = NULL, }, }; ret = usb_host_client_register(&client_config, &s_client_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "USB Client register failed: %s", esp_err_to_name(ret)); return ret; } s_initialized = true; // USB Host Task starten xTaskCreate(usb_host_task, "usb_host", 4096, NULL, 5, &s_usb_task_handle); ESP_LOGI(TAG, "USB Audio Host initialisiert"); return ESP_OK; } esp_err_t usb_audio_host_deinit(void) { if (!s_initialized) return ESP_OK; ESP_LOGI(TAG, "Deinitalisiere USB Audio Host"); s_initialized = false; // Warten auf Task-Ende if (s_usb_task_handle) { vTaskDelay(pdMS_TO_TICKS(200)); } // Gerät schließen if (s_device_handle && s_client_handle) { usb_host_device_close(s_client_handle, s_device_handle); s_device_handle = NULL; } // Client deregistrieren if (s_client_handle) { usb_host_client_deregister(s_client_handle); s_client_handle = NULL; } // USB Host deinstallieren if (s_host_installed) { usb_host_uninstall(); s_host_installed = false; } // Buffers freigeben if (s_audio_in_ringbuf) { vRingbufferDelete(s_audio_in_ringbuf); s_audio_in_ringbuf = NULL; } if (s_audio_out_ringbuf) { vRingbufferDelete(s_audio_out_ringbuf); s_audio_out_ringbuf = NULL; } return ESP_OK; } usb_audio_state_t usb_audio_host_get_state(void) { return s_state; } bool usb_audio_host_is_connected(void) { return s_state >= USB_AUDIO_STATE_CONNECTED; } esp_err_t usb_audio_host_get_info(usb_audio_info_t* info) { if (!info) return ESP_ERR_INVALID_ARG; if (s_state == USB_AUDIO_STATE_NOT_CONNECTED) return ESP_ERR_INVALID_STATE; memcpy(info, &s_device_info, sizeof(usb_audio_info_t)); return ESP_OK; } esp_err_t usb_audio_host_start_stream(void) { if (s_state < USB_AUDIO_STATE_CONNECTED) { return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "Starte USB Audio Stream"); // TODO: Echte USB Audio Stream Konfiguration // Dies würde das Konfigurieren der Audio-Interfaces, // Setzen der Samplerate und Starten der Isochronen Transfers beinhalten s_state = USB_AUDIO_STATE_STREAMING; notify_state_change(USB_AUDIO_STATE_STREAMING); return ESP_OK; } esp_err_t usb_audio_host_stop_stream(void) { if (s_state != USB_AUDIO_STATE_STREAMING) { return ESP_OK; } ESP_LOGI(TAG, "Stoppe USB Audio Stream"); s_state = USB_AUDIO_STATE_CONNECTED; notify_state_change(USB_AUDIO_STATE_CONNECTED); return ESP_OK; } esp_err_t usb_audio_host_send(const uint8_t* data, size_t len) { if (s_state != USB_AUDIO_STATE_STREAMING || !s_audio_out_ringbuf) { return ESP_ERR_INVALID_STATE; } if (xRingbufferSend(s_audio_out_ringbuf, data, len, 0) != pdTRUE) { ESP_LOGW(TAG, "Audio buffer overflow"); return ESP_ERR_NO_MEM; } return ESP_OK; } esp_err_t usb_audio_host_set_volume(uint8_t volume) { if (s_state < USB_AUDIO_STATE_CONNECTED) { return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "Setze Lautstärke: %d%%", volume); // TODO: USB Audio Class Volume Control implementieren return ESP_OK; } esp_err_t usb_audio_host_set_mute(bool mute) { if (s_state < USB_AUDIO_STATE_CONNECTED) { return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "Mute: %s", mute ? "an" : "aus"); // TODO: USB Audio Class Mute Control implementieren return ESP_OK; } void usb_audio_host_register_state_callback(usb_audio_state_callback_t callback) { s_state_callback = callback; } void usb_audio_host_register_data_callback(usb_audio_data_callback_t callback) { s_data_callback = callback; } void usb_audio_host_register_button_callback(usb_button_callback_t callback) { s_button_callback = callback; } bool usb_audio_host_is_available(void) { return true; } #else // Nicht ESP32-S3 - Stub-Implementierungen esp_err_t usb_audio_host_init(void) { ESP_LOGW(TAG, "USB Audio Host nicht verfügbar (nur ESP32-S3)"); return ESP_ERR_NOT_SUPPORTED; } esp_err_t usb_audio_host_deinit(void) { return ESP_OK; } usb_audio_state_t usb_audio_host_get_state(void) { return USB_AUDIO_STATE_NOT_CONNECTED; } bool usb_audio_host_is_connected(void) { return false; } esp_err_t usb_audio_host_get_info(usb_audio_info_t* info) { (void)info; return ESP_ERR_NOT_SUPPORTED; } esp_err_t usb_audio_host_start_stream(void) { return ESP_ERR_NOT_SUPPORTED; } esp_err_t usb_audio_host_stop_stream(void) { return ESP_OK; } esp_err_t usb_audio_host_send(const uint8_t* data, size_t len) { (void)data; (void)len; return ESP_ERR_NOT_SUPPORTED; } esp_err_t usb_audio_host_set_volume(uint8_t volume) { (void)volume; return ESP_ERR_NOT_SUPPORTED; } esp_err_t usb_audio_host_set_mute(bool mute) { (void)mute; return ESP_ERR_NOT_SUPPORTED; } void usb_audio_host_register_state_callback(usb_audio_state_callback_t callback) { (void)callback; } void usb_audio_host_register_data_callback(usb_audio_data_callback_t callback) { (void)callback; } void usb_audio_host_register_button_callback(usb_button_callback_t callback) { (void)callback; } bool usb_audio_host_is_available(void) { return false; } #endif // CONFIG_IDF_TARGET_ESP32S3