Rust firmware and a macOS menu-bar app for using an M5StickS3 as either a standalone USB microphone or a wireless microphone.
The firmware supports two microphone paths:
- USB Audio Class microphone. Plug it in and select
m5micin your OS or app. No Wi-Fi, receiver, menu-bar app, or driver install is required. - Wireless
pcm_s16leover WebSocket. On macOS, the m5mic menu-bar app exposes this live stream as a system input device through a Rust CoreAudio driver namedm5mic. The standalone receiver CLI can optionally save raw WAV captures for debugging.
Wireless discovery is handled two ways:
- The receiver advertises
_m5mic._tcp.localvia mDNS. - The firmware falls back to UDP broadcast on port
47777.
The macOS menu-bar app is only required for wireless virtual microphone mode. It can also switch between wireless and USB mode, set the macOS default input, and send a UDP mode command to the StickS3 on port 47779; the USB menu item only appears while macOS sees the USB m5mic input.
USB mic mode does not require the Mac app: plug in the StickS3 and select m5mic as the input. The menu-bar app is for wireless virtual mic mode on macOS, plus optional status and mode switching. It shows a red outline dot while idle, a filled red dot while the StickS3 is streaming audio, receiver status, driver status, USB connection status, and mode-switch actions.
The menu-bar app does not save recordings by default. It receives live audio and feeds the virtual microphone only.
Download the latest notarized macOS release from GitHub Releases. Unzip it, drag m5mic.app to /Applications, then open it. If the CoreAudio virtual microphone driver is not installed yet, the app prompts to install it with the standard macOS administrator dialog.
The release also includes install-driver.sh for manual repair if the in-app install is skipped or interrupted.
To build from source instead:
scripts/build-statusbar-app.sh
open target/m5mic.appFor source builds, the app bundles target/m5mic.driver and uses the same in-app installer. You can still install or repair the driver manually:
scripts/install-coreaudio-driver.shAfter install, macOS apps can select wireless m5mic as an input device. Use the menu to:
- switch to wireless mode and select the virtual
m5micinput - switch to USB mode and select the USB
m5micinput, when plugged in - open Sound Settings
- quit the receiver
Uninstall the driver:
scripts/uninstall-coreaudio-driver.shThe iOS app lives in ios/. It uses SwiftUI for the interface and public iOS frameworks for platform access, with the shared m5mic protocol, Bluetooth frame reassembly, ADPCM decode, level metering, and 16 kHz to 48 kHz monitor conversion in the Rust m5mic-ios-core static library.
It supports:
- Bluetooth LE receive through the custom m5mic GATT service
- Wi-Fi receive by hosting the same
_m5mic._tcpWebSocket receiver that the firmware already discovers - live audio monitoring through AVFoundation
- device mode commands for Wi-Fi, Bluetooth, and USB
iOS does not allow third-party apps to install audio drivers or expose a system-wide microphone input. The iOS app therefore receives and monitors m5mic audio inside the app only. Its App Store posture is intentionally conservative: it uses CoreBluetooth, Network.framework, Bonjour, UDP/TCP on the local network, and AVFoundation playback, with NSBluetoothAlwaysUsageDescription, NSLocalNetworkUsageDescription, and NSBonjourServices declared in ios/M5Mic/Info.plist.
Build the Rust iOS static library directly:
PLATFORM_NAME=iphonesimulator ARCHS=arm64 ios/scripts/build-rust-ios.shBuild the app from the Xcode workspace when an iOS simulator/device runtime matching the active Xcode install is available:
xcodebuildmcp simulator build --workspace-path ios/M5Mic.xcworkspace --scheme M5Mic --simulator-name "iPhone 17 Pro"scripts/release-local.sh builds a signed release zip containing m5mic.app with the driver bundled inside it, plus m5mic.driver and install/uninstall scripts for manual repair. It uses a local Developer ID Application certificate and optionally notarizes through a local notarytool Keychain profile. No Apple credentials need to go into GitHub.
One-time notarization setup:
xcrun notarytool store-credentials "m5mic-notary" \
--apple-id "<apple-id>" \
--team-id "<team-id>" \
--password "<app-specific-password>"Then create .env.release.local, which is ignored by git:
M5MIC_NOTARY_PROFILE='m5mic-notary'The profile name can be any local notarytool Keychain profile that belongs to the signing team.
Build the release:
scripts/release-local.shIf M5MIC_NOTARY_PROFILE is unset, the script still creates a Developer ID signed archive, but it skips notarization.
The standalone receiver CLI is mainly for development and debugging. Unlike the menu-bar app, it saves each incoming stream as an uncompressed WAV file by default.
Foreground WAV capture:
mkdir -p captures
cargo run -p m5mic-receiver -- --output-dir capturesIt listens on 0.0.0.0:47776, accepts WebSocket connections at /audio, and writes each stream to captures/ as a raw WAV file.
Virtual mic mode without WAV files:
cargo run -p m5mic-receiver -- --virtual-mic --no-recordingsDetached tmux session:
tmux new-session -d -s m5mic-receiver -c "$PWD" 'mkdir -p captures && cargo run -p m5mic-receiver -- --output-dir captures'Useful commands:
tmux attach -t m5mic-receiver
tmux kill-session -t m5mic-receiver
lsof -nP -iTCP:47776In wireless mode, tap BtnA once to start a latched live stream and tap BtnA again to stop. Hold BtnA for push-to-talk; release it to stop. The menu-bar app uses that audio live only; the standalone receiver CLI creates WAV files only when recordings are enabled.
Short-tap BtnB to cycle the active mode: Wi-Fi, Bluetooth, USB, then back to Wi-Fi. The menu-bar app exposes separate mode items for Wi-Fi, Bluetooth, and USB. Hold BtnB during boot, or hold BtnB for about two seconds while idle, to start the captive setup portal. In setup mode, tap BtnB to reboot back into mic mode.
Wireless and power settings are configurable from the setup portal. Wi-Fi and Bluetooth are separate runtime modes, not firmware variants. The Wi-Fi audio codec defaults to pcm_s16le; ima_adpcm4 can be selected there to reduce Wi-Fi audio payload size while the receiver/menu-bar app decodes it back to PCM for the virtual microphone. Bluetooth uses the custom m5mic BLE GATT service and sends ima_adpcm4 audio fragments. By default, battery mode uses dim screen brightness, and recording on battery pauses the live level/buffer UI to save power. Tap BtnB during battery recording to turn the screen fully off or back on. When external power is connected, it keeps the full recording UI.
Wi-Fi setup is optional. Join the M5Mic-XXXX access point and open http://192.168.71.1 if the captive page does not appear automatically. The setup page has one save action for Wi-Fi credentials, Wi-Fi codec, and power settings. Bluetooth does not need OS pairing; switch to it with BtnB or the menu-bar app. The setup screen shows an 8-digit Bluetooth setup code, and the menu-bar app can share the Mac's current Wi-Fi network over an encrypted BLE provisioning write using that code. On macOS, the app reads the current SSID, tries non-interactive user keychain password lookup, and falls back to asking for the password and setup code without making you retype the network name. Saved Wi-Fi takes priority over the build-time WIFI_SSID / WIFI_PASS fallback.
tmux new-session -d -s m5mic-preview -c "$PWD" 'uv run python -m http.server 4177 --bind 127.0.0.1 --directory preview'Open http://127.0.0.1:4177/.
Install the ESP Rust toolchain if needed:
espup install
. ~/export-esp.shPut build-time fallback Wi-Fi credentials in .env.local at the repo root; that file is ignored by git:
WIFI_SSID='your ssid'
WIFI_PASS='your pass'Build for StickS3:
cd firmware
. ~/export-esp.sh
set -a
. ../.env.local
set +a
cargo +esp build --releaseThe firmware is not split into codec variants or transport variants. Use BtnB or the menu-bar app to switch between Wi-Fi, Bluetooth, and USB. Use the setup portal only for Wi-Fi credentials, Wi-Fi codec, and power settings; saved settings apply at stream start.
Flash the StickS3:
cd firmware
espflash flash --port <serial-port> target/xtensa-esp32s3-espidf/release/m5mic-firmwareAfter flashing USB Audio firmware, the app owns the native USB device stack while running. If serial monitoring over the same USB cable is unavailable, flash without --monitor and use the screen state for basic feedback.
For M5Launcher on StickS3, use the exported app binary, not the ELF that espflash flash accepts:
scripts/export-m5launcher-firmware.shThe script builds the firmware, writes target/m5mic-sticks3-m5launcher.bin, verifies the ESP app-image magic byte, and checks that it fits the StickS3 Launcher app partition. Copy that .bin to the Launcher SD card, or upload it through Launcher WebUI, then install it from Launcher.
The Launcher export does not load .env.local by default, so local Wi-Fi credentials are not embedded in a shareable .bin. For a private build with your local fallback Wi-Fi compiled in, run M5MIC_INCLUDE_LOCAL_WIFI=1 scripts/export-m5launcher-firmware.sh.
Optional direct receiver override:
cd firmware
set -a
. ../.env.local
set +a
M5MIC_SERVER_URL='ws://192.168.1.10:47776/audio' cargo +esp build --release
espflash flash --port <serial-port> target/xtensa-esp32s3-espidf/release/m5mic-firmwareThe StickS3 audio path uses an ES8311 codec at I2C 0x18, not a direct PDM mic.
Relevant pins from the M5Stack StickS3 docs and M5Unified:
| Signal | GPIO |
|---|---|
| ES8311 MCLK | 18 |
| ES8311 DOUT to ESP32-S3 DIN | 16 |
| ES8311 BCLK | 17 |
| ES8311 LRCK | 15 |
| ES8311 I2C SCL | 48 |
| ES8311 I2C SDA | 47 |
| BtnA / KEY1 | 11 |
| BtnB / KEY2 | 12 |



