Skip to content

Keep Opus PCM little-endian on big-endian hosts - #200

Merged
kahrendt merged 3 commits into
mainfrom
opus-big-endian
Oct 12, 2026
Merged

kahrendt merged 3 commits into
mainfrom
opus-big-endian

Conversation

@kahrendt

Copy link
Copy Markdown
Contributor

On a big-endian host, Opus audio reaches the listener with every sample's bytes reversed, and an Opus source sends the server byte-swapped samples. libopus reads and writes host-order opus_int16, but the player's on_audio_write() output and the source's write_audio() input are little-endian.

The samples are now swapped after opus_decode() and before opus_encode(). The swap is behind if constexpr (std::endian::native == std::endian::big), so on ESP32 and other little-endian hosts it compiles to nothing.

Fixes #132.

Notes

  • New private helper src/pcm_byte_order.h (pcm16_le_to_from_host()), shared by the decoder and the source's Opus encoder. It swaps byte by byte, so it needs no alignment.
  • The encoder swaps its pcm_scratch_ in place. SourceTask refills the scratch completely for each chunk, so nothing reads the swapped data afterwards. warm_up() encodes silence and needs no swap.
  • Host micro-flac pin: v0.1.1 -> v0.3.0. v0.3.0's decoder output is little-endian on big-endian hosts (write_samples(): optimized paths emit native-endian PCM, so big-endian output depends on buffer alignment esphome-libs/micro-flac#36), which covers FLAC for host consumers such as sendspin-cli. The only API change that touches this code is [[nodiscard]] on decode(), and every call already uses the result.
  • idf_component.yml stays at >=0.1.1 until ESPHome moves to micro-flac v0.3.0. ESP targets are little-endian, so they are unaffected either way.
  • on_audio_write()'s doc now states the data is interleaved little-endian, matching write_audio().

Testing

  • 703/703 host tests pass with ASan against micro-flac v0.3.0.
  • No new test: on a little-endian host the swap is a constexpr no-op, so a host test can't catch a regression. A standalone run with the big-endian branch forced on turns the bytes ff d3 from the issue's big-endian harness into -45, the value libopus wrote there.
  • Not yet run on a big-endian target; @mguaylam offered to test on MIPS hardware.

libopus reads and writes host-order opus_int16, but the player's
on_audio_write() output and the source's write_audio() input are
little-endian. Swap the samples after opus_decode() and before
opus_encode() on big-endian hosts; the swap compiles away elsewhere.

Bump the host micro-flac pin to v0.3.0, whose decoder output is
little-endian on big-endian hosts too. The IDF component minimum stays
until ESPHome moves to v0.3.0.

Fixes #132
@kahrendt
kahrendt marked this pull request as ready for review October 12, 2026 16:02
@kahrendt
kahrendt requested a balanced review from Copilot October 12, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The integration guide must also document the updated public listener contract.

1 open finding
What changed in this PR

Ensures Opus PCM remains little-endian on big-endian hosts.

Changes:

  • Adds shared in-place PCM16 byte-order conversion.
  • Applies conversion around Opus decoding and encoding.
  • Updates the player contract and micro-flac dependency.
File Description
src/​pcm_byte_order.h Adds endian conversion helper.
src/​decoder.cpp Converts decoded Opus PCM to little-endian.
src/​source_encoder_opus.cpp Converts source PCM to host order before encoding.
include/​sendspin/​player_role.h Documents listener PCM byte order.
cmake/​host.cmake Updates micro-flac to v0.3.0.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread include/sendspin/player_role.h
@kahrendt kahrendt added bugfix dependencies Pull requests that update a dependency file labels Oct 12, 2026
@kahrendt
kahrendt merged commit 415d272 into main Oct 12, 2026
16 checks passed
@kahrendt
kahrendt deleted the opus-big-endian branch October 12, 2026 16:14
mguaylam added a commit to mguaylam/openwrt-sendspin that referenced this pull request Oct 13, 2026
Sendspin/sendspin-cpp#200, merged on 2026-10-12, byte-swaps Opus samples
after opus_decode() on big-endian hosts and moves the host build to
micro-flac v0.3.0, the release that carries esphome-libs/micro-flac#54.
sendspin-cpp v0.9.4 shipped both the same day.

All three byte-order defects are now fixed upstream, but @!BIG_ENDIAN
stays: sendspin-cli has not released the volume fix nor pinned v0.9.4,
and sendspin-cpp v0.9.x speaks only the rc1 protocol, so the fixes
arrive with the rc1 bump, which waits for Music Assistant stable.

Signed-off-by: Michaël Guay-Lambert <mguaylam.dev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Opus output is written in host byte order but consumed as little-endian PCM

2 participants