penguin/loopdeck

A simple player that support A-B looping for FiiO M11S

app/src/main/cpp/decoder.cpp

raw · 11776 bytes

#include "decoder.h"
#include <algorithm>
#include <cerrno>
#include <cstring>
#include <stdexcept>
#include <numeric>
#include <sys/stat.h>
#include <unistd.h>
extern "C" {
#include <libavutil/opt.h>
}

Decoder::Decoder(int inputFd) {
    fd = dup(inputFd);
    if (fd < 0) throw std::runtime_error("Cannot duplicate file descriptor");
}
Decoder::~Decoder() {
    av_packet_free(&packet);
    av_frame_free(&frame);
    swr_free(&swr);
    avcodec_free_context(&codec);
    avformat_close_input(&format);
    if (io) { av_freep(&io->buffer); avio_context_free(&io); }
    if (fd >= 0) close(fd);
}
void Decoder::check(int result, const char* operation) {
    if (result >= 0) return;
    if (result == AVERROR_PATCHWELCOME && codec && codec->codec_id == AV_CODEC_ID_TAK)
        throw std::runtime_error("This TAK codec type is not supported by the available decoders.");
    char detail[AV_ERROR_MAX_STRING_SIZE];
    av_strerror(result, detail, sizeof(detail));
    throw std::runtime_error(std::string(operation) + ": " + detail);
}
int Decoder::readFd(void* opaque, uint8_t* data, int size) {
    auto* d = static_cast<Decoder*>(opaque);
    ssize_t count;
    do { count = ::read(d->fd, data, size); } while (count < 0 && errno == EINTR);
    return count > 0 ? static_cast<int>(count) : count == 0 ? AVERROR_EOF : AVERROR(errno);
}
int64_t Decoder::seekFd(void* opaque, int64_t offset, int whence) {
    auto* d = static_cast<Decoder*>(opaque);
    if (whence & AVSEEK_SIZE) {
        struct stat info {};
        return fstat(d->fd, &info) == 0 && S_ISREG(info.st_mode) ? info.st_size : AVERROR(ENOSYS);
    }
    const off_t result = lseek(d->fd, offset, whence & ~AVSEEK_FORCE);
    return result < 0 ? AVERROR(errno) : result;
}
void Decoder::open() {
    format = avformat_alloc_context();
    if (!format) throw std::bad_alloc();
    auto* buffer = static_cast<unsigned char*>(av_malloc(65536));
    if (!buffer) throw std::bad_alloc();
    io = avio_alloc_context(buffer, 65536, 0, this, readFd, nullptr, seekFd);
    if (!io) { av_free(buffer); throw std::bad_alloc(); }
    format->pb = io;
    format->flags |= AVFMT_FLAG_CUSTOM_IO;
    check(avformat_open_input(&format, nullptr, nullptr, nullptr), "Open file");
    // TAK's demuxer exposes STREAMINFO directly. Avoid probing codec type 0
    // through FFmpeg's unsupported decoder before selecting our native path.
    const auto* first = format->nb_streams == 1 ? format->streams[0]->codecpar : nullptr;
    const bool legacyTak = first && first->codec_id == AV_CODEC_ID_TAK && first->extradata_size >= 10
        && (first->extradata[0] & 63) == 0;
    if (!legacyTak) check(avformat_find_stream_info(format, nullptr), "Read stream information");
    const AVCodec* decoder = nullptr;
    streamIndex = av_find_best_stream(format, AVMEDIA_TYPE_AUDIO, -1, -1, &decoder, 0);
    check(streamIndex, "Find supported audio stream");
    stream = format->streams[streamIndex];
    codec = avcodec_alloc_context3(decoder);
    if (!codec) throw std::bad_alloc();
    check(avcodec_parameters_to_context(codec, stream->codecpar), "Read codec parameters");
    codec->pkt_timebase=stream->time_base;
    codec->thread_count = 2;
    if (legacyTak) {
        tak1 = std::make_unique<Tak1Decoder>(stream->codecpar->extradata, stream->codecpar->extradata_size);
        const auto& info = tak1->info();
        codec->sample_rate = info.rate;
        codec->sample_fmt = AV_SAMPLE_FMT_S32P;
        codec->bits_per_raw_sample = info.bits;
        av_channel_layout_uninit(&codec->ch_layout);
        av_channel_layout_default(&codec->ch_layout, info.channels);
    } else check(avcodec_open2(codec, decoder, nullptr), "Open audio decoder");
    if (codec->sample_rate <= 0) throw std::runtime_error("Invalid audio sample rate");
    frame = av_frame_alloc();
    packet = av_packet_alloc();
    if (!frame || !packet) throw std::bad_alloc();
    outputRate(codec->sample_rate);
}
void Decoder::outputRate(int value) {
    if (value <= 0) throw std::runtime_error("Invalid output rate");
    rate = value;
    swr_free(&swr);
    AVChannelLayout stereo = AV_CHANNEL_LAYOUT_STEREO;
    check(swr_alloc_set_opts2(&swr, &stereo, AV_SAMPLE_FMT_FLT, rate,
        &codec->ch_layout, codec->sample_fmt, codec->sample_rate, 0, nullptr), "Configure PCM conversion");
    check(swr_init(swr), "Initialize PCM conversion");
}
int64_t Decoder::durationMs() const {
    if (stream->duration != AV_NOPTS_VALUE)
        return av_rescale_q(stream->duration, stream->time_base, AVRational{1,1000});
    return format->duration == AV_NOPTS_VALUE ? 0 : format->duration / 1000;
}
std::string Decoder::description() const {
    const auto* desc = avcodec_descriptor_get(codec->codec_id);
    int bits = tak1 ? tak1->info().bits : stream->codecpar->bits_per_raw_sample;
    return std::string(tak1 ? "TAK 1.0 (native)" : desc ? desc->name : "audio") + " · " + std::to_string(codec->sample_rate)
        + " Hz · " + std::to_string(codec->ch_layout.nb_channels) + " ch"
        + (bits > 0 ? " · " + std::to_string(bits) + " bit" : "");
}
int Decoder::receiveFrame() {
    if (!tak1) return avcodec_receive_frame(codec, frame);
    while (true) {
        int result = av_read_frame(format, packet);
        if (result < 0) return result;
        if (packet->stream_index != streamIndex) { av_packet_unref(packet); continue; }
        auto audio = tak1->decode(packet->data, packet->size);
        frame->format = AV_SAMPLE_FMT_S32P;
        frame->sample_rate = codec->sample_rate;
        frame->nb_samples = audio.samples;
        frame->pts = frame->best_effort_timestamp = packet->pts;
        check(av_channel_layout_copy(&frame->ch_layout, &codec->ch_layout), "Copy TAK channel layout");
        check(av_frame_get_buffer(frame, 0), "Allocate TAK PCM frame");
        const int shift = 32-tak1->info().bits;
        for (int c=0; c<codec->ch_layout.nb_channels; ++c) {
            auto* destination = reinterpret_cast<int32_t*>(frame->data[c]);
            for (int i=0; i<audio.samples; ++i) {
                uint32_t value = uint32_t(audio.channel[c][i]) << shift;
                std::memcpy(destination+i, &value, sizeof(value));
            }
        }
        av_packet_unref(packet);
        return 0;
    }
}
bool Decoder::fill() {
    pending.clear(); pendingOffset = 0;
    while (true) {
        int result = receiveFrame();
        if (result == AVERROR_EOF) {
            if (resamplerEnded) return false;
            resamplerEnded = true;
            const int capacity = swr_get_out_samples(swr, 0);
            pending.resize(static_cast<size_t>(std::max(capacity, 1)) * 2);
            uint8_t* out[] = {reinterpret_cast<uint8_t*>(pending.data())};
            int n = swr_convert(swr, out, capacity, nullptr, 0);
            check(n, "Drain audio converter");
            pending.resize(n * 2);
            return n > 0;
        }
        if (result >= 0) {
            if (!resamplerStarted) {
                int64_t sourceStart = av_rescale_q(decodedNext, AVRational{1,rate}, AVRational{1,codec->sample_rate});
                if (frame->best_effort_timestamp != AV_NOPTS_VALUE) {
                    int64_t origin = stream->start_time == AV_NOPTS_VALUE ? 0 : stream->start_time;
                    sourceStart = av_rescale_q(frame->best_effort_timestamp-origin,stream->time_base,AVRational{1,codec->sample_rate});
                }
                if (timelineOrigin == AV_NOPTS_VALUE) timelineOrigin=sourceStart;
                sourceStart-=timelineOrigin;
                if ((codec->codec_id==AV_CODEC_ID_WMAV1 || codec->codec_id==AV_CODEC_ID_WMAV2) && frame->nb_samples>0) {
                    // ASF timestamps are milliseconds, while WMA v1/v2 emits
                    // fixed-sized sample frames. Recover the exact frame boundary.
                    int64_t size=frame->nb_samples;
                    sourceStart=((sourceStart+size/2)/size)*size;
                }
                // Restore the global rational resampling phase after a seek.
                // Without this, e.g. 44.1 -> 48 kHz seeks shift by a fractional sample.
                int period = codec->sample_rate / std::gcd(codec->sample_rate,rate);
                int prefix = static_cast<int>((sourceStart % period + period) % period);
                if (prefix) check(swr_inject_silence(swr,prefix),"Align audio converter");
                decodedNext = av_rescale_q(sourceStart-prefix,AVRational{1,codec->sample_rate},AVRational{1,rate});
                resamplerStarted=true;
            }
            int capacity = swr_get_out_samples(swr, frame->nb_samples);
            pending.resize(static_cast<size_t>(std::max(capacity, 1)) * 2);
            uint8_t* out[] = {reinterpret_cast<uint8_t*>(pending.data())};
            int n = swr_convert(swr, out, capacity, const_cast<const uint8_t**>(frame->extended_data), frame->nb_samples);
            check(n, "Convert decoded audio");
            int64_t start = decodedNext;
            decodedNext = start + n;
            pending.resize(n * 2);
            pendingOffset = static_cast<size_t>(std::clamp<int64_t>(skipUntil - start, 0, n)) * 2;
            av_frame_unref(frame);
            if (pendingOffset < pending.size()) return true;
            continue;
        }
        if (result != AVERROR(EAGAIN)) check(result, "Decode audio");
        if (inputEnded) {
            if (drainSent) throw std::runtime_error("Decoder stalled while draining");
            check(avcodec_send_packet(codec, nullptr), "Drain decoder");
            drainSent = true;
            continue;
        }
        while (true) {
            result = av_read_frame(format, packet);
            if (result == AVERROR_EOF) { inputEnded = true; break; }
            check(result, "Read audio packet");
            if (packet->stream_index == streamIndex) {
                result = avcodec_send_packet(codec, packet);
                av_packet_unref(packet);
                check(result, "Submit audio packet");
                break;
            }
            av_packet_unref(packet);
        }
    }
}
int Decoder::read(float* output, int frames) {
    if (frames <= 0) return 0;
    int copied = 0;
    while (copied < frames) {
        if (pendingOffset == pending.size() && !fill()) break;
        size_t count = std::min(pending.size() - pendingOffset, static_cast<size_t>((frames - copied) * 2));
        std::memcpy(output + copied * 2, pending.data() + pendingOffset, count * sizeof(float));
        pendingOffset += count;
        copied += static_cast<int>(count / 2);
    }
    return copied;
}
void Decoder::seek(int64_t outputFrame) {
    // Establish the decoded timeline origin. Some containers (notably ASF/WMA)
    // start decoded audio later than their stream start timestamp.
    if (timelineOrigin == AV_NOPTS_VALUE && !fill()) throw std::runtime_error("No decoded audio");
    int64_t origin = stream->start_time == AV_NOPTS_VALUE ? 0 : stream->start_time;
    // Give predictive codecs and resampling filters enough history before A.
    int64_t preroll=std::max<int64_t>(0,outputFrame-int64_t(rate)*2);
    int64_t target = preroll==0 ? origin : av_rescale_q(preroll, AVRational{1,rate}, stream->time_base)
        + av_rescale_q(timelineOrigin,AVRational{1,codec->sample_rate},stream->time_base) + origin;
    check(avformat_seek_file(format, streamIndex, INT64_MIN, target, target, 0), "Seek audio");
    if (tak1) tak1->reset();
    else avcodec_flush_buffers(codec);
    av_packet_unref(packet);
    av_frame_unref(frame);
    swr_close(swr);
    check(swr_init(swr), "Reset audio converter");
    inputEnded = drainSent = resamplerEnded = resamplerStarted = false;
    pending.clear(); pendingOffset = 0;
    skipUntil = decodedNext = outputFrame;
}