// Copyright (c) 2026 Tencent. All rights reserved.
//
// ESP32 多平台音频/视频设备实现。
//   - ESP32-P4 (WT99P4C5-S1): ES8311 音频上下行 + MIPI-CSI 视频上行
//   - ESP32-S3-Korvo-2 V3.0: ES7210 音频上行 + ES8311 音频下行
//
// 上行音频：从 I2S RX 读取立体声帧，下混为单声道 MIC 帧。
// 下行音频：远端单声道 PCM 复制为双声道，经 I2S TX 送硬件播放。

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "av_device.h"
#include "driver/gpio.h"
#include "driver/i2c_master.h"
#include "driver/i2s_common.h"
#include "esp_afe_aec.h"
#include "esp_check.h"
#include "esp_codec_dev.h"
#include "esp_codec_dev_defaults.h"
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// ---- 平台差异化头文件 ----
#ifdef CONFIG_IDF_TARGET_ESP32P4
#  include <fcntl.h>
#  include <sys/ioctl.h>
#  include <sys/mman.h>

#  include "driver/i2s_std.h"
#  include "esp_h264_alloc.h"
#  include "esp_h264_enc_single.h"
#  include "esp_h264_enc_single_hw.h"
#  include "esp_video_device.h"
#  include "esp_video_init.h"
#  include "linux/videodev2.h"
#else
#  include "driver/i2s_tdm.h"
#endif

#define TAG "av_esp"

// ==================== 平台管脚宏定义 ====================
// 两款芯片使用相同的宏名称，通过 #ifdef 赋不同管脚值，
// 使得公共函数中无需再判断平台。

#ifdef CONFIG_IDF_TARGET_ESP32P4
// ---- ESP32-P4 (WT99P4C5-S1) 管脚 ----
#  define AV_ESP_I2C_PORT I2C_NUM_0
#  define AV_ESP_I2C_SDA_GPIO GPIO_NUM_7
#  define AV_ESP_I2C_SCL_GPIO GPIO_NUM_8

#  define AV_ESP_I2S_PORT I2S_NUM_0
#  define AV_ESP_I2S_MCLK_GPIO GPIO_NUM_13
#  define AV_ESP_I2S_BCLK_GPIO GPIO_NUM_12
#  define AV_ESP_I2S_WS_GPIO GPIO_NUM_10
#  define AV_ESP_I2S_DOUT_GPIO GPIO_NUM_9
#  define AV_ESP_I2S_DIN_GPIO GPIO_NUM_11
#  define AV_ESP_PA_GPIO GPIO_NUM_53
#else
// ---- ESP32-S3-Korvo-2 V3.0 管脚 ----
#  define AV_ESP_I2C_PORT I2C_NUM_0
#  define AV_ESP_I2C_SDA_GPIO GPIO_NUM_17
#  define AV_ESP_I2C_SCL_GPIO GPIO_NUM_18

#  define AV_ESP_I2S_PORT I2S_NUM_0
#  define AV_ESP_I2S_MCLK_GPIO GPIO_NUM_16
#  define AV_ESP_I2S_BCLK_GPIO GPIO_NUM_9
#  define AV_ESP_I2S_WS_GPIO GPIO_NUM_45
#  define AV_ESP_I2S_DOUT_GPIO GPIO_NUM_8
#  define AV_ESP_I2S_DIN_GPIO GPIO_NUM_10
#  define AV_ESP_I2S_TDM_SLOT_MASK (I2S_TDM_SLOT0 | I2S_TDM_SLOT1)
#  define AV_ESP_PA_GPIO GPIO_NUM_48
#endif

// ==================== 公共宏定义 ====================

// ---- 音频：Codec 与播放缓冲 ----
#define AV_ESP_CODEC_CHANNELS 2
#define AV_ESP_ES8311_OUT_VOL 70

#define AV_ESP_CAPTURE_FRAME_BYTES \
  PCM_FRAME_BYTES(MIC_SAMPLE_RATE, AV_ESP_CODEC_CHANNELS, MIC_FRAME_MS)
#define AV_ESP_PLAYOUT_FRAME_BYTES \
  PCM_FRAME_BYTES(SPK_SAMPLE_RATE, AV_ESP_CODEC_CHANNELS, SPK_FRAME_MS)

// ---- 音频采集任务 ----
#define AV_ESP_AUDIO_TASK_STACK (8 * 1024)
#define AV_ESP_AUDIO_TASK_PRIORITY 5
#define AV_ESP_AUDIO_READ_TIMEOUT_MS 100
#define AV_ESP_AUDIO_STOP_WAIT_MS 1000

// ---- ES8311 麦克风增益（仅 P4 使用 ES8311 录音；S3 使用 ES7210） ----
#ifdef CONFIG_IDF_TARGET_ESP32P4
#  ifndef AV_ESP_ES8311_MIC_GAIN_DB
#    define AV_ESP_ES8311_MIC_GAIN_DB 30.0f
#  endif
#else
#  define AV_ESP_ES7210_MIC_GAIN_DB 30.0f
#  define AV_ESP_ES7210_MIC_SELECTED (ES7210_SEL_MIC1 | ES7210_SEL_MIC2)
#endif

// ---- AEC 回声消除（esp-sr AFE）----
// 采集链路加入声学回声消除：以"播放出去的远端音频"为参考信号，
// 从麦克风信号中消除被采回的扬声器/Codec 回采声，避免机器人听到自己说话
// 而产生回声/自激（即使不接扬声器，DAC 回采/内部串扰也会形成回声）。
// AEC 输入通道格式：M=麦克风，R=播放参考。本实现为 1 麦 + 1 参考。
#define AV_ESP_AEC_INPUT_FORMAT "MR"
// 滤波器长度：P4/S3 推荐 4，越大 CPU 占用越高、可消除的回声拖尾越长。
#define AV_ESP_AEC_FILTER_LENGTH 4
// 参考信号环形缓冲容量（单声道样本数），约 500ms @16kHz，吸收播放突发与时序抖动。
#define AV_ESP_AEC_REFERENCE_RING_SAMPLES (MIC_SAMPLE_RATE / 2)

// ==================== 公共全局变量 ====================

// ---- 生命周期与回调 ----
static bool audio_hardware_initialized = false;
static volatile bool audio_started = false;
static volatile bool audio_task_running = false;
static TaskHandle_t audio_task_handle = NULL;
static audio_device_callback_t audio_callback = NULL;
static void *audio_callback_context = NULL;

// ---- I2C / I2S 句柄 ----
static i2c_master_bus_handle_t audio_i2c_bus = NULL;
static i2s_chan_handle_t i2s_speaker_channel = NULL;
static i2s_chan_handle_t i2s_microphone_channel = NULL;
static const audio_codec_data_if_t *i2s_data_interface = NULL;

// ---- 播放 Codec（两款芯片均使用 ES8311 播放） ----
static const audio_codec_ctrl_if_t *speaker_control_interface = NULL;
static const audio_codec_gpio_if_t *speaker_gpio_interface = NULL;
static const audio_codec_if_t *speaker_codec_interface = NULL;
static esp_codec_dev_handle_t speaker_codec_device = NULL;

// ---- 录音 Codec（P4 用 ES8311 BOTH 模式，S3 用 ES7210） ----
static esp_codec_dev_handle_t microphone_codec_device = NULL;
static const audio_codec_if_t *microphone_codec_interface = NULL;
static const audio_codec_ctrl_if_t *microphone_control_interface = NULL;

// ---- 音频帧缓存 ----
static int16_t capture_stereo_buffer[AV_ESP_CAPTURE_FRAME_BYTES / sizeof(int16_t)];
static int16_t capture_mono_buffer[MIC_FRAME_BYTES / sizeof(int16_t)];
static int16_t playback_stereo_buffer[AV_ESP_PLAYOUT_FRAME_BYTES / sizeof(int16_t)];

// ---- 下行音频播放诊断 ----
static uint32_t audio_playback_received_frame_count = 0;
static uint32_t audio_playback_discard_frame_count = 0;
static uint32_t audio_playback_write_fail_frame_count = 0;

// ---- 声学回声消除（Acoustic Echo Cancellation）状态 ----
static afe_aec_handle_t *aec_handle = NULL;
static int aec_chunk_samples = 0;               // 每次处理的单声道样本数
static int16_t *aec_microphone_buffer = NULL;   // chunk 单声道麦克风信号
static int16_t *aec_reference_buffer = NULL;    // chunk 单声道参考信号
static int16_t *aec_interleaved_buffer = NULL;  // chunk*2 交织 [M,R,...]
static int16_t *aec_output_buffer = NULL;       // chunk 单声道输出
static size_t aec_frame_accumulated = 0;        // 20ms 上抛帧已累计样本数
// 参考信号环形缓冲：播放线程写入，采集线程读取
static int16_t *aec_reference_ring_buffer = NULL;
static volatile uint32_t aec_reference_ring_buffer_head = 0;
static volatile uint32_t aec_reference_ring_buffer_tail = 0;
static portMUX_TYPE aec_reference_ring_buffer_lock = portMUX_INITIALIZER_UNLOCKED;

// ---- ESP32P4 视频 ----
#ifdef CONFIG_IDF_TARGET_ESP32P4
#  define AV_ESP_VIDEO_TASK_STACK (8 * 1024)
#  define AV_ESP_VIDEO_TASK_PRIORITY 4
#  define AV_ESP_VIDEO_STOP_WAIT_MS 1000
#  define AV_ESP_CAM_BUF_NUM 4

#  define AV_ESP_CAM_I2C_PORT I2C_NUM_1
#  define AV_ESP_CAM_I2C_SCL GPIO_NUM_8
#  define AV_ESP_CAM_I2C_SDA GPIO_NUM_7
#  define AV_ESP_CAM_I2C_FREQ 100000

#  define AV_ESP_H264_BITRATE (300 * 1000)

static bool camera_initialized = false;
static int camera_fd = -1;
static esp_h264_enc_handle_t h264_encoder_handle = NULL;
static video_device_callback_t video_callback = NULL;
static void *video_callback_context = NULL;
static volatile bool video_stream_activate = false;
static volatile bool idr_frame_requested = false;
static TaskHandle_t video_task_handle = NULL;
static uint8_t *camera_buffer[AV_ESP_CAM_BUF_NUM];
static size_t camera_buffer_length = 0;
static uint8_t *video_combined_buffer = NULL;
static size_t video_combined_buffer_length = 0;
static uint8_t *yuv_input_buffer = NULL;
static size_t yuv_input_buffer_length = 0;
static uint8_t *h264_output_buffer = NULL;
static size_t h264_output_buffer_length = 0;
#endif

// ==================== 前置声明 ====================

// 音频内部实现
static esp_err_t _i2c_open(void);
static esp_err_t _i2s_open(void);  // P4=STD, S3=TDM
static esp_err_t _speaker_open(void);
static esp_err_t _microphone_open(void);  // P4=ES8311, S3=ES7210
static void _i2c_close(void);
static void _i2s_close(void);
static void _speaker_close(void);
static void _microphone_close(void);
static void _get_microphone_audio_stream(void *arg);

// 声学回声消除内部实现
static esp_err_t _aec_open(void);
static void _aec_close(void);

// 视频内部实现
#ifdef CONFIG_IDF_TARGET_ESP32P4
static int _camera_open(void);
static void _camera_close(void);
static esp_err_t _h264_encoder_init(void);
static void _h264_encoder_deinit(void);
static void _get_camera_video_stream(void *arg);
#endif

// ==================== 公开接口 ====================

void av_device_init(const av_device_config_t *cfg) {
  (void)cfg;
  if (audio_hardware_initialized) {
    return;
  }

  esp_err_t err = _i2c_open();
  if (err != ESP_OK) {
    ESP_LOGW(TAG, "I2C init failed: %s", esp_err_to_name(err));
    return;
  }
  err = _i2s_open();
  if (err != ESP_OK) {
    ESP_LOGW(TAG, "I2S init failed: %s", esp_err_to_name(err));
    return;
  }
  err = _speaker_open();
  if (err != ESP_OK) {
    ESP_LOGW(TAG, "play codec init failed: %s", esp_err_to_name(err));
    return;
  }
  err = _microphone_open();
  if (err != ESP_OK) {
    ESP_LOGW(TAG, "record codec init failed: %s", esp_err_to_name(err));
    return;
  }

  if (_aec_open() != ESP_OK) {
    ESP_LOGW(TAG, "AEC init failed, capture will run WITHOUT echo cancellation");
  }

  audio_hardware_initialized = true;
  ESP_LOGI(TAG,
           "initialized: i2c_sda=%d i2c_scl=%d "
           "mclk=%d bclk=%d ws=%d dout=%d din=%d pa=%d",
           (int)AV_ESP_I2C_SDA_GPIO, (int)AV_ESP_I2C_SCL_GPIO, (int)AV_ESP_I2S_MCLK_GPIO,
           (int)AV_ESP_I2S_BCLK_GPIO, (int)AV_ESP_I2S_WS_GPIO, (int)AV_ESP_I2S_DOUT_GPIO,
           (int)AV_ESP_I2S_DIN_GPIO, (int)AV_ESP_PA_GPIO);

#ifdef CONFIG_IDF_TARGET_ESP32P4
  if (!camera_initialized) {
    camera_fd = _camera_open();
    if (camera_fd < 0) {
      ESP_LOGW(TAG, "camera init failed, video will be unavailable");
    } else if (_h264_encoder_init() != ESP_OK) {
      ESP_LOGW(TAG, "H.264 encoder init failed, video will be unavailable");
      _camera_close();
      camera_fd = -1;
    } else {
      camera_initialized = true;
      ESP_LOGI(TAG, "camera + H.264 encoder initialized and kept alive");
    }
  }
#endif
}

void av_device_deinit(void) {
#ifdef CONFIG_IDF_TARGET_ESP32P4
  if (camera_initialized) {
    video_stream_activate = false;
    if (video_task_handle) {
      int wait = AV_ESP_VIDEO_STOP_WAIT_MS / 20;
      while (video_task_handle && wait-- > 0) {
        rtos_delay_milliseconds(20);
      }
      video_task_handle = NULL;
    }
    _camera_close();
    camera_initialized = false;
  }
#endif
  if (audio_started) {
    audio_task_running = false;
    int wait = AV_ESP_AUDIO_STOP_WAIT_MS / 20;
    while (audio_task_handle != NULL && wait-- > 0) {
      rtos_delay_milliseconds(20);
    }
    audio_started = false;
  }
  _aec_close();
  _microphone_close();
  _speaker_close();
  _i2s_close();
  _i2c_close();
  audio_hardware_initialized = false;
  ESP_LOGI(TAG, "deinitialized");
}

int av_device_start_audio(const char *audio_file_path, audio_device_callback_t callback,
                          void *user_data) {
  if (!audio_hardware_initialized || !callback) {
    return -1;
  }
  if (!microphone_codec_device || !i2s_microphone_channel) {
    ESP_LOGE(TAG, "mic not ready");
    return -1;
  }
  if (audio_callback && audio_callback == callback && audio_callback_context == user_data) {
    return 0;
  }
  if (audio_callback) {
    ESP_LOGE(TAG, "audio listener already occupied");
    return -1;
  }

  audio_callback = callback;
  audio_callback_context = user_data;

  if (!audio_started) {
    audio_task_running = true;
    audio_started = true;
    BaseType_t ok = xTaskCreate(_get_microphone_audio_stream, "av_esp_mic", AV_ESP_AUDIO_TASK_STACK,
                                NULL, AV_ESP_AUDIO_TASK_PRIORITY, &audio_task_handle);
    if (ok != pdPASS) {
      audio_task_running = false;
      audio_started = false;
      audio_task_handle = NULL;
      audio_callback = NULL;
      audio_callback_context = NULL;
      ESP_LOGE(TAG, "failed to create audio task");
      return -1;
    }
    ESP_LOGI(TAG, "audio capture started: raw_ch=%d out_frame=%d bytes", AV_ESP_CODEC_CHANNELS,
             MIC_FRAME_BYTES);
  }

  return 0;
}

void av_device_stop_audio(audio_device_callback_t callback, void *user_data) {
  if (!audio_callback || audio_callback != callback || audio_callback_context != user_data) {
    return;
  }

  audio_callback = NULL;
  audio_callback_context = NULL;

  if (audio_started) {
    audio_task_running = false;
    int wait = AV_ESP_AUDIO_STOP_WAIT_MS / 20;
    while (audio_task_handle != NULL && wait-- > 0) {
      rtos_delay_milliseconds(20);
    }
    audio_started = false;
    ESP_LOGI(TAG, "audio capture stopped");
  }
}

void av_device_playout_audio(const uint8_t *data, uint32_t len) {
  static bool s_playout_first_logged = false;

  audio_playback_received_frame_count++;

  if (!audio_hardware_initialized || !speaker_codec_device || !data || len == 0) {
    audio_playback_discard_frame_count++;
    return;
  }

  if (!s_playout_first_logged) {
    ESP_LOGI(TAG, "[PLY] first frame: len=%u", (unsigned)len);
    s_playout_first_logged = true;
  } else if (audio_playback_received_frame_count % 2000 == 0) {
    ESP_LOGI(TAG, "[PLY] count=%u drop=%u fail=%u", (unsigned)audio_playback_received_frame_count,
             (unsigned)audio_playback_discard_frame_count,
             (unsigned)audio_playback_write_fail_frame_count);
  }

  // 远端下行单声道 S16LE → I2S 双声道，复制到 L/R。
  const uint8_t *src = data;
  uint32_t remaining = len;
  while (remaining >= sizeof(int16_t)) {
    uint32_t chunk = remaining;
    if (chunk > SPK_FRAME_BYTES) {
      chunk = SPK_FRAME_BYTES;
    }
    chunk &= ~(uint32_t)(sizeof(int16_t) - 1);
    if (chunk == 0) {
      break;
    }

    size_t samples = chunk / sizeof(int16_t);
    // 单声道 S16LE → 立体声：L/R 复制
    {
      const int16_t *s16 = (const int16_t *)(const void *)src;
      for (size_t i = 0; i < samples; ++i) {
        int16_t s = s16[i];
        playback_stereo_buffer[i * AV_ESP_CODEC_CHANNELS] = s;
        playback_stereo_buffer[i * AV_ESP_CODEC_CHANNELS + 1] = s;
      }
    }

    // 将本帧远端单声道 PCM 推入 AEC 参考环形缓冲，作为回声消除的参考信号。
    // 与 esp_codec_dev_write 紧邻推送，使参考信号尽量贴近实际播放时刻。
    if (aec_reference_ring_buffer) {
      const int16_t *reference_source = (const int16_t *)(const void *)src;
      portENTER_CRITICAL(&aec_reference_ring_buffer_lock);
      for (size_t i = 0; i < samples; ++i) {
        uint32_t next_index =
            (aec_reference_ring_buffer_head + 1) % AV_ESP_AEC_REFERENCE_RING_SAMPLES;
        if (next_index == aec_reference_ring_buffer_tail) {
          aec_reference_ring_buffer_tail =
              (aec_reference_ring_buffer_tail + 1) % AV_ESP_AEC_REFERENCE_RING_SAMPLES;
        }
        aec_reference_ring_buffer[aec_reference_ring_buffer_head] = reference_source[i];
        aec_reference_ring_buffer_head = next_index;
      }
      portEXIT_CRITICAL(&aec_reference_ring_buffer_lock);
    }

    int out_bytes = (int)(samples * AV_ESP_CODEC_CHANNELS * sizeof(int16_t));
    int ret = esp_codec_dev_write(speaker_codec_device, playback_stereo_buffer, out_bytes);
    if (ret != ESP_CODEC_DEV_OK) {
      audio_playback_write_fail_frame_count++;
      ESP_LOGW(TAG, "[PLY] write fail #%u: ret=%d out_bytes=%d",
               (unsigned)audio_playback_write_fail_frame_count, ret, out_bytes);
      break;
    }
    src += chunk;
    remaining -= chunk;
  }
}

int av_device_start_video(const char *video_file_path, video_device_callback_t callback,
                          void *user_data) {
#ifdef CONFIG_IDF_TARGET_ESP32P4
  if (!callback) {
    return -1;
  }
  if (!camera_initialized) {
    ESP_LOGE(TAG, "camera not initialized, call av_device_init first");
    return -1;
  }
  if (video_stream_activate) {
    ESP_LOGW(TAG, "video already running");
    return 0;
  }

  video_callback = callback;
  video_callback_context = user_data;
  video_stream_activate = true;

  // 关闭并重开编码器，强制下一个编码帧为 IDR 关键帧，
  // 确保手机端重新进房时能立即解码。
  if (h264_encoder_handle) {
    esp_h264_enc_close(h264_encoder_handle);
    esp_h264_enc_open(h264_encoder_handle);
    ESP_LOGI(TAG, "encoder reopened for IDR");
  }

  if (xTaskCreate(_get_camera_video_stream, "av_esp_cam", AV_ESP_VIDEO_TASK_STACK, NULL,
                  AV_ESP_VIDEO_TASK_PRIORITY, &video_task_handle) != pdPASS) {
    video_stream_activate = false;
    video_callback = NULL;
    ESP_LOGE(TAG, "failed to create video task");
    return -1;
  }
  ESP_LOGI(TAG, "video capture started: %dx%d", (int)VIDEO_WIDTH, (int)VIDEO_HEIGHT);
#else
  (void)callback;
  (void)user_data;
#endif
  return 0;
}

void av_device_stop_video(video_device_callback_t callback, void *user_data) {
#ifdef CONFIG_IDF_TARGET_ESP32P4
  video_stream_activate = false;
  int wait = AV_ESP_VIDEO_STOP_WAIT_MS / 20;
  while (video_task_handle != NULL && wait-- > 0) {
    rtos_delay_milliseconds(20);
  }
  if (video_task_handle) {
    ESP_LOGW(TAG, "video task did not exit in time");
  }
#else
  (void)callback;
  (void)user_data;
#endif
}

// esp32目前没有h264硬件编码器，不实现视频下行
void av_device_playout_video(const uint8_t *data, uint32_t len) {
  (void)data;
  (void)len;
}

void av_device_request_idr(void) {
#ifdef CONFIG_IDF_TARGET_ESP32P4
  idr_frame_requested = true;
#endif
}

uint64_t av_device_now_ms(void) {
  return rtos_get_time();
}

// MP4 解封装：硬件平台不支持，返回空桩
uint64_t av_device_get_mp4_duration_ms(const char *mp4_file_path) {
  (void)mp4_file_path;
  return 0;
}

uint64_t av_device_seek_mp4_ms(uint64_t target_ms) {
  (void)target_ms;
  return 0;
}

void audio_device_sleep_ms(uint32_t ms) {
  rtos_delay_milliseconds(ms);
}

// ==================== 公共音频实现 ====================
// I2C 总线初始化（两款芯片逻辑一致，管脚由宏区分）
static esp_err_t _i2c_open(void) {
  if (audio_i2c_bus) {
    return ESP_OK;
  }

  i2c_master_bus_config_t i2c_cfg = {
      .i2c_port = AV_ESP_I2C_PORT,
      .sda_io_num = AV_ESP_I2C_SDA_GPIO,
      .scl_io_num = AV_ESP_I2C_SCL_GPIO,
      .clk_source = I2C_CLK_SRC_DEFAULT,
      .glitch_ignore_cnt = 7,
      .flags.enable_internal_pullup = true,
  };
  esp_err_t err = i2c_new_master_bus(&i2c_cfg, &audio_i2c_bus);
  if (err != ESP_OK) {
    audio_i2c_bus = NULL;
  }
  return err;
}

// 播放 Codec 初始化（两款芯片均使用 ES8311 DAC，逻辑一致）
static esp_err_t _speaker_open(void) {
  if (speaker_codec_device) {
    return ESP_OK;
  }
  if (!i2s_data_interface || !audio_i2c_bus) {
    return ESP_ERR_INVALID_STATE;
  }

  audio_codec_i2c_cfg_t i2c_cfg = {
      .port = AV_ESP_I2C_PORT,
      .addr = ES8311_CODEC_DEFAULT_ADDR,
      .bus_handle = audio_i2c_bus,
  };
  speaker_control_interface = audio_codec_new_i2c_ctrl(&i2c_cfg);
  ESP_RETURN_ON_FALSE(speaker_control_interface, ESP_ERR_NO_MEM, TAG, "play ctrl_if alloc fail");

  speaker_gpio_interface = audio_codec_new_gpio();
  ESP_RETURN_ON_FALSE(speaker_gpio_interface, ESP_ERR_NO_MEM, TAG, "gpio_if alloc fail");

  es8311_codec_cfg_t es8311_cfg = {
      .ctrl_if = speaker_control_interface,
      .gpio_if = speaker_gpio_interface,
      .codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC,
      .master_mode = false,
      .use_mclk = (AV_ESP_I2S_MCLK_GPIO >= 0),
      .pa_pin = AV_ESP_PA_GPIO,
      .pa_reverted = false,
      .no_dac_ref = true,
      .hw_gain = {.pa_voltage = 5.0, .codec_dac_voltage = 3.3},
  };
  speaker_codec_interface = es8311_codec_new(&es8311_cfg);
  ESP_RETURN_ON_FALSE(speaker_codec_interface, ESP_ERR_NO_MEM, TAG, "play codec_if alloc fail");

  esp_codec_dev_cfg_t dev_cfg = {
      .dev_type = ESP_CODEC_DEV_TYPE_OUT,
      .codec_if = speaker_codec_interface,
      .data_if = i2s_data_interface,
  };
  speaker_codec_device = esp_codec_dev_new(&dev_cfg);
  ESP_RETURN_ON_FALSE(speaker_codec_device, ESP_ERR_NO_MEM, TAG, "play codec_dev alloc fail");

  esp_codec_dev_sample_info_t sample_cfg = {
      .bits_per_sample = 16,
      .channel = AV_ESP_CODEC_CHANNELS,
      .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0) | ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1),
      .sample_rate = SPK_SAMPLE_RATE,
      .mclk_multiple = I2S_MCLK_MULTIPLE_256,
  };
  ESP_RETURN_ON_FALSE(esp_codec_dev_open(speaker_codec_device, &sample_cfg) == ESP_CODEC_DEV_OK,
                      ESP_FAIL, TAG, "play codec_dev open fail");
  ESP_RETURN_ON_FALSE(
      esp_codec_dev_set_out_vol(speaker_codec_device, AV_ESP_ES8311_OUT_VOL) == ESP_CODEC_DEV_OK,
      ESP_FAIL, TAG, "set output vol fail");

  ESP_LOGI(TAG, "ES8311 speaker codec opened: sr=%d ch=%d vol=%d", SPK_SAMPLE_RATE,
           AV_ESP_CODEC_CHANNELS, AV_ESP_ES8311_OUT_VOL);
  return ESP_OK;
}

// ---- I2C 总线反初始化 ----
static void _i2c_close(void) {
  if (audio_i2c_bus) {
    i2c_del_master_bus(audio_i2c_bus);
    audio_i2c_bus = NULL;
    ESP_LOGI(TAG, "i2c closed");
  }
}

// ---- 录音 Codec 反初始化 ----
static void _microphone_close(void) {
  if (microphone_codec_device) {
    esp_codec_dev_close(microphone_codec_device);
    esp_codec_dev_delete(microphone_codec_device);
    microphone_codec_device = NULL;
  }
  if (microphone_codec_interface) {
    audio_codec_delete_codec_if(microphone_codec_interface);
    microphone_codec_interface = NULL;
  }
  if (microphone_control_interface) {
    audio_codec_delete_ctrl_if(microphone_control_interface);
    microphone_control_interface = NULL;
  }
  ESP_LOGI(TAG, "record codec closed");
}

// ---- 播放 Codec 反初始化 ----
static void _speaker_close(void) {
  if (speaker_codec_device) {
    esp_codec_dev_close(speaker_codec_device);
    esp_codec_dev_delete(speaker_codec_device);
    speaker_codec_device = NULL;
  }
  if (speaker_codec_interface) {
    audio_codec_delete_codec_if(speaker_codec_interface);
    speaker_codec_interface = NULL;
  }
  if (speaker_control_interface) {
    audio_codec_delete_ctrl_if(speaker_control_interface);
    speaker_control_interface = NULL;
  }
  if (speaker_gpio_interface) {
    audio_codec_delete_gpio_if(speaker_gpio_interface);
    speaker_gpio_interface = NULL;
  }
  ESP_LOGI(TAG, "play codec closed");
}

// ---- I2S 通道反初始化 ----
static void _i2s_close(void) {
  if (i2s_data_interface) {
    audio_codec_delete_data_if(i2s_data_interface);
    i2s_data_interface = NULL;
  }
  if (i2s_microphone_channel) {
    i2s_del_channel(i2s_microphone_channel);
    i2s_microphone_channel = NULL;
  }
  if (i2s_speaker_channel) {
    i2s_del_channel(i2s_speaker_channel);
    i2s_speaker_channel = NULL;
  }
  ESP_LOGI(TAG, "i2s closed");
}

// ==================== AEC 回声消除实现 ====================

// ---- 创建回声消除器实例与相关缓冲 ----
static esp_err_t _aec_open(void) {
  if (aec_handle) {
    return ESP_OK;
  }

  aec_handle = afe_aec_create(AV_ESP_AEC_INPUT_FORMAT, AV_ESP_AEC_FILTER_LENGTH, AFE_TYPE_VC,
                              AFE_MODE_HIGH_PERF);
  if (!aec_handle) {
    ESP_LOGE(TAG, "afe_aec_create failed");
    return ESP_FAIL;
  }

  aec_chunk_samples = afe_aec_get_chunksize(aec_handle);
  if (aec_chunk_samples <= 0) {
    ESP_LOGE(TAG, "invalid AEC chunk=%d", aec_chunk_samples);
    _aec_close();
    return ESP_FAIL;
  }

  // 采集缓冲按 20ms 立体声分配，AEC chunk 不能超过它，否则单次读取会溢出。
  if ((size_t)aec_chunk_samples * AV_ESP_CODEC_CHANNELS * sizeof(int16_t) >
      sizeof(capture_stereo_buffer)) {
    ESP_LOGE(TAG, "AEC chunk %d too large for capture buffer", aec_chunk_samples);
    _aec_close();
    return ESP_FAIL;
  }

  // AFE 要求 16 字节对齐的输入/输出缓冲
  aec_microphone_buffer =
      heap_caps_aligned_calloc(16, (size_t)aec_chunk_samples, sizeof(int16_t), MALLOC_CAP_INTERNAL);
  aec_reference_buffer =
      heap_caps_aligned_calloc(16, (size_t)aec_chunk_samples, sizeof(int16_t), MALLOC_CAP_INTERNAL);
  aec_interleaved_buffer = heap_caps_aligned_calloc(16, (size_t)aec_chunk_samples * 2,
                                                    sizeof(int16_t), MALLOC_CAP_INTERNAL);
  aec_output_buffer =
      heap_caps_aligned_calloc(16, (size_t)aec_chunk_samples, sizeof(int16_t), MALLOC_CAP_INTERNAL);
  aec_reference_ring_buffer =
      heap_caps_calloc(AV_ESP_AEC_REFERENCE_RING_SAMPLES, sizeof(int16_t), MALLOC_CAP_INTERNAL);
  if (!aec_microphone_buffer || !aec_reference_buffer || !aec_interleaved_buffer ||
      !aec_output_buffer || !aec_reference_ring_buffer) {
    ESP_LOGE(TAG, "AEC buffer alloc failed");
    _aec_close();
    return ESP_ERR_NO_MEM;
  }

  aec_reference_ring_buffer_head = 0;
  aec_reference_ring_buffer_tail = 0;
  aec_frame_accumulated = 0;

  ESP_LOGI(TAG, "AEC ready: fmt=%s filter=%d chunk=%d (%d ms)", AV_ESP_AEC_INPUT_FORMAT,
           AV_ESP_AEC_FILTER_LENGTH, aec_chunk_samples, aec_chunk_samples * 1000 / MIC_SAMPLE_RATE);
  return ESP_OK;
}

// ---- 释放回声消除器实例与相关缓冲 ----
static void _aec_close(void) {
  if (aec_handle) {
    afe_aec_destroy(aec_handle);
    aec_handle = NULL;
  }
  if (aec_microphone_buffer) {
    heap_caps_free(aec_microphone_buffer);
    aec_microphone_buffer = NULL;
  }
  if (aec_reference_buffer) {
    heap_caps_free(aec_reference_buffer);
    aec_reference_buffer = NULL;
  }
  if (aec_interleaved_buffer) {
    heap_caps_free(aec_interleaved_buffer);
    aec_interleaved_buffer = NULL;
  }
  if (aec_output_buffer) {
    heap_caps_free(aec_output_buffer);
    aec_output_buffer = NULL;
  }
  if (aec_reference_ring_buffer) {
    heap_caps_free(aec_reference_ring_buffer);
    aec_reference_ring_buffer = NULL;
  }
  aec_chunk_samples = 0;
  aec_frame_accumulated = 0;
  ESP_LOGI(TAG, "AEC closed");
}

// MIC 采集任务：从 I2S RX 读取立体声帧，下混为单声道；
// 若回声消除器可用则经回声消除后再上抛，否则直接上抛。
static void _get_microphone_audio_stream(void *arg) {
  (void)arg;

  uint32_t frame_count = 0;
  const size_t output_frame_samples = MIC_FRAME_BYTES / sizeof(int16_t);

  if (aec_handle) {
    const int chunk_samples = aec_chunk_samples;
    const size_t read_bytes = (size_t)chunk_samples * AV_ESP_CODEC_CHANNELS * sizeof(int16_t);
    aec_frame_accumulated = 0;

    while (audio_task_running) {
      size_t bytes_read = 0;
      esp_err_t err = i2s_channel_read(i2s_microphone_channel, capture_stereo_buffer, read_bytes,
                                       &bytes_read, AV_ESP_AUDIO_READ_TIMEOUT_MS);
      if (!audio_task_running) {
        break;
      }
      if (err == ESP_ERR_TIMEOUT) {
        continue;
      }
      if (err != ESP_OK) {
        ESP_LOGW(TAG, "i2s read failed: %s", esp_err_to_name(err));
        rtos_delay_milliseconds(10);
        continue;
      }
      if (bytes_read != read_bytes) {
        ESP_LOGW(TAG, "drop partial AEC frame: %u/%u", (unsigned)bytes_read, (unsigned)read_bytes);
        continue;
      }

      // 立体声下混单声道，得到近端麦克风信号
      for (int i = 0; i < chunk_samples; ++i) {
        int32_t mixed = (int32_t)capture_stereo_buffer[i * AV_ESP_CODEC_CHANNELS] +
                        (int32_t)capture_stereo_buffer[i * AV_ESP_CODEC_CHANNELS + 1];
        aec_microphone_buffer[i] = (int16_t)(mixed / AV_ESP_CODEC_CHANNELS);
      }
      // 取出与本帧对应的播放参考信号
      portENTER_CRITICAL(&aec_reference_ring_buffer_lock);
      for (int i = 0; i < chunk_samples; ++i) {
        if (aec_reference_ring_buffer_tail == aec_reference_ring_buffer_head) {
          aec_reference_buffer[i] = 0;
        } else {
          aec_reference_buffer[i] = aec_reference_ring_buffer[aec_reference_ring_buffer_tail];
          aec_reference_ring_buffer_tail =
              (aec_reference_ring_buffer_tail + 1) % AV_ESP_AEC_REFERENCE_RING_SAMPLES;
        }
      }
      portEXIT_CRITICAL(&aec_reference_ring_buffer_lock);
      // 交织为 AEC 输入格式 "MR"：[M0,R0,M1,R1,...]
      for (int i = 0; i < chunk_samples; ++i) {
        aec_interleaved_buffer[2 * i] = aec_microphone_buffer[i];
        aec_interleaved_buffer[2 * i + 1] = aec_reference_buffer[i];
      }
      // 回声消除：输出为去回声后的近端单声道
      size_t output_bytes = afe_aec_process(aec_handle, aec_interleaved_buffer, aec_output_buffer);
      int output_samples = (int)(output_bytes / sizeof(int16_t));
      if (output_samples <= 0 || output_samples > chunk_samples) {
        output_samples = chunk_samples;  // 兜底
      }

      // 按 20ms 组装上抛帧，保持与上层 MIC_FRAME_MS 口径一致
      for (int i = 0; i < output_samples; ++i) {
        capture_mono_buffer[aec_frame_accumulated++] = aec_output_buffer[i];
        if (aec_frame_accumulated >= output_frame_samples) {
          audio_device_callback_t cb = audio_callback;
          void *user_data = audio_callback_context;
          if (cb && audio_started) {
            cb((const uint8_t *)capture_mono_buffer, MIC_FRAME_BYTES, user_data);
            frame_count++;
            if (frame_count == 1 || frame_count % 500 == 0) {
              ESP_LOGI(TAG, "captured AEC frames=%u", (unsigned)frame_count);
            }
          }
          aec_frame_accumulated = 0;
        }
      }
    }

    audio_task_handle = NULL;
    vTaskDelete(NULL);
    return;
  }

  // AEC 不可用，沿用原始下混直传 ----
  const size_t samples_per_channel = MIC_FRAME_BYTES / sizeof(int16_t);

  while (audio_task_running) {
    size_t bytes_read = 0;
    esp_err_t err =
        i2s_channel_read(i2s_microphone_channel, capture_stereo_buffer, AV_ESP_CAPTURE_FRAME_BYTES,
                         &bytes_read, AV_ESP_AUDIO_READ_TIMEOUT_MS);
    if (!audio_task_running) {
      break;
    }
    if (err == ESP_ERR_TIMEOUT) {
      continue;
    }
    if (err != ESP_OK) {
      ESP_LOGW(TAG, "i2s read failed: %s", esp_err_to_name(err));
      rtos_delay_milliseconds(10);
      continue;
    }
    if (bytes_read != AV_ESP_CAPTURE_FRAME_BYTES) {
      ESP_LOGW(TAG, "drop partial audio frame: %u/%u", (unsigned)bytes_read,
               (unsigned)AV_ESP_CAPTURE_FRAME_BYTES);
      continue;
    }

    // 立体声 S16LE (LRLR…) → 单声道：取左右声道平均值
    for (size_t i = 0; i < samples_per_channel; ++i) {
      int32_t mixed = (int32_t)capture_stereo_buffer[i * AV_ESP_CODEC_CHANNELS] +
                      (int32_t)capture_stereo_buffer[i * AV_ESP_CODEC_CHANNELS + 1];
      capture_mono_buffer[i] = (int16_t)(mixed / AV_ESP_CODEC_CHANNELS);
    }

    audio_device_callback_t cb = audio_callback;
    void *user_data = audio_callback_context;
    if (cb && audio_started) {
      cb((const uint8_t *)capture_mono_buffer, MIC_FRAME_BYTES, user_data);
      frame_count++;
      if (frame_count == 1 || frame_count % 500 == 0) {
        ESP_LOGI(TAG, "captured audio frames=%u", (unsigned)frame_count);
      }
    }
  }

  audio_task_handle = NULL;
  vTaskDelete(NULL);
}

// ==================== 平台差异化 I2S 初始化 ====================

static esp_err_t _i2s_open(void) {
  if (i2s_speaker_channel || i2s_microphone_channel) {
    return ESP_OK;
  }

  i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(AV_ESP_I2S_PORT, I2S_ROLE_MASTER);
  chan_cfg.dma_desc_num = 6;

#ifdef CONFIG_IDF_TARGET_ESP32P4
  chan_cfg.dma_frame_num = SPK_SAMPLE_RATE * SPK_FRAME_MS / 1000;
#else
  chan_cfg.dma_frame_num = MIC_SAMPLE_RATE * MIC_FRAME_MS / 1000;
#endif
  chan_cfg.auto_clear = true;

  esp_err_t err = i2s_new_channel(&chan_cfg, &i2s_speaker_channel, &i2s_microphone_channel);
  if (err != ESP_OK) {
    i2s_speaker_channel = NULL;
    i2s_microphone_channel = NULL;
    return err;
  }

#ifdef CONFIG_IDF_TARGET_ESP32P4
  // ESP32-P4: I2S 标准模式 (STD)
  i2s_std_config_t std_cfg = {
      .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SPK_SAMPLE_RATE),
      .slot_cfg =
          I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
      .gpio_cfg =
          {
              .mclk = AV_ESP_I2S_MCLK_GPIO,
              .bclk = AV_ESP_I2S_BCLK_GPIO,
              .ws = AV_ESP_I2S_WS_GPIO,
              .dout = AV_ESP_I2S_DOUT_GPIO,
              .din = AV_ESP_I2S_DIN_GPIO,
              .invert_flags = {.mclk_inv = false, .bclk_inv = false, .ws_inv = false},
          },
  };

  if (i2s_speaker_channel) {
    ESP_RETURN_ON_ERROR(i2s_channel_init_std_mode(i2s_speaker_channel, &std_cfg), TAG,
                        "tx std init");
    ESP_RETURN_ON_ERROR(i2s_channel_enable(i2s_speaker_channel), TAG, "tx enable");
  }
  if (i2s_microphone_channel) {
    ESP_RETURN_ON_ERROR(i2s_channel_init_std_mode(i2s_microphone_channel, &std_cfg), TAG,
                        "rx std init");
    ESP_RETURN_ON_ERROR(i2s_channel_enable(i2s_microphone_channel), TAG, "rx enable");
  }
#else
  // ESP32-S3: I2S TDM 模式
  i2s_tdm_config_t tdm_cfg = {
      .slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(
          I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO, AV_ESP_I2S_TDM_SLOT_MASK),
      .clk_cfg =
          {
              .clk_src = I2S_CLK_SRC_DEFAULT,
              .sample_rate_hz = MIC_SAMPLE_RATE,
              .mclk_multiple = I2S_MCLK_MULTIPLE_256,
          },
      .gpio_cfg =
          {
              .mclk = AV_ESP_I2S_MCLK_GPIO,
              .bclk = AV_ESP_I2S_BCLK_GPIO,
              .ws = AV_ESP_I2S_WS_GPIO,
              .dout = AV_ESP_I2S_DOUT_GPIO,
              .din = AV_ESP_I2S_DIN_GPIO,
              .invert_flags = {.mclk_inv = false, .bclk_inv = false, .ws_inv = false},
          },
  };

  if (i2s_speaker_channel) {
    ESP_RETURN_ON_ERROR(i2s_channel_init_tdm_mode(i2s_speaker_channel, &tdm_cfg), TAG,
                        "tx tdm init");
    ESP_RETURN_ON_ERROR(i2s_channel_enable(i2s_speaker_channel), TAG, "tx enable");
  }
  if (i2s_microphone_channel) {
    ESP_RETURN_ON_ERROR(i2s_channel_init_tdm_mode(i2s_microphone_channel, &tdm_cfg), TAG,
                        "rx tdm init");
    ESP_RETURN_ON_ERROR(i2s_channel_enable(i2s_microphone_channel), TAG, "rx enable");
  }
#endif

  audio_codec_i2s_cfg_t i2s_cfg = {
      .port = AV_ESP_I2S_PORT,
      .rx_handle = i2s_microphone_channel,
      .tx_handle = i2s_speaker_channel,
  };
  i2s_data_interface = audio_codec_new_i2s_data(&i2s_cfg);
  ESP_RETURN_ON_FALSE(i2s_data_interface, ESP_ERR_NO_MEM, TAG, "i2s data_if alloc");

  return ESP_OK;
}

// ==================== 平台差异化录音 Codec ====================

#ifdef CONFIG_IDF_TARGET_ESP32P4

// ESP32-P4: ES8311 BOTH 模式（共用同一 Codec 芯片完成录音和播放）
static esp_err_t _microphone_open(void) {
  if (microphone_codec_device) {
    return ESP_OK;
  }
  if (!i2s_data_interface || !audio_i2c_bus) {
    return ESP_ERR_INVALID_STATE;
  }

  audio_codec_i2c_cfg_t i2c_cfg = {
      .port = AV_ESP_I2C_PORT,
      .addr = ES8311_CODEC_DEFAULT_ADDR,
      .bus_handle = audio_i2c_bus,
  };
  microphone_control_interface = audio_codec_new_i2c_ctrl(&i2c_cfg);
  ESP_RETURN_ON_FALSE(microphone_control_interface, ESP_ERR_NO_MEM, TAG,
                      "record ctrl_if alloc fail");

  es8311_codec_cfg_t es8311_cfg = {
      .ctrl_if = microphone_control_interface,
      .gpio_if = speaker_gpio_interface,
      .codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH,
      .master_mode = false,
      .use_mclk = true,
      .pa_pin = AV_ESP_PA_GPIO,
      .pa_reverted = false,
      .no_dac_ref = true,
      .hw_gain = {.pa_voltage = 5.0, .codec_dac_voltage = 3.3},
  };
  microphone_codec_interface = es8311_codec_new(&es8311_cfg);
  ESP_RETURN_ON_FALSE(microphone_codec_interface, ESP_ERR_NO_MEM, TAG,
                      "record codec_if alloc fail");

  esp_codec_dev_cfg_t dev_cfg = {
      .dev_type = ESP_CODEC_DEV_TYPE_IN,
      .codec_if = microphone_codec_interface,
      .data_if = i2s_data_interface,
  };
  microphone_codec_device = esp_codec_dev_new(&dev_cfg);
  ESP_RETURN_ON_FALSE(microphone_codec_device, ESP_ERR_NO_MEM, TAG, "record codec_dev alloc fail");

  esp_codec_dev_sample_info_t sample_cfg = {
      .bits_per_sample = 16,
      .channel = AV_ESP_CODEC_CHANNELS,
      .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0) | ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1),
      .sample_rate = MIC_SAMPLE_RATE,
      .mclk_multiple = I2S_MCLK_MULTIPLE_256,
  };
  ESP_RETURN_ON_FALSE(esp_codec_dev_open(microphone_codec_device, &sample_cfg) == ESP_CODEC_DEV_OK,
                      ESP_FAIL, TAG, "record codec_dev open fail");

  esp_err_t gain_err =
      esp_codec_dev_set_in_gain(microphone_codec_device, AV_ESP_ES8311_MIC_GAIN_DB);
  if (gain_err != ESP_CODEC_DEV_OK) {
    ESP_LOGW(TAG, "set mic gain to %.1f dB failed (err=%d), using codec default",
             (double)AV_ESP_ES8311_MIC_GAIN_DB, gain_err);
  } else {
    ESP_LOGI(TAG, "ES8311 mic gain set to %.1f dB", (double)AV_ESP_ES8311_MIC_GAIN_DB);
  }

  ESP_LOGI(TAG, "ES8311 mic codec opened: sr=%d ch=%d", MIC_SAMPLE_RATE, AV_ESP_CODEC_CHANNELS);
  return ESP_OK;
}

#else  // ESP32-S3: ES7210 独立录音 Codec

static esp_err_t _microphone_open(void) {
  if (microphone_codec_device) {
    return ESP_OK;
  }

  audio_codec_i2c_cfg_t i2c_cfg = {
      .port = AV_ESP_I2C_PORT,
      .addr = ES7210_CODEC_DEFAULT_ADDR,
      .bus_handle = audio_i2c_bus,
  };
  microphone_control_interface = audio_codec_new_i2c_ctrl(&i2c_cfg);
  ESP_RETURN_ON_FALSE(microphone_control_interface, ESP_ERR_NO_MEM, TAG,
                      "record ctrl_if alloc fail");

  es7210_codec_cfg_t es7210_cfg = {
      .ctrl_if = microphone_control_interface,
      .master_mode = false,
      .mic_selected = AV_ESP_ES7210_MIC_SELECTED,
      .mclk_src = ES7210_MCLK_FROM_PAD,
      .mclk_div = I2S_MCLK_MULTIPLE_256,
  };
  microphone_codec_interface = es7210_codec_new(&es7210_cfg);
  ESP_RETURN_ON_FALSE(microphone_codec_interface, ESP_ERR_NO_MEM, TAG,
                      "record codec_if alloc fail");

  esp_codec_dev_cfg_t dev_cfg = {
      .dev_type = ESP_CODEC_DEV_TYPE_IN,
      .codec_if = microphone_codec_interface,
      .data_if = i2s_data_interface,
  };
  microphone_codec_device = esp_codec_dev_new(&dev_cfg);
  ESP_RETURN_ON_FALSE(microphone_codec_device, ESP_ERR_NO_MEM, TAG, "record codec_dev alloc fail");

  esp_codec_dev_sample_info_t sample_cfg = {
      .bits_per_sample = 16,
      .channel = AV_ESP_CODEC_CHANNELS,
      .channel_mask = AV_ESP_ES7210_MIC_SELECTED,
      .sample_rate = MIC_SAMPLE_RATE,
      .mclk_multiple = I2S_MCLK_MULTIPLE_256,
  };
  ESP_RETURN_ON_FALSE(esp_codec_dev_open(microphone_codec_device, &sample_cfg) == ESP_CODEC_DEV_OK,
                      ESP_FAIL, TAG, "record codec_dev open fail");
  ESP_RETURN_ON_FALSE(esp_codec_dev_set_in_gain(microphone_codec_device,
                                                AV_ESP_ES7210_MIC_GAIN_DB) == ESP_CODEC_DEV_OK,
                      ESP_FAIL, TAG, "set mic gain fail");

  ESP_LOGI(TAG, "ES7210 mic codec opened: sr=%d ch=%d", MIC_SAMPLE_RATE, AV_ESP_CODEC_CHANNELS);
  return ESP_OK;
}

#endif

// ==================== ESP32-P4 视频实现 ====================

#ifdef CONFIG_IDF_TARGET_ESP32P4

static int _camera_open(void) {
  esp_video_init_csi_config_t csi_config[] = {
      {
          .sccb_config =
              {
                  .init_sccb = true,
                  .i2c_config =
                      {
                          .port = AV_ESP_CAM_I2C_PORT,
                          .scl_pin = AV_ESP_CAM_I2C_SCL,
                          .sda_pin = AV_ESP_CAM_I2C_SDA,
                      },
                  .freq = AV_ESP_CAM_I2C_FREQ,
              },
          .reset_pin = -1,
          .pwdn_pin = -1,
      },
  };

  if (audio_i2c_bus != NULL) {
    csi_config[0].sccb_config.init_sccb = false;
    csi_config[0].sccb_config.i2c_handle = audio_i2c_bus;
  }

  esp_video_init_config_t cam_cfg = {
      .csi = csi_config,
  };
  esp_video_init(&cam_cfg);

  int fd = open(ESP_VIDEO_MIPI_CSI_DEVICE_NAME, O_RDONLY);
  if (fd < 0) {
    ESP_LOGE(TAG, "failed to open %s", ESP_VIDEO_MIPI_CSI_DEVICE_NAME);
    return -1;
  }

  struct v4l2_format fmt = {0};
  fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  if (ioctl(fd, VIDIOC_G_FMT, &fmt) != 0) {
    ESP_LOGE(TAG, "VIDIOC_G_FMT failed");
    goto fail;
  }
  ESP_LOGI(TAG, "camera default: %ux%u pixelformat=0x%x", fmt.fmt.pix.width, fmt.fmt.pix.height,
           fmt.fmt.pix.pixelformat);

  fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
  fmt.fmt.pix.width = VIDEO_WIDTH;
  fmt.fmt.pix.height = VIDEO_HEIGHT;
  if (ioctl(fd, VIDIOC_S_FMT, &fmt) != 0) {
    ESP_LOGE(TAG, "VIDIOC_S_FMT UYVY %dx%d failed", (int)VIDEO_WIDTH, (int)VIDEO_HEIGHT);
    goto fail;
  }
  ESP_LOGI(TAG, "camera set: %ux%u UYVY", fmt.fmt.pix.width, fmt.fmt.pix.height);

  struct v4l2_requestbuffers req = {0};
  req.count = AV_ESP_CAM_BUF_NUM;
  req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  req.memory = V4L2_MEMORY_MMAP;
  if (ioctl(fd, VIDIOC_REQBUFS, &req) != 0) {
    ESP_LOGE(TAG, "VIDIOC_REQBUFS failed");
    goto fail;
  }

  for (uint32_t i = 0; i < AV_ESP_CAM_BUF_NUM; i++) {
    struct v4l2_buffer buf = {0};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    if (ioctl(fd, VIDIOC_QUERYBUF, &buf) != 0) {
      ESP_LOGE(TAG, "VIDIOC_QUERYBUF[%lu] failed", (unsigned long)i);
      goto fail;
    }
    camera_buffer[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
    if (camera_buffer[i] == MAP_FAILED) {
      ESP_LOGE(TAG, "mmap[%lu] failed", (unsigned long)i);
      goto fail;
    }
    camera_buffer_length = buf.length;

    if (ioctl(fd, VIDIOC_QBUF, &buf) != 0) {
      ESP_LOGE(TAG, "VIDIOC_QBUF[%lu] failed", (unsigned long)i);
      goto fail;
    }
  }

  int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  if (ioctl(fd, VIDIOC_STREAMON, &type) != 0) {
    ESP_LOGE(TAG, "VIDIOC_STREAMON failed");
    goto fail;
  }
  ESP_LOGI(TAG, "camera opened: fd=%d buf_num=%d buf_len=%u", fd, AV_ESP_CAM_BUF_NUM,
           (unsigned)camera_buffer_length);
  return fd;

fail:
  close(fd);
  return -1;
}

static void _camera_close(void) {
  if (camera_fd < 0) {
    return;
  }
  int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  ioctl(camera_fd, VIDIOC_STREAMOFF, &type);
  for (int i = 0; i < AV_ESP_CAM_BUF_NUM; i++) {
    if (camera_buffer[i]) {
      munmap(camera_buffer[i], camera_buffer_length);
      camera_buffer[i] = NULL;
    }
  }
  close(camera_fd);
  camera_fd = -1;
  esp_video_deinit();
  ESP_LOGI(TAG, "camera closed");
}

static esp_err_t _h264_encoder_init(void) {
  uint16_t aligned_w = (VIDEO_WIDTH + 15) / 16 * 16;
  uint16_t aligned_h = (VIDEO_HEIGHT + 15) / 16 * 16;

  esp_h264_enc_cfg_hw_t cfg = {
      .pic_type = ESP_H264_RAW_FMT_UYVY,
      .gop = VIDEO_FPS,
      .fps = VIDEO_FPS,
      .res = {.width = aligned_w, .height = aligned_h},
      .rc =
          {
              .bitrate = AV_ESP_H264_BITRATE,
              .qp_min = 20,
              .qp_max = 40,
          },
  };

  esp_h264_err_t err = esp_h264_enc_hw_new(&cfg, &h264_encoder_handle);
  if (err != ESP_H264_ERR_OK || !h264_encoder_handle) {
    ESP_LOGE(TAG, "h264 encoder create failed: %d", err);
    return ESP_FAIL;
  }

  err = esp_h264_enc_open(h264_encoder_handle);
  if (err != ESP_H264_ERR_OK) {
    ESP_LOGE(TAG, "h264 encoder open failed: %d", err);
    return ESP_FAIL;
  }

  yuv_input_buffer_length = (size_t)aligned_w * aligned_h * 2;
  h264_output_buffer_length = yuv_input_buffer_length;

  video_combined_buffer_length = yuv_input_buffer_length + h264_output_buffer_length;
  video_combined_buffer = heap_caps_aligned_alloc(64, video_combined_buffer_length,
                                                  MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
  if (!video_combined_buffer) {
    video_combined_buffer = heap_caps_aligned_alloc(64, video_combined_buffer_length,
                                                    MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
  }
  if (!video_combined_buffer) {
    ESP_LOGE(TAG, "failed to alloc combined video buffer (%u bytes)",
             (unsigned)video_combined_buffer_length);
    return ESP_ERR_NO_MEM;
  }

  yuv_input_buffer = video_combined_buffer;
  h264_output_buffer = video_combined_buffer + yuv_input_buffer_length;

  ESP_LOGI(TAG, "H.264 HW encoder ready: %ux%u UYVY bitrate=%lu", (unsigned)aligned_w,
           (unsigned)aligned_h, (unsigned long)AV_ESP_H264_BITRATE);
  return ESP_OK;
}

static void _h264_encoder_deinit(void) {
  if (video_combined_buffer) {
    heap_caps_free(video_combined_buffer);
    video_combined_buffer = NULL;
    video_combined_buffer_length = 0;
  }
  yuv_input_buffer = NULL;
  yuv_input_buffer_length = 0;
  h264_output_buffer = NULL;
  h264_output_buffer_length = 0;
  ESP_LOGI(TAG, "H.264 encoder deinit done");
}

static void _get_camera_video_stream(void *arg) {
  (void)arg;
  // 宽度向上对齐到16的倍数，满足H.264编码器要求
  uint16_t aligned_w = (uint16_t)((VIDEO_WIDTH + 15) / 16 * 16);
  // 标记是否已发送第一个IDR关键帧（解码器必须从IDR开始解码）
  bool first_idr_sent = false;

  while (video_stream_activate) {
    // --- 1. 从摄像头驱动出队一帧YUV数据 ---
    struct v4l2_buffer buf = {0};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;  // mmap共享内存方式
    if (ioctl(camera_fd, VIDIOC_DQBUF, &buf) != 0) {
      if (video_stream_activate) {
        ESP_LOGW(TAG, "VIDIOC_DQBUF failed");
        rtos_delay_milliseconds(10);
      }
      continue;
    }

    uint8_t *cam_data = camera_buffer[buf.index];

    // --- 2. 将原始YUV数据逐行拷贝到编码器输入缓冲区 ---
    size_t copy_per_row = (size_t)VIDEO_WIDTH * 2;  // YUV422每像素2字节
    size_t stride = (size_t)aligned_w * 2;          // 对齐后的行步长
    for (uint16_t row = 0; row < (uint16_t)VIDEO_HEIGHT; row++) {
      memcpy(yuv_input_buffer + row * stride, cam_data + row * copy_per_row, copy_per_row);
    }
    // --- 3. 用最后一列像素填充右侧padding区域，避免黑边 ---
    if (aligned_w > (uint16_t)VIDEO_WIDTH) {
      for (uint16_t row = 0; row < (uint16_t)VIDEO_HEIGHT; row++) {
        uint8_t *edge = yuv_input_buffer + row * stride + copy_per_row;
        uint8_t *last = edge - 4;            // 最后一列4字节(YUYV)
        size_t pad = stride - copy_per_row;  // 需要填充的字节数
        for (size_t p = 0; p < pad; p++) {
          edge[p] = last[p % 4];  // 循环复制最后一列像素
        }
      }
    }

    // --- 4. 按需请求IDR帧：关闭并重开编码器强制产出一个IDR ---
    if (idr_frame_requested && h264_encoder_handle) {
      esp_h264_enc_close(h264_encoder_handle);
      esp_h264_enc_open(h264_encoder_handle);
      idr_frame_requested = false;
      ESP_LOGI(TAG, "IDR requested, encoder reset");
    }

    // --- 5. H.264编码 ---
    esp_h264_enc_in_frame_t in_frame = {
        .raw_data = {.buffer = yuv_input_buffer, .len = (uint32_t)yuv_input_buffer_length},
        .pts = (uint32_t)(esp_timer_get_time() / 1000),  // 微秒转毫秒时间戳
    };
    esp_h264_enc_out_frame_t out_frame = {
        .raw_data = {.buffer = h264_output_buffer, .len = (uint32_t)h264_output_buffer_length},
    };

    esp_h264_err_t enc_ret = esp_h264_enc_process(h264_encoder_handle, &in_frame, &out_frame);
    if (enc_ret == ESP_H264_ERR_OK && out_frame.length > 0) {
      bool is_key = (out_frame.frame_type == ESP_H264_FRAME_TYPE_IDR ||
                     out_frame.frame_type == ESP_H264_FRAME_TYPE_I);
      // 丢弃首帧之前的所有非关键帧，确保解码器能从IDR开始解码
      if (!first_idr_sent && !is_key) {
        goto requeue;
      }
      first_idr_sent = true;

      // 通过回调将编码后的H.264数据交给上层（如网络推流）
      if (video_callback && video_stream_activate) {
        video_callback(out_frame.raw_data.buffer, out_frame.length, is_key, TC_IOT_VIDEO_CODEC_H264,
                       video_callback_context);
      }
    } else if (enc_ret != ESP_H264_ERR_OK) {
      ESP_LOGW(TAG, "H.264 encode failed: %d", enc_ret);
    }

  requeue:
    // --- 6. 将缓冲区归还摄像头驱动，形成循环缓冲 ---
    if (ioctl(camera_fd, VIDIOC_QBUF, &buf) != 0) {
      ESP_LOGW(TAG, "VIDIOC_QBUF failed");
    }
  }

  // 循环退出后不清理硬件资源，相机和编码器已由 av_device_init() 统一初始化，
  // 由 av_device_deinit() 统一回收。手机退房后再次进房可直接复用。
  video_task_handle = NULL;
  vTaskDelete(NULL);
}

#endif  // CONFIG_IDF_TARGET_ESP32P4
