/* * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_check.h" #include "esp_err.h" #include "esp_private/usb_phy.h" #include "esp_timer.h" #include "tusb.h" #include "uac_config.h" #include "usb_device_uac.h" #include "uac_descriptors.h" static const char *TAG = "usbd_uac"; const uint32_t sample_rates[] = {DEFAULT_SAMPLE_RATE}; #define N_SAMPLE_RATES TU_ARRAY_SIZE(sample_rates) enum { VOLUME_CTRL_0_DB = 0, VOLUME_CTRL_10_DB = 2560, VOLUME_CTRL_20_DB = 5120, VOLUME_CTRL_30_DB = 7680, VOLUME_CTRL_40_DB = 10240, VOLUME_CTRL_50_DB = 12800, VOLUME_CTRL_60_DB = 15360, VOLUME_CTRL_70_DB = 17920, VOLUME_CTRL_80_DB = 20480, VOLUME_CTRL_90_DB = 23040, VOLUME_CTRL_100_DB = 25600, VOLUME_CTRL_SILENCE = 0x8000, }; // Resolution per format const uint8_t spk_resolutions_per_format[CFG_TUD_AUDIO_FUNC_1_N_FORMATS] = {CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX}; const uint8_t mic_resolutions_per_format[CFG_TUD_AUDIO_FUNC_1_N_FORMATS] = {CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_TX}; typedef struct { usb_phy_handle_t phy_hdl; uac_device_config_t user_cfg; int8_t mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 int16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX + 1]; // +1 for master channel 0 int16_t mic_buf1[CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ / 2]; // Buffer for microphone data int16_t mic_buf2[CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ / 2]; // Buffer for microphone data int16_t spk_buf[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ / 2]; // Buffer for speaker data int16_t *mic_buf_write; // Pointer to the buffer to write to int16_t *mic_buf_read; // Pointer to the buffer to read from int spk_data_size; // Speaker data size received in the last frame int mic_data_size; int spk_itf_num; int mic_itf_num; uint8_t spk_resolution; uint8_t mic_resolution; uint32_t current_sample_rate; // Current resolution, update on format change TaskHandle_t mic_task_handle; TaskHandle_t spk_task_handle; size_t spk_bytes_per_ms; size_t mic_bytes_per_ms; bool spk_active; bool mic_active; } uac_device_t; static uac_device_t *s_uac_device = NULL; static portMUX_TYPE s_mux = portMUX_INITIALIZER_UNLOCKED; #define UAC_ENTER_CRITICAL() portENTER_CRITICAL(&s_mux) #define UAC_EXIT_CRITICAL() portEXIT_CRITICAL(&s_mux) static void usb_phy_init(void) { // Configure USB PHY usb_phy_config_t phy_conf = { .controller = USB_PHY_CTRL_OTG, .otg_mode = USB_OTG_MODE_DEVICE, .target = USB_PHY_TARGET_INT, #if CONFIG_TINYUSB_RHPORT_HS .otg_speed = USB_PHY_SPEED_HIGH, #endif }; usb_new_phy(&phy_conf, &s_uac_device->phy_hdl); } static void tusb_device_task(void *arg) { while (1) { tud_task(); } } #if !CONFIG_USB_DEVICE_UAC_AS_PART // Invoked when device is mounted void tud_mount_cb(void) { s_uac_device->spk_active = false; s_uac_device->mic_active = false; ESP_LOGI(TAG, "USB mounted"); } // Invoked when device is unmounted void tud_umount_cb(void) { ESP_LOGI(TAG, "USB unmounted"); } // Invoked when usb bus is suspended // remote_wakeup_en : if host allow us to perform remote wakeup // Within 7ms, device must draw an average of current less than 2.5 mA from bus void tud_suspend_cb(bool remote_wakeup_en) { (void)remote_wakeup_en; s_uac_device->spk_active = false; s_uac_device->mic_active = false; ESP_LOGI(TAG, "USB suspended"); } // Invoked when usb bus is resumed void tud_resume_cb(void) { ESP_LOGI(TAG, "USB resumed"); } #endif // Helper for clock get requests static bool tud_audio_clock_get_request(uint8_t rhport, audio_control_request_t const *request) { TU_ASSERT(request->bEntityID == UAC2_ENTITY_CLOCK); if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ) { if (request->bRequest == AUDIO_CS_REQ_CUR) { TU_LOG1("Clock get current freq %lu\r\n", s_uac_device->current_sample_rate); audio_control_cur_4_t curf = { (int32_t) tu_htole32(s_uac_device->current_sample_rate) }; return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &curf, sizeof(curf)); } else if (request->bRequest == AUDIO_CS_REQ_RANGE) { audio_control_range_4_n_t(N_SAMPLE_RATES) rangef = { .wNumSubRanges = tu_htole16(N_SAMPLE_RATES) }; TU_LOG1("Clock get %d freq ranges\r\n", N_SAMPLE_RATES); for (uint8_t i = 0; i < N_SAMPLE_RATES; i++) { rangef.subrange[i].bMin = (int32_t) sample_rates[i]; rangef.subrange[i].bMax = (int32_t) sample_rates[i]; rangef.subrange[i].bRes = 0; TU_LOG1("Range %d (%d, %d, %d)\r\n", i, (int)rangef.subrange[i].bMin, (int)rangef.subrange[i].bMax, (int)rangef.subrange[i].bRes); } return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &rangef, sizeof(rangef)); } } else if (request->bControlSelector == AUDIO_CS_CTRL_CLK_VALID && request->bRequest == AUDIO_CS_REQ_CUR) { audio_control_cur_1_t cur_valid = { .bCur = 1 }; TU_LOG1("Clock get is valid %u\r\n", cur_valid.bCur); return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &cur_valid, sizeof(cur_valid)); } TU_LOG1("Clock get request not supported, entity = %u, selector = %u, request = %u\r\n", request->bEntityID, request->bControlSelector, request->bRequest); return false; } // Helper for clock set requests static bool tud_audio_clock_set_request(uint8_t rhport, audio_control_request_t const *request, uint8_t const *buf) { (void)rhport; TU_ASSERT(request->bEntityID == UAC2_ENTITY_CLOCK); TU_VERIFY(request->bRequest == AUDIO_CS_REQ_CUR); if (request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ) { TU_VERIFY(request->wLength == sizeof(audio_control_cur_4_t)); uint32_t target_sample_rate = (uint32_t)((audio_control_cur_4_t const *)buf)->bCur; TU_LOG1("Clock set current freq: %ld\r\n", target_sample_rate); if (target_sample_rate != s_uac_device->current_sample_rate) { // For now, we only support one sample rate return false; } return true; } else { TU_LOG1("Clock set request not supported, entity = %u, selector = %u, request = %u\r\n", request->bEntityID, request->bControlSelector, request->bRequest); return false; } } void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t* feedback_param) { (void)func_id; (void)alt_itf; // Set feedback method to fifo counting feedback_param->method = AUDIO_FEEDBACK_METHOD_FIFO_COUNT; feedback_param->sample_freq = s_uac_device->current_sample_rate; ESP_LOGD(TAG, "Feedback method: %d, sample freq: %"PRIu32"", feedback_param->method, feedback_param->sample_freq); } // Helper for feature unit get requests static bool tud_audio_feature_unit_get_request(uint8_t rhport, audio_control_request_t const *request) { TU_ASSERT(request->bEntityID == UAC2_ENTITY_SPK_FEATURE_UNIT); if (request->bControlSelector == AUDIO_FU_CTRL_MUTE && request->bRequest == AUDIO_CS_REQ_CUR) { audio_control_cur_1_t mute1 = { .bCur = s_uac_device->mute[request->bChannelNumber] }; TU_LOG1("Get channel %u mute %d\r\n", request->bChannelNumber, mute1.bCur); return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &mute1, sizeof(mute1)); } else if (UAC2_ENTITY_SPK_FEATURE_UNIT && request->bControlSelector == AUDIO_FU_CTRL_VOLUME) { if (request->bRequest == AUDIO_CS_REQ_RANGE) { audio_control_range_2_n_t(1) range_vol = { .wNumSubRanges = tu_htole16(1), .subrange[0] = { .bMin = tu_htole16(-VOLUME_CTRL_50_DB), tu_htole16(VOLUME_CTRL_0_DB), tu_htole16(256) } }; TU_LOG1("Get channel %u volume range (%d, %d, %u) dB\r\n", request->bChannelNumber, range_vol.subrange[0].bMin / 256, range_vol.subrange[0].bMax / 256, range_vol.subrange[0].bRes / 256); return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &range_vol, sizeof(range_vol)); } else if (request->bRequest == AUDIO_CS_REQ_CUR) { audio_control_cur_2_t cur_vol = { .bCur = tu_htole16(s_uac_device->volume[request->bChannelNumber]) }; TU_LOG1("Get channel %u volume %d dB\r\n", request->bChannelNumber, cur_vol.bCur / 256); return tud_audio_buffer_and_schedule_control_xfer(rhport, (tusb_control_request_t const *)request, &cur_vol, sizeof(cur_vol)); } } TU_LOG1("Feature unit get request not supported, entity = %u, selector = %u, request = %u\r\n", request->bEntityID, request->bControlSelector, request->bRequest); return false; } static bool tud_audio_feature_unit_set_request(uint8_t rhport, audio_control_request_t const *request, uint8_t const *buf) { (void)rhport; TU_ASSERT(request->bEntityID == UAC2_ENTITY_SPK_FEATURE_UNIT); TU_VERIFY(request->bRequest == AUDIO_CS_REQ_CUR); if (request->bControlSelector == AUDIO_FU_CTRL_MUTE) { TU_VERIFY(request->wLength == sizeof(audio_control_cur_1_t)); s_uac_device->mute[request->bChannelNumber] = ((audio_control_cur_1_t const *)buf)->bCur; TU_LOG1("Set speaker channel %d Mute: %d\r\n", request->bChannelNumber, s_uac_device->mute[request->bChannelNumber]); if (s_uac_device->user_cfg.set_mute_cb) { s_uac_device->user_cfg.set_mute_cb(s_uac_device->mute[request->bChannelNumber], s_uac_device->user_cfg.cb_ctx); } return true; } else if (request->bControlSelector == AUDIO_FU_CTRL_VOLUME) { TU_VERIFY(request->wLength == sizeof(audio_control_cur_2_t)); s_uac_device->volume[request->bChannelNumber] = ((audio_control_cur_2_t const *)buf)->bCur; int volume_db = s_uac_device->volume[request->bChannelNumber] / 256; // Convert to dB int volume = (volume_db + 50) * 2; // Map to range 0 to 100 TU_LOG1("Set speaker channel %d volume: %d dB (%d)\r\n", request->bChannelNumber, volume_db, volume); if (s_uac_device->user_cfg.set_volume_cb) { s_uac_device->user_cfg.set_volume_cb(volume, s_uac_device->user_cfg.cb_ctx); } return true; } else { TU_LOG1("Feature unit set request not supported, entity = %u, selector = %u, request = %u\r\n", request->bEntityID, request->bControlSelector, request->bRequest); return false; } } //--------------------------------------------------------------------+ // Application Callback API Implementations //--------------------------------------------------------------------+ // Invoked when audio class specific get request received for an entity bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const *p_request) { audio_control_request_t const *request = (audio_control_request_t const *)p_request; if (request->bEntityID == UAC2_ENTITY_CLOCK) { return tud_audio_clock_get_request(rhport, request); } if (request->bEntityID == UAC2_ENTITY_SPK_FEATURE_UNIT) { return tud_audio_feature_unit_get_request(rhport, request); } else { TU_LOG1("Get request not handled, entity = %d, selector = %d, request = %d\r\n", request->bEntityID, request->bControlSelector, request->bRequest); } return false; } // Invoked when audio class specific set request received for an entity bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *buf) { audio_control_request_t const *request = (audio_control_request_t const *)p_request; if (request->bEntityID == UAC2_ENTITY_SPK_FEATURE_UNIT) { return tud_audio_feature_unit_set_request(rhport, request, buf); } if (request->bEntityID == UAC2_ENTITY_CLOCK) { return tud_audio_clock_set_request(rhport, request, buf); } TU_LOG1("Set request not handled, entity = %d, selector = %d, request = %d\r\n", request->bEntityID, request->bControlSelector, request->bRequest); return false; } bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const *p_request) { (void)rhport; uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex)); uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue)); #if CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX if (s_uac_device->spk_itf_num == itf && alt == 0) { TU_LOG2("Speaker interface closed"); s_uac_device->spk_data_size = 0; s_uac_device->spk_active = false; } #endif #if CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX if (s_uac_device->mic_itf_num == itf && alt == 0) { TU_LOG2("Microphone interface closed"); s_uac_device->mic_data_size = 0; s_uac_device->mic_active = false; } #endif return true; } bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const *p_request) { (void)rhport; uint8_t const itf = tu_u16_low(tu_le16toh(p_request->wIndex)); uint8_t const alt = tu_u16_low(tu_le16toh(p_request->wValue)); TU_LOG2("Set interface %d alt %d\r\n", itf, alt); #if CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX if (s_uac_device->spk_itf_num == itf && alt != 0) { s_uac_device->spk_data_size = 0; s_uac_device->spk_resolution = spk_resolutions_per_format[alt - 1]; s_uac_device->spk_active = true; s_uac_device->spk_bytes_per_ms = s_uac_device->current_sample_rate / 1000 * SPEAK_CHANNEL_NUM * s_uac_device->spk_resolution / 8; xTaskNotifyGive(s_uac_device->spk_task_handle); TU_LOG1("Speaker interface %d-%d opened", itf, alt); printf("Speaker interface %d-%d opened\n", itf, alt); } #endif #if CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX if (s_uac_device->mic_itf_num == itf && alt != 0) { s_uac_device->mic_data_size = 0; s_uac_device->mic_resolution = mic_resolutions_per_format[alt - 1]; s_uac_device->mic_active = true; s_uac_device->mic_bytes_per_ms = s_uac_device->current_sample_rate / 1000 * MIC_CHANNEL_NUM * s_uac_device->mic_resolution / 8; xTaskNotifyGive(s_uac_device->mic_task_handle); TU_LOG1("Microphone interface %d-%d opened", itf, alt); printf("Microphone interface %d-%d opened\n", itf, alt); } #endif return true; } bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting) { (void)rhport; (void)func_id; (void)ep_out; (void)cur_alt_setting; static bool new_play = false; static int64_t last_time = 0; int64_t now = esp_timer_get_time(); /** * @brief If no data is received for a certain period, it is considered as the initiation * of a new audio transmission. At this point, the FIFO data is cleared, and a segment * of data is buffered in the I2S. */ if (now - last_time > 100 * CONFIG_UAC_SPK_NEW_PLAY_INTERVAL) { new_play = true; tud_audio_clear_ep_out_ff(); } last_time = now; int bytes_remained = tud_audio_available(); size_t bytes_require = s_uac_device->spk_bytes_per_ms; if (new_play) { /*!< Buffer a segment of data in the I2S and control the data size to be half of the UAC FIFO size. */ bytes_require = SPK_INTERVAL_MS * s_uac_device->spk_bytes_per_ms / 2; if (bytes_remained < bytes_require) { return true; } new_play = false; } s_uac_device->spk_data_size = tud_audio_read(s_uac_device->spk_buf, bytes_require); xTaskNotifyGive(s_uac_device->spk_task_handle); return true; } bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t itf, uint8_t ep_in, uint8_t cur_alt_setting) { (void)rhport; (void)itf; (void)ep_in; (void)cur_alt_setting; size_t bytes_require = MIC_INTERVAL_MS * s_uac_device->mic_bytes_per_ms; tu_fifo_t *sw_in_fifo = tud_audio_get_ep_in_ff(); uint16_t fifo_remained = tu_fifo_remaining(sw_in_fifo); if (fifo_remained < bytes_require) { return true; } // load data chunk by chunk UAC_ENTER_CRITICAL(); if (s_uac_device->mic_data_size > 0) { tud_audio_write((void *)s_uac_device->mic_buf_read, s_uac_device->mic_data_size); s_uac_device->mic_data_size = 0; } UAC_EXIT_CRITICAL(); return true; } #if CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX static void usb_spk_task(void *pvParam) { while (1) { if (s_uac_device->spk_active == false) { ulTaskNotifyTake(pdFAIL, portMAX_DELAY); continue; } // clear the notification ulTaskNotifyTake(pdTRUE, portMAX_DELAY); if (s_uac_device->spk_data_size == 0) { continue; } // playback the data from the ring buffer chunk by chunk if (s_uac_device->user_cfg.output_cb) { s_uac_device->user_cfg.output_cb((uint8_t *)s_uac_device->spk_buf, s_uac_device->spk_data_size, s_uac_device->user_cfg.cb_ctx); } s_uac_device->spk_data_size = 0; } } #endif #if CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX static void usb_mic_task(void *pvParam) { while (1) { if (s_uac_device->mic_active == false) { // clear the notification ulTaskNotifyTake(pdTRUE, portMAX_DELAY); continue; } // clear the notification // read data from the microphone chunk by chunk size_t bytes_require = MIC_INTERVAL_MS * s_uac_device->mic_bytes_per_ms; if (s_uac_device->user_cfg.input_cb) { size_t bytes_read = 0; esp_err_t ret = s_uac_device->user_cfg.input_cb((uint8_t *)s_uac_device->mic_buf_write, bytes_require, &bytes_read, s_uac_device->user_cfg.cb_ctx); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to read data from mic"); continue; } int16_t *tmp_buf = s_uac_device->mic_buf_write; UAC_ENTER_CRITICAL(); s_uac_device->mic_buf_write = s_uac_device->mic_buf_read; s_uac_device->mic_buf_read = tmp_buf; s_uac_device->mic_data_size = bytes_read; UAC_EXIT_CRITICAL(); } } } #endif esp_err_t uac_device_init(uac_device_config_t *config) { ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "config is NULL"); if (s_uac_device != NULL) { ESP_LOGW(TAG, "uac device already initialized"); return ESP_OK; } s_uac_device = calloc(1, sizeof(uac_device_t)); ESP_RETURN_ON_FALSE(s_uac_device != NULL, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for uac device"); s_uac_device->user_cfg.output_cb = config->output_cb; s_uac_device->user_cfg.input_cb = config->input_cb; s_uac_device->user_cfg.cb_ctx = config->cb_ctx; s_uac_device->user_cfg.set_mute_cb = config->set_mute_cb; s_uac_device->user_cfg.set_volume_cb = config->set_volume_cb; s_uac_device->current_sample_rate = DEFAULT_SAMPLE_RATE; s_uac_device->mic_buf_write = s_uac_device->mic_buf1; s_uac_device->mic_buf_read = s_uac_device->mic_buf2; #if CONFIG_USB_DEVICE_UAC_AS_PART s_uac_device->spk_itf_num = config->spk_itf_num; s_uac_device->mic_itf_num = config->mic_itf_num; #else s_uac_device->spk_itf_num = ITF_NUM_AUDIO_STREAMING_SPK; s_uac_device->mic_itf_num = ITF_NUM_AUDIO_STREAMING_MIC; #endif BaseType_t ret_val; if (!config->skip_tinyusb_init) { usb_phy_init(); bool usb_init = tusb_init(); if (!usb_init) { ESP_LOGE(TAG, "USB Device Stack Init Fail"); return ESP_FAIL; } ret_val = xTaskCreatePinnedToCore(tusb_device_task, "TinyUSB", 4096, NULL, CONFIG_UAC_TINYUSB_TASK_PRIORITY, NULL, CONFIG_UAC_TINYUSB_TASK_CORE == -1 ? tskNO_AFFINITY : CONFIG_UAC_TINYUSB_TASK_CORE); ESP_RETURN_ON_FALSE(ret_val == pdPASS, ESP_FAIL, TAG, "Failed to create TinyUSB task"); } #if CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX ret_val = xTaskCreatePinnedToCore(usb_mic_task, "usb_mic_task", 4096, NULL, CONFIG_UAC_MIC_TASK_PRIORITY, &s_uac_device->mic_task_handle, CONFIG_UAC_MIC_TASK_CORE == -1 ? tskNO_AFFINITY : CONFIG_UAC_MIC_TASK_CORE); ESP_RETURN_ON_FALSE(ret_val == pdPASS, ESP_FAIL, TAG, "Failed to create usb_mic task"); #endif #if CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX ret_val = xTaskCreatePinnedToCore(usb_spk_task, "usb_spk_task", 4096, NULL, CONFIG_UAC_SPK_TASK_PRIORITY, &s_uac_device->spk_task_handle, CONFIG_UAC_SPK_TASK_CORE == -1 ? tskNO_AFFINITY : CONFIG_UAC_SPK_TASK_CORE); ESP_RETURN_ON_FALSE(ret_val == pdPASS, ESP_FAIL, TAG, "Failed to create usb_spk task"); #endif ESP_LOGI(TAG, "UAC Device Start, Version: %d.%d.%d", USB_DEVICE_UAC_VER_MAJOR, USB_DEVICE_UAC_VER_MINOR, USB_DEVICE_UAC_VER_PATCH); return ESP_OK; }