first commit
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Audio-Quelle
|
||||
typedef enum {
|
||||
AUDIO_SOURCE_NONE = 0,
|
||||
AUDIO_SOURCE_USB, // USB Headset (höchste Priorität)
|
||||
AUDIO_SOURCE_BLUETOOTH // Bluetooth Headset
|
||||
} audio_source_t;
|
||||
|
||||
// Audio-Format
|
||||
typedef struct {
|
||||
uint32_t sample_rate;
|
||||
uint8_t channels;
|
||||
uint8_t bits_per_sample;
|
||||
} audio_format_t;
|
||||
|
||||
// Audio-Statistiken
|
||||
typedef struct {
|
||||
uint32_t packets_sent;
|
||||
uint32_t packets_received;
|
||||
uint32_t underruns;
|
||||
uint32_t overruns;
|
||||
int16_t input_level_db;
|
||||
int16_t output_level_db;
|
||||
} audio_stats_t;
|
||||
|
||||
// Button Event Callback (vereinheitlicht USB und BT)
|
||||
typedef enum {
|
||||
HEADSET_BUTTON_ANSWER = 0,
|
||||
HEADSET_BUTTON_HANGUP,
|
||||
HEADSET_BUTTON_REJECT,
|
||||
HEADSET_BUTTON_MUTE,
|
||||
HEADSET_BUTTON_VOLUME_UP,
|
||||
HEADSET_BUTTON_VOLUME_DOWN
|
||||
} headset_button_t;
|
||||
|
||||
typedef void (*headset_button_callback_t)(headset_button_t button, audio_source_t source);
|
||||
typedef void (*audio_source_change_callback_t)(audio_source_t old_source, audio_source_t new_source);
|
||||
|
||||
/**
|
||||
* Initialisiert den Audio-Router
|
||||
*/
|
||||
esp_err_t audio_router_init(void);
|
||||
|
||||
/**
|
||||
* Deinitalisiert den Audio-Router
|
||||
*/
|
||||
esp_err_t audio_router_deinit(void);
|
||||
|
||||
/**
|
||||
* Gibt die aktuell aktive Audio-Quelle zurück
|
||||
* USB hat immer Priorität über Bluetooth
|
||||
*/
|
||||
audio_source_t audio_router_get_active_source(void);
|
||||
|
||||
/**
|
||||
* Prüft ob eine Audio-Quelle verfügbar ist
|
||||
*/
|
||||
bool audio_router_is_source_available(audio_source_t source);
|
||||
|
||||
/**
|
||||
* Startet Audio-Routing für einen Anruf
|
||||
*/
|
||||
esp_err_t audio_router_start_call(void);
|
||||
|
||||
/**
|
||||
* Stoppt Audio-Routing
|
||||
*/
|
||||
esp_err_t audio_router_stop_call(void);
|
||||
|
||||
/**
|
||||
* Sendet Audio-Daten von SIP zum aktiven Headset
|
||||
*/
|
||||
esp_err_t audio_router_send_to_headset(const uint8_t* data, size_t len, const audio_format_t* format);
|
||||
|
||||
/**
|
||||
* Empfängt Audio-Daten vom aktiven Headset für SIP
|
||||
* Wird intern über Callback verarbeitet
|
||||
*/
|
||||
typedef void (*audio_from_headset_callback_t)(const uint8_t* data, size_t len, const audio_format_t* format);
|
||||
void audio_router_register_input_callback(audio_from_headset_callback_t callback);
|
||||
|
||||
/**
|
||||
* Registriert Callback für Headset-Button-Events
|
||||
*/
|
||||
void audio_router_register_button_callback(headset_button_callback_t callback);
|
||||
|
||||
/**
|
||||
* Registriert Callback für Quellenwechsel
|
||||
*/
|
||||
void audio_router_register_source_change_callback(audio_source_change_callback_t callback);
|
||||
|
||||
/**
|
||||
* Setzt die Master-Lautstärke (0-100)
|
||||
*/
|
||||
esp_err_t audio_router_set_volume(uint8_t volume);
|
||||
|
||||
/**
|
||||
* Gibt die aktuelle Lautstärke zurück
|
||||
*/
|
||||
uint8_t audio_router_get_volume(void);
|
||||
|
||||
/**
|
||||
* Setzt Mute
|
||||
*/
|
||||
esp_err_t audio_router_set_mute(bool mute);
|
||||
|
||||
/**
|
||||
* Gibt den Mute-Status zurück
|
||||
*/
|
||||
bool audio_router_is_muted(void);
|
||||
|
||||
/**
|
||||
* Gibt Audio-Statistiken zurück
|
||||
*/
|
||||
esp_err_t audio_router_get_stats(audio_stats_t* stats);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user