// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include "audio_core/hle/ffmpeg_decoder.h" #include "common/dynamic_library/ffmpeg.h" using namespace DynamicLibrary; namespace AudioCore::HLE { class FFMPEGDecoder::Impl { public: explicit Impl(Memory::MemorySystem& memory); ~Impl(); std::optional ProcessRequest(const BinaryMessage& request); bool IsValid() const { return have_ffmpeg_dl; } private: std::optional Initalize(const BinaryMessage& request); void Clear(); std::optional Decode(const BinaryMessage& request); struct AVPacketDeleter { void operator()(AVPacket* packet) const { FFmpeg::av_packet_free(&packet); } }; struct AVCodecContextDeleter { void operator()(AVCodecContext* context) const { FFmpeg::avcodec_free_context(&context); } }; struct AVCodecParserContextDeleter { void operator()(AVCodecParserContext* parser) const { FFmpeg::av_parser_close(parser); } }; struct AVFrameDeleter { void operator()(AVFrame* frame) const { FFmpeg::av_frame_free(&frame); } }; bool initalized = false; bool have_ffmpeg_dl; Memory::MemorySystem& memory; const AVCodec* codec; std::unique_ptr av_context; std::unique_ptr parser; std::unique_ptr av_packet; std::unique_ptr decoded_frame; }; FFMPEGDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { have_ffmpeg_dl = FFmpeg::LoadFFmpeg(); } FFMPEGDecoder::Impl::~Impl() = default; std::optional FFMPEGDecoder::Impl::ProcessRequest(const BinaryMessage& request) { if (request.header.codec != DecoderCodec::DecodeAAC) { LOG_ERROR(Audio_DSP, "Got wrong codec {}", static_cast(request.header.codec)); return {}; } switch (request.header.cmd) { case DecoderCommand::Init: { return Initalize(request); } case DecoderCommand::EncodeDecode: { return Decode(request); } case DecoderCommand::Shutdown: case DecoderCommand::SaveState: case DecoderCommand::LoadState: { LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}", static_cast(request.header.cmd)); BinaryMessage response = request; response.header.result = ResultStatus::Success; return response; } default: LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", static_cast(request.header.cmd)); return {}; } } std::optional FFMPEGDecoder::Impl::Initalize(const BinaryMessage& request) { if (initalized) { Clear(); } BinaryMessage response = request; response.header.result = ResultStatus::Success; if (!have_ffmpeg_dl) { return response; } av_packet.reset(FFmpeg::av_packet_alloc()); codec = FFmpeg::avcodec_find_decoder(AV_CODEC_ID_AAC); if (!codec) { LOG_ERROR(Audio_DSP, "Codec not found\n"); return response; } parser.reset(FFmpeg::av_parser_init(codec->id)); if (!parser) { LOG_ERROR(Audio_DSP, "Parser not found\n"); return response; } av_context.reset(FFmpeg::avcodec_alloc_context3(codec)); if (!av_context) { LOG_ERROR(Audio_DSP, "Could not allocate audio codec context\n"); return response; } if (FFmpeg::avcodec_open2(av_context.get(), codec, nullptr) < 0) { LOG_ERROR(Audio_DSP, "Could not open codec\n"); return response; } initalized = true; return response; } void FFMPEGDecoder::Impl::Clear() { if (!have_ffmpeg_dl) { return; } av_context.reset(); parser.reset(); decoded_frame.reset(); av_packet.reset(); } std::optional FFMPEGDecoder::Impl::Decode(const BinaryMessage& request) { BinaryMessage response{}; response.header.codec = request.header.codec; response.header.cmd = request.header.cmd; response.decode_aac_response.size = request.decode_aac_request.size; if (!initalized) { LOG_DEBUG(Audio_DSP, "Decoder not initalized"); // This is a hack to continue games that are not compiled with the aac codec response.decode_aac_response.num_channels = 2; response.decode_aac_response.num_samples = 1024; return response; } if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR || request.decode_aac_request.src_addr + request.decode_aac_request.size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", request.decode_aac_request.src_addr); return {}; } u8* data = memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR); std::array, 2> out_streams; std::size_t data_size = request.decode_aac_request.size; while (data_size > 0) { if (!decoded_frame) { decoded_frame.reset(FFmpeg::av_frame_alloc()); if (!decoded_frame) { LOG_ERROR(Audio_DSP, "Could not allocate audio frame"); return {}; } } int ret = FFmpeg::av_parser_parse2(parser.get(), av_context.get(), &av_packet->data, &av_packet->size, data, static_cast(data_size), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); if (ret < 0) { LOG_ERROR(Audio_DSP, "Error while parsing"); return {}; } data += ret; data_size -= ret; ret = FFmpeg::avcodec_send_packet(av_context.get(), av_packet.get()); if (ret < 0) { LOG_ERROR(Audio_DSP, "Error submitting the packet to the decoder"); return {}; } if (av_packet->size) { while (ret >= 0) { ret = FFmpeg::avcodec_receive_frame(av_context.get(), decoded_frame.get()); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break; else if (ret < 0) { LOG_ERROR(Audio_DSP, "Error during decoding"); return {}; } int bytes_per_sample = FFmpeg::av_get_bytes_per_sample(av_context->sample_fmt); if (bytes_per_sample < 0) { LOG_ERROR(Audio_DSP, "Failed to calculate data size"); return {}; } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100) auto num_channels = static_cast(decoded_frame->ch_layout.nb_channels); #else auto num_channels = static_cast(decoded_frame->channels); #endif ASSERT(num_channels <= out_streams.size()); std::size_t size = bytes_per_sample * (decoded_frame->nb_samples); response.decode_aac_response.sample_rate = GetSampleRateEnum(decoded_frame->sample_rate); response.decode_aac_response.num_channels = num_channels; response.decode_aac_response.num_samples += decoded_frame->nb_samples; // FFmpeg converts to 32 signed floating point PCM, we need s16 PCM so we need to // convert it f32 val_float; for (std::size_t current_pos(0); current_pos < size;) { for (std::size_t channel(0); channel < num_channels; channel++) { std::memcpy(&val_float, decoded_frame->data[channel] + current_pos, sizeof(val_float)); val_float = std::clamp(val_float, -1.0f, 1.0f); s16 val = static_cast(0x7FFF * val_float); out_streams[channel].push_back(val & 0xFF); out_streams[channel].push_back(val >> 8); } current_pos += sizeof(val_float); } } } } if (out_streams[0].size() != 0) { if (request.decode_aac_request.dst_addr_ch0 < Memory::FCRAM_PADDR || request.decode_aac_request.dst_addr_ch0 + out_streams[0].size() > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}", request.decode_aac_request.dst_addr_ch0); return {}; } std::memcpy( memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch0 - Memory::FCRAM_PADDR), out_streams[0].data(), out_streams[0].size()); } if (out_streams[1].size() != 0) { if (request.decode_aac_request.dst_addr_ch1 < Memory::FCRAM_PADDR || request.decode_aac_request.dst_addr_ch1 + out_streams[1].size() > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}", request.decode_aac_request.dst_addr_ch1); return {}; } std::memcpy( memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch1 - Memory::FCRAM_PADDR), out_streams[1].data(), out_streams[1].size()); } return response; } FFMPEGDecoder::FFMPEGDecoder(Memory::MemorySystem& memory) : impl(std::make_unique(memory)) {} FFMPEGDecoder::~FFMPEGDecoder() = default; std::optional FFMPEGDecoder::ProcessRequest(const BinaryMessage& request) { return impl->ProcessRequest(request); } bool FFMPEGDecoder::IsValid() const { return impl->IsValid(); } } // namespace AudioCore::HLE