diff --git a/cmake/host.cmake b/cmake/host.cmake index 46da9c59..cedb81b9 100644 --- a/cmake/host.cmake +++ b/cmake/host.cmake @@ -68,7 +68,7 @@ function(sendspin_configure_host TARGET_LIB SOURCE_DIR) FetchContent_Declare( micro_flac GIT_REPOSITORY https://github.com/esphome-libs/micro-flac.git - GIT_TAG v0.1.1 + GIT_TAG v0.3.0 GIT_SUBMODULES "lib/micro-ogg-demuxer" ) FetchContent_MakeAvailable(micro_flac) diff --git a/docs/integration-guide.md b/docs/integration-guide.md index 2af1880f..b25367a6 100644 --- a/docs/integration-guide.md +++ b/docs/integration-guide.md @@ -230,7 +230,7 @@ the other is `SendspinNetworkProvider::is_network_ready()`. ```cpp struct MyPlayerListener : PlayerRoleListener { - // REQUIRED: Write decoded PCM audio to your audio output. + // REQUIRED: Write decoded PCM audio (interleaved, little-endian) to your audio output. // Called from a background thread. May block up to timeout_ms. // Must return the number of bytes actually written. size_t on_audio_write(uint8_t* data, size_t length, uint32_t timeout_ms) override { diff --git a/include/sendspin/player_role.h b/include/sendspin/player_role.h index 438429ba..3119b7ab 100644 --- a/include/sendspin/player_role.h +++ b/include/sendspin/player_role.h @@ -81,7 +81,7 @@ class PlayerRoleListener { /// @brief Writes decoded PCM audio to the platform's audio output /// /// Fires on the sync task's background thread. May block up to timeout_ms. - /// @param data Pointer to the decoded PCM audio data + /// @param data Decoded PCM audio data, interleaved and little-endian /// @param length Number of bytes to write; always a whole number of PCM frames /// @param timeout_ms Maximum time to wait for the write to complete /// @return Number of bytes actually written. Partial writes are allowed but must be a diff --git a/src/decoder.cpp b/src/decoder.cpp index 14f14955..3dff139a 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -15,6 +15,7 @@ #include "decoder.h" #include "opus_state_location.h" +#include "pcm_byte_order.h" #include "platform/logging.h" #include @@ -197,6 +198,7 @@ bool SendspinDecoder::decode_audio_chunk(const uint8_t* data, size_t data_size, } *consumed = data_size; *decoded_size = this->current_stream_info_.frames_to_bytes(output_frames); + pcm16_le_to_from_host(output_buffer, *decoded_size); #endif } else { return false; diff --git a/src/pcm_byte_order.h b/src/pcm_byte_order.h new file mode 100644 index 00000000..2d02718a --- /dev/null +++ b/src/pcm_byte_order.h @@ -0,0 +1,38 @@ +// Copyright 2026 Sendspin Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// @file pcm_byte_order.h +/// @brief Converts 16-bit PCM between the library's little-endian buffers and host order + +#pragma once + +#include +#include +#include +#include + +namespace sendspin { + +/// @brief Converts 16-bit samples between little-endian and host byte order, in place +/// @param data Sample buffer; needs no alignment +/// @param size Buffer size in bytes +inline void pcm16_le_to_from_host(uint8_t* data, size_t size) { + if constexpr (std::endian::native == std::endian::big) { + for (size_t i = 0; i + 1 < size; i += 2) { + std::swap(data[i], data[i + 1]); + } + } +} + +} // namespace sendspin diff --git a/src/source_encoder_opus.cpp b/src/source_encoder_opus.cpp index 937f8750..8c9fedeb 100644 --- a/src/source_encoder_opus.cpp +++ b/src/source_encoder_opus.cpp @@ -15,6 +15,7 @@ #include "source_encoder_opus.h" #include "opus_state_location.h" +#include "pcm_byte_order.h" #include "platform/logging.h" #include "source_task.h" #include @@ -87,6 +88,7 @@ size_t OpusSourceEncoder::encode(const uint8_t* in, size_t in_len, uint8_t* out, if (in != this->pcm_scratch_.data()) { std::memcpy(this->pcm_scratch_.data(), in, in_len); } + pcm16_le_to_from_host(this->pcm_scratch_.data(), in_len); // max_data_bytes is a hard packet cap: libopus lowers quality to fit rather than failing. // The task offers at least MAX_PACKET_BYTES, so its chunks never degrade. const auto capacity =