a2dp_sink.c 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090
  1. /**
  2. * Bluetooth A2DP Sink implementation for ESP32.
  3. *
  4. * Uses the ESP-IDF Bluedroid stack to receive A2DP audio (SBC codec,
  5. * decoded internally to PCM) and play it through the shared I2S output.
  6. *
  7. * Architecture:
  8. * BT stack → data callback → ringbuffer → I2S writer task → I2S DMA
  9. *
  10. * Integration with AirPlay:
  11. * - On BT connect: notify main → stop AirPlay services
  12. * - On BT disconnect: notify main → restart AirPlay services
  13. * - LED/display updates via rtsp_events (reuses the same event types)
  14. * - AVRCP metadata → RTSP_EVENT_METADATA for display
  15. */
  16. #include "a2dp_sink.h"
  17. #include "spiram_task.h"
  18. #include "audio_output.h"
  19. #include "dac.h"
  20. #include "dac_njw1195.h"
  21. #include "led.h"
  22. #include "rtsp_events.h"
  23. #include "settings.h"
  24. #include "panel_uart.h"
  25. #include "esp_a2dp_api.h"
  26. #include "esp_avrc_api.h"
  27. #include "esp_hf_client_api.h"
  28. #include "esp_bt.h"
  29. #include "esp_bt_device.h"
  30. #include "esp_bt_main.h"
  31. #include "esp_gap_bt_api.h"
  32. #include "esp_log.h"
  33. #include "esp_timer.h"
  34. #include "freertos/FreeRTOS.h"
  35. #include "freertos/queue.h"
  36. #include "freertos/ringbuf.h"
  37. #include "freertos/semphr.h"
  38. #include "freertos/task.h"
  39. #include <inttypes.h>
  40. #include <string.h>
  41. #include "playback_control.h"
  42. #include "audio_prompt.h"
  43. static const char *TAG = "bt_a2dp";
  44. /* ========================================================================== */
  45. /* Configuration */
  46. /* ========================================================================== */
  47. #define RINGBUF_SIZE ((size_t)64 * 1024)
  48. #define RINGBUF_PREFETCH (RINGBUF_SIZE * 6 / 10)
  49. #define BT_TASK_STACK 4096
  50. #define BT_TASK_PRIO (configMAX_PRIORITIES - 3)
  51. #define BT_TASK_QLEN 10
  52. #define I2S_TASK_STACK 2560
  53. #define I2S_TASK_PRIO 7
  54. #if CONFIG_FREERTOS_UNICORE
  55. #define I2S_TASK_CORE 0
  56. #else
  57. #define I2S_TASK_CORE 1
  58. #endif
  59. /* ========================================================================== */
  60. /* BT App Task Types */
  61. /* ========================================================================== */
  62. typedef void (*bt_app_cb_t)(uint16_t event, void *param);
  63. typedef struct {
  64. uint16_t sig;
  65. uint16_t event;
  66. bt_app_cb_t cb;
  67. void *param;
  68. } bt_app_msg_t;
  69. enum { BT_APP_SIG_WORK_DISPATCH = 0 };
  70. enum { BT_APP_EVT_STACK_UP = 0 };
  71. /* ========================================================================== */
  72. /* Ringbuffer mode */
  73. /* ========================================================================== */
  74. typedef enum {
  75. RINGBUF_MODE_PREFETCHING,
  76. RINGBUF_MODE_PROCESSING,
  77. RINGBUF_MODE_DROPPING,
  78. } ringbuf_mode_t;
  79. /* ========================================================================== */
  80. /* State */
  81. /* ========================================================================== */
  82. static bt_a2dp_state_cb_t s_state_cb = NULL;
  83. static volatile bool s_connected = false;
  84. static volatile bool s_hfp_connected = false;
  85. static volatile bool s_audio_started = false;
  86. static volatile bool s_avrc_playing = false; /* AVRCP play state (instant) */
  87. static volatile bool s_i2s_task_running = false;
  88. static bool s_bt_discoverable = true;
  89. static uint8_t s_avrc_volume = 64; /* 0-127, AVRCP absolute volume */
  90. static volatile bool s_vol_ntf_pending =
  91. false; /* phone registered for volume change */
  92. static int64_t s_last_local_vol_time = 0; /* 防止回音震荡的时间戳 */
  93. static esp_bd_addr_t s_reconnect_bda;
  94. static esp_bd_addr_t s_current_bda;
  95. static RingbufHandle_t s_ringbuf = NULL;
  96. static SemaphoreHandle_t s_i2s_sem = NULL;
  97. static ringbuf_mode_t s_ringbuf_mode = RINGBUF_MODE_PREFETCHING;
  98. static TaskHandle_t s_bt_task_handle = NULL;
  99. static TaskHandle_t s_i2s_task_handle = NULL;
  100. static QueueHandle_t s_bt_task_queue = NULL;
  101. static uint32_t s_sample_rate = 44100;
  102. /* ========================================================================== */
  103. /* BT App Task — context-switch from BT stack to app task */
  104. /* ========================================================================== */
  105. static bool bt_app_send_msg(bt_app_msg_t *msg) {
  106. if (s_bt_task_queue == NULL) {
  107. return false;
  108. }
  109. return xQueueSend(s_bt_task_queue, msg, pdMS_TO_TICKS(10)) == pdTRUE;
  110. }
  111. static bool bt_app_work_dispatch(bt_app_cb_t cb, uint16_t event, void *param,
  112. int param_len) {
  113. bt_app_msg_t msg = {
  114. .sig = BT_APP_SIG_WORK_DISPATCH,
  115. .event = event,
  116. .cb = cb,
  117. .param = NULL,
  118. };
  119. if (param_len > 0 && param != NULL) {
  120. msg.param = malloc((size_t)param_len);
  121. if (!msg.param) {
  122. return false;
  123. }
  124. memcpy(msg.param, param, (size_t)param_len);
  125. }
  126. if (!bt_app_send_msg(&msg)) {
  127. free(msg.param);
  128. return false;
  129. }
  130. return true;
  131. }
  132. static void bt_app_task(void *arg) {
  133. bt_app_msg_t msg;
  134. while (true) {
  135. if (xQueueReceive(s_bt_task_queue, &msg, portMAX_DELAY) == pdTRUE) {
  136. if (msg.sig == BT_APP_SIG_WORK_DISPATCH && msg.cb) {
  137. msg.cb(msg.event, msg.param);
  138. }
  139. free(msg.param);
  140. }
  141. }
  142. }
  143. /* ========================================================================== */
  144. /* I2S Writer Task — reads ringbuffer, writes to I2S */
  145. /* ========================================================================== */
  146. static void bt_i2s_writer_task(void *arg) {
  147. // Wait for ringbuffer to reach prefetch level
  148. xSemaphoreTake(s_i2s_sem, portMAX_DELAY);
  149. int16_t silence[256] = {0};
  150. while (s_i2s_task_running) {
  151. size_t item_size = 0;
  152. void *data =
  153. xRingbufferReceiveUpTo(s_ringbuf, &item_size, pdMS_TO_TICKS(20), 512);
  154. if (data != NULL && item_size > 0) {
  155. audio_output_write(data, item_size, portMAX_DELAY);
  156. vRingbufferReturnItem(s_ringbuf, data);
  157. } else {
  158. // Buffer underrun — write silence to keep I2S fed
  159. audio_output_write(silence, sizeof(silence), pdMS_TO_TICKS(10));
  160. }
  161. }
  162. s_i2s_task_handle = NULL;
  163. vTaskDelete(NULL);
  164. }
  165. static void i2s_task_start(void) {
  166. if (s_i2s_task_handle != NULL) {
  167. return;
  168. }
  169. s_ringbuf_mode = RINGBUF_MODE_PREFETCHING;
  170. if (s_ringbuf == NULL) {
  171. s_ringbuf = xRingbufferCreate(RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF);
  172. }
  173. if (s_i2s_sem == NULL) {
  174. s_i2s_sem = xSemaphoreCreateBinary();
  175. }
  176. s_i2s_task_running = true;
  177. xTaskCreatePinnedToCore(bt_i2s_writer_task, "bt_i2s", I2S_TASK_STACK, NULL,
  178. I2S_TASK_PRIO, &s_i2s_task_handle, I2S_TASK_CORE);
  179. }
  180. static void i2s_task_stop(void) {
  181. s_i2s_task_running = false;
  182. // Unblock the writer if it's waiting on the semaphore
  183. if (s_i2s_sem) {
  184. xSemaphoreGive(s_i2s_sem);
  185. }
  186. // Wait for the task to exit
  187. int timeout = 20;
  188. while (s_i2s_task_handle != NULL && timeout-- > 0) {
  189. vTaskDelay(pdMS_TO_TICKS(50));
  190. }
  191. // Flush and free the ringbuffer so heap is returned before AirPlay restarts
  192. if (s_ringbuf) {
  193. void *data;
  194. size_t size;
  195. while ((data = xRingbufferReceive(s_ringbuf, &size, 0)) != NULL) {
  196. vRingbufferReturnItem(s_ringbuf, data);
  197. }
  198. vRingbufferDelete(s_ringbuf);
  199. s_ringbuf = NULL;
  200. }
  201. if (s_i2s_sem) {
  202. vSemaphoreDelete(s_i2s_sem);
  203. s_i2s_sem = NULL;
  204. }
  205. // Reset sample rate to AirPlay default
  206. audio_output_set_sample_rate(44100);
  207. audio_output_flush();
  208. }
  209. /* ========================================================================== */
  210. /* A2DP data callback — BT stack context → ringbuffer */
  211. /* ========================================================================== */
  212. static void bt_a2dp_data_cb(const uint8_t *data, uint32_t len) {
  213. // Snapshot the ringbuf handle so it can't be freed between the NULL
  214. // check and the API calls (i2s_task_stop sets s_ringbuf = NULL).
  215. RingbufHandle_t rb = s_ringbuf;
  216. if (rb == NULL || len == 0) {
  217. return;
  218. }
  219. // Feed LED VU meter from the decoded PCM
  220. led_audio_feed((const int16_t *)data, len / 4);
  221. size_t free_size = xRingbufferGetCurFreeSize(rb);
  222. switch (s_ringbuf_mode) {
  223. case RINGBUF_MODE_PREFETCHING:
  224. xRingbufferSend(rb, data, len, 0);
  225. if ((RINGBUF_SIZE - free_size + len) >= RINGBUF_PREFETCH) {
  226. s_ringbuf_mode = RINGBUF_MODE_PROCESSING;
  227. xSemaphoreGive(s_i2s_sem);
  228. }
  229. break;
  230. case RINGBUF_MODE_PROCESSING:
  231. if (free_size < (size_t)len) {
  232. s_ringbuf_mode = RINGBUF_MODE_DROPPING;
  233. ESP_LOGW(TAG, "Ringbuf full, dropping");
  234. } else {
  235. xRingbufferSend(rb, data, len, 0);
  236. }
  237. break;
  238. case RINGBUF_MODE_DROPPING:
  239. if (free_size > RINGBUF_PREFETCH) {
  240. s_ringbuf_mode = RINGBUF_MODE_PROCESSING;
  241. xRingbufferSend(rb, data, len, 0);
  242. }
  243. break;
  244. }
  245. }
  246. /* ========================================================================== */
  247. /* A2DP event handler (runs in app task context) */
  248. /* ========================================================================== */
  249. static volatile bool s_reconnect_abort = false;
  250. static void bt_reconnect_task(void *arg) {
  251. ESP_LOGI(TAG, "蓝牙回连启动... 等待1秒");
  252. vTaskDelay(pdMS_TO_TICKS(1000));
  253. s_reconnect_abort = false;
  254. for (int i = 0; i < 60; i++) { // 60次 * 5秒 = 5分钟
  255. if (s_connected || s_reconnect_abort) {
  256. ESP_LOGI(TAG, "蓝牙已连接或回连被取消,回连结束。");
  257. break;
  258. }
  259. if (playback_control_get_source() == PLAYBACK_SOURCE_AIRPLAY) {
  260. ESP_LOGD(TAG, "AirPlay 正在使用中,暂缓蓝牙回连...");
  261. vTaskDelay(pdMS_TO_TICKS(5000));
  262. continue;
  263. }
  264. ESP_LOGI(TAG, "自动回连中... ( %d / 60 )", i + 1);
  265. esp_err_t err = esp_a2d_sink_connect(s_reconnect_bda);
  266. if (err != ESP_OK) {
  267. ESP_LOGE(TAG, "自动回连呼叫失败: %s", esp_err_to_name(err));
  268. }
  269. vTaskDelay(pdMS_TO_TICKS(5000));
  270. }
  271. ESP_LOGI(TAG, "回连重试任务结束。");
  272. vTaskDelete(NULL);
  273. }
  274. static void bt_a2dp_evt_handler(uint16_t event, void *param) {
  275. esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)param;
  276. switch (event) {
  277. case ESP_A2D_CONNECTION_STATE_EVT: {
  278. esp_a2d_connection_state_t state = a2d->conn_stat.state;
  279. if (state == ESP_A2D_CONNECTION_STATE_CONNECTING) {
  280. ESP_LOGI(TAG, "A2DP connecting...");
  281. } else if (state == ESP_A2D_CONNECTION_STATE_CONNECTED) {
  282. ESP_LOGI(TAG, "A2DP connected");
  283. s_connected = true;
  284. audio_prompt_play(PROMPT_BT_CONNECTED);
  285. // Stop AirPlay and prepare I2S for Bluetooth
  286. playback_control_set_source(PLAYBACK_SOURCE_BLUETOOTH);
  287. memcpy(s_current_bda, a2d->conn_stat.remote_bda, sizeof(esp_bd_addr_t));
  288. settings_set_last_bt_bda(a2d->conn_stat.remote_bda);
  289. // Notify main app to disable AirPlay (stops playback task + RTSP)
  290. if (s_state_cb) {
  291. s_state_cb(true);
  292. }
  293. // Start BT playback
  294. i2s_task_start();
  295. // LED/display: show connected state
  296. rtsp_events_emit(RTSP_EVENT_CLIENT_CONNECTED, NULL);
  297. // Don't accept more connections while connected
  298. esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
  299. } else if (state == ESP_A2D_CONNECTION_STATE_DISCONNECTING) {
  300. ESP_LOGI(TAG, "A2DP disconnecting...");
  301. } else if (state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
  302. ESP_LOGI(TAG, "A2DP disconnected");
  303. s_connected = false;
  304. s_audio_started = false;
  305. s_avrc_playing = false;
  306. audio_prompt_play(PROMPT_BT_DISCONNECTED);
  307. // Tell playback_control BT stopped streaming, so it can switch back to
  308. // AirPlay or Idle
  309. playback_control_set_source(PLAYBACK_SOURCE_NONE);
  310. // Stop BT playback
  311. i2s_task_stop();
  312. // LED/display: show disconnected state
  313. rtsp_events_emit(RTSP_EVENT_DISCONNECTED, NULL);
  314. // Notify main app to re-enable AirPlay (restarts playback + RTSP)
  315. if (s_state_cb) {
  316. s_state_cb(false);
  317. }
  318. // Discoverable state is managed by main via set_discoverable()
  319. // Persist the last-used volume to NVS once at disconnect
  320. settings_persist_bt_volume();
  321. }
  322. break;
  323. }
  324. case ESP_A2D_AUDIO_STATE_EVT: {
  325. esp_a2d_audio_state_t state = a2d->audio_stat.state;
  326. if (state == ESP_A2D_AUDIO_STATE_STARTED) {
  327. ESP_LOGI(TAG, "Audio stream started");
  328. s_audio_started = true;
  329. rtsp_events_emit(RTSP_EVENT_PLAYING, NULL);
  330. } else {
  331. ESP_LOGI(TAG, "Audio stream stopped/suspended");
  332. s_audio_started = false;
  333. rtsp_events_emit(RTSP_EVENT_PAUSED, NULL);
  334. }
  335. break;
  336. }
  337. case ESP_A2D_AUDIO_CFG_EVT: {
  338. ESP_LOGI(TAG, "Audio codec configured, type=%d", a2d->audio_cfg.mcc.type);
  339. if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) {
  340. // Use the new struct-based CIE accessors
  341. uint8_t sf = a2d->audio_cfg.mcc.cie.sbc_info.samp_freq;
  342. if (sf & ESP_A2D_SBC_CIE_SF_48K) {
  343. s_sample_rate = 48000;
  344. } else if (sf & ESP_A2D_SBC_CIE_SF_44K) {
  345. s_sample_rate = 44100;
  346. } else if (sf & ESP_A2D_SBC_CIE_SF_32K) {
  347. s_sample_rate = 32000;
  348. } else if (sf & ESP_A2D_SBC_CIE_SF_16K) {
  349. s_sample_rate = 16000;
  350. }
  351. ESP_LOGI(TAG, "SBC sample rate: %" PRIu32 " Hz", s_sample_rate);
  352. audio_output_set_sample_rate(s_sample_rate);
  353. }
  354. break;
  355. }
  356. case ESP_A2D_PROF_STATE_EVT:
  357. ESP_LOGI(TAG, "A2DP profile %s",
  358. a2d->a2d_prof_stat.init_state == ESP_A2D_INIT_SUCCESS
  359. ? "inited"
  360. : "deinited");
  361. if (a2d->a2d_prof_stat.init_state == ESP_A2D_INIT_SUCCESS) {
  362. if (settings_get_last_bt_bda(s_reconnect_bda) == ESP_OK) {
  363. ESP_LOGI(TAG, "准备蓝牙回连: %02x:%02x:%02x:%02x:%02x:%02x",
  364. s_reconnect_bda[0], s_reconnect_bda[1], s_reconnect_bda[2],
  365. s_reconnect_bda[3], s_reconnect_bda[4], s_reconnect_bda[5]);
  366. xTaskCreate(bt_reconnect_task, "bt_reconn", 3072, NULL, 5, NULL);
  367. }
  368. }
  369. break;
  370. default:
  371. break;
  372. }
  373. }
  374. /* A2DP callback (BT stack context) → dispatch to app task */
  375. static void bt_a2dp_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) {
  376. bt_app_work_dispatch(bt_a2dp_evt_handler, (uint16_t)event, param,
  377. sizeof(esp_a2d_cb_param_t));
  378. }
  379. /* ========================================================================== */
  380. /* AVRCP Controller — get metadata from source device */
  381. /* ========================================================================== */
  382. static void bt_avrc_ct_evt_handler(uint16_t event, void *param) {
  383. esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)param;
  384. static rtsp_event_data_t meta_data;
  385. switch (event) {
  386. case ESP_AVRC_CT_CONNECTION_STATE_EVT:
  387. ESP_LOGI(TAG, "AVRC CT %s",
  388. rc->conn_stat.connected ? "connected" : "disconnected");
  389. if (rc->conn_stat.connected) {
  390. // Request track metadata
  391. esp_avrc_ct_send_metadata_cmd(
  392. 0, ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST |
  393. ESP_AVRC_MD_ATTR_ALBUM | ESP_AVRC_MD_ATTR_GENRE);
  394. // Request current play status (duration + position + play state)
  395. esp_avrc_ct_send_get_play_status_cmd(0);
  396. // Register for notifications: track change, play status, position
  397. esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_TRACK_CHANGE,
  398. 0);
  399. esp_avrc_ct_send_register_notification_cmd(
  400. 2, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
  401. esp_avrc_ct_send_register_notification_cmd(3,
  402. ESP_AVRC_RN_PLAY_POS_CHANGED,
  403. 10); // report every 10 seconds
  404. }
  405. break;
  406. case ESP_AVRC_CT_METADATA_RSP_EVT: {
  407. // Metadata response — one attribute per event.
  408. // attr_text was deep-copied in bt_avrc_ct_cb and must be freed here.
  409. uint8_t attr_id = rc->meta_rsp.attr_id;
  410. uint8_t *text = rc->meta_rsp.attr_text;
  411. int len = rc->meta_rsp.attr_length;
  412. if (text == NULL || len == 0) {
  413. free(text);
  414. break;
  415. }
  416. // Build metadata event for display
  417. char *dst = NULL;
  418. size_t dst_size = 0;
  419. switch (attr_id) {
  420. case ESP_AVRC_MD_ATTR_TITLE:
  421. dst = meta_data.metadata.title;
  422. dst_size = sizeof(meta_data.metadata.title);
  423. break;
  424. case ESP_AVRC_MD_ATTR_ARTIST:
  425. dst = meta_data.metadata.artist;
  426. dst_size = sizeof(meta_data.metadata.artist);
  427. break;
  428. case ESP_AVRC_MD_ATTR_ALBUM:
  429. dst = meta_data.metadata.album;
  430. dst_size = sizeof(meta_data.metadata.album);
  431. break;
  432. case ESP_AVRC_MD_ATTR_GENRE:
  433. dst = meta_data.metadata.genre;
  434. dst_size = sizeof(meta_data.metadata.genre);
  435. break;
  436. default:
  437. break;
  438. }
  439. if (dst) {
  440. size_t copy_len = (size_t)len < dst_size - 1 ? (size_t)len : dst_size - 1;
  441. memcpy(dst, text, copy_len);
  442. dst[copy_len] = '\0';
  443. ESP_LOGI(TAG, "Metadata attr %d: %s", attr_id, dst);
  444. // Emit metadata event after each attribute update
  445. rtsp_events_emit(RTSP_EVENT_METADATA, &meta_data);
  446. }
  447. free(text);
  448. break;
  449. }
  450. case ESP_AVRC_CT_PLAY_STATUS_RSP_EVT: {
  451. uint32_t duration_ms = rc->play_status_rsp.song_length;
  452. uint32_t position_ms = rc->play_status_rsp.song_position;
  453. esp_avrc_playback_stat_t status = rc->play_status_rsp.play_status;
  454. ESP_LOGI(TAG, "Play status: state=%d pos=%lums dur=%lums", status,
  455. (unsigned long)position_ms, (unsigned long)duration_ms);
  456. // Update duration and position in metadata for display
  457. // 0xFFFFFFFF means "not available" per AVRCP spec
  458. if (duration_ms != 0xFFFFFFFF) {
  459. meta_data.metadata.duration_secs = duration_ms / 1000;
  460. }
  461. if (position_ms != 0xFFFFFFFF) {
  462. meta_data.metadata.position_secs = position_ms / 1000;
  463. }
  464. rtsp_events_emit(RTSP_EVENT_METADATA, &meta_data);
  465. // Emit play state from AVRCP (faster than A2D audio state)
  466. if (status == ESP_AVRC_PLAYBACK_PLAYING) {
  467. s_avrc_playing = true;
  468. rtsp_events_emit(RTSP_EVENT_PLAYING, NULL);
  469. } else if (status == ESP_AVRC_PLAYBACK_PAUSED ||
  470. status == ESP_AVRC_PLAYBACK_STOPPED) {
  471. s_avrc_playing = false;
  472. rtsp_events_emit(RTSP_EVENT_PAUSED, NULL);
  473. }
  474. break;
  475. }
  476. case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
  477. if (rc->change_ntf.event_id == ESP_AVRC_RN_TRACK_CHANGE) {
  478. ESP_LOGI(TAG, "Track changed");
  479. // Clear old metadata so stale fields don't persist
  480. memset(&meta_data, 0, sizeof(meta_data));
  481. // Request new metadata and play status
  482. esp_avrc_ct_send_metadata_cmd(
  483. 0, ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST |
  484. ESP_AVRC_MD_ATTR_ALBUM | ESP_AVRC_MD_ATTR_GENRE);
  485. esp_avrc_ct_send_get_play_status_cmd(0);
  486. // Re-register for track change
  487. esp_avrc_ct_send_register_notification_cmd(1, ESP_AVRC_RN_TRACK_CHANGE,
  488. 0);
  489. } else if (rc->change_ntf.event_id == ESP_AVRC_RN_PLAY_STATUS_CHANGE) {
  490. esp_avrc_playback_stat_t status = rc->change_ntf.event_parameter.playback;
  491. ESP_LOGI(TAG, "Play status changed: %d", status);
  492. // Refresh position/duration from source
  493. esp_avrc_ct_send_get_play_status_cmd(0);
  494. if (status == ESP_AVRC_PLAYBACK_PLAYING) {
  495. s_avrc_playing = true;
  496. rtsp_events_emit(RTSP_EVENT_PLAYING, NULL);
  497. } else if (status == ESP_AVRC_PLAYBACK_PAUSED ||
  498. status == ESP_AVRC_PLAYBACK_STOPPED) {
  499. s_avrc_playing = false;
  500. rtsp_events_emit(RTSP_EVENT_PAUSED, NULL);
  501. }
  502. // Re-register
  503. esp_avrc_ct_send_register_notification_cmd(
  504. 2, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0);
  505. } else if (rc->change_ntf.event_id == ESP_AVRC_RN_PLAY_POS_CHANGED) {
  506. uint32_t pos_ms = rc->change_ntf.event_parameter.play_pos;
  507. ESP_LOGD(TAG, "Play position: %lums", (unsigned long)pos_ms);
  508. if (pos_ms != 0xFFFFFFFF) {
  509. meta_data.metadata.position_secs = pos_ms / 1000;
  510. rtsp_events_emit(RTSP_EVENT_METADATA, &meta_data);
  511. }
  512. // Re-register
  513. esp_avrc_ct_send_register_notification_cmd(
  514. 3, ESP_AVRC_RN_PLAY_POS_CHANGED, 10);
  515. }
  516. break;
  517. default:
  518. break;
  519. }
  520. }
  521. static void bt_avrc_ct_cb(esp_avrc_ct_cb_event_t event,
  522. esp_avrc_ct_cb_param_t *param) {
  523. esp_avrc_ct_cb_param_t local = *param;
  524. // Deep-copy attr_text for metadata responses — the BT stack frees the
  525. // original buffer when this callback returns, so the shallow copy made
  526. // by bt_app_work_dispatch would leave a dangling pointer that the
  527. // handler later free()s, corrupting the heap.
  528. //
  529. // Always overwrite local.meta_rsp.attr_text so the stack-owned pointer
  530. // never escapes this function: on success it points at our malloc'd
  531. // copy, on failure (zero-length, malloc returns NULL, or any non-
  532. // metadata event that happens to alias the union) it is NULL. The
  533. // handler does free(text) unconditionally, and free(NULL) is a no-op.
  534. if (event == ESP_AVRC_CT_METADATA_RSP_EVT) {
  535. uint8_t *copy = NULL;
  536. if (param->meta_rsp.attr_text && param->meta_rsp.attr_length > 0) {
  537. copy = malloc((size_t)param->meta_rsp.attr_length + 1);
  538. if (copy) {
  539. memcpy(copy, param->meta_rsp.attr_text,
  540. (size_t)param->meta_rsp.attr_length);
  541. copy[param->meta_rsp.attr_length] = '\0';
  542. }
  543. }
  544. local.meta_rsp.attr_text = copy;
  545. }
  546. bt_app_work_dispatch(bt_avrc_ct_evt_handler, (uint16_t)event, &local,
  547. sizeof(esp_avrc_ct_cb_param_t));
  548. }
  549. /* ========================================================================== */
  550. /* AVRCP Target — handle volume commands from source device */
  551. /* ========================================================================== */
  552. static void bt_avrc_tg_evt_handler(uint16_t event, void *param) {
  553. esp_avrc_tg_cb_param_t *tg = (esp_avrc_tg_cb_param_t *)param;
  554. switch (event) {
  555. case ESP_AVRC_TG_CONNECTION_STATE_EVT:
  556. ESP_LOGI(TAG, "AVRC TG %s",
  557. tg->conn_stat.connected ? "connected" : "disconnected");
  558. break;
  559. case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: {
  560. uint8_t volume = tg->set_abs_vol.volume; // 0-127
  561. // 始终更新本地保存的手机端音量,防止状态脱节导致后续面板调节时不发通知
  562. s_avrc_volume = volume;
  563. // Ignore incoming command if it arrives within 500ms of a manual
  564. // volume change to prevent echo-rollback.
  565. if (esp_timer_get_time() - s_last_local_vol_time < 500000) {
  566. ESP_LOGI(TAG, "Ignore echo absolute volume: %d/127", volume);
  567. break;
  568. }
  569. ESP_LOGI(TAG, "Set absolute volume: %d/127", volume);
  570. uint8_t pct = (volume * 100) / 127;
  571. dac_njw1195_set_vol_main(pct);
  572. break;
  573. }
  574. case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT:
  575. if (tg->reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) {
  576. // Respond with current volume (interim response)
  577. esp_avrc_rn_param_t rn_param;
  578. rn_param.volume = s_avrc_volume;
  579. esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE,
  580. ESP_AVRC_RN_RSP_INTERIM, &rn_param);
  581. s_vol_ntf_pending = true;
  582. }
  583. break;
  584. default:
  585. break;
  586. }
  587. }
  588. static void bt_avrc_tg_cb(esp_avrc_tg_cb_event_t event,
  589. esp_avrc_tg_cb_param_t *param) {
  590. bt_app_work_dispatch(bt_avrc_tg_evt_handler, (uint16_t)event, param,
  591. sizeof(esp_avrc_tg_cb_param_t));
  592. }
  593. /* ========================================================================== */
  594. /* GAP callback */
  595. /* ========================================================================== */
  596. static void bt_gap_cb(esp_bt_gap_cb_event_t event,
  597. esp_bt_gap_cb_param_t *param) {
  598. switch (event) {
  599. case ESP_BT_GAP_ACL_DISCONN_CMPL_STAT_EVT: {
  600. uint8_t reason = param->acl_disconn_cmpl_stat.reason;
  601. ESP_LOGI(TAG, "ACL Disconnected. Reason: 0x%02x", reason);
  602. // HCI Reason 0x13: Remote User Terminated Connection
  603. // HCI Reason 0x14: Remote Device Terminated Connection due to Low Resources
  604. // HCI Reason 0x15: Remote Device Terminated Connection due to Power Off
  605. // HCI Reason 0x16: Connection Terminated By Local Host (本地主动断开)
  606. if (reason == 0x13 || reason == 0x14 || reason == 0x15 || reason == 0x16) {
  607. ESP_LOGI(TAG, "主动断开,设备可被发现 。");
  608. s_reconnect_abort = true;
  609. esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
  610. } else {
  611. ESP_LOGI(TAG, "意外断开,尝试回连5min...");
  612. if (settings_get_last_bt_bda(s_reconnect_bda) == ESP_OK) {
  613. xTaskCreate(bt_reconnect_task, "bt_reconn", 3072, NULL, 5, NULL);
  614. }
  615. }
  616. break;
  617. }
  618. case ESP_BT_GAP_AUTH_CMPL_EVT:
  619. if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
  620. ESP_LOGI(TAG, "Auth success: %s", param->auth_cmpl.device_name);
  621. } else {
  622. ESP_LOGW(TAG, "Auth failed: status=%d", param->auth_cmpl.stat);
  623. }
  624. break;
  625. case ESP_BT_GAP_CFM_REQ_EVT:
  626. ESP_LOGI(TAG, "SSP confirm request (num=%" PRIu32 "), auto-accepting",
  627. param->cfm_req.num_val);
  628. esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
  629. break;
  630. case ESP_BT_GAP_PIN_REQ_EVT:
  631. ESP_LOGI(TAG, "Legacy PIN request");
  632. break;
  633. case ESP_BT_GAP_KEY_NOTIF_EVT:
  634. ESP_LOGI(TAG, "SSP passkey notify: %" PRIu32, param->key_notif.passkey);
  635. break;
  636. case ESP_BT_GAP_KEY_REQ_EVT:
  637. ESP_LOGI(TAG, "SSP passkey request");
  638. break;
  639. case ESP_BT_GAP_MODE_CHG_EVT:
  640. ESP_LOGD(TAG, "GAP mode changed to %d", param->mode_chg.mode);
  641. break;
  642. default:
  643. break;
  644. }
  645. }
  646. /* ========================================================================== */
  647. /* HFP Client callback */
  648. /* ========================================================================== */
  649. static void bt_hf_client_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *param) {
  650. switch (event) {
  651. case ESP_HF_CLIENT_CONNECTION_STATE_EVT:
  652. if (param->conn_stat.state == ESP_HF_CLIENT_CONNECTION_STATE_SLC_CONNECTED) {
  653. ESP_LOGI(TAG, "HFP Client SLC connected");
  654. s_hfp_connected = true;
  655. // 注册苹果专属的电量上报和底座状态特性
  656. esp_hf_client_send_xapl("0505-1995-0610", ESP_HF_CLIENT_XAPL_FEAT_BATTERY_REPORT | ESP_HF_CLIENT_XAPL_FEAT_DOCKED);
  657. // 立刻上报一次当前电量
  658. bt_a2dp_send_battery_level(panel_uart_get_battery_percent());
  659. } else if (param->conn_stat.state == ESP_HF_CLIENT_CONNECTION_STATE_DISCONNECTED) {
  660. ESP_LOGI(TAG, "HFP Client disconnected");
  661. s_hfp_connected = false;
  662. }
  663. break;
  664. default:
  665. break;
  666. }
  667. }
  668. /* ========================================================================== */
  669. /* Stack-up handler — called when Bluedroid is (re-)enabled */
  670. /* ========================================================================== */
  671. static void bt_stack_evt_handler(uint16_t event, void *param) {
  672. (void)param;
  673. if (event != BT_APP_EVT_STACK_UP) {
  674. return;
  675. }
  676. ESP_LOGI(TAG, "BT stack up, initializing profiles");
  677. // GAP
  678. esp_bt_gap_register_callback(bt_gap_cb);
  679. #ifdef CONFIG_BT_SSP_ENABLED
  680. // Secure Simple Pairing — Just Works (No Input No Output)
  681. esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
  682. esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_NONE;
  683. esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
  684. #endif
  685. // Legacy Pairing — fixed PIN from Kconfig
  686. esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED;
  687. esp_bt_pin_code_t pin_code;
  688. const char *pin_str = CONFIG_BT_PIN_CODE;
  689. uint8_t pin_len = strlen(pin_str);
  690. if (pin_len > ESP_BT_PIN_CODE_LEN) {
  691. pin_len = ESP_BT_PIN_CODE_LEN;
  692. }
  693. memcpy(pin_code, pin_str, pin_len);
  694. esp_bt_gap_set_pin(pin_type, pin_len, pin_code);
  695. ESP_LOGI(TAG, "BT PIN set (%d digits)", pin_len);
  696. // AVRC Controller (to get metadata from source)
  697. esp_avrc_ct_register_callback(bt_avrc_ct_cb);
  698. esp_avrc_ct_init();
  699. // AVRC Target (to receive volume commands)
  700. esp_avrc_tg_register_callback(bt_avrc_tg_cb);
  701. esp_avrc_tg_init();
  702. esp_avrc_rn_evt_cap_mask_t evt_set = {0};
  703. esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE);
  704. esp_avrc_tg_set_rn_evt_cap(&evt_set);
  705. // Set supported passthrough commands (iOS requires VOL_UP/VOL_DOWN to enable Absolute Volume)
  706. esp_avrc_psth_bit_mask_t cmd_set = {0};
  707. esp_avrc_psth_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &cmd_set, ESP_AVRC_PT_CMD_VOL_UP);
  708. esp_avrc_psth_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &cmd_set, ESP_AVRC_PT_CMD_VOL_DOWN);
  709. esp_avrc_tg_set_psth_cmd_filter(ESP_AVRC_PSTH_FILTER_SUPPORTED_CMD, &cmd_set);
  710. // A2DP Sink
  711. esp_a2d_register_callback(bt_a2dp_cb);
  712. esp_a2d_sink_register_data_callback(bt_a2dp_data_cb);
  713. esp_a2d_sink_init();
  714. // HFP Client
  715. esp_hf_client_register_callback(bt_hf_client_cb);
  716. esp_hf_client_init();
  717. // Apply saved discoverable state
  718. if (s_bt_discoverable) {
  719. esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
  720. } else {
  721. esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
  722. }
  723. // Force Class of Device AFTER A2DP and HFP are initialized
  724. // A2DP and HFP initializations internally reset the CoD, so we must set it here.
  725. esp_bt_cod_t cod;
  726. memset(&cod, 0, sizeof(cod));
  727. cod.major = 4; // Audio/Video
  728. cod.minor = 5; // Loudspeaker
  729. cod.service = (1 << 8) | (1 << 5); // Audio (bit 21) + Rendering (bit 18)
  730. esp_bt_gap_set_cod(cod, ESP_BT_SET_COD_MAJOR_MINOR);
  731. esp_bt_gap_set_cod(cod, ESP_BT_SET_COD_SERVICE_CLASS);
  732. {
  733. uint8_t saved_vol;
  734. if (settings_get_bt_volume(&saved_vol) == ESP_OK) {
  735. s_avrc_volume = saved_vol;
  736. }
  737. ESP_LOGI(TAG, "BT volume restored from settings: %d/127", s_avrc_volume);
  738. }
  739. ESP_LOGI(TAG, "BT A2DP sink ready, waiting for connections");
  740. }
  741. /* ========================================================================== */
  742. /* Public API */
  743. /* ========================================================================== */
  744. esp_err_t bt_a2dp_sink_init(const char *device_name,
  745. bt_a2dp_state_cb_t state_cb) {
  746. s_state_cb = state_cb;
  747. ESP_LOGI(TAG, "Initializing Bluetooth A2DP Sink: %s", device_name);
  748. // Release BLE memory — we only need Classic BT
  749. ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
  750. // Initialize BT controller
  751. esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
  752. esp_err_t err = esp_bt_controller_init(&bt_cfg);
  753. if (err != ESP_OK) {
  754. ESP_LOGE(TAG, "BT controller init failed: %s", esp_err_to_name(err));
  755. return err;
  756. }
  757. err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);
  758. if (err != ESP_OK) {
  759. ESP_LOGE(TAG, "BT controller enable failed: %s", esp_err_to_name(err));
  760. return err;
  761. }
  762. // Initialize Bluedroid
  763. esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
  764. #ifndef CONFIG_BT_SSP_ENABLED
  765. bluedroid_cfg.ssp_en = false; // Disable SSP to force legacy PIN pairing
  766. #endif
  767. err = esp_bluedroid_init_with_cfg(&bluedroid_cfg);
  768. if (err != ESP_OK) {
  769. ESP_LOGE(TAG, "Bluedroid init failed: %s", esp_err_to_name(err));
  770. return err;
  771. }
  772. err = esp_bluedroid_enable();
  773. if (err != ESP_OK) {
  774. ESP_LOGE(TAG, "Bluedroid enable failed: %s", esp_err_to_name(err));
  775. return err;
  776. }
  777. // Set device name
  778. esp_bt_gap_set_device_name(device_name);
  779. // Create the BT app task and event queue
  780. s_bt_task_queue = xQueueCreate(BT_TASK_QLEN, sizeof(bt_app_msg_t));
  781. if (s_bt_task_queue == NULL) {
  782. ESP_LOGE(TAG, "Failed to create BT task queue");
  783. return ESP_ERR_NO_MEM;
  784. }
  785. BaseType_t ret =
  786. task_create_spiram(bt_app_task, "bt_app", BT_TASK_STACK, NULL,
  787. BT_TASK_PRIO, &s_bt_task_handle, NULL);
  788. if (ret != pdPASS) {
  789. ESP_LOGE(TAG, "Failed to create BT app task");
  790. return ESP_ERR_NO_MEM;
  791. }
  792. // Dispatch stack-up event to app task
  793. bt_app_work_dispatch(bt_stack_evt_handler, BT_APP_EVT_STACK_UP, NULL, 0);
  794. return ESP_OK;
  795. }
  796. bool bt_a2dp_sink_is_connected(void) {
  797. return s_connected;
  798. }
  799. void bt_a2dp_sink_disconnect_current(void) {
  800. esp_bd_addr_t bda;
  801. bool has_bda = false;
  802. if (s_connected) {
  803. memcpy(bda, s_current_bda, sizeof(esp_bd_addr_t));
  804. has_bda = true;
  805. } else {
  806. if (settings_get_last_bt_bda(bda) == ESP_OK) {
  807. has_bda = true;
  808. }
  809. }
  810. if (has_bda) {
  811. ESP_LOGI(TAG, "Forcing disconnect of BT device: %02X:%02X:%02X:%02X:%02X:%02X",
  812. bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]);
  813. // 强制断开 A2DP (音频流)
  814. esp_a2d_sink_disconnect(bda);
  815. // 强制断开 HFP (免提/电量报告通道,手机常常因为这个不断开而显示“已连接”)
  816. esp_hf_client_disconnect(bda);
  817. } else {
  818. ESP_LOGW(TAG, "No BT device known to disconnect");
  819. }
  820. }
  821. void bt_a2dp_sink_set_discoverable(bool discoverable) {
  822. s_bt_discoverable = discoverable;
  823. if (s_connected) {
  824. return;
  825. }
  826. if (discoverable) {
  827. ESP_LOGI(TAG, "BT discoverable enabled");
  828. esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
  829. } else {
  830. ESP_LOGI(TAG, "BT discoverable disabled");
  831. esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
  832. }
  833. }
  834. // ============================================================================
  835. // AVRCP passthrough commands for hardware button control
  836. // ============================================================================
  837. static void send_passthrough(uint8_t key_code) {
  838. if (!s_connected) {
  839. return;
  840. }
  841. esp_avrc_ct_send_passthrough_cmd(0, key_code, ESP_AVRC_PT_CMD_STATE_PRESSED);
  842. vTaskDelay(pdMS_TO_TICKS(50));
  843. esp_avrc_ct_send_passthrough_cmd(0, key_code, ESP_AVRC_PT_CMD_STATE_RELEASED);
  844. }
  845. void bt_a2dp_send_playpause(void) {
  846. if (s_avrc_playing) {
  847. send_passthrough(ESP_AVRC_PT_CMD_PAUSE);
  848. ESP_LOGI(TAG, "AVRCP: pause");
  849. } else {
  850. send_passthrough(ESP_AVRC_PT_CMD_PLAY);
  851. ESP_LOGI(TAG, "AVRCP: play");
  852. }
  853. }
  854. void bt_a2dp_send_next(void) {
  855. send_passthrough(ESP_AVRC_PT_CMD_FORWARD);
  856. ESP_LOGI(TAG, "AVRCP: next");
  857. }
  858. void bt_a2dp_send_prev(void) {
  859. send_passthrough(ESP_AVRC_PT_CMD_BACKWARD);
  860. ESP_LOGI(TAG, "AVRCP: prev");
  861. }
  862. /**
  863. * Notify the source (phone) that local volume changed via AVRCP TG.
  864. * Only sends if the phone previously registered for the notification.
  865. */
  866. static void notify_volume_changed(void) {
  867. if (s_vol_ntf_pending) {
  868. esp_avrc_rn_param_t rn_param;
  869. rn_param.volume = s_avrc_volume;
  870. esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_CHANGED,
  871. &rn_param);
  872. s_vol_ntf_pending = false;
  873. }
  874. }
  875. void bt_a2dp_send_volume_up(void) {
  876. // Adjust local AVRCP volume by ~10 units (≈3 dB equivalent)
  877. uint8_t new_vol = s_avrc_volume;
  878. if (new_vol <= 117) {
  879. new_vol += 10;
  880. } else {
  881. new_vol = 127;
  882. }
  883. s_avrc_volume = new_vol;
  884. s_last_local_vol_time = esp_timer_get_time();
  885. float volume_db = ((float)new_vol / 127.0f) * 30.0f - 30.0f;
  886. dac_set_volume(volume_db);
  887. notify_volume_changed();
  888. ESP_LOGI(TAG, "AVRCP: volume up -> %d/127", new_vol);
  889. }
  890. void bt_a2dp_send_volume_down(void) {
  891. uint8_t new_vol = s_avrc_volume;
  892. if (new_vol >= 10) {
  893. new_vol -= 10;
  894. } else {
  895. new_vol = 0;
  896. }
  897. s_avrc_volume = new_vol;
  898. s_last_local_vol_time = esp_timer_get_time();
  899. float volume_db = ((float)new_vol / 127.0f) * 30.0f - 30.0f;
  900. dac_set_volume(volume_db);
  901. notify_volume_changed();
  902. ESP_LOGI(TAG, "AVRCP: volume down -> %d/127", new_vol);
  903. }
  904. void bt_a2dp_set_volume(uint8_t volume) {
  905. if (volume > 127) {
  906. volume = 127;
  907. }
  908. if (s_avrc_volume != volume) {
  909. s_avrc_volume = volume;
  910. s_last_local_vol_time = esp_timer_get_time();
  911. float volume_db = ((float)volume / 127.0f) * 30.0f - 30.0f;
  912. dac_set_volume(volume_db);
  913. notify_volume_changed();
  914. }
  915. }
  916. void bt_a2dp_send_battery_level(uint8_t percent) {
  917. if (!s_hfp_connected) {
  918. return;
  919. }
  920. if (percent > 100) percent = 100;
  921. // Convert 0-100 to 0-9
  922. uint8_t bat_level = (percent + 5) / 10;
  923. if (bat_level > 9) bat_level = 9;
  924. ESP_LOGI(TAG, "Report battery to iOS: %d%% -> level %d", percent, bat_level);
  925. esp_hf_client_send_iphoneaccev(bat_level, false);
  926. }