#include "lcs_media_decoder.hpp" #include #include #include extern "C" { #include #include #include #include #include #include } namespace lcs { namespace { struct DecodeCommon { AVFormatContext *format{}; AVCodecContext *codec{}; AVPacket *packet{}; AVFrame *frame{}; int stream_index{-1}; bool eof{}; std::vector pending; std::size_t pending_read{}; ~DecodeCommon() { release(); } void release() noexcept { if (frame != nullptr) av_frame_free(&frame); if (packet != nullptr) av_packet_free(&packet); if (codec != nullptr) avcodec_free_context(&codec); if (format != nullptr) avformat_close_input(&format); stream_index = -1; eof = false; pending.clear(); pending_read = 0u; } [[nodiscard]] bool open_stream(const std::filesystem::path &path, AVMediaType type) { release(); if (avformat_open_input(&format, path.string().c_str(), nullptr, nullptr) < 0) return false; if (avformat_find_stream_info(format, nullptr) < 0) return false; const AVCodec *decoder = nullptr; stream_index = av_find_best_stream(format, type, -1, -1, &decoder, 0); if (stream_index < 0 || decoder == nullptr) return false; codec = avcodec_alloc_context3(decoder); if (codec == nullptr) return false; if (avcodec_parameters_to_context(codec, format->streams[stream_index]->codecpar) < 0) return false; if (avcodec_open2(codec, decoder, nullptr) < 0) return false; packet = av_packet_alloc(); frame = av_frame_alloc(); return packet != nullptr && frame != nullptr; } template bool decode_one(Consume &&consume) { for (;;) { const int received = avcodec_receive_frame(codec, frame); if (received == 0) { consume(frame); av_frame_unref(frame); return true; } if (received != AVERROR(EAGAIN) && received != AVERROR_EOF) return false; if (received == AVERROR_EOF) return false; if (eof) { if (avcodec_send_packet(codec, nullptr) < 0) return false; const int flushed = avcodec_receive_frame(codec, frame); if (flushed < 0) return false; consume(frame); av_frame_unref(frame); return true; } const int read = av_read_frame(format, packet); if (read < 0) { eof = true; continue; } if (packet->stream_index != stream_index) { av_packet_unref(packet); continue; } const int sent = avcodec_send_packet(codec, packet); av_packet_unref(packet); if (sent < 0 && sent != AVERROR(EAGAIN)) return false; } } template std::size_t drain(std::span output, Refill &&refill) { std::size_t written = 0u; while (written < output.size()) { if (pending_read >= pending.size()) { pending.clear(); pending_read = 0u; if (!refill()) break; if (pending.empty()) break; } const std::size_t available = pending.size() - pending_read; const std::size_t take = std::min(available, output.size() - written); std::copy_n(pending.begin() + static_cast(pending_read), take, output.begin() + static_cast(written)); pending_read += take; written += take; } return written; } }; } struct AudioStreamDecoder::State { DecodeCommon common; SwrContext *resampler{}; std::uint32_t sample_rate{}; std::uint32_t channels{}; ~State() { if (resampler != nullptr) swr_free(&resampler); } }; AudioStreamDecoder::AudioStreamDecoder() : state_(std::make_unique()) {} AudioStreamDecoder::~AudioStreamDecoder() = default; AudioStreamDecoder::AudioStreamDecoder(AudioStreamDecoder &&) noexcept = default; AudioStreamDecoder &AudioStreamDecoder::operator=(AudioStreamDecoder &&) noexcept = default; bool AudioStreamDecoder::is_open() const noexcept { return state_->common.codec != nullptr; } void AudioStreamDecoder::close() noexcept { if (state_->resampler != nullptr) swr_free(&state_->resampler); state_->common.release(); } bool AudioStreamDecoder::open(const std::filesystem::path &path, std::uint32_t sample_rate, std::uint32_t channels, std::uint64_t start_sample) { close(); if (sample_rate == 0u || channels == 0u) return false; if (!state_->common.open_stream(path, AVMEDIA_TYPE_AUDIO)) return false; state_->sample_rate = sample_rate; state_->channels = channels; AVChannelLayout out_layout{}; av_channel_layout_default(&out_layout, static_cast(channels)); if (swr_alloc_set_opts2(&state_->resampler, &out_layout, AV_SAMPLE_FMT_S16, static_cast(sample_rate), &state_->common.codec->ch_layout, state_->common.codec->sample_fmt, state_->common.codec->sample_rate, 0, nullptr) < 0) { av_channel_layout_uninit(&out_layout); return false; } av_channel_layout_uninit(&out_layout); if (swr_init(state_->resampler) < 0) return false; if (start_sample != 0u) { const AVStream *stream = state_->common.format->streams[state_->common.stream_index]; const std::int64_t timestamp = av_rescale_q( static_cast(start_sample), AVRational{1, static_cast(sample_rate)}, stream->time_base); if (av_seek_frame(state_->common.format, state_->common.stream_index, timestamp, AVSEEK_FLAG_BACKWARD) >= 0) { avcodec_flush_buffers(state_->common.codec); } } return true; } std::size_t AudioStreamDecoder::read(std::span output) { if (!is_open()) return 0u; State &state = *state_; return state.common.drain(output, [&state]() -> bool { return state.common.decode_one([&state](AVFrame *frame) { const int out_samples = static_cast(av_rescale_rnd( swr_get_delay(state.resampler, frame->sample_rate) + frame->nb_samples, static_cast(state.sample_rate), frame->sample_rate, AV_ROUND_UP)); const std::size_t bytes = static_cast(out_samples) * state.channels * sizeof(std::int16_t); state.common.pending.resize(bytes); std::uint8_t *destination = state.common.pending.data(); const int converted = swr_convert(state.resampler, &destination, out_samples, const_cast(frame->data), frame->nb_samples); state.common.pending.resize(converted <= 0 ? 0u : static_cast(converted) * state.channels * sizeof(std::int16_t)); }); }); } namespace { std::vector extract_pmf_private_stream(const std::filesystem::path &path) { std::vector file; { std::FILE *handle = std::fopen(path.string().c_str(), "rb"); if (handle == nullptr) return {}; std::fseek(handle, 0, SEEK_END); const long size = std::ftell(handle); std::fseek(handle, 0, SEEK_SET); if (size > 0) { file.resize(static_cast(size)); if (std::fread(file.data(), 1u, file.size(), handle) != file.size()) file.clear(); } std::fclose(handle); } std::vector elementary; for (std::size_t i = 0u; i + 9u < file.size();) { if (!(file[i] == 0x00u && file[i + 1u] == 0x00u && file[i + 2u] == 0x01u && file[i + 3u] == 0xBDu)) { ++i; continue; } const std::size_t packet_length = static_cast(file[i + 4u]) * 256u + file[i + 5u]; const std::size_t header_data_length = file[i + 8u]; const std::size_t payload = i + 9u + header_data_length; if (packet_length < 3u + header_data_length) { ++i; continue; } const std::size_t payload_size = packet_length - 3u - header_data_length; constexpr std::size_t kSubstreamHeader = 4u; if (payload + payload_size > file.size() || payload_size <= kSubstreamHeader) { ++i; continue; } elementary.insert(elementary.end(), file.begin() + static_cast(payload + kSubstreamHeader), file.begin() + static_cast(payload + payload_size)); i = payload + payload_size; } return elementary; } constexpr std::size_t kPmfFrameHeader = 8u; std::size_t measure_atrac3p_frame_size(std::span stream) { const auto sync_at = [&](std::size_t index) { return index + 1u < stream.size() && stream[index] == 0x0Fu && stream[index + 1u] == 0xD0u; }; std::size_t first = stream.size(); for (std::size_t i = 0u; i + 1u < stream.size(); ++i) { if (sync_at(i)) { first = i; break; } } if (first == stream.size()) return 0u; for (std::size_t i = first + 2u; i + 1u < stream.size(); ++i) { if (sync_at(i)) return i - first; } return 0u; } } struct PmfAudioDecoder::State { AVCodecContext *codec{}; AVPacket *packet{}; AVFrame *frame{}; SwrContext *resampler{}; std::vector stream; std::size_t cursor{}; std::size_t frame_size{}; bool diag{}; std::vector pcm; std::size_t pcm_read{}; ~State() { release(); } void release() noexcept { if (resampler != nullptr) swr_free(&resampler); if (frame != nullptr) av_frame_free(&frame); if (packet != nullptr) av_packet_free(&packet); if (codec != nullptr) avcodec_free_context(&codec); stream.clear(); pcm.clear(); pcm.shrink_to_fit(); cursor = 0u; pcm_read = 0u; frame_size = 0u; } }; PmfAudioDecoder::PmfAudioDecoder() : state_(std::make_unique()) {} PmfAudioDecoder::~PmfAudioDecoder() = default; PmfAudioDecoder::PmfAudioDecoder(PmfAudioDecoder &&) noexcept = default; PmfAudioDecoder &PmfAudioDecoder::operator=(PmfAudioDecoder &&) noexcept = default; bool PmfAudioDecoder::is_open() const noexcept { return state_->codec != nullptr; } void PmfAudioDecoder::close() noexcept { state_->release(); } bool PmfAudioDecoder::open(const std::filesystem::path &path) { close(); State &state = *state_; state.stream = extract_pmf_private_stream(path); const bool diag = std::getenv("PSPRECOMP_MPEG_DIAG") != nullptr; state.diag = diag; if (diag) std::fprintf(stderr, "[pmf-audio] elementary=%zu bytes\n", state.stream.size()); if (state.stream.empty()) return false; state.frame_size = measure_atrac3p_frame_size(state.stream); if (diag) std::fprintf(stderr, "[pmf-audio] frame_size=%zu\n", state.frame_size); if (state.frame_size == 0u) return false; for (std::size_t i = 0u; i + 1u < state.stream.size(); ++i) { if (state.stream[i] == 0x0Fu && state.stream[i + 1u] == 0xD0u) { state.cursor = i; break; } } const AVCodec *decoder = avcodec_find_decoder(AV_CODEC_ID_ATRAC3P); if (decoder == nullptr) return false; state.codec = avcodec_alloc_context3(decoder); if (state.codec == nullptr) return false; state.codec->sample_rate = 44100; state.codec->block_align = static_cast(state.frame_size - kPmfFrameHeader); av_channel_layout_default(&state.codec->ch_layout, 2); if (avcodec_open2(state.codec, decoder, nullptr) < 0) { if (diag) std::fprintf(stderr, "[pmf-audio] avcodec_open2 failed\n"); return false; } state.packet = av_packet_alloc(); state.frame = av_frame_alloc(); if (state.packet == nullptr || state.frame == nullptr) return false; state.pcm.reserve(static_cast(state.stream.size() / state.frame_size) * 2048u * 2u * sizeof(std::int16_t)); while (state.cursor + state.frame_size <= state.stream.size()) { const std::size_t payload = state.cursor + kPmfFrameHeader; const std::size_t payload_size = state.frame_size - kPmfFrameHeader; state.cursor += state.frame_size; av_packet_unref(state.packet); if (av_new_packet(state.packet, static_cast(payload_size)) < 0) break; std::copy_n(state.stream.begin() + static_cast(payload), payload_size, state.packet->data); const int sent = avcodec_send_packet(state.codec, state.packet); av_packet_unref(state.packet); if (sent < 0) { if (diag) std::fprintf(stderr, "[pmf-audio] send_packet=%d\n", sent); continue; } if (avcodec_receive_frame(state.codec, state.frame) < 0) continue; const int samples = state.frame->nb_samples; const auto *left = reinterpret_cast(state.frame->data[0]); const auto *right = state.frame->ch_layout.nb_channels > 1 ? reinterpret_cast(state.frame->data[1]) : left; const auto to_pcm = [](float value) { return static_cast(std::clamp(value, -1.0f, 1.0f) * 32767.0f); }; for (int i = 0; i < samples; ++i) { const std::int16_t pair[2]{to_pcm(left[i]), to_pcm(right[i])}; const auto *bytes = reinterpret_cast(pair); state.pcm.insert(state.pcm.end(), bytes, bytes + sizeof(pair)); } av_frame_unref(state.frame); } if (diag) std::fprintf(stderr, "[pmf-audio] pcm=%zu bytes\n", state.pcm.size()); state.pcm_read = 0u; return !state.pcm.empty(); } std::size_t PmfAudioDecoder::read(std::span output) { if (!is_open()) return 0u; State &state = *state_; const std::size_t available = state.pcm.size() - state.pcm_read; const std::size_t take = std::min(available, output.size()); if (take == 0u) return 0u; std::copy_n(state.pcm.begin() + static_cast(state.pcm_read), take, output.begin()); state.pcm_read += take; return take; } struct VideoStreamDecoder::State { DecodeCommon common; SwsContext *scaler{}; int width{}; int height{}; ~State() { if (scaler != nullptr) sws_freeContext(scaler); } }; VideoStreamDecoder::VideoStreamDecoder() : state_(std::make_unique()) {} VideoStreamDecoder::~VideoStreamDecoder() = default; VideoStreamDecoder::VideoStreamDecoder(VideoStreamDecoder &&) noexcept = default; VideoStreamDecoder &VideoStreamDecoder::operator=(VideoStreamDecoder &&) noexcept = default; bool VideoStreamDecoder::is_open() const noexcept { return state_->common.codec != nullptr; } void VideoStreamDecoder::close() noexcept { if (state_->scaler != nullptr) { sws_freeContext(state_->scaler); state_->scaler = nullptr; } state_->common.release(); } bool VideoStreamDecoder::open(const std::filesystem::path &path) { close(); return state_->common.open_stream(path, AVMEDIA_TYPE_VIDEO); } std::size_t VideoStreamDecoder::read(std::span output) { if (!is_open()) return 0u; State &state = *state_; return state.common.drain(output, [&state]() -> bool { return state.common.decode_one([&state](AVFrame *frame) { if (state.scaler == nullptr || state.width != frame->width || state.height != frame->height) { if (state.scaler != nullptr) sws_freeContext(state.scaler); state.width = frame->width; state.height = frame->height; state.scaler = sws_getContext( frame->width, frame->height, static_cast(frame->format), frame->width, frame->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr, nullptr, nullptr); } if (state.scaler == nullptr) return; const std::size_t bytes = static_cast(frame->width) * frame->height * 4u; state.common.pending.resize(bytes); std::uint8_t *destination = state.common.pending.data(); const int stride = frame->width * 4; sws_scale(state.scaler, frame->data, frame->linesize, 0, frame->height, &destination, &stride); }); }); } }