ESP32 source: 48 kHz stereo PCM overflows capture buffer; Opus registers as player
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 58/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- cpp
調査の方向性
Start with the source capture and send path around components/sendspin/sendspin_hub.h, using the provided 48 kHz stereo YAML to reproduce the capture-buffer warnings and timestamp resets. Trace the client/hello source advertisement and codec-dependent registration path to determine why Opus registers as a player. Reproduction is complete when 48 kHz stereo no longer overflows and the Opus build is registered as a source without bridge resets.
索引モデルが issue の本文から書いたものです。
説明
ESP32-A1S Sendspin source: 48 kHz stereo PCM overflows the capture buffer (32 kHz stereo / 48 kHz mono are clean); Opus makes MA register the board as a player
Tested with the joshualh/sendspin-a1s-source ESPHome test rig (that repo has issues disabled), which uses the sendspin-cpp source role from #166/#167/#172/#175.
Summary
Using the sendspin-a1s-source test rig as a Sendspin source (line-in from a stereo → Music Assistant Live Input → AirPlay speaker):
- 48 kHz / 16-bit stereo PCM (~1.5 Mbit/s) drops audio. The board logs
Capture buffer full; dropping writes until it drains, and Music Assistant (aiosendspinSourceBridge) logsSource timestamp jumped …, resetting bridge. Each reset is an audible gap. - The data rate is the limit. 48 kHz mono (~0.77 Mbit/s) and 32 kHz stereo (~1.0 Mbit/s) both play cleanly, with zero board drops and zero bridge resets, on the same hardware, Wi-Fi, server and speaker.
codec: opusmakes Music Assistant register the board as a player ("type protocol", then wrapped in a Universal Player that "requires setup") instead of a source, so Opus can't be used to reduce the data rate.
The playback path is not the cause: the same Music Assistant server and the same AirPlay player play normal library content cleanly.
I searched the issues in joshualh/sendspin-a1s-source, Sendspin/sendspin-cpp, Sendspin/aiosendspin and esphome/esphome (including draft PR #21696) and found no existing report.
Environment
| Item | Version / detail |
|---|---|
| Board | ESP32-A1S Audio Kit V2.2, ES8388 codec (I²C 0x10), 8 MB PSRAM, chip rev 3.1, DIP switches as shipped |
| Component | joshualh/sendspin-a1s-source @ b7a93b6 (local copy) |
| sendspin-cpp | joshualh/sendspin-cpp fork @ 6d7ecef (includes upstream #175 "Ride out source send stalls"; no source changes upstream since the fork's base) |
| Local patch | Added to components/sendspin/sendspin_hub.h (build failed without it): StringRef get_client_id() const { return this->client_ == nullptr ? StringRef() : StringRef(this->client_->client_id()); } |
| ESPHome | 2026.10.1 (Home Assistant add-on), ESP-IDF 5.5.6 |
| Home Assistant | Core 2026.10.4, OS 18.3 |
| Music Assistant | 2.10.8, aiosendspin 10.2.1 |
| Wi-Fi | RSSI −51 to −54 dBm, power_save_mode: none, consumer router (not a mesh) |
| Input | Stereo tape-record output (line level) → A1S LINE2 input |
| Output in MA | Live Input → AirPlay 2 player (Arylic LP10) |
Working config (32 kHz stereo, clean)
esp32:
board: esp-wrover-kit
flash_frequency: 80MHZ
framework:
type: esp-idf
psram:
speed: 80MHZ
ignore_not_found: false # enables ESPHome's high-performance lwip/Wi-Fi tier
logger:
level: INFO
wifi:
power_save_mode: none
external_components:
- source:
type: local
path: sendspin-a1s/components
components: [sendspin]
# ES8388 on_boot register writes: 0x0C=0x0C, 0x0B=0x00, 0x12=0x22, 0x16=0x00
# select adc_input: LINE2
microphone:
- platform: i2s_audio
id: a1s_adc
adc_type: external
i2s_din_pin: GPIO35
pdm: false
sample_rate: 32000 # 48000 drops (see below)
bits_per_sample: 16bit
channel: stereo
use_apll: true
# I2S: MCLK GPIO0, BCLK GPIO27, LRCK GPIO25
sendspin:
id: sendspin_hub
task_stack_in_psram: false
static_pairing_code: "<redacted>"
source:
microphone:
microphone: a1s_adc
channels: [0, 1]
capture_buffer: 2000ms
# chunk_duration: default 20 ms
(Full YAML available on request.)
Steps to reproduce
- Flash the config above with
sample_rate: 48000, and pair the board in Music Assistant. - Feed a continuous line-level signal (a record playing) into LINE2.
- Select the board's Live Input in MA and play it to an AirPlay player.
- Watch the ESPHome log and the MA server log.
Observed at 48 kHz stereo
Board (ESPHome log):
[W][sendspin.source_task]: Capture buffer full; dropping writes until it drains
[W][sendspin.source_task]: Dropped 1 capture writes
Music Assistant log:
WARNING aiosendspin.audio.source_bridge: Source timestamp jumped 2495928 us, resetting bridge
- Each board overflow is followed by one MA bridge reset, and each reset is an audible gap.
- The gap size tracks
capture_buffer. With 500 ms, the board warnings came in runs exactly ~0.62 s apart, and MA's jumps were whole multiples of ~0.62 s (1.24, 1.87, 2.48 s). With 2000 ms, the jumps are ~2.5 s. This fits the source task flushing the whole backlog on overflow and resuming from live capture. - The buffer fills steadily rather than from occasional stalls. Raising
capture_bufferfrom 500 ms to 2000 ms made overflows less frequent (about every 3.5 s, then every 10–15 s) but each one longer. The user heard playback start clean, then gaps getting closer together. A 2 s buffer still overflowing means sending lags capture all the time, not just during brief Wi-Fi stalls. - In mono and at 32 kHz stereo: no overflows and no resets over 20+ minutes of playback.
What I tried (one change at a time)
| Change | Board drops | Result |
|---|---|---|
| Baseline (repo defaults, 48 kHz stereo) | 0.82 /s | — |
task_stack_in_psram: false |
0.36 /s | better |
| + PSRAM and flash at 80 MHz | 0.23 /s | better |
| + 44.1 kHz instead of 48 kHz | 0.69 /s | worse, reverted |
| 40 ms chunks instead of 20 ms | 0.57 /s | worse, reverted |
| 60 ms chunks | — | boot/crash loop, reverted |
use_apll: true |
0.28 /s | no meaningful change (kept) |
capture_buffer: 2000ms |
~0.09 /s, but ~2.5 s gaps | fewer but longer gaps (kept) |
psram: ignore_not_found: false (ESPHome high-performance lwip/Wi-Fi tier confirmed in build log) |
~0.065 /s | slight improvement only (kept) |
Mono, 48 kHz (channels: [0]) |
0 | clean |
codec: opus (stereo, 48 kHz) |
— | MA registers the board as a player, not a source |
| 32 kHz stereo | 0 | clean (current config) |
Opus registration problem
With codec: opus as the only change, Music Assistant logs:
INFO [music_assistant.players] Player (type protocol) registered: <id>/Sansui Source
INFO [music_assistant.Universal Player] Creating universal player <id> with protocol players: ['<id>']
INFO [music_assistant.players] Player (type player) registered: <id>/Sansui Source
With PCM, the same board registers as Player (type source). In sendspin-cpp, the client/hello source support object doesn't appear to depend on the codec, so I'm not sure whether the difference comes from the build (Opus-enabled sdkconfig) or from the MA side.
Other observations
- MA logs "non-compliant client" warnings for the board:
client/hello sent supported_pair_methods as a list,client sent client_stream/start, superseded by client-stream/start(and/end). They look like the fork's aiosendspin 9.1.1 message names meeting aiosendspin 10.2.1. Harmless here, but noting them. - Pairing persists across power cycles: the board is powered off nightly and re-registers as a source without re-pairing.
Questions
- Is ~1.5 Mbit/s (48 kHz/16-bit stereo PCM) expected to be beyond the A1S source pipeline? Wi-Fi capacity shouldn't be the limit at −52 dBm, and ESPHome's high-performance network tier barely changed it. Is the cost per message (e.g. Noise encryption, websocket framing), or something else in the send path?
- Why does
codec: opuschange how MA registers the board (player instead of source)? Opus would be the natural fix for stereo at 48 kHz. - On overflow, would dropping only the oldest chunk(s) instead of flushing the whole backlog keep each gap small (tens of ms rather than the full buffer length)?
- Should the MA bridge insert silence for a gap this size instead of resetting?
Happy to run test builds or capture DEBUG logs.
- 主要言語
- C++
- スター
- 32
- フォーク
- 19
- 平均マージ
- 14時間 54分
- マージ済み PR(30日)
- 49
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Sendspin/sendspin-cpp のほかの issue
-
難易度 1/5 1時間未満 初心者へのやさしさ 68/100
Sendspin/sendspin-cpp#257 ·
メンテナーはふだん 1 日以内に返信
-
Task watchdog on ESP32 after a long receive stall: sync task starves the main loop while catching up対応中かも @Boisti13 が 7 日前に担当しました。 オープン
難易度 4/5 3〜5日 初心者へのやさしさ 35/100
Sendspin/sendspin-cpp#223 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
Support clearing external buffers再び着手できるかも @kahrendt が 120 日前に担当しましたが、オープン中のプルリクエストはありません。 オープンenhancement
Sendspin/sendspin-cpp#55 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
Sendspin/sendspin-cpp の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
ROCm/rocm-systems#15475 ·
メンテナーはふだん 2 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
pocoproject/poco#5664 ·
メンテナーはふだん 2 日以内に返信
-
community:good-first-issue kind:bug kind:test scope:control status:needs-triage vehicle:rover
難易度 2/5 1〜3時間 初心者へのやさしさ 85/100
PX4/PX4-Autopilot#29727 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
better error message error not reported I/O ICE low priority semantics
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信