Ctrl K
ComunidadGuías…

sbencoder.cpp

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// 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);
    }
}