Read-confirmed, not reproduced — saying so up front, because the two sibling defects below were reproduced and this one was not.
SendspinDecoder::decode() in src/decoder.cpp hands libopus the output buffer through a native-width cast:
int output_frames = opus_decode(
this->opus_decoder_buf_.as<OpusDecoder>(), data, data_size, (int16_t*)output_buffer,
this->current_stream_info_.bytes_to_frames(output_buffer_size), 0);
opus_decode() writes opus_int16 in the host's byte order — that is its documented contract, not an inference. The buffer it fills is then consumed as little-endian PCM: a host sink opens SND_PCM_FORMAT_S16_LE, and the Sendspin PCM encoding is little-endian throughout.
On a little-endian build those agree and nothing is wrong. On a big-endian one every decoded Opus sample reaches the device with its bytes reversed.
Nothing byte-swaps between the two, as far as I can see. If there is a swap somewhere I have missed, I would be glad to be corrected — that is the main reason I am filing this rather than a pull request.
Why I think it is worth looking at
The same shape of defect exists in two places around it, and in both I was able to demonstrate it rather than argue it:
Three places, one pattern: the careful path is byte-wise and the convenient path is a cast. This one is the third, and it is the only one of the three I did not manage to put under a test, because doing so means cross-building libopus for a big-endian target rather than compiling one self-contained file.
Context
Found packaging sendspin-cli for OpenWrt. ath79 — MIPS 24Kc, big-endian, still on kernel 6.18 — is a common host for this sort of thing: an older router with a USB port and a cheap DAC. My package carries @!BIG_ENDIAN for now, so it is simply not offered there rather than offered and wrong. I would rather drop that guard.
Happy to test a branch on real big-endian hardware, or to build the cross-compiled libopus harness if that would help more than it costs.
Environment
|
|
| Library |
sendspin-cpp v0.8.0, as pinned by sendspin-cli 0.3.0 |
| Where |
src/decoder.cpp, the opus_decode() call in SendspinDecoder::decode() |
| Consumer |
ALSA sink opening SND_PCM_FORMAT_S16_LE |
Read-confirmed, not reproduced — saying so up front, because the two sibling defects below were reproduced and this one was not.
SendspinDecoder::decode()insrc/decoder.cpphands libopus the output buffer through a native-width cast:opus_decode()writesopus_int16in the host's byte order — that is its documented contract, not an inference. The buffer it fills is then consumed as little-endian PCM: a host sink opensSND_PCM_FORMAT_S16_LE, and the Sendspin PCM encoding is little-endian throughout.On a little-endian build those agree and nothing is wrong. On a big-endian one every decoded Opus sample reaches the device with its bytes reversed.
Nothing byte-swaps between the two, as far as I can see. If there is a swap somewhere I have missed, I would be glad to be corrected — that is the main reason I am filing this rather than a pull request.
Why I think it is worth looking at
The same shape of defect exists in two places around it, and in both I was able to demonstrate it rather than argue it:
apply_volume()honours its documented little-endian contract on its 24-bit path, written byte by byte, but reinterprets the buffer at native width for 16- and 32-bit. On MIPS big-endian under QEMU, 57 of 64 16-bit samples and 60 of 64 32-bit samples come back wrong, where x86_64 is exact.write_samples()packs byte by byte on its generic and 24-bit paths and casts on its optimized ones, and picks between them on buffer alignment. Same result: correct or corrupt depending on where the buffer landed.Three places, one pattern: the careful path is byte-wise and the convenient path is a cast. This one is the third, and it is the only one of the three I did not manage to put under a test, because doing so means cross-building libopus for a big-endian target rather than compiling one self-contained file.
Context
Found packaging sendspin-cli for OpenWrt.
ath79— MIPS 24Kc, big-endian, still on kernel 6.18 — is a common host for this sort of thing: an older router with a USB port and a cheap DAC. My package carries@!BIG_ENDIANfor now, so it is simply not offered there rather than offered and wrong. I would rather drop that guard.Happy to test a branch on real big-endian hardware, or to build the cross-compiled libopus harness if that would help more than it costs.
Environment
src/decoder.cpp, theopus_decode()call inSendspinDecoder::decode()SND_PCM_FORMAT_S16_LE