Repository navigation
Keep Opus PCM little-endian on big-endian hosts - #200
Merged
Merged
Conversation
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
Contributor
There was a problem hiding this comment.
🟡 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.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

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'son_audio_write()output and the source'swrite_audio()input are little-endian.The samples are now swapped after
opus_decode()and beforeopus_encode(). The swap is behindif constexpr (std::endian::native == std::endian::big), so on ESP32 and other little-endian hosts it compiles to nothing.Fixes #132.
Notes
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.pcm_scratch_in place.SourceTaskrefills the scratch completely for each chunk, so nothing reads the swapped data afterwards.warm_up()encodes silence and needs no swap.[[nodiscard]]ondecode(), and every call already uses the result.idf_component.ymlstays at>=0.1.1until 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, matchingwrite_audio().Testing
ff d3from the issue's big-endian harness into -45, the value libopus wrote there.