sbencoder.cpp
Rama main · réplica de lectura del repositorio original.
// sbencoder.cpp -- streams decoded PCM into a FLAC-encoded byte stream for Sonos
//
// Copyright (c) 2026 Jaap van Vliet
//
// Original implementation for the YeneY project.
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Licensed under the PolyForm Noncommercial License 1.0.0. See LICENSE.
//
// THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
#include "sbencoder.h"
#include "start_lead.h"
#include <chrono>
#include <cstdio>
#include <cstring>
#include <unistd.h>
namespace {
// One PCM conversion batch's worth of interleaved stereo samples.
constexpr int kSamplesPerChunk = 1024;
constexpr int kEncodedRingCapacity = 256;
constexpr int kChannelCount = 2;
// Exact legacy pacing window when the configured start lead is zero.
constexpr uint32_t kMaxEncodeLeadMs = 250;
// Unpacks one little-endian PCM sample at `cursor` (advancing it) into the
// FLAC__int32 form libFLAC's interleaved encoder expects.
FLAC__int32 nextSampleAsInt32(const char*& cursor, int bitDepth)
{
switch (bitDepth) {
case 8: {
FLAC__int32 v = (unsigned char)(*cursor) - 128;
cursor += 1;
return v;
}
case 16: {
FLAC__int32 v = upnp::littleEndianSample(cursor, 16);
cursor += 2;
return v;
}
case 24: {
FLAC__int32 v = upnp::littleEndianSample(cursor, 24);
cursor += 3;
return v;
}
case 32: {
FLAC__int32 v = upnp::littleEndianSample(cursor, 32);
cursor += 4;
return v;
}
default:
return 0;
}
}
} // namespace
extern "C" {
uint64_t get_sb_time_ms(void);
unsigned get_squeezebox_stream_id(void);
int yeney_is_paused(void);
} // extern "C"
using namespace bridge;
SBEncoder::SBEncoder(unsigned streamId)
: m_phase(Phase::Init)
, m_firstReadAtMs(0)
, m_pcmBytesAccepted(0)
, m_bytesPerFrame(0)
, m_sampleBits(0)
, m_streamId(streamId)
, m_interleaveBuf(nullptr)
, m_encodedRing(nullptr)
, m_pendingPacket(nullptr)
, m_pendingPacketConsumed(0)
, m_flac(nullptr)
{
m_encodedRing = new upnp::EncodedBuffer(kEncodedRingCapacity);
m_flac = new WriteBridge(this);
}
SBEncoder::~SBEncoder()
{
m_cancelled = true;
m_flac->finish();
delete m_flac;
delete[] m_interleaveBuf;
if (m_pendingPacket)
m_encodedRing->freePacket(m_pendingPacket);
delete m_encodedRing;
}
bool SBEncoder::open()
{
return open(16);
}
bool SBEncoder::open(uint8_t sampleBits, unsigned sampleRate)
{
if (m_phase != Phase::Init) {
printf("SBEncoder::open(stream=%u) -- already opened\n", m_streamId);
return false;
}
m_flac->set_verify(true);
m_flac->set_compression_level(5);
m_flac->set_channels(kChannelCount);
m_flac->set_bits_per_sample(sampleBits);
m_flac->set_sample_rate(sampleRate);
m_sampleRate = sampleRate;
m_bytesPerFrame = (sampleBits / 8) * kChannelCount;
m_sampleBits = sampleBits;
m_encodedRing->clear();
if (m_pendingPacket) {
m_encodedRing->freePacket(m_pendingPacket);
m_pendingPacket = nullptr;
}
delete[] m_interleaveBuf;
m_interleaveBuf = new FLAC__int32[kSamplesPerChunk * kChannelCount];
FLAC__StreamEncoderInitStatus status = m_flac->init();
if (status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
printf("SBEncoder::open(stream=%u) -- FLAC encoder error %s\n",
m_streamId, FLAC__StreamEncoderInitStatusString[status]);
m_phase = Phase::Closed;
return false;
}
m_phase = Phase::Encoding;
return true;
}
void SBEncoder::close()
{
std::lock_guard<std::mutex> lock(m_writeMutex);
if (m_phase == Phase::Closed)
return;
m_flac->finish();
m_phase = Phase::Closed;
}
int SBEncoder::pendingEncodedBytes() const
{
if (m_pendingPacket)
return m_pendingPacket->size - m_pendingPacketConsumed;
return m_encodedRing->bytesAvailable();
}
int SBEncoder::drainEncodedBytes(char* data, int maxlen)
{
if (!m_pendingPacket) {
m_pendingPacket = m_encodedRing->read();
m_pendingPacketConsumed = 0;
}
if (!m_pendingPacket)
return 0;
int available = m_pendingPacket->size - m_pendingPacketConsumed;
int n = (maxlen < available) ? maxlen : available;
memcpy(data, m_pendingPacket->data + m_pendingPacketConsumed, n);
m_pendingPacketConsumed += n;
if (n) m_encodedRing->readerProgress();
if (m_pendingPacketConsumed >= m_pendingPacket->size) {
m_encodedRing->freePacket(m_pendingPacket);
m_pendingPacket = nullptr;
}
return n;
}
int SBEncoder::acceptEncodedBytes(const char* data, int len)
{
history.append(data, len);
return m_historyReader.load() ? len : m_encodedRing->write(data, len, [this] {
return cancelled() || responseEnded() || producerRetired() || (m_writeInterrupted && m_writeInterrupted());
});
}
int SBEncoder::encodePcm(const char* data, int len, const std::function<bool()> &interrupted)
{
m_writeInterrupted = interrupted;
struct Reset { std::function<bool()> &callback; ~Reset() { callback = {}; } } reset{m_writeInterrupted};
int samplesLeft = len / m_bytesPerFrame;
while (samplesLeft > 0) {
int chunk = (samplesLeft > kSamplesPerChunk) ? kSamplesPerChunk : samplesLeft;
const char* cursor = data;
for (int i = 0; i < chunk * kChannelCount; ++i)
m_interleaveBuf[i] = nextSampleAsInt32(cursor, m_sampleBits);
data = cursor;
if (!m_flac->process_interleaved(m_interleaveBuf, chunk))
break;
samplesLeft -= chunk;
}
return len - samplesLeft*m_bytesPerFrame;
}
FLAC__StreamEncoderWriteStatus SBEncoder::WriteBridge::write_callback(
const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame)
{
(void)current_frame;
m_owner->m_encodedFrames.fetch_add(samples);
int written = m_owner->acceptEncodedBytes((const char*)buffer, (int)bytes);
if (samples && written == (int)bytes)
m_owner->m_producedAudio.store(true);
return (written == (int)bytes) ? FLAC__STREAM_ENCODER_WRITE_STATUS_OK
: FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
}
int SBEncoder::read(char* data, int maxlen, unsigned timeout, bool holdWhilePaused,
const std::function<bool()>& peerClosed)
{
const bool limited = timeout != 0;
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout);
for (;;) {
if (cancelled() || responseEnded() || (peerClosed && peerClosed()))
return 0;
if (m_phase == Phase::Closed) {
printf("SBEncoder::read: encoder is closed\n");
return 0;
}
if (m_streamId != get_squeezebox_stream_id()) {
printf("SBEncoder::read: stream mismatch (%u != %u)\n", m_streamId, get_squeezebox_stream_id());
usleep(1000);
return 0;
}
if (pendingEncodedBytes()) {
if (!m_firstReadAtMs)
m_firstReadAtMs = get_sb_time_ms();
return drainEncodedBytes(data, maxlen);
}
if (m_phase == Phase::Closing) {
printf("All data consumed\n");
close();
return 0;
}
auto now = std::chrono::steady_clock::now();
if (holdWhilePaused && yeney_is_paused())
deadline = now + std::chrono::milliseconds(timeout);
if (limited && now >= deadline) {
printf("SBEncoder::read: timeout\n");
return 0;
}
usleep(1000);
}
}
int SBEncoder::write(const char* data, int len, unsigned timeout, const std::function<void()>& firstPcm,
const std::function<bool()>& interrupted)
{
const bool limited = timeout != 0;
const bool sonos = streamPacingSonos();
const int requested = len;
int accepted = 0;
for (;;) {
if (interrupted && interrupted()) return 0;
if (cancelled() || responseEnded() || producerRetired())
return 0;
if (m_phase != Phase::Encoding) {
printf("SBEncoder::write: encoder not active\n");
return 0;
}
if (m_streamId != get_squeezebox_stream_id()) {
printf("SBEncoder::write: stream mismatch (%u != %u)\n", m_streamId, get_squeezebox_stream_id());
usleep(1000);
return 0;
}
if (len == 0) {
printf("Reached end of stream\n");
m_phase = Phase::Closing;
if (m_historyReader.load()) close();
return 0;
}
const uint64_t encodedMs = m_pcmBytesAccepted / m_bytesPerFrame * 1000ULL / m_sampleRate;
const uint64_t firstRead = m_firstReadAtMs.load();
const uint64_t playedMs = firstRead ? get_sb_time_ms() - firstRead : 0;
if (sonos && len > 0) {
auto clock = m_clock ? m_clock() : StreamClock{};
double played = m_pacer.update(get_sb_time_ms(), firstRead, clock, yeney_is_paused());
double allowance = StreamPacer::ceiling(played, yeney_start_lead_ms()) -
double(m_pcmBytesAccepted.load()/m_bytesPerFrame)*1000/m_sampleRate;
auto frames = uint64_t(std::max(0., allowance)*m_sampleRate/1000);
if (!yeney_is_paused() && frames) {
int count = int(std::min<uint64_t>({frames, uint64_t(len/m_bytesPerFrame), uint64_t(kSamplesPerChunk)}))*m_bytesPerFrame;
if (!count) return accepted;
std::lock_guard<std::mutex> lock(m_writeMutex);
if (cancelled() || responseEnded() || producerRetired() || m_phase != Phase::Encoding) return 0;
if (!m_pcmBytesAccepted && firstPcm) firstPcm();
m_pcmBytesAccepted += count;
if (encodePcm(data,count,interrupted) != count) return 0;
data += count; len -= count; accepted += count;
if (!len) return requested;
continue;
}
}
// The same per-encoder clock covers the initial burst and steady
// delivery. Checking before accepting a whole batch bounds overshoot
// to one producer batch. Gapless writes keep this clock and allowance.
if (!sonos && !yeney_is_paused() && encodedMs < playedMs + (yeney_start_lead_ms() ? yeney_start_lead_ms() : kMaxEncodeLeadMs)) {
std::lock_guard<std::mutex> lock(m_writeMutex);
if (cancelled() || responseEnded() || producerRetired() || m_phase != Phase::Encoding)
return 0;
if (!m_pcmBytesAccepted && firstPcm) firstPcm();
m_pcmBytesAccepted += len;
return encodePcm(data, len, interrupted);
}
if (limited && !yeney_is_paused()) {
if (!timeout--) {
printf("SBEncoder::write: timeout\n");
return 0;
}
}
usleep(1000);
}
}