357 lines
10 KiB
C
357 lines
10 KiB
C
/**
|
|
* USB Audio Host - USB Headset Unterstützung
|
|
*
|
|
* Verwendet ESP32-S3 USB OTG im Host-Modus für USB Audio Class Geräte
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include "usb_audio_host.h"
|
|
#include "esp_log.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/ringbuf.h"
|
|
#include "usb/usb_host.h"
|
|
|
|
static const char* TAG = "USB_AUDIO";
|
|
|
|
// 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;
|
|
}
|