402 lines
9.7 KiB
C
402 lines
9.7 KiB
C
/**
|
|
* Audio Router - Verwaltet Audio-Quellen und -Routing
|
|
*
|
|
* USB hat Priorität über Bluetooth
|
|
* Automatischer Wechsel bei Verbindungsänderungen
|
|
*/
|
|
|
|
#include <string.h>
|
|
#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;
|
|
}
|