main.c 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. /*
  2. * main.c — application entry point for the ESP32-P4 Dante/AES67 endpoint.
  3. *
  4. * Phase 1 target is AES67 interop. This file is adapted from the example
  5. * application in DatanoiseTV/aes67-esp32p4 (Apache-2.0) — the AES67 logic is
  6. * kept as-is; the hardware pin map is sourced from board_config.h so there is
  7. * a single place to retarget the board (see CLAUDE.md / README.md).
  8. *
  9. * SPDX-FileCopyrightText: 2026 DatanoiseTV (original example)
  10. * SPDX-FileCopyrightText: 2026 Benjamin Harris (adaptation)
  11. * SPDX-License-Identifier: Apache-2.0
  12. *
  13. * Flow: init Ethernet (IP101 RMII PHY), wait for IP, bring up the AES67 stack,
  14. * auto-subscribe to the first SAP-discovered source and create a matching
  15. * source stream, capturing/streaming L24 audio via the ES8311 codec.
  16. */
  17. #include <string.h>
  18. #include "freertos/FreeRTOS.h"
  19. #include "freertos/task.h"
  20. #include "freertos/event_groups.h"
  21. #include "esp_log.h"
  22. #include "esp_err.h"
  23. #include "esp_event.h"
  24. #include "esp_netif.h"
  25. #include "nvs_flash.h"
  26. #include "driver/gpio.h"
  27. #include "esp_eth.h"
  28. #include "esp_eth_mac.h"
  29. #include "esp_eth_phy.h"
  30. #include "aes67.h"
  31. #include "aes67_config.h"
  32. #include "aes67_convert.h"
  33. #include "aes67_session.h"
  34. #include "aes67_rtp.h"
  35. #include "aes67_sap.h"
  36. #include "aes67_audio.h"
  37. #include "aes67_webui.h"
  38. #include "dante_discovery.h"
  39. #include "dante_advertise.h"
  40. #include "dante_cmc.h"
  41. #include "esp_mac.h"
  42. #include "dsps_tone_gen.h"
  43. #include "driver/i2c.h"
  44. #include "freertos/stream_buffer.h"
  45. #include "esp_cache.h"
  46. #include "esp_ldo_regulator.h"
  47. #include "driver/i2s_std.h"
  48. #include "es8311.h"
  49. #include "board_config.h"
  50. /* ---- Map the board pin table (board_config.h) onto the names the AES67
  51. * example code below expects. Edit board_config.h, not these. ---- */
  52. #define ETH_MDC_GPIO BOARD_ETH_MDC_GPIO
  53. #define ETH_MDIO_GPIO BOARD_ETH_MDIO_GPIO
  54. #define ETH_REF_CLK_GPIO BOARD_ETH_REF_CLK_GPIO
  55. #define ETH_PHY_RST_GPIO BOARD_ETH_PHY_RST_GPIO
  56. #define ETH_PHY_ADDR BOARD_ETH_PHY_ADDR
  57. #define I2S_MCLK_GPIO BOARD_I2S_MCLK_GPIO
  58. #define I2S_SCLK_GPIO BOARD_I2S_BCLK_GPIO
  59. #define I2S_LRCK_GPIO BOARD_I2S_LRCLK_GPIO
  60. #define I2S_DOUT_GPIO BOARD_I2S_DOUT_GPIO
  61. #define I2S_DIN_GPIO BOARD_I2S_DIN_GPIO
  62. #define PA_CTRL_GPIO BOARD_PA_ENABLE_GPIO
  63. #define I2C_SCL_GPIO BOARD_I2C_SCL_GPIO
  64. #define I2C_SDA_GPIO BOARD_I2C_SDA_GPIO
  65. #define I2C_PORT I2C_NUM_0
  66. #define I2C_CLK_HZ 100000
  67. #define ES8311_ADDR ES8311_ADDRESS_0 /* 0x18, CE pin low */
  68. /* Session handle for SAP callback */
  69. static aes67_session_handle_t s_session = NULL;
  70. static bool s_sink_added = false;
  71. /* Stream params for the Ethernet RTP hook (defined below, used by SAP callback) */
  72. extern uint8_t s_hook_word_length;
  73. extern uint8_t s_hook_channels;
  74. /* SAP discovery callback: auto-subscribe to discovered remote sources */
  75. static void sap_discovery_cb(bool is_announce,
  76. const aes67_sap_remote_source_t *source,
  77. void *user_data)
  78. {
  79. if (!is_announce || !source || s_sink_added || !s_session) {
  80. return;
  81. }
  82. /* Subscribe to any discovered source (first one wins) */
  83. ESP_LOGI("main", "Auto-subscribing to \"%s\" from SAP", source->name);
  84. uint8_t sink_id = 0;
  85. esp_err_t err = aes67_session_add_sink(s_session, source->name,
  86. source->sdp, &sink_id);
  87. if (err == ESP_OK) {
  88. s_sink_added = true;
  89. /* Update Ethernet hook params from the SDP */
  90. aes67_sink_t sink;
  91. if (aes67_session_get_sink(s_session, sink_id, &sink) == ESP_OK) {
  92. s_hook_word_length = sink.rtp_config.word_length;
  93. s_hook_channels = sink.rtp_config.channels;
  94. ESP_LOGI("main", "Hook: wl=%u ch=%u rate=%lu ptime=%u",
  95. s_hook_word_length, s_hook_channels,
  96. (unsigned long)sink.rtp_config.sample_rate,
  97. sink.rtp_config.packet_time_us);
  98. /* Create a matching source with same codec/channels/ptime */
  99. aes67_session_set_packet_time(s_session, sink.rtp_config.packet_time_us);
  100. uint8_t src_id = 0;
  101. esp_err_t src_err = aes67_session_add_source(
  102. s_session, "ESP32-P4 AES67",
  103. sink.rtp_config.channels, sink.rtp_config.codec, &src_id);
  104. if (src_err == ESP_OK) {
  105. ESP_LOGI("main", "Source matched to sink: %uch %s %uus",
  106. sink.rtp_config.channels,
  107. aes67_codec_to_str(sink.rtp_config.codec),
  108. sink.rtp_config.packet_time_us);
  109. }
  110. }
  111. ESP_LOGI("main", "Sink added (id=%u) -- receiving \"%s\"",
  112. sink_id, source->name);
  113. } else {
  114. ESP_LOGW("main", "Failed to add sink for \"%s\": %s",
  115. source->name, esp_err_to_name(err));
  116. }
  117. }
  118. static const char *TAG = "main";
  119. /* Event group bit set when the Ethernet interface obtains an IP */
  120. #define ETH_GOT_IP_BIT BIT0
  121. static EventGroupHandle_t s_eth_event_group;
  122. /* Ethernet link event handler */
  123. static void eth_event_handler(void *arg, esp_event_base_t event_base,
  124. int32_t event_id, void *event_data)
  125. {
  126. switch (event_id) {
  127. case ETHERNET_EVENT_CONNECTED:
  128. ESP_LOGI(TAG, "ethernet link up");
  129. break;
  130. case ETHERNET_EVENT_DISCONNECTED:
  131. ESP_LOGW(TAG, "ethernet link down");
  132. break;
  133. case ETHERNET_EVENT_START:
  134. ESP_LOGI(TAG, "ethernet started");
  135. break;
  136. case ETHERNET_EVENT_STOP:
  137. ESP_LOGI(TAG, "ethernet stopped");
  138. break;
  139. default:
  140. break;
  141. }
  142. }
  143. /* IP acquisition event handler */
  144. static void ip_event_handler(void *arg, esp_event_base_t event_base,
  145. int32_t event_id, void *event_data)
  146. {
  147. if (event_id == IP_EVENT_ETH_GOT_IP) {
  148. ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
  149. ESP_LOGI(TAG, "got IP: " IPSTR, IP2STR(&event->ip_info.ip));
  150. xEventGroupSetBits(s_eth_event_group, ETH_GOT_IP_BIT);
  151. }
  152. }
  153. /* --- Ethernet RTP hook: intercept RTP frames before lwIP --- */
  154. static esp_err_t (*s_original_input)(esp_eth_handle_t, uint8_t*, uint32_t, void*, void*) = NULL;
  155. static void *s_original_priv = NULL;
  156. static i2s_chan_handle_t s_hook_tx_chan = NULL;
  157. static int32_t *s_hook_tmp32 = NULL;
  158. static int16_t *s_hook_i16 = NULL;
  159. uint32_t s_hook_pkt_count = 0; /* Visible to webui for stats */
  160. uint32_t s_hook_drop_count = 0;
  161. uint32_t s_hook_fwd_count = 0;
  162. uint32_t s_hook_total_frames = 0;
  163. static uint16_t s_hook_last_seq = 0;
  164. static uint32_t s_hook_seq_gaps = 0;
  165. uint32_t s_hook_seq_lost = 0;
  166. StreamBufferHandle_t s_hook_sbuf = NULL; /* Global: shared with playback task */
  167. uint8_t s_hook_word_length = 3; /* Default L24. Updated from SDP. */
  168. uint8_t s_hook_channels = 2; /* Default stereo. Updated from SDP. */
  169. /* Called for EVERY Ethernet frame, before lwIP.
  170. * RTP multicast on port 5004: process + free. Everything else: forward to lwIP. */
  171. static esp_err_t IRAM_ATTR eth_rtp_hook(esp_eth_handle_t eth_handle,
  172. uint8_t *buffer, uint32_t length,
  173. void *priv, void *info)
  174. {
  175. s_hook_total_frames++;
  176. /* Quick reject: minimum Ethernet+IP+UDP+RTP header = 54 bytes */
  177. if (length < 54 || !s_hook_tx_chan) goto forward;
  178. /* EtherType must be IPv4 */
  179. if (buffer[12] != 0x08 || buffer[13] != 0x00) goto forward;
  180. /* IP protocol must be UDP (17) */
  181. uint8_t ihl = (buffer[14] & 0x0F) * 4;
  182. if (buffer[14 + 9] != 17) goto forward;
  183. /* Must be IPv4 multicast destination (224.0.0.0/4) */
  184. if ((buffer[14 + 16] & 0xF0) != 0xE0) goto forward;
  185. /* UDP destination port must be 5004 */
  186. int udp_off = 14 + ihl;
  187. uint16_t dst_port = (buffer[udp_off + 2] << 8) | buffer[udp_off + 3];
  188. if (dst_port != 5004) goto forward;
  189. /* RTP version must be 2 */
  190. int rtp_off = udp_off + 8;
  191. if ((buffer[rtp_off] >> 6) != 2) goto forward;
  192. /* Track RTP sequence numbers to detect packet loss */
  193. {
  194. uint16_t seq = (buffer[rtp_off + 2] << 8) | buffer[rtp_off + 3];
  195. if (s_hook_pkt_count > 0) {
  196. uint16_t expected = s_hook_last_seq + 1;
  197. if (seq != expected) {
  198. int16_t gap = (int16_t)(seq - expected);
  199. if (gap > 0 && gap < 100) {
  200. s_hook_seq_gaps++;
  201. s_hook_seq_lost += gap;
  202. }
  203. }
  204. }
  205. s_hook_last_seq = seq;
  206. }
  207. /* Extract RTP payload */
  208. uint8_t cc = buffer[rtp_off] & 0x0F;
  209. int payload_off = rtp_off + 12 + cc * 4;
  210. /* Handle RTP header extension */
  211. if (buffer[rtp_off] & 0x10) {
  212. if ((int)length < payload_off + 4) goto forward;
  213. uint16_t ext_len = (buffer[payload_off + 2] << 8) | buffer[payload_off + 3];
  214. payload_off += 4 + ext_len * 4;
  215. }
  216. if (payload_off >= (int)length) goto forward;
  217. const uint8_t *payload = buffer + payload_off;
  218. int payload_len = length - payload_off;
  219. /* ESP-IDF Ethernet driver already does M2C cache sync in
  220. * emac_esp_dma_receive_frame(). No additional sync needed. */
  221. /* Handle RTP padding (P bit) */
  222. if (buffer[rtp_off] & 0x20) {
  223. uint8_t pad_len = buffer[length - 1];
  224. payload_len -= pad_len;
  225. if (payload_len <= 0) goto forward;
  226. }
  227. /* Use UDP length field instead of Ethernet frame length to avoid
  228. * including Ethernet FCS or padding bytes in the audio data.
  229. * UDP length = header(8) + payload. */
  230. uint16_t udp_len = (buffer[udp_off + 4] << 8) | buffer[udp_off + 5];
  231. int rtp_total = udp_len - 8; /* UDP payload = RTP header + RTP payload */
  232. int rtp_payload = rtp_total - (payload_off - rtp_off);
  233. if (rtp_payload > 0 && rtp_payload < payload_len) {
  234. payload_len = rtp_payload; /* Trim to actual RTP payload */
  235. }
  236. /* Use word length and channel count from SDP (set when sink is added) */
  237. int wl = s_hook_word_length;
  238. int ch = s_hook_channels;
  239. int frame_bytes = ch * wl;
  240. int frames = payload_len / frame_bytes;
  241. if (frames <= 0 || frames > AES67_MAX_TIC_FRAMES) goto forward;
  242. /* Log first packet details */
  243. if (s_hook_pkt_count == 0) {
  244. ESP_DRAM_LOGI("eth_hook", "FIRST PKT: payload=%d bytes, wl=%d, ch=%d, frames=%d, "
  245. "write_bytes=%d", payload_len, wl, ch, frames,
  246. frames * ch * (int)sizeof(int32_t));
  247. }
  248. /* Convert to int32 -- stereo passthrough, no mono mix */
  249. aes67_convert_from_net(payload, s_hook_tmp32, frames, ch, wl);
  250. /* Write int32 stereo directly to stream buffer (read by DMA ISR) */
  251. extern void aes67_audio_slot_write(const void *data, uint32_t len);
  252. aes67_audio_slot_write(s_hook_tmp32, frames * ch * sizeof(int32_t));
  253. s_hook_pkt_count++;
  254. /* Rate-limited loss reporting: max once per second */
  255. static uint32_t s_last_seq_lost = 0;
  256. static uint32_t s_last_loss_log_pkt = 0;
  257. if (s_hook_seq_lost != s_last_seq_lost &&
  258. (s_hook_pkt_count - s_last_loss_log_pkt) > 8000) {
  259. ESP_LOGW("eth_hook", "SEQ LOSS: %lu gaps, %lu lost (total %lu pkts)",
  260. (unsigned long)s_hook_seq_gaps,
  261. (unsigned long)s_hook_seq_lost,
  262. (unsigned long)s_hook_pkt_count);
  263. s_last_seq_lost = s_hook_seq_lost;
  264. s_last_loss_log_pkt = s_hook_pkt_count;
  265. }
  266. free(buffer); /* We consumed the frame */
  267. return ESP_OK;
  268. forward:
  269. s_hook_fwd_count++;
  270. /* Not RTP -- forward to lwIP via esp_netif_receive */
  271. if (s_original_priv) {
  272. return esp_netif_receive((esp_netif_t *)s_original_priv, buffer, length, NULL);
  273. }
  274. free(buffer);
  275. return ESP_OK;
  276. }
  277. /*
  278. * Initialize the IP101 Ethernet PHY over RMII.
  279. * The ESP32-P4-NANO uses an external 50MHz crystal for the RMII reference
  280. * clock, so we configure EMAC_CLK_EXT_IN on GPIO50.
  281. */
  282. static esp_err_t ethernet_init(esp_eth_handle_t *out_eth_handle)
  283. {
  284. /* EMAC configuration with RMII pin mapping */
  285. eth_esp32_emac_config_t emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
  286. emac_config.smi_gpio.mdc_num = ETH_MDC_GPIO;
  287. emac_config.smi_gpio.mdio_num = ETH_MDIO_GPIO;
  288. emac_config.interface = EMAC_DATA_INTERFACE_RMII;
  289. emac_config.clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN;
  290. emac_config.clock_config.rmii.clock_gpio = ETH_REF_CLK_GPIO;
  291. eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
  292. mac_config.flags = ETH_MAC_FLAG_PIN_TO_CORE; /* Pin to core 0 */
  293. mac_config.rx_task_prio = 24; /* Highest: above lwIP (23), feeds audio ISR directly */
  294. esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&emac_config, &mac_config);
  295. if (!mac) {
  296. ESP_LOGE(TAG, "failed to create EMAC");
  297. return ESP_FAIL;
  298. }
  299. /* IP101 PHY at address 1, with hardware reset on GPIO51 */
  300. eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
  301. phy_config.phy_addr = ETH_PHY_ADDR;
  302. phy_config.reset_gpio_num = ETH_PHY_RST_GPIO;
  303. esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
  304. if (!phy) {
  305. ESP_LOGE(TAG, "failed to create IP101 PHY");
  306. return ESP_FAIL;
  307. }
  308. /* Install the Ethernet driver */
  309. esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
  310. esp_eth_handle_t eth_handle = NULL;
  311. esp_err_t ret = esp_eth_driver_install(&eth_config, &eth_handle);
  312. if (ret != ESP_OK) {
  313. ESP_LOGE(TAG, "ethernet driver install failed: %s", esp_err_to_name(ret));
  314. return ret;
  315. }
  316. /* Create the netif with key "ETH_0" so the L2 TAP interface used by
  317. * ptpd can find it. The default ESP_NETIF_DEFAULT_ETH uses "ETH_DEF"
  318. * which does not match what ptpd_start("ETH_0") expects. */
  319. esp_netif_inherent_config_t base_cfg = ESP_NETIF_INHERENT_DEFAULT_ETH();
  320. base_cfg.if_key = "ETH_0";
  321. esp_netif_config_t netif_config = {
  322. .base = &base_cfg,
  323. .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH,
  324. };
  325. esp_netif_t *eth_netif = esp_netif_new(&netif_config);
  326. if (!eth_netif) {
  327. ESP_LOGE(TAG, "failed to create ethernet netif");
  328. return ESP_FAIL;
  329. }
  330. /* Attach the driver to the netif via the default glue layer */
  331. esp_eth_netif_glue_handle_t glue = esp_eth_new_netif_glue(eth_handle);
  332. ret = esp_netif_attach(eth_netif, glue);
  333. if (ret != ESP_OK) {
  334. ESP_LOGE(TAG, "failed to attach ethernet to netif: %s", esp_err_to_name(ret));
  335. return ret;
  336. }
  337. /* Register event handlers for link and IP events */
  338. ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID,
  339. &eth_event_handler, NULL));
  340. ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP,
  341. &ip_event_handler, NULL));
  342. /* Ethernet RTP hook: intercept RTP at MAC level before lwIP */
  343. s_original_priv = eth_netif;
  344. /* 192 frames * max channels covers the AES67 practical max (4ms at 48kHz) */
  345. s_hook_tmp32 = heap_caps_malloc(
  346. 192 * AES67_MAX_CHANNELS_PER_STREAM * sizeof(int32_t),
  347. MALLOC_CAP_INTERNAL);
  348. s_hook_i16 = heap_caps_malloc(
  349. 192 * AES67_MAX_CHANNELS_PER_STREAM * sizeof(int16_t),
  350. MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
  351. esp_eth_update_input_path_info(eth_handle, eth_rtp_hook, eth_netif);
  352. ESP_LOGI(TAG, "Ethernet RTP hook installed");
  353. /* Enable Ethernet flow control to prevent frame drops under load.
  354. * Without this, the EMAC drops ~8% of multicast frames at high
  355. * packet rates (1000 pps) because the DMA can't keep up. */
  356. bool flow_ctrl = true;
  357. esp_eth_ioctl(eth_handle, ETH_CMD_S_FLOW_CTRL, &flow_ctrl);
  358. /* Start Ethernet */
  359. ret = esp_eth_start(eth_handle);
  360. if (ret != ESP_OK) {
  361. ESP_LOGE(TAG, "ethernet start failed: %s", esp_err_to_name(ret));
  362. return ret;
  363. }
  364. *out_eth_handle = eth_handle;
  365. return ESP_OK;
  366. }
  367. /* Enable the power amplifier on the ES8311 output stage */
  368. static void pa_ctrl_enable(void)
  369. {
  370. gpio_config_t io_conf = {
  371. .pin_bit_mask = (1ULL << PA_CTRL_GPIO),
  372. .mode = GPIO_MODE_OUTPUT,
  373. .pull_up_en = GPIO_PULLUP_DISABLE,
  374. .pull_down_en = GPIO_PULLDOWN_DISABLE,
  375. .intr_type = GPIO_INTR_DISABLE,
  376. };
  377. gpio_config(&io_conf);
  378. gpio_set_level(PA_CTRL_GPIO, 1);
  379. ESP_LOGI(TAG, "PA enabled (GPIO%d high)", PA_CTRL_GPIO);
  380. }
  381. /*
  382. * Initialize the ES8311 audio codec via I2C.
  383. * Configures it for 48kHz 24-bit I2S operation with both DAC (playback)
  384. * and ADC (capture) enabled.
  385. */
  386. static esp_err_t es8311_codec_init(void)
  387. {
  388. /* Initialize I2C master bus */
  389. i2c_config_t i2c_cfg = {
  390. .mode = I2C_MODE_MASTER,
  391. .sda_io_num = I2C_SDA_GPIO,
  392. .scl_io_num = I2C_SCL_GPIO,
  393. .sda_pullup_en = GPIO_PULLUP_ENABLE,
  394. .scl_pullup_en = GPIO_PULLUP_ENABLE,
  395. .master.clk_speed = I2C_CLK_HZ,
  396. };
  397. esp_err_t ret = i2c_param_config(I2C_PORT, &i2c_cfg);
  398. if (ret != ESP_OK) {
  399. ESP_LOGE(TAG, "I2C param config failed: %s", esp_err_to_name(ret));
  400. return ret;
  401. }
  402. ret = i2c_driver_install(I2C_PORT, I2C_MODE_MASTER, 0, 0, 0);
  403. if (ret != ESP_OK) {
  404. ESP_LOGE(TAG, "I2C driver install failed: %s", esp_err_to_name(ret));
  405. return ret;
  406. }
  407. /* Create ES8311 codec handle */
  408. es8311_handle_t codec = es8311_create(I2C_PORT, ES8311_ADDR);
  409. if (!codec) {
  410. ESP_LOGE(TAG, "ES8311 create failed");
  411. return ESP_FAIL;
  412. }
  413. /* Configure clock: MCLK from MCLK pin, 48kHz sample rate.
  414. * I2S uses MCLK_MULTIPLE_384 (matching official ESP-IDF example).
  415. * MCLK = 48000 * 384 = 18.432 MHz */
  416. es8311_clock_config_t clk_cfg = {
  417. .mclk_inverted = false,
  418. .sclk_inverted = false,
  419. .mclk_from_mclk_pin = true,
  420. .mclk_frequency = 48000 * 384, /* 18.432 MHz */
  421. .sample_frequency = 48000,
  422. };
  423. /* 32-bit resolution -- int32 maps directly, no truncation */
  424. ret = es8311_init(codec, &clk_cfg, ES8311_RESOLUTION_32, ES8311_RESOLUTION_32);
  425. if (ret != ESP_OK) {
  426. ESP_LOGE(TAG, "ES8311 init failed: %s", esp_err_to_name(ret));
  427. return ret;
  428. }
  429. ret = es8311_voice_volume_set(codec, 70, NULL);
  430. if (ret != ESP_OK) {
  431. ESP_LOGW(TAG, "ES8311 volume set failed: %s", esp_err_to_name(ret));
  432. }
  433. /* Unmute DAC output */
  434. ret = es8311_voice_mute(codec, false);
  435. if (ret != ESP_OK) {
  436. ESP_LOGW(TAG, "ES8311 unmute failed: %s", esp_err_to_name(ret));
  437. }
  438. /* Configure microphone: analog mic, 24dB gain */
  439. es8311_microphone_config(codec, false);
  440. es8311_microphone_gain_set(codec, ES8311_MIC_GAIN_24DB);
  441. /* Dump ES8311 register state for debugging */
  442. es8311_register_dump(codec);
  443. ESP_LOGI(TAG, "ES8311 codec initialized (48kHz, 16-bit, MCLK=18.432MHz, vol=100)");
  444. return ESP_OK;
  445. }
  446. void app_main(void)
  447. {
  448. /* Initialize NVS -- required by some ESP-IDF components internally */
  449. esp_err_t ret = nvs_flash_init();
  450. if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
  451. ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
  452. ESP_ERROR_CHECK(nvs_flash_erase());
  453. ret = nvs_flash_init();
  454. }
  455. ESP_ERROR_CHECK(ret);
  456. /* Configure LDO4 to 3.3V for SDIO/SPI power domain.
  457. * Must be done early before any peripherals on this rail. */
  458. esp_ldo_channel_handle_t ldo4 = NULL;
  459. esp_ldo_channel_config_t ldo_cfg = {
  460. .chan_id = 4,
  461. .voltage_mv = 3300,
  462. };
  463. if (esp_ldo_acquire_channel(&ldo_cfg, &ldo4) == ESP_OK) {
  464. ESP_LOGI(TAG, "LDO4 set to 3.3V");
  465. } else {
  466. ESP_LOGW(TAG, "LDO4 config failed (may already be set)");
  467. }
  468. /* Create the default event loop used by Ethernet and AES67 events */
  469. ESP_ERROR_CHECK(esp_event_loop_create_default());
  470. /* Initialize the TCP/IP stack */
  471. ESP_ERROR_CHECK(esp_netif_init());
  472. /* Set up the event group used to synchronize on IP acquisition */
  473. s_eth_event_group = xEventGroupCreate();
  474. /* Bring up Ethernet with the IP101 PHY */
  475. esp_eth_handle_t eth_handle = NULL;
  476. ESP_ERROR_CHECK(ethernet_init(&eth_handle));
  477. /* Block until we have an IP address. The AES67 stack needs network
  478. * connectivity for PTP, RTP multicast and SAP. */
  479. ESP_LOGI(TAG, "waiting for IP address...");
  480. xEventGroupWaitBits(s_eth_event_group, ETH_GOT_IP_BIT,
  481. pdFALSE, pdTRUE, portMAX_DELAY);
  482. /* Enable the power amplifier before starting audio */
  483. pa_ctrl_enable();
  484. /* ES8311 codec init is deferred until AFTER the AES67 I2S driver starts.
  485. * The ES8311 needs MCLK to be running during initialization. */
  486. /* Build the AES67 node configuration with our I2S pins and codec settings */
  487. aes67_config_t aes67_cfg = AES67_CONFIG_DEFAULT();
  488. strlcpy(aes67_cfg.node_name, "ESP32-P4 AES67", sizeof(aes67_cfg.node_name));
  489. aes67_cfg.net.eth_handle = eth_handle;
  490. aes67_cfg.i2s_pins.mck_gpio = I2S_MCLK_GPIO;
  491. aes67_cfg.i2s_pins.bck_gpio = I2S_SCLK_GPIO;
  492. aes67_cfg.i2s_pins.ws_gpio = I2S_LRCK_GPIO;
  493. aes67_cfg.i2s_pins.dout_gpio = I2S_DOUT_GPIO;
  494. aes67_cfg.i2s_pins.din_gpio = I2S_DIN_GPIO;
  495. aes67_cfg.audio.sample_rate = 48000;
  496. aes67_cfg.audio.channels = 2;
  497. aes67_cfg.audio.codec = AES67_CODEC_L24;
  498. aes67_cfg.audio.word_length = 3; /* 24-bit = 3 bytes */
  499. aes67_cfg.audio.packet_time_us = 1000; /* 1ms packet time per AES67 */
  500. /* Initialize the AES67 node */
  501. aes67_node_handle_t node = NULL;
  502. ESP_ERROR_CHECK(aes67_node_init(&aes67_cfg, &node));
  503. /* Create stream buffer BEFORE starting the node. The DMA ISR callback
  504. * reads directly from this buffer, bypassing the playback task.
  505. * 30 packets (~120ms at 4ms ptime) for jitter absorption.
  506. * Trigger=1: ISR always reads whatever is available. */
  507. {
  508. uint32_t pkt_bytes = 192 * aes67_cfg.audio.channels * sizeof(int32_t);
  509. s_hook_sbuf = xStreamBufferCreate(pkt_bytes * 30, 1);
  510. /* Wire the stream buffer to the audio driver's DMA ISR */
  511. aes67_audio_handle_t audio = NULL;
  512. aes67_node_get_audio(node, &audio);
  513. aes67_audio_set_isr_stream_buf(audio, s_hook_sbuf);
  514. /* Also register it as the node's playout source for the (non-ISR)
  515. * playback-task fallback path. */
  516. aes67_node_set_playout_buf(node, s_hook_sbuf);
  517. /* Enable I2S RX capture for source (TX) streams */
  518. aes67_audio_init_capture(audio);
  519. s_hook_tx_chan = aes67_audio_get_tx_chan(audio);
  520. ESP_LOGI(TAG, "Ethernet hook -> stream buffer -> DMA ISR (zero-overhead)");
  521. ESP_LOGI(TAG, "I2S RX capture -> stream buffer -> RTP TX (ISR-driven)");
  522. }
  523. /* Start PTP sync, RTP engine, SAP announcements and audio I/O.
  524. * This enables I2S which starts MCLK generation. The DMA ISR
  525. * callback is registered during audio start. */
  526. ESP_ERROR_CHECK(aes67_node_start(node));
  527. /* Initialize ES8311 codec AFTER I2S is running (MCLK must be active).
  528. * This is the critical init order discovered through testing. */
  529. esp_err_t codec_ret = es8311_codec_init();
  530. if (codec_ret != ESP_OK) {
  531. ESP_LOGE(TAG, "ES8311 codec init FAILED: %s", esp_err_to_name(codec_ret));
  532. } else {
  533. ESP_LOGI(TAG, "ES8311 codec initialized (MCLK running)");
  534. }
  535. /* Obtain the session manager handle from the node so we can add streams */
  536. aes67_session_handle_t session = NULL;
  537. ESP_ERROR_CHECK(aes67_node_get_session(node, &session));
  538. s_session = session;
  539. /* Register SAP callback to auto-subscribe to discovered sources.
  540. * This will add a sink for the first remote source found (e.g. SIENNA). */
  541. aes67_sap_handle_t sap = NULL;
  542. /* Get SAP handle from the node config - it's stored internally.
  543. * For now, register via the session manager's SAP handle. */
  544. if (aes67_node_get_sap(node, &sap) == ESP_OK && sap) {
  545. aes67_sap_register_cb(sap, sap_discovery_cb, NULL);
  546. ESP_LOGI(TAG, "SAP auto-subscribe enabled");
  547. /* Check if any sources were already discovered before callback
  548. * was registered (SAP starts during node_start). */
  549. int remote_count = aes67_sap_get_remote_count(sap);
  550. for (int i = 0; i < remote_count && !s_sink_added; i++) {
  551. aes67_sap_remote_source_t remote;
  552. if (aes67_sap_get_remote(sap, i, &remote) == ESP_OK) {
  553. sap_discovery_cb(true, &remote, NULL);
  554. }
  555. }
  556. }
  557. /* Source stream is created in the SAP callback to match the
  558. * discovered sink's codec and channel count. */
  559. /* Start the WebUI (HTTP + WebSocket) on port 80 */
  560. aes67_webui_handle_t webui = NULL;
  561. if (aes67_webui_init(node, 80, &webui) == ESP_OK) {
  562. aes67_webui_start(webui);
  563. }
  564. ESP_LOGI(TAG, "node ready");
  565. #if CONFIG_DANTE_DISCOVERY_ON_BOOT
  566. /* Read-only background browse for Dante devices on the network. Logs what
  567. * it finds (devices, TX channels, multicast bundles); does not advertise
  568. * this node. Useful for AES67 bring-up — see components/dante. */
  569. dante_discovery_start(CONFIG_DANTE_DISCOVERY_INTERVAL_MS, NULL, NULL);
  570. #endif
  571. #if CONFIG_DANTE_ADVERTISE_ON_BOOT || CONFIG_DANTE_CMC_ON_BOOT
  572. /* Build a Dante identity from this board's MAC + IP, then bring up the
  573. * advertisement and/or CMC responder. PROTOTYPE — the device is only
  574. * fully usable from Dante Controller once ARC also exists. */
  575. {
  576. static dante_identity_t s_dante_id;
  577. uint8_t mac[6] = {0};
  578. esp_read_mac(mac, ESP_MAC_ETH);
  579. esp_netif_ip_info_t ip_info = {0};
  580. esp_netif_t *dante_netif = esp_netif_get_handle_from_ifkey("ETH_0");
  581. if (dante_netif) {
  582. esp_netif_get_ip_info(dante_netif, &ip_info);
  583. }
  584. uint32_t ipv4 =
  585. ((uint32_t)esp_ip4_addr1_16(&ip_info.ip) << 24) |
  586. ((uint32_t)esp_ip4_addr2_16(&ip_info.ip) << 16) |
  587. ((uint32_t)esp_ip4_addr3_16(&ip_info.ip) << 8) |
  588. ((uint32_t)esp_ip4_addr4_16(&ip_info.ip));
  589. dante_identity_init_default(&s_dante_id, ipv4, mac);
  590. #if CONFIG_DANTE_CMC_ON_BOOT
  591. dante_cmc_start(&s_dante_id); /* answer queries before we advertise */
  592. #endif
  593. #if CONFIG_DANTE_ADVERTISE_ON_BOOT
  594. dante_advertise_start(&s_dante_id);
  595. #endif
  596. }
  597. #endif
  598. /* Monitor loop: ISR health only */
  599. extern void aes67_audio_log_isr_stats(void);
  600. while (1) {
  601. vTaskDelay(pdMS_TO_TICKS(2000));
  602. aes67_audio_log_isr_stats();
  603. }
  604. }