wifi_audio_tx

Example of the component embedblocks/i2s_mic v0.2.1
# i2s_mic Wi-Fi audio sender

Captures audio with `i2s_mic` and streams it over Wi-Fi, as UDP datagrams
sent with the `udp_link` component, to one statically configured IPv4 address.

This is the sending half of a Wi-Fi walkie-talkie. The receiver is either the
`i2s_spk` Wi-Fi example (plays the audio on a speaker) or
`tools/udp_audio_rx.py` on a PC (plays or records it). A later version makes
both boards send and receive.

```
INMP441 ──I2S──▶ ESP32 ──Wi-Fi / UDP (udp_link)──▶ receiver board or PC
```

---

## How it works

Every 30 ms:

1. `i2s_mic_read()` returns the next DMA buffer: 480 stereo frames of 32-bit
   samples.
2. One slot of each frame is kept and shifted to 16-bit: 480 mono samples,
   960 bytes.
3. `udp_link_send()` sends those 960 bytes as one UDP datagram (about 33 per
   second, 256 kbit/s of audio). udp_link adds a header with a sequence number,
   so the receiver can detect lost or reordered packets.

The audio task never waits for the network. If `udp_link_send()` reports no
buffer or no route, for example during a Wi-Fi reconnect, that frame is
dropped and counted, and capture continues. Late audio is useless for live
speech, so frames are never resent.

### Stream format

`main/audio_stream_format.h` is the contract between sender and receiver:

| Property | Value |
|---|---|
| Sample rate | 16 kHz |
| Samples | signed 16-bit, little-endian, mono |
| Frame | 480 samples = 30 ms = 960 bytes, one frame per datagram |
| Payload header | none (udp_link's own header carries sequence and session) |
| udp_link stream_id | `0xA1` (identifies this format) |
| UDP port | 5004 by default, same on both sides |

The receiver uses an identical copy of this header. If the format ever
changes, change `AUDIO_STREAM_ID` too, so mismatched builds reject each
other's packets instead of playing noise.

---

## Dependencies

| Component | Where it comes from |
|---|---|
| `i2s_mic` | this repository (`EXTRA_COMPONENT_DIRS ../../`) |
| `udp_link` | local path in `main/idf_component.yml`; set it to your checkout |
| `protocol_examples_common` | ESP-IDF's `examples/common_components` (`example_connect()`) |

**Set the udp_link path before building.** Open `main/idf_component.yml` and
point `udp_link: path:` at your udp_link component folder (relative to that file).
Once udp_link is published, replace the entry with the registry version, e.g.
`embedblocks/udp_link: "^0.1.0"`.

---

## Wiring

| INMP441 | GPIO (Kconfig) | ESP32 | ESP32-C3 | ESP32-S3 |
|---|---|---|---|---|
| SCK | `AUDIO_TX_MIC_GPIO_BCK` | 16 | 7 | 4 |
| WS  | `AUDIO_TX_MIC_GPIO_WS` | 17 | 8 | 5 |
| SD  | `AUDIO_TX_MIC_GPIO_DATA` | 18 | 9 | 6 |
| L/R | GND | | | |
| VDD | 3.3V | | | |
| GND | GND | | | |

On ESP32 WROVER modules, GPIO 16/17 are used by PSRAM; pick other pins there.

---

## Configure

```bash
idf.py set-target esp32          # or esp32c3, esp32s3
idf.py menuconfig
```

- **Example Connection Configuration** → WiFi SSID and WiFi Password.
- **i2s_mic Wi-Fi audio sender** → Receiver IPv4 address (the speaker board
  or your PC), UDP port (default 5004, must match the receiver), mic pins.

Wi-Fi connection type is preset to Wi-Fi only, IPv4 only
(`sdkconfig.defaults`).

---

## Build, flash, check

```bash
idf.py build flash monitor
```

```
I (...) example_connect: Connected to example_netif_sta
I (...) audio_tx: sending to 192.168.1.100:5004, stream 0xA1, 960-byte frames (30 ms of 16000 Hz 16-bit mono)
I (...) audio_tx: sent 66 frames (33/s, 253 kbit/s) | dropped 0 | send errors 0 | mic lost 0
```

On a healthy link: about **33 frames/s**, and **dropped**, **send errors**
and **mic lost** all stay at **0**.

| Counter | Rising means |
|---|---|
| dropped | udp_link had no buffer or no route; the network is momentarily unavailable |
| send errors | an unexpected udp_link error, logged with its name |
| mic lost | the audio task fell more than ~150 ms behind; capture data was lost before sending |

---

## Test without a receiver board

`tools/udp_audio_rx.py` receives the stream on a PC:

```bash
python tools/udp_audio_rx.py --wav out.wav            # record
pip install sounddevice
python tools/udp_audio_rx.py --play                   # listen live
```

Set the board's receiver address to the PC's IP and allow UDP port 5004
through the PC firewall. The script removes udp_link's 12-byte header and skips
any datagram that isn't exactly one frame. It assumes the header comes
before the payload and does not check the header fields.

---

## Not in this version

- One direction only; the full-duplex walkie-talkie comes later.
- No jitter buffer or packet-loss concealment: that belongs on the receiving
  side.
- No push-to-talk: audio streams continuously.
- Static peer address; no discovery.

To create a project from this example, run:

idf.py create-project-from-example "embedblocks/i2s_mic=0.2.1:wifi_audio_tx"

or download archive (~10.74 KB)