#include "audio_output.h" #include "rtsp_server.h" #include "audio_resample.h" #include "dac.h" #include "led.h" #include "driver/i2s_std.h" #include "driver/gpio.h" #include "esp_check.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "audio_receiver.h" #include #include // SIDE NOTE; providing power from GPIO pins is capped ~20mA. #if CONFIG_I2S_GND_IO >= 0 #define I2S_GND_PIN CONFIG_I2S_GND_IO #endif #if CONFIG_I2S_VCC_IO >= 0 #define I2S_VCC_PIN CONFIG_I2S_VCC_IO #endif // DACXSMT引脚必须拉高电平才能在外部DAC上启用输出 #define DAC_XSMT_PIN 19 #define TAG "audio_output" #define I2S_SCK_PIN CONFIG_I2S_SCK_IO #define I2S_BCK_PIN CONFIG_I2S_BCK_IO #define I2S_LRCK_PIN CONFIG_I2S_WS_IO #define I2S_DOUT_PIN CONFIG_I2S_DO_IO #define OUTPUT_RATE CONFIG_OUTPUT_SAMPLE_RATE_HZ #define FRAME_SAMPLES 352 // DMA ring-buffer configuration. Total DMA latency (in samples) is // I2S_DMA_DESC_NUM × I2S_DMA_FRAME_NUM // which at OUTPUT_RATE gives the hardware pipeline delay in µs. // Keep these in sync with the i2s_chan_config_t initialisation below. #define I2S_DMA_DESC_NUM 8 #define I2S_DMA_FRAME_NUM 256 /* Max output frames after resampling one input frame */ #define MAX_RESAMPLE_FRAMES \ ((size_t)((FRAME_SAMPLES + 2) * ((double)OUTPUT_RATE / 44100) + 16)) #if CONFIG_FREERTOS_UNICORE #define PLAYBACK_CORE 0 #else #define PLAYBACK_CORE 1 #endif static i2s_chan_handle_t tx_handle = NULL; static TaskHandle_t playback_task_handle = NULL; static volatile bool playback_running = false; static volatile bool flush_requested = false; static SemaphoreHandle_t s_audio_mutex = NULL; static volatile int source_rate = 44100; static volatile bool resample_reinit_needed = false; static volatile audio_channel_mode_t channel_mode = AUDIO_CHANNEL_STEREO; static void apply_volume(int16_t *buf, size_t n) { #ifndef CONFIG_DAC_CONTROLS_VOLUME int32_t vol = airplay_get_volume_q15(); for (size_t i = 0; i < n; i++) { buf[i] = (int16_t)(((int32_t)buf[i] * vol) >> 15); } #endif } // Apply the selected channel mode to an interleaved stereo buffer (L,R,...). // LEFT/RIGHT route the chosen source channel to BOTH outputs so the selected // track is heard from both speakers; STEREO leaves the buffer untouched. static void apply_channel_mode(int16_t *buf, size_t frames) { audio_channel_mode_t mode = channel_mode; if (mode == AUDIO_CHANNEL_STEREO) { return; } size_t src = (mode == AUDIO_CHANNEL_RIGHT) ? 1 : 0; for (size_t i = 0; i < frames; i++) { int16_t s = buf[i * 2 + src]; buf[i * 2] = s; buf[i * 2 + 1] = s; } } static void playback_task(void *arg) { int16_t *pcm = malloc((size_t)(FRAME_SAMPLES + 1) * 2 * sizeof(int16_t)); int16_t *silence = calloc((size_t)FRAME_SAMPLES * 2, sizeof(int16_t)); int16_t *resample_buf = malloc(MAX_RESAMPLE_FRAMES * 2 * sizeof(int16_t)); if (!pcm || !silence || !resample_buf) { ESP_LOGE(TAG, "Failed to allocate buffers"); free(pcm); free(silence); playback_task_handle = NULL; free(resample_buf); vTaskDelete(NULL); return; } size_t written; while (playback_running) { if (resample_reinit_needed) { resample_reinit_needed = false; audio_resample_init((uint32_t)source_rate, OUTPUT_RATE, 2); } if (flush_requested) { flush_requested = false; audio_resample_reset(); i2s_channel_disable(tx_handle); i2s_channel_enable(tx_handle); } size_t samples = audio_receiver_read(pcm, FRAME_SAMPLES + 1); if (samples > 0) { if (s_audio_mutex) xSemaphoreTakeRecursive(s_audio_mutex, portMAX_DELAY); ESP_LOGD(TAG, "Read %u samples from receiver", (unsigned int)samples); int16_t *play_buf = pcm; size_t play_samples = samples; if (audio_resample_is_active()) { play_samples = audio_resample_process(pcm, samples, resample_buf, MAX_RESAMPLE_FRAMES); play_buf = resample_buf; } ESP_LOGD(TAG, "Resampled to %u samples", (unsigned int)play_samples); apply_volume(play_buf, play_samples * 2); apply_channel_mode(play_buf, play_samples); led_audio_feed(play_buf, play_samples); i2s_channel_write(tx_handle, play_buf, play_samples * 4, &written, portMAX_DELAY); if (s_audio_mutex) xSemaphoreGiveRecursive(s_audio_mutex); ESP_LOGD(TAG, "I2S write: %u bytes written", (unsigned int)written); taskYIELD(); } else { // ESP_LOGW(TAG, "Receiver underflow - playing silence"); led_audio_feed(silence, FRAME_SAMPLES); i2s_channel_write(tx_handle, silence, (size_t)FRAME_SAMPLES * 4, &written, pdMS_TO_TICKS(10)); vTaskDelay(1); } } free(pcm); free(silence); playback_task_handle = NULL; vTaskDelete(NULL); } esp_err_t audio_output_init(void) { if (s_audio_mutex == NULL) { s_audio_mutex = xSemaphoreCreateRecursiveMutex(); } i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); chan_cfg.dma_desc_num = I2S_DMA_DESC_NUM; chan_cfg.dma_frame_num = I2S_DMA_FRAME_NUM; ESP_RETURN_ON_ERROR(i2s_new_channel(&chan_cfg, &tx_handle, NULL), TAG, "channel create failed"); i2s_std_config_t std_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(OUTPUT_RATE), .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), .gpio_cfg = { .mclk = I2S_SCK_PIN, .bclk = I2S_BCK_PIN, .ws = I2S_LRCK_PIN, .dout = I2S_DOUT_PIN, .din = I2S_GPIO_UNUSED, }, }; #ifdef I2S_GND_PIN gpio_reset_pin(I2S_GND_PIN); gpio_set_direction(I2S_GND_PIN, GPIO_MODE_OUTPUT); gpio_set_level(I2S_GND_PIN, 0); #endif #ifdef I2S_VCC_PIN gpio_reset_pin(I2S_VCC_PIN); gpio_set_direction(I2S_VCC_PIN, GPIO_MODE_OUTPUT); gpio_set_level(I2S_VCC_PIN, 1); #endif ESP_RETURN_ON_ERROR(i2s_channel_init_std_mode(tx_handle, &std_cfg), TAG, "std mode init failed"); ESP_RETURN_ON_ERROR(i2s_channel_enable(tx_handle), TAG, "channel enable failed"); ESP_LOGI(TAG, "I2S initialized: Rate=%u, DMA_Desc=%d, DMA_Frame=%d", (unsigned int)OUTPUT_RATE, I2S_DMA_DESC_NUM, I2S_DMA_FRAME_NUM); // DAC XSMT: pull high to enable external DAC output #ifdef DAC_XSMT_PIN gpio_reset_pin(DAC_XSMT_PIN); gpio_set_direction(DAC_XSMT_PIN, GPIO_MODE_OUTPUT); gpio_set_level(DAC_XSMT_PIN, 1); ESP_LOGI(TAG, "DAC XSMT pin %d set high", DAC_XSMT_PIN); #endif // MCLK/BCLK/LRCK are now running. Some codecs need this edge to finish their // clock setup; amplifiers that manage power from board RTSP events can ignore // the hook. dac_on_i2s_started(); audio_resample_init(44100, OUTPUT_RATE, 2); return ESP_OK; } void audio_output_start(void) { if (playback_task_handle != NULL) { return; // already running } playback_running = true; xTaskCreatePinnedToCore(playback_task, "audio_play", 4096, NULL, 7, &playback_task_handle, PLAYBACK_CORE); } void audio_output_stop(void) { if (playback_task_handle == NULL) { return; } playback_running = false; // Wait for task to exit cleanly int timeout = 40; while (playback_task_handle != NULL && timeout-- > 0) { vTaskDelay(pdMS_TO_TICKS(50)); } if (playback_task_handle != NULL) { ESP_LOGW(TAG, "Playback task did not exit within timeout"); } else { ESP_LOGI(TAG, "Playback task stopped"); } } esp_err_t audio_output_write(const void *data, size_t bytes, TickType_t wait) { size_t written = 0; if (s_audio_mutex) xSemaphoreTakeRecursive(s_audio_mutex, portMAX_DELAY); esp_err_t err = i2s_channel_write(tx_handle, data, bytes, &written, wait); if (s_audio_mutex) xSemaphoreGiveRecursive(s_audio_mutex); return err; } void audio_output_set_sample_rate(uint32_t rate) { // Only safe to call when no writer task is actively using I2S // (AirPlay playback task must be stopped, BT calls this before // the I2S writer task starts consuming data) ESP_LOGI(TAG, "Setting sample rate to %" PRIu32 " Hz", rate); if (tx_handle == NULL) { ESP_LOGW(TAG, "I2S 通道未初始化,跳过采样率时钟配置"); return; } i2s_channel_disable(tx_handle); i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(rate); i2s_channel_reconfig_std_clock(tx_handle, &clk_cfg); i2s_channel_enable(tx_handle); } void audio_output_flush(void) { flush_requested = true; } void audio_output_lock(void) { if (s_audio_mutex) xSemaphoreTakeRecursive(s_audio_mutex, portMAX_DELAY); } void audio_output_unlock(void) { if (s_audio_mutex) xSemaphoreGiveRecursive(s_audio_mutex); } void audio_output_set_source_rate(int rate) { if (rate > 0 && rate != source_rate) { source_rate = rate; resample_reinit_needed = true; } } uint32_t audio_output_get_hardware_latency_us(void) { return ( uint32_t)(((uint64_t)I2S_DMA_DESC_NUM * I2S_DMA_FRAME_NUM * 1000000ULL) / OUTPUT_RATE); } audio_channel_mode_t audio_output_cycle_channel_mode(void) { audio_channel_mode_t next; switch (channel_mode) { case AUDIO_CHANNEL_STEREO: next = AUDIO_CHANNEL_LEFT; break; case AUDIO_CHANNEL_LEFT: next = AUDIO_CHANNEL_RIGHT; break; default: next = AUDIO_CHANNEL_STEREO; break; } channel_mode = next; ESP_LOGI(TAG, "Channel mode: %s", next == AUDIO_CHANNEL_LEFT ? "LEFT only" : next == AUDIO_CHANNEL_RIGHT ? "RIGHT only" : "STEREO"); return next; } audio_channel_mode_t audio_output_get_channel_mode(void) { return channel_mode; }