diff --git a/.travis/linux-frozen/docker.sh b/.travis/linux-frozen/docker.sh index a4814da1a..0c4e72b4f 100755 --- a/.travis/linux-frozen/docker.sh +++ b/.travis/linux-frozen/docker.sh @@ -3,7 +3,7 @@ cd /citra mkdir build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON +cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_FFMPEG=OFF make -j4 ctest -VV -C Release diff --git a/.travis/linux/docker.sh b/.travis/linux/docker.sh index 10ce0900c..c52590119 100755 --- a/.travis/linux/docker.sh +++ b/.travis/linux/docker.sh @@ -3,7 +3,7 @@ cd /citra mkdir build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_SCRIPTING=ON +cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_SCRIPTING=ON -DENABLE_FFMPEG=OFF make -j4 ctest -VV -C Release diff --git a/.travis/macos/deps.sh b/.travis/macos/deps.sh index dbb6b984b..6eaaf050f 100755 --- a/.travis/macos/deps.sh +++ b/.travis/macos/deps.sh @@ -1,4 +1,4 @@ #!/bin/sh -ex brew update -brew install qt5 sdl2 dylibbundler p7zip ccache +brew install qt5 sdl2 dylibbundler p7zip ccache ffmpeg diff --git a/.travis/transifex/docker.sh b/.travis/transifex/docker.sh index 99b415459..2d40ba663 100644 --- a/.travis/transifex/docker.sh +++ b/.travis/transifex/docker.sh @@ -26,7 +26,7 @@ tx --version cd /citra mkdir build && cd build -cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF -DENABLE_SCRIPTING=OFF +cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_SDL2=OFF -DENABLE_SCRIPTING=OFF -DENABLE_FFMPEG=OFF make translation cd .. diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ba175a1d..2ce8de3af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,10 +20,14 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) +option(ENABLE_FFMPEG "Enable FFmpeg decoder/encoder" ON) + option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) option(ENABLE_SCRIPTING "Enables scripting support" OFF) +CMAKE_DEPENDENT_OPTION(CITRA_USE_BUNDLED_FFMPEG "Download bundled FFmpeg binaries" ON "MSVC" OFF) + if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit) message(STATUS "Copying pre-commit hook") file(COPY hooks/pre-commit @@ -253,6 +257,31 @@ if (ENABLE_QT) endif() endif() +if (ENABLE_FFMPEG) + if (CITRA_USE_BUNDLED_FFMPEG) + if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64) + set(FFmpeg_VER "ffmpeg-4.0.2-msvc") + else() + message(FATAL_ERROR "No bundled FFmpeg binaries for your toolchain. Disable CITRA_USE_BUNDLED_FFMPEG and provide your own.") + endif() + + if (DEFINED FFmpeg_VER) + download_bundled_external("ffmpeg/" ${FFmpeg_VER} FFmpeg_PREFIX) + set(FFMPEG_DIR "${FFmpeg_PREFIX}") + set(FFMPEG_FOUND YES) + endif() + else() + find_package(FFmpeg REQUIRED COMPONENTS avcodec) + if ("${FFmpeg_avcodec_VERSION}" VERSION_LESS "57.48.101") + message(FATAL_ERROR "Found version for libavcodec is too low. The required version is at least 57.48.101 (included in FFmpeg 3.1 and later).") + else() + set(FFMPEG_FOUND YES) + endif() + endif() +else() + set(FFMPEG_FOUND NO) +endif() + if (ENABLE_SCRIPTING) add_definitions(-DENABLE_SCRIPTING) endif() diff --git a/appveyor.yml b/appveyor.yml index 3e4299250..fcb824d74 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ install: - git submodule update --init --recursive - ps: | if ($env:BUILD_TYPE -eq 'mingw') { - $dependencies = "mingw64/mingw-w64-x86_64-qt5" + $dependencies = "mingw64/mingw-w64-x86_64-qt5 mingw64/mingw-w64-x86_64-ffmpeg" # redirect err to null to prevent warnings from becoming errors # workaround to prevent pacman from failing due to cyclical dependencies C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw64/mingw-w64-x86_64-freetype mingw64/mingw-w64-x86_64-fontconfig" 2> $null diff --git a/externals/cmake-modules/FindFFmpeg.cmake b/externals/cmake-modules/FindFFmpeg.cmake new file mode 100644 index 000000000..6cb5960bb --- /dev/null +++ b/externals/cmake-modules/FindFFmpeg.cmake @@ -0,0 +1,183 @@ +# FindFFmpeg +# ---------- +# +# Find the native FFmpeg includes and libraries +# +# This module defines the following variables: +# +# FFmpeg_INCLUDE_: where to find .h +# FFmpeg_LIBRARY_: where to find the library +# FFmpeg_INCLUDES: aggregate all the include paths +# FFmpeg_LIBRARIES: aggregate all the paths to the libraries +# FFmpeg_FOUND: True if all components have been found +# +# This module defines the following targets, which are prefered over variables: +# +# FFmpeg::: Target to use directly, with include path, +# library and dependencies set up. If you are using a static build, you are +# responsible for adding any external dependencies (such as zlib, bzlib...). +# +# can be one of: +# avcodec +# avdevice +# avfilter +# avformat +# postproc +# swresample +# swscale +# + +set(_FFmpeg_ALL_COMPONENTS + avcodec + avdevice + avfilter + avformat + avutil + postproc + swresample + swscale +) + +set(_FFmpeg_DEPS_avcodec avutil) +set(_FFmpeg_DEPS_avdevice avcodec avformat avutil) +set(_FFmpeg_DEPS_avfilter avutil) +set(_FFmpeg_DEPS_avformat avcodec avutil) +set(_FFmpeg_DEPS_postproc avutil) +set(_FFmpeg_DEPS_swresample avutil) +set(_FFmpeg_DEPS_swscale avutil) + +function(find_ffmpeg LIBNAME) + if(DEFINED ENV{FFMPEG_DIR}) + set(FFMPEG_DIR $ENV{FFMPEG_DIR}) + endif() + + if(FFMPEG_DIR) + list(APPEND INCLUDE_PATHS + ${FFMPEG_DIR} + ${FFMPEG_DIR}/ffmpeg + ${FFMPEG_DIR}/lib${LIBNAME} + ${FFMPEG_DIR}/include/lib${LIBNAME} + ${FFMPEG_DIR}/include/ffmpeg + ${FFMPEG_DIR}/include + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH + ) + list(APPEND LIB_PATHS + ${FFMPEG_DIR} + ${FFMPEG_DIR}/lib + ${FFMPEG_DIR}/lib${LIBNAME} + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH + ) + else() + list(APPEND INCLUDE_PATHS + /usr/local/include/ffmpeg + /usr/local/include/lib${LIBNAME} + /usr/include/ffmpeg + /usr/include/lib${LIBNAME} + /usr/include/ffmpeg/lib${LIBNAME} + ) + + list(APPEND LIB_PATHS + /usr/local/lib + /usr/lib + ) + endif() + + find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h + HINTS ${INCLUDE_PATHS} + ) + + find_library(FFmpeg_LIBRARY_${LIBNAME} ${LIBNAME} + HINTS ${LIB_PATHS} + ) + + if(NOT FFMPEG_DIR AND (NOT FFmpeg_LIBRARY_${LIBNAME} OR NOT FFmpeg_INCLUDE_${LIBNAME})) + # Didn't find it in the usual paths, try pkg-config + find_package(PkgConfig QUIET) + pkg_check_modules(FFmpeg_PKGCONFIG_${LIBNAME} QUIET lib${LIBNAME}) + + find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h + ${FFmpeg_PKGCONFIG_${LIBNAME}_INCLUDE_DIRS} + ) + + find_library(FFmpeg_LIBRARY_${LIBNAME} ${LIBNAME} + ${FFmpeg_PKGCONFIG_${LIBNAME}_LIBRARY_DIRS} + ) + endif() + + if(FFmpeg_INCLUDE_${LIBNAME} AND FFmpeg_LIBRARY_${LIBNAME}) + set(FFmpeg_INCLUDE_${LIBNAME} "${FFmpeg_INCLUDE_${LIBNAME}}" PARENT_SCOPE) + set(FFmpeg_LIBRARY_${LIBNAME} "${FFmpeg_LIBRARY_${LIBNAME}}" PARENT_SCOPE) + + # Extract FFmpeg version from version.h + foreach(v MAJOR MINOR MICRO) + set(FFmpeg_${LIBNAME}_VERSION_${v} 0) + endforeach() + string(TOUPPER ${LIBNAME} LIBNAME_UPPER) + file(STRINGS "${FFmpeg_INCLUDE_${LIBNAME}}/lib${LIBNAME}/version.h" _FFmpeg_VERSION_H_CONTENTS REGEX "#define LIB${LIBNAME_UPPER}_VERSION_(MAJOR|MINOR|MICRO) ") + set(_FFmpeg_VERSION_REGEX "([0-9]+)") + foreach(v MAJOR MINOR MICRO) + if("${_FFmpeg_VERSION_H_CONTENTS}" MATCHES "#define LIB${LIBNAME_UPPER}_VERSION_${v}[\\t ]+${_FFmpeg_VERSION_REGEX}") + set(FFmpeg_${LIBNAME}_VERSION_${v} "${CMAKE_MATCH_1}") + endif() + endforeach() + set(FFmpeg_${LIBNAME}_VERSION "${FFmpeg_${LIBNAME}_VERSION_MAJOR}.${FFmpeg_${LIBNAME}_VERSION_MINOR}.${FFmpeg_${LIBNAME}_VERSION_MICRO}") + set(FFmpeg_${c}_VERSION "${FFmpeg_${LIBNAME}_VERSION}" PARENT_SCOPE) + unset(_FFmpeg_VERSION_REGEX) + unset(_FFmpeg_VERSION_H_CONTENTS) + + set(FFmpeg_${c}_FOUND TRUE PARENT_SCOPE) + if(NOT FFmpeg_FIND_QUIETLY) + message("-- Found ${LIBNAME}: ${FFmpeg_INCLUDE_${LIBNAME}} ${FFmpeg_LIBRARY_${LIBNAME}} (version: ${FFmpeg_${LIBNAME}_VERSION})") + endif() + endif() +endfunction() + +foreach(c ${_FFmpeg_ALL_COMPONENTS}) + find_ffmpeg(${c}) +endforeach() + +foreach(c ${_FFmpeg_ALL_COMPONENTS}) + if(FFmpeg_${c}_FOUND) + list(APPEND FFmpeg_INCLUDES ${FFmpeg_INCLUDE_${c}}) + list(APPEND FFmpeg_LIBRARIES ${FFmpeg_LIBRARY_${c}}) + + add_library(FFmpeg::${c} IMPORTED UNKNOWN) + set_target_properties(FFmpeg::${c} PROPERTIES + IMPORTED_LOCATION ${FFmpeg_LIBRARY_${c}} + INTERFACE_INCLUDE_DIRECTORIES ${FFmpeg_INCLUDE_${c}} + ) + if(_FFmpeg_DEPS_${c}) + set(deps) + foreach(dep ${_FFmpeg_DEPS_${c}}) + list(APPEND deps FFmpeg::${dep}) + endforeach() + + set_target_properties(FFmpeg::${c} PROPERTIES + INTERFACE_LINK_LIBRARIES "${deps}" + ) + unset(deps) + endif() + endif() +endforeach() + +if(FFmpeg_INCLUDES) + list(REMOVE_DUPLICATES FFmpeg_INCLUDES) +endif() + +foreach(c ${FFmpeg_FIND_COMPONENTS}) + list(APPEND _FFmpeg_REQUIRED_VARS FFmpeg_INCLUDE_${c} FFmpeg_LIBRARY_${c}) +endforeach() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FFmpeg + REQUIRED_VARS ${_FFmpeg_REQUIRED_VARS} + HANDLE_COMPONENTS +) + +foreach(c ${_FFmpeg_ALL_COMPONENTS}) + unset(_FFmpeg_DEPS_${c}) +endforeach() +unset(_FFmpeg_ALL_COMPONENTS) +unset(_FFmpeg_REQUIRED_VARS) diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index de6079edc..ce73ecc03 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -5,6 +5,8 @@ add_library(audio_core STATIC dsp_interface.cpp dsp_interface.h hle/common.h + hle/decoder.cpp + hle/decoder.h hle/filter.cpp hle/filter.h hle/hle.cpp @@ -25,6 +27,7 @@ add_library(audio_core STATIC $<$:sdl2_sink.cpp sdl2_sink.h> $<$:cubeb_sink.cpp cubeb_sink.h> + $<$:hle/aac_decoder.cpp hle/aac_decoder.h hle/ffmpeg_dl.h> ) create_target_directory_groups(audio_core) @@ -42,3 +45,12 @@ if(ENABLE_CUBEB) add_definitions(-DHAVE_CUBEB=1) endif() +if(FFMPEG_FOUND) + if(UNIX) + target_link_libraries(audio_core PRIVATE FFmpeg::avcodec) + else() + target_include_directories(audio_core PRIVATE ${FFMPEG_DIR}/include) + endif() + target_compile_definitions(audio_core PRIVATE HAVE_FFMPEG) +endif() + diff --git a/src/audio_core/hle/aac_decoder.cpp b/src/audio_core/hle/aac_decoder.cpp new file mode 100644 index 000000000..1e8c56163 --- /dev/null +++ b/src/audio_core/hle/aac_decoder.cpp @@ -0,0 +1,241 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/hle/aac_decoder.h" +#include "audio_core/hle/ffmpeg_dl.h" + +namespace AudioCore::HLE { + +class AACDecoder::Impl { +public: + Impl(Memory::MemorySystem& memory); + ~Impl(); + std::optional ProcessRequest(const BinaryRequest& request); + +private: + std::optional Initalize(const BinaryRequest& request); + + void Clear(); + + std::optional Decode(const BinaryRequest& request); + + bool initalized; + bool have_ffmpeg_dl; + + Memory::MemorySystem& memory; + + AVCodec* codec; + AVCodecContext* av_context = nullptr; + AVCodecParserContext* parser = nullptr; + AVPacket* av_packet; + AVFrame* decoded_frame = nullptr; +}; + +AACDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { + initalized = false; + + have_ffmpeg_dl = InitFFmpegDL(); +} + +AACDecoder::Impl::~Impl() { + if (initalized) + Clear(); +} + +std::optional AACDecoder::Impl::ProcessRequest(const BinaryRequest& request) { + if (request.codec != DecoderCodec::AAC) { + LOG_ERROR(Audio_DSP, "Got wrong codec {}", static_cast(request.codec)); + return {}; + } + + switch (request.cmd) { + case DecoderCommand::Init: { + return Initalize(request); + break; + } + case DecoderCommand::Decode: { + return Decode(request); + break; + } + case DecoderCommand::Unknown: { + BinaryResponse response; + std::memcpy(&response, &request, sizeof(response)); + response.unknown1 = 0x0; + return response; + break; + } + default: + LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", static_cast(request.cmd)); + return {}; + break; + } +} + +std::optional AACDecoder::Impl::Initalize(const BinaryRequest& request) { + if (initalized) { + Clear(); + } + + if (!have_ffmpeg_dl) { + return {}; + } + + av_packet = av_packet_alloc_dl(); + + codec = avcodec_find_decoder_dl(AV_CODEC_ID_AAC); + if (!codec) { + LOG_ERROR(Audio_DSP, "Codec not found\n"); + return {}; + } + + parser = av_parser_init_dl(codec->id); + if (!parser) { + LOG_ERROR(Audio_DSP, "Parser not found\n"); + return {}; + } + + av_context = avcodec_alloc_context3_dl(codec); + if (!av_context) { + LOG_ERROR(Audio_DSP, "Could not allocate audio codec context\n"); + return {}; + } + + if (avcodec_open2_dl(av_context, codec, NULL) < 0) { + LOG_ERROR(Audio_DSP, "Could not open codec\n"); + return {}; + } + + initalized = true; + + BinaryResponse response; + std::memcpy(&response, &request, sizeof(response)); + response.unknown1 = 0x0; + return response; +} + +void AACDecoder::Impl::Clear() { + if (!have_ffmpeg_dl) { + return; + } + + avcodec_free_context_dl(&av_context); + av_parser_close_dl(parser); + av_frame_free_dl(&decoded_frame); + av_packet_free_dl(&av_packet); +} + +std::optional AACDecoder::Impl::Decode(const BinaryRequest& request) { + if (!initalized) { + LOG_DEBUG(Audio_DSP, "Decoder not initalized"); + + // This is a hack to continue games that are not compiled with the aac codec + BinaryResponse response; + response.codec = request.codec; + response.cmd = request.cmd; + response.num_channels = 2; + response.num_samples = 1024; + response.size = request.size; + return response; + } + + if (request.src_addr < Memory::FCRAM_PADDR) { + LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", request.src_addr); + return {}; + } + u8* data = memory.GetFCRAMPointer(request.src_addr - Memory::FCRAM_PADDR); + + std::array, 2> out_streams; + + std::size_t data_size = request.size; + while (data_size > 0) { + if (!decoded_frame) { + if (!(decoded_frame = av_frame_alloc_dl())) { + LOG_ERROR(Audio_DSP, "Could not allocate audio frame"); + return {}; + } + } + + int ret = av_parser_parse2_dl(parser, av_context, &av_packet->data, &av_packet->size, data, + 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 = avcodec_send_packet_dl(av_context, av_packet); + if (ret < 0) { + LOG_ERROR(Audio_DSP, "Error submitting the packet to the decoder"); + return {}; + } + + if (av_packet->size) { + while (ret >= 0) { + ret = avcodec_receive_frame_dl(av_context, decoded_frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + break; + else if (ret < 0) { + LOG_ERROR(Audio_DSP, "Error during decoding"); + return {}; + } + int bytes_per_sample = av_get_bytes_per_sample_dl(av_context->sample_fmt); + if (bytes_per_sample < 0) { + LOG_ERROR(Audio_DSP, "Failed to calculate data size"); + return {}; + } + + ASSERT(decoded_frame->channels == out_streams.size()); + + std::size_t size = bytes_per_sample * (decoded_frame->nb_samples); + + // FFmpeg converts to 32 signed floating point PCM, we need s32 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 < out_streams.size(); channel++) { + std::memcpy(&val_float, decoded_frame->data[0] + current_pos, + sizeof(val_float)); + 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 (request.dst_addr_ch0 < Memory::FCRAM_PADDR) { + LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}", request.dst_addr_ch0); + return {}; + } + std::memcpy(memory.GetFCRAMPointer(request.dst_addr_ch0 - Memory::FCRAM_PADDR), + out_streams[0].data(), out_streams[0].size()); + + if (request.dst_addr_ch1 < Memory::FCRAM_PADDR) { + LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}", request.dst_addr_ch1); + return {}; + } + std::memcpy(memory.GetFCRAMPointer(request.dst_addr_ch1 - Memory::FCRAM_PADDR), + out_streams[1].data(), out_streams[1].size()); + + BinaryResponse response; + response.codec = request.codec; + response.cmd = request.cmd; + response.num_channels = 2; + response.num_samples = decoded_frame->nb_samples; + response.size = request.size; + return response; +} + +AACDecoder::AACDecoder(Memory::MemorySystem& memory) : impl(std::make_unique(memory)) {} + +AACDecoder::~AACDecoder() = default; + +std::optional AACDecoder::ProcessRequest(const BinaryRequest& request) { + return impl->ProcessRequest(request); +} + +} // namespace AudioCore::HLE diff --git a/src/audio_core/hle/aac_decoder.h b/src/audio_core/hle/aac_decoder.h new file mode 100644 index 000000000..96e47b61b --- /dev/null +++ b/src/audio_core/hle/aac_decoder.h @@ -0,0 +1,22 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "audio_core/hle/decoder.h" + +namespace AudioCore::HLE { + +class AACDecoder final : public DecoderBase { +public: + AACDecoder(Memory::MemorySystem& memory); + ~AACDecoder() override; + std::optional ProcessRequest(const BinaryRequest& request) override; + +private: + class Impl; + std::unique_ptr impl; +}; + +} // namespace AudioCore::HLE diff --git a/src/audio_core/hle/decoder.cpp b/src/audio_core/hle/decoder.cpp new file mode 100644 index 000000000..4832aab73 --- /dev/null +++ b/src/audio_core/hle/decoder.cpp @@ -0,0 +1,36 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/hle/decoder.h" + +namespace AudioCore::HLE { + +NullDecoder::NullDecoder() {} + +NullDecoder::~NullDecoder() = default; + +std::optional NullDecoder::ProcessRequest(const BinaryRequest& request) { + BinaryResponse response; + switch (request.cmd) { + case DecoderCommand::Init: + case DecoderCommand::Unknown: + std::memcpy(&response, &request, sizeof(response)); + response.unknown1 = 0x0; + return response; + break; + case DecoderCommand::Decode: + response.codec = request.codec; + response.cmd = DecoderCommand::Decode; + response.num_channels = 2; // Just assume stereo here + response.size = request.size; + response.num_samples = 1024; // Just assume 1024 here + return response; + break; + default: + LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", static_cast(request.cmd)); + return {}; + break; + } +}; +} // namespace AudioCore::HLE diff --git a/src/audio_core/hle/decoder.h b/src/audio_core/hle/decoder.h new file mode 100644 index 000000000..fc2859760 --- /dev/null +++ b/src/audio_core/hle/decoder.h @@ -0,0 +1,68 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "common/common_types.h" +#include "common/swap.h" +#include "core/core.h" + +namespace AudioCore::HLE { + +enum class DecoderCommand : u16 { + Init, + Decode, + Unknown, +}; + +enum class DecoderCodec : u16 { + None, + AAC, +}; + +struct BinaryRequest { + enum_le codec = + DecoderCodec::None; // this is a guess. until now only 0x1 was observed here + enum_le cmd = DecoderCommand::Init; + u32_le fixed = 0; + u32_le src_addr = 0; + u32_le size = 0; + u32_le dst_addr_ch0 = 0; + u32_le dst_addr_ch1 = 0; + u32_le unknown1 = 0; + u32_le unknown2 = 0; +}; +static_assert(sizeof(BinaryRequest) == 32, "Unexpected struct size for BinaryRequest"); + +struct BinaryResponse { + enum_le codec = + DecoderCodec::None; // this could be something else. until now only 0x1 was observed here + enum_le cmd = DecoderCommand::Init; + u32_le unknown1 = 0; + u32_le unknown2 = 0; + u32_le num_channels = 0; // this is a guess, so far I only observed 2 here + u32_le size = 0; + u32_le unknown3 = 0; + u32_le unknown4 = 0; + u32_le num_samples = 0; // this is a guess, so far I only observed 1024 here +}; +static_assert(sizeof(BinaryResponse) == 32, "Unexpected struct size for BinaryResponse"); + +class DecoderBase { +public: + virtual ~DecoderBase(){}; + virtual std::optional ProcessRequest(const BinaryRequest& request) = 0; +}; + +class NullDecoder final : public DecoderBase { +public: + NullDecoder(); + ~NullDecoder() override; + std::optional ProcessRequest(const BinaryRequest& request) override; +}; + +} // namespace AudioCore::HLE diff --git a/src/audio_core/hle/ffmpeg_dl.h b/src/audio_core/hle/ffmpeg_dl.h new file mode 100644 index 000000000..26aad1d96 --- /dev/null +++ b/src/audio_core/hle/ffmpeg_dl.h @@ -0,0 +1,213 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#ifdef _WIN32 +#include +#endif + +#include "common/logging/log.h" + +extern "C" { +#include "libavcodec/avcodec.h" +} + +#ifdef _WIN32 + +template +struct FuncDL { + FuncDL() = default; + FuncDL(HMODULE dll, const char* name) { + ptr_function = nullptr; + if (dll) { + *(void**)&ptr_function = (void*)GetProcAddress(dll, name); + } + } + + operator T*() const { + return ptr_function; + } + + operator bool() const { + return ptr_function != nullptr; + } + + T* ptr_function = nullptr; + ; +}; + +FuncDL av_get_bytes_per_sample_dl; +FuncDL av_frame_alloc_dl; +FuncDL av_frame_free_dl; +FuncDL avcodec_alloc_context3_dl; +FuncDL avcodec_free_context_dl; +FuncDL avcodec_open2_dl; +FuncDL av_packet_alloc_dl; +FuncDL av_packet_free_dl; +FuncDL avcodec_find_decoder_dl; +FuncDL avcodec_send_packet_dl; +FuncDL avcodec_receive_frame_dl; +FuncDL av_parser_init_dl; +FuncDL + av_parser_parse2_dl; +FuncDL av_parser_close_dl; + +bool InitFFmpegDL() { + HMODULE dll_util = nullptr; + dll_util = LoadLibrary("avutil-56.dll"); + if (!dll_util) { + DWORD errorMessageID = GetLastError(); + LPSTR messageBuffer = nullptr; + size_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&messageBuffer, 0, NULL); + + std::string message(messageBuffer, size); + + // Free the buffer. + LocalFree(messageBuffer); + LOG_ERROR(Audio_DSP, "Could not load avcodec-58.dll: {}", message); + return false; + } + + HMODULE dll_codec = nullptr; + dll_codec = LoadLibrary("avcodec-58.dll"); + if (!dll_codec) { + DWORD errorMessageID = GetLastError(); + LPSTR messageBuffer = nullptr; + size_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&messageBuffer, 0, NULL); + + std::string message(messageBuffer, size); + + // Free the buffer. + LocalFree(messageBuffer); + LOG_ERROR(Audio_DSP, "Could not load avcodec-58.dll: {}", message); + return false; + } + av_get_bytes_per_sample_dl = FuncDL(dll_util, "av_get_bytes_per_sample"); + if (!av_get_bytes_per_sample_dl) { + LOG_ERROR(Audio_DSP, "Can not load function av_get_bytes_per_sample"); + return false; + } + + av_frame_alloc_dl = FuncDL(dll_util, "av_frame_alloc"); + if (!av_frame_alloc_dl) { + LOG_ERROR(Audio_DSP, "Can not load function av_frame_alloc"); + return false; + } + + av_frame_free_dl = FuncDL(dll_util, "av_frame_free"); + if (!av_frame_free_dl) { + LOG_ERROR(Audio_DSP, "Can not load function av_frame_free"); + return false; + } + + avcodec_alloc_context3_dl = + FuncDL(dll_codec, "avcodec_alloc_context3"); + if (!avcodec_alloc_context3_dl) { + LOG_ERROR(Audio_DSP, "Can not load function avcodec_alloc_context3"); + return false; + } + + avcodec_free_context_dl = FuncDL(dll_codec, "avcodec_free_context"); + if (!av_get_bytes_per_sample_dl) { + LOG_ERROR(Audio_DSP, "Can not load function avcodec_free_context"); + return false; + } + + avcodec_open2_dl = + FuncDL(dll_codec, "avcodec_open2"); + if (!avcodec_open2_dl) { + LOG_ERROR(Audio_DSP, "Can not load function avcodec_open2"); + return false; + } + av_packet_alloc_dl = FuncDL(dll_codec, "av_packet_alloc"); + if (!av_packet_alloc_dl) { + LOG_ERROR(Audio_DSP, "Can not load function av_packet_alloc"); + return false; + } + + av_packet_free_dl = FuncDL(dll_codec, "av_packet_free"); + if (!av_packet_free_dl) { + LOG_ERROR(Audio_DSP, "Can not load function av_packet_free"); + return false; + } + + avcodec_find_decoder_dl = FuncDL(dll_codec, "avcodec_find_decoder"); + if (!avcodec_find_decoder_dl) { + LOG_ERROR(Audio_DSP, "Can not load function avcodec_find_decoder"); + return false; + } + + avcodec_send_packet_dl = + FuncDL(dll_codec, "avcodec_send_packet"); + if (!avcodec_send_packet_dl) { + LOG_ERROR(Audio_DSP, "Can not load function avcodec_send_packet"); + return false; + } + + avcodec_receive_frame_dl = + FuncDL(dll_codec, "avcodec_receive_frame"); + if (!avcodec_receive_frame_dl) { + LOG_ERROR(Audio_DSP, "Can not load function avcodec_receive_frame"); + return false; + } + + av_parser_init_dl = FuncDL(dll_codec, "av_parser_init"); + if (!av_parser_init_dl) { + LOG_ERROR(Audio_DSP, "Can not load function av_parser_init"); + return false; + } + + av_parser_parse2_dl = + FuncDL(dll_codec, "av_parser_parse2"); + if (!av_parser_parse2_dl) { + LOG_ERROR(Audio_DSP, "Can not load function av_parser_parse2"); + return false; + } + + av_parser_close_dl = FuncDL(dll_codec, "av_parser_close"); + if (!av_parser_close_dl) { + LOG_ERROR(Audio_DSP, "Can not load function av_parser_close"); + return false; + } + + return true; +} + +#endif // _Win32 + +#if defined(__APPLE__) || defined(__linux__) + +// No dynamic loading for Unix and Apple + +const auto av_get_bytes_per_sample_dl = &av_get_bytes_per_sample; +const auto av_frame_alloc_dl = &av_frame_alloc; +const auto av_frame_free_dl = &av_frame_free; +const auto avcodec_alloc_context3_dl = &avcodec_alloc_context3; +const auto avcodec_free_context_dl = &avcodec_free_context; +const auto avcodec_open2_dl = &avcodec_open2; +const auto av_packet_alloc_dl = &av_packet_alloc; +const auto av_packet_free_dl = &av_packet_free; +const auto avcodec_find_decoder_dl = &avcodec_find_decoder; +const auto avcodec_send_packet_dl = &avcodec_send_packet; +const auto avcodec_receive_frame_dl = &avcodec_receive_frame; +const auto av_parser_init_dl = &av_parser_init; +const auto av_parser_parse2_dl = &av_parser_parse2; +const auto av_parser_close_dl = &av_parser_close; + +bool InitFFmpegDL() { + return true; +} + +#endif // defined(__APPLE__) || defined(__linux__) diff --git a/src/audio_core/hle/hle.cpp b/src/audio_core/hle/hle.cpp index 4fa4145e2..fca606218 100644 --- a/src/audio_core/hle/hle.cpp +++ b/src/audio_core/hle/hle.cpp @@ -3,7 +3,11 @@ // Refer to the license.txt file included. #include "audio_core/audio_types.h" +#ifdef HAVE_FFMPEG +#include "audio_core/hle/aac_decoder.h" +#endif #include "audio_core/hle/common.h" +#include "audio_core/hle/decoder.h" #include "audio_core/hle/hle.h" #include "audio_core/hle/mixers.h" #include "audio_core/hle/shared_memory.h" @@ -66,6 +70,8 @@ private: DspHle& parent; Core::TimingEventType* tick_event; + std::unique_ptr decoder; + std::weak_ptr dsp_dsp; }; @@ -76,6 +82,13 @@ DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory) : parent(paren source.SetMemory(memory); } +#ifdef HAVE_FFMPEG + decoder = std::make_unique(memory); +#else + LOG_WARNING(Audio_DSP, "FFmpeg missing, this could lead to missing audio"); + decoder = std::make_unique(); +#endif // HAVE_FFMPEG + Core::Timing& timing = Core::System::GetInstance().CoreTiming(); tick_event = timing.RegisterEvent("AudioCore::DspHle::tick_event", [this](u64, s64 cycles_late) { @@ -189,6 +202,28 @@ void DspHle::Impl::PipeWrite(DspPipe pipe_number, const std::vector& buffer) return; } + case DspPipe::Binary: { + // TODO(B3N30): Make this async, and signal the interrupt + HLE::BinaryRequest request; + if (sizeof(request) != buffer.size()) { + LOG_CRITICAL(Audio_DSP, "got binary pipe with wrong size {}", buffer.size()); + UNIMPLEMENTED(); + return; + } + std::memcpy(&request, buffer.data(), buffer.size()); + if (request.codec != HLE::DecoderCodec::AAC) { + LOG_CRITICAL(Audio_DSP, "got unknown codec {}", static_cast(request.codec)); + UNIMPLEMENTED(); + return; + } + std::optional response = decoder->ProcessRequest(request); + if (response) { + const HLE::BinaryResponse& value = *response; + pipe_data[static_cast(pipe_number)].resize(sizeof(value)); + std::memcpy(pipe_data[static_cast(pipe_number)].data(), &value, sizeof(value)); + } + break; + } default: LOG_CRITICAL(Audio_DSP, "pipe_number = {} unimplemented", static_cast(pipe_number));