diff --git a/.travis/linux-mingw/docker.sh b/.travis/linux-mingw/docker.sh index 2514a5058..5a3b8f2b0 100755 --- a/.travis/linux-mingw/docker.sh +++ b/.travis/linux-mingw/docker.sh @@ -5,7 +5,7 @@ cd /citra echo 'max_size = 3.0G' > "$HOME/.ccache/ccache.conf" mkdir build && cd build -cmake .. -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON +cmake .. -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_MF=ON make -j4 echo "Tests skipped" diff --git a/.travis/macos/build.sh b/.travis/macos/build.sh index bb3c687e6..7a4b32e4f 100755 --- a/.travis/macos/build.sh +++ b/.travis/macos/build.sh @@ -7,7 +7,7 @@ export Qt5_DIR=$(brew --prefix)/opt/qt5 export PATH="/usr/local/opt/ccache/libexec:$PATH" mkdir build && cd build -cmake .. -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON +cmake .. -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_FFMPEG=ON 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/macos/upload.sh b/.travis/macos/upload.sh index 26fa9c15c..9a81ac989 100755 --- a/.travis/macos/upload.sh +++ b/.travis/macos/upload.sh @@ -21,10 +21,10 @@ $(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app" -executable= dylibbundler -b -x "${REV_NAME}/citra" -cd -d "${REV_NAME}/libs" -p "@executable_path/libs/" # TODO(merry): Figure out why these libraries are not automatically processed -install_name_tool -change /usr/local/Cellar/ffmpeg/4.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib "${REV_NAME}/citra-qt.app/Contents/Frameworks/libavcodec.58.dylib" -install_name_tool -change /usr/local/Cellar/ffmpeg/4.1_1/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib "${REV_NAME}/citra-qt.app/Contents/Frameworks/libswresample.3.dylib" -install_name_tool -change /usr/local/Cellar/ffmpeg/4.1_1/lib/libavutil.56.dylib @executable_path/libs/libavutil.56.dylib "${REV_NAME}/libs/libavcodec.58.dylib" -install_name_tool -change /usr/local/Cellar/ffmpeg/4.1_1/lib/libavutil.56.dylib @executable_path/libs/libavutil.56.dylib "${REV_NAME}/libs/libswresample.3.dylib" +install_name_tool -change /usr/local/Cellar/ffmpeg/4.1_6/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib "${REV_NAME}/citra-qt.app/Contents/Frameworks/libavcodec.58.dylib" +install_name_tool -change /usr/local/Cellar/ffmpeg/4.1_6/lib/libavutil.56.dylib @executable_path/../Frameworks/libavutil.56.dylib "${REV_NAME}/citra-qt.app/Contents/Frameworks/libswresample.3.dylib" +install_name_tool -change /usr/local/Cellar/ffmpeg/4.1_6/lib/libavutil.56.dylib @executable_path/libs/libavutil.56.dylib "${REV_NAME}/libs/libavcodec.58.dylib" +install_name_tool -change /usr/local/Cellar/ffmpeg/4.1_6/lib/libavutil.56.dylib @executable_path/libs/libavutil.56.dylib "${REV_NAME}/libs/libswresample.3.dylib" install_name_tool -change /usr/local/Cellar/libvorbis/1.3.6/lib/libvorbis.0.dylib @executable_path/libs/libavutil.56.dylib "${REV_NAME}/libs/libvorbisenc.2.dylib" # Make the citra-qt.app application launch a debugging terminal. diff --git a/CMakeLists.txt b/CMakeLists.txt index 491a6d6e4..cba5c70ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,12 @@ 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" OFF) + option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) +CMAKE_DEPENDENT_OPTION(ENABLE_MF "Use Media Foundation decoder" ON "WIN32;NOT ENABLE_FFMPEG" OFF) + if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit) message(STATUS "Copying pre-commit hook") file(COPY hooks/pre-commit @@ -251,6 +255,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() + # Platform-specific library requirements # ====================================== diff --git a/appveyor.yml b/appveyor.yml index 3e4299250..0e1ee94c4 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 @@ -43,9 +43,9 @@ before_build: $COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING} if ($env:BUILD_TYPE -eq 'msvc') { # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning - cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0' + cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_MF=ON .. 2>&1 && exit 0' } else { - C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON .. 2>&1" + C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_MF=ON .. 2>&1" } - cd .. 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 538cfd894..27e7d723b 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -4,7 +4,11 @@ add_library(audio_core STATIC codec.h dsp_interface.cpp dsp_interface.h + hle/adts.h + hle/adts_reader.cpp hle/common.h + hle/decoder.cpp + hle/decoder.h hle/filter.cpp hle/filter.h hle/hle.cpp @@ -27,6 +31,8 @@ add_library(audio_core STATIC $<$:sdl2_sink.cpp sdl2_sink.h> $<$:cubeb_sink.cpp cubeb_sink.h> + $<$:hle/ffmpeg_decoder.cpp hle/ffmpeg_decoder.h hle/ffmpeg_dl.cpp hle/ffmpeg_dl.h> + $<$:hle/wmf_decoder.cpp hle/wmf_decoder.h hle/wmf_decoder_utils.cpp hle/wmf_decoder_utils.h> ) create_target_directory_groups(audio_core) @@ -34,6 +40,20 @@ create_target_directory_groups(audio_core) target_link_libraries(audio_core PUBLIC common core) target_link_libraries(audio_core PRIVATE SoundTouch teakra) +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 PUBLIC HAVE_FFMPEG) +endif() + +if(ENABLE_MF) + target_link_libraries(audio_core PRIVATE mf.lib mfplat.lib mfuuid.lib) + target_compile_definitions(audio_core PUBLIC HAVE_MF) +endif() + if(SDL2_FOUND) target_link_libraries(audio_core PRIVATE SDL2) target_compile_definitions(audio_core PRIVATE HAVE_SDL2) @@ -43,3 +63,4 @@ if(ENABLE_CUBEB) target_link_libraries(audio_core PRIVATE cubeb) add_definitions(-DHAVE_CUBEB=1) endif() + diff --git a/src/audio_core/hle/adts.h b/src/audio_core/hle/adts.h new file mode 100644 index 000000000..9aba09fc3 --- /dev/null +++ b/src/audio_core/hle/adts.h @@ -0,0 +1,23 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +#pragma once + +#include "common/common_types.h" + +struct ADTSData { + bool MPEG2; + u8 profile; + u8 channels; + u8 channel_idx; + u8 framecount; + u8 samplerate_idx; + u32 length; + u32 samplerate; +}; + +ADTSData ParseADTS(const char* buffer); + +// last two bytes of MF AAC decoder user data +// see https://docs.microsoft.com/en-us/windows/desktop/medfound/aac-decoder#example-media-types +u16 MFGetAACTag(const ADTSData& input); diff --git a/src/audio_core/hle/adts_reader.cpp b/src/audio_core/hle/adts_reader.cpp new file mode 100644 index 000000000..e70c32a3b --- /dev/null +++ b/src/audio_core/hle/adts_reader.cpp @@ -0,0 +1,61 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +#include +#include "adts.h" + +constexpr std::array freq_table = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0}; +constexpr std::array channel_table = {0, 1, 2, 3, 4, 5, 6, 8}; + +ADTSData ParseADTS(const char* buffer) { + u32 tmp = 0; + ADTSData out; + + // sync word 0xfff + tmp = (buffer[0] << 8) | (buffer[1] & 0xf0); + if ((tmp & 0xffff) != 0xfff0) { + out.length = 0; + return out; + } + out.MPEG2 = (buffer[1] >> 3) & 0x1; + // bit 17 to 18 + out.profile = (buffer[2] >> 6) + 1; + // bit 19 to 22 + tmp = (buffer[2] >> 2) & 0xf; + out.samplerate_idx = tmp; + out.samplerate = (tmp > 15) ? 0 : freq_table[tmp]; + // bit 24 to 26 + tmp = ((buffer[2] & 0x1) << 2) | ((buffer[3] >> 6) & 0x3); + out.channel_idx = tmp; + out.channels = (tmp > 7) ? 0 : channel_table[tmp]; + + // bit 55 to 56 + out.framecount = (buffer[6] & 0x3) + 1; + + // bit 31 to 43 + tmp = (buffer[3] & 0x3) << 11; + tmp |= (buffer[4] << 3) & 0x7f8; + tmp |= (buffer[5] >> 5) & 0x7; + + out.length = tmp; + + return out; +} + +// last two bytes of MF AAC decoder user data +// Audio object type (5 bits) +// Sample rate profile (4 bits) +// Channel configuration profile (4 bits) +// Frame length flag (1 bit) +// Depends on core coder (1 bit) +// Extension flag (1 bit) +u16 MFGetAACTag(const ADTSData& input) { + u16 tag = 0; + + tag |= input.profile << 11; + tag |= input.samplerate_idx << 7; + tag |= input.channel_idx << 3; + + return tag; +} diff --git a/src/audio_core/hle/decoder.cpp b/src/audio_core/hle/decoder.cpp new file mode 100644 index 000000000..6569a6f45 --- /dev/null +++ b/src/audio_core/hle/decoder.cpp @@ -0,0 +1,35 @@ +// 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 { + +DecoderBase::~DecoderBase(){}; + +NullDecoder::NullDecoder() = default; + +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; + 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; + default: + LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", static_cast(request.cmd)); + return {}; + } +}; +} // 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..a551df4eb --- /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_decoder.cpp b/src/audio_core/hle/ffmpeg_decoder.cpp new file mode 100644 index 000000000..0bed4bbb4 --- /dev/null +++ b/src/audio_core/hle/ffmpeg_decoder.cpp @@ -0,0 +1,263 @@ +// 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 "audio_core/hle/ffmpeg_dl.h" + +namespace AudioCore::HLE { + +class FFMPEGDecoder::Impl { +public: + explicit 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); + + struct AVPacketDeleter { + void operator()(AVPacket* packet) const { + av_packet_free_dl(&packet); + } + }; + + struct AVCodecContextDeleter { + void operator()(AVCodecContext* context) const { + avcodec_free_context_dl(&context); + } + }; + + struct AVCodecParserContextDeleter { + void operator()(AVCodecParserContext* parser) const { + av_parser_close_dl(parser); + } + }; + + struct AVFrameDeleter { + void operator()(AVFrame* frame) const { + av_frame_free_dl(&frame); + } + }; + + bool initalized = false; + bool have_ffmpeg_dl; + + Memory::MemorySystem& memory; + + 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 = InitFFmpegDL(); +} + +FFMPEGDecoder::Impl::~Impl() = default; + +std::optional FFMPEGDecoder::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); + } + case DecoderCommand::Decode: { + return Decode(request); + } + case DecoderCommand::Unknown: { + BinaryResponse response; + std::memcpy(&response, &request, sizeof(response)); + response.unknown1 = 0x0; + return response; + } + default: + LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", static_cast(request.cmd)); + return {}; + } +} + +std::optional FFMPEGDecoder::Impl::Initalize(const BinaryRequest& request) { + if (initalized) { + Clear(); + } + + BinaryResponse response; + std::memcpy(&response, &request, sizeof(response)); + response.unknown1 = 0x0; + + if (!have_ffmpeg_dl) { + return response; + } + + av_packet.reset(av_packet_alloc_dl()); + + codec = avcodec_find_decoder_dl(AV_CODEC_ID_AAC); + if (!codec) { + LOG_ERROR(Audio_DSP, "Codec not found\n"); + return response; + } + + parser.reset(av_parser_init_dl(codec->id)); + if (!parser) { + LOG_ERROR(Audio_DSP, "Parser not found\n"); + return response; + } + + av_context.reset(avcodec_alloc_context3_dl(codec)); + if (!av_context) { + LOG_ERROR(Audio_DSP, "Could not allocate audio codec context\n"); + return response; + } + + if (avcodec_open2_dl(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 BinaryRequest& request) { + BinaryResponse response; + response.codec = request.codec; + response.cmd = request.cmd; + response.size = 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.num_channels = 2; + response.num_samples = 1024; + return response; + } + + if (request.src_addr < Memory::FCRAM_PADDR || + request.src_addr + request.size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { + 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) { + decoded_frame.reset(av_frame_alloc_dl()); + if (!decoded_frame) { + LOG_ERROR(Audio_DSP, "Could not allocate audio frame"); + return {}; + } + } + + int ret = + av_parser_parse2_dl(parser.get(), av_context.get(), &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.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 = avcodec_receive_frame_dl(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 = 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); + + response.num_channels = decoded_frame->channels; + 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 < decoded_frame->channels; channel++) { + std::memcpy(&val_float, decoded_frame->data[channel] + 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 (out_streams[0].size() != 0) { + if (request.dst_addr_ch0 < Memory::FCRAM_PADDR || + 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.dst_addr_ch0); + return {}; + } + std::memcpy(memory.GetFCRAMPointer(request.dst_addr_ch0 - Memory::FCRAM_PADDR), + out_streams[0].data(), out_streams[0].size()); + } + + if (out_streams[1].size() != 0) { + if (request.dst_addr_ch1 < Memory::FCRAM_PADDR || + 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.dst_addr_ch1); + return {}; + } + std::memcpy(memory.GetFCRAMPointer(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 BinaryRequest& request) { + return impl->ProcessRequest(request); +} + +} // namespace AudioCore::HLE diff --git a/src/audio_core/hle/ffmpeg_decoder.h b/src/audio_core/hle/ffmpeg_decoder.h new file mode 100644 index 000000000..190251543 --- /dev/null +++ b/src/audio_core/hle/ffmpeg_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 FFMPEGDecoder final : public DecoderBase { +public: + explicit FFMPEGDecoder(Memory::MemorySystem& memory); + ~FFMPEGDecoder() 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/ffmpeg_dl.cpp b/src/audio_core/hle/ffmpeg_dl.cpp new file mode 100644 index 000000000..073d0aedd --- /dev/null +++ b/src/audio_core/hle/ffmpeg_dl.cpp @@ -0,0 +1,178 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef _WIN32 + +#include +#include "audio_core/hle/ffmpeg_dl.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" + +namespace { + +struct LibraryDeleter { + using pointer = HMODULE; + void operator()(HMODULE h) const { + if (h != nullptr) + FreeLibrary(h); + } +}; + +std::unique_ptr dll_util{nullptr}; +std::unique_ptr dll_codec{nullptr}; + +} // namespace + +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() { + std::string dll_path = FileUtil::GetUserPath(FileUtil::UserPath::DLLDir); + FileUtil::CreateDir(dll_path); + std::wstring w_dll_path = Common::UTF8ToUTF16W(dll_path); + SetDllDirectoryW(w_dll_path.c_str()); + + dll_util.reset(LoadLibrary("avutil-56.dll")); + if (!dll_util) { + DWORD error_message_id = GetLastError(); + LPSTR message_buffer = nullptr; + size_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message_buffer), 0, nullptr); + + std::string message(message_buffer, size); + + LocalFree(message_buffer); + LOG_ERROR(Audio_DSP, "Could not load avutil-56.dll: {}", message); + return false; + } + + dll_codec.reset(LoadLibrary("avcodec-58.dll")); + if (!dll_codec) { + DWORD error_message_id = GetLastError(); + LPSTR message_buffer = nullptr; + size_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message_buffer), 0, nullptr); + + std::string message(message_buffer, size); + + LocalFree(message_buffer); + LOG_ERROR(Audio_DSP, "Could not load avcodec-58.dll: {}", message); + return false; + } + av_get_bytes_per_sample_dl = + FuncDL(dll_util.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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.get(), "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 diff --git a/src/audio_core/hle/ffmpeg_dl.h b/src/audio_core/hle/ffmpeg_dl.h new file mode 100644 index 000000000..6ca2066fc --- /dev/null +++ b/src/audio_core/hle/ffmpeg_dl.h @@ -0,0 +1,79 @@ +// 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 // _WIN32 + +extern "C" { +#include +} + +#ifdef _WIN32 + +template +struct FuncDL { + FuncDL() = default; + FuncDL(HMODULE dll, const char* name) { + if (dll) { + ptr_function = reinterpret_cast(GetProcAddress(dll, name)); + } + } + + operator T*() const { + return ptr_function; + } + + explicit operator bool() const { + return ptr_function != nullptr; + } + + T* ptr_function = nullptr; +}; + +extern FuncDL av_get_bytes_per_sample_dl; +extern FuncDL av_frame_alloc_dl; +extern FuncDL av_frame_free_dl; +extern FuncDL avcodec_alloc_context3_dl; +extern FuncDL avcodec_free_context_dl; +extern FuncDL avcodec_open2_dl; +extern FuncDL av_packet_alloc_dl; +extern FuncDL av_packet_free_dl; +extern FuncDL avcodec_find_decoder_dl; +extern FuncDL avcodec_send_packet_dl; +extern FuncDL avcodec_receive_frame_dl; +extern FuncDL av_parser_init_dl; +extern FuncDL + av_parser_parse2_dl; +extern FuncDL av_parser_close_dl; + +bool InitFFmpegDL(); + +#else // _Win32 + +// 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 // _Win32 diff --git a/src/audio_core/hle/hle.cpp b/src/audio_core/hle/hle.cpp index 6e7b45278..0bb7d4413 100644 --- a/src/audio_core/hle/hle.cpp +++ b/src/audio_core/hle/hle.cpp @@ -3,7 +3,13 @@ // Refer to the license.txt file included. #include "audio_core/audio_types.h" +#ifdef HAVE_MF +#include "audio_core/hle/wmf_decoder.h" +#elif HAVE_FFMPEG +#include "audio_core/hle/ffmpeg_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" @@ -69,6 +75,8 @@ private: DspHle& parent; Core::TimingEventType* tick_event; + std::unique_ptr decoder; + std::weak_ptr dsp_dsp; }; @@ -79,6 +87,15 @@ DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory) : parent(paren source.SetMemory(memory); } +#ifdef HAVE_MF + decoder = std::make_unique(memory); +#elif HAVE_FFMPEG + decoder = std::make_unique(memory); +#else + LOG_WARNING(Audio_DSP, "No decoder found, this could lead to missing audio"); + decoder = std::make_unique(); +#endif // HAVE_MF + Core::Timing& timing = Core::System::GetInstance().CoreTiming(); tick_event = timing.RegisterEvent("AudioCore::DspHle::tick_event", [this](u64, s64 cycles_late) { @@ -215,6 +232,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)); diff --git a/src/audio_core/hle/wmf_decoder.cpp b/src/audio_core/hle/wmf_decoder.cpp new file mode 100644 index 000000000..e78f24ca1 --- /dev/null +++ b/src/audio_core/hle/wmf_decoder.cpp @@ -0,0 +1,274 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/hle/wmf_decoder.h" +#include "audio_core/hle/wmf_decoder_utils.h" + +namespace AudioCore::HLE { + +class WMFDecoder::Impl { +public: + explicit Impl(Memory::MemorySystem& memory); + ~Impl(); + std::optional ProcessRequest(const BinaryRequest& request); + +private: + std::optional Initalize(const BinaryRequest& request); + + std::optional Decode(const BinaryRequest& request); + + MFOutputState DecodingLoop(ADTSData adts_header, std::array, 2>& out_streams); + + bool transform_initialized = false; + bool format_selected = false; + + Memory::MemorySystem& memory; + + unique_mfptr transform; + DWORD in_stream_id = 0; + DWORD out_stream_id = 0; +}; + +WMFDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { + HRESULT hr = S_OK; + hr = CoInitialize(NULL); + // S_FALSE will be returned when COM has already been initialized + if (hr != S_OK && hr != S_FALSE) { + ReportError("Failed to start COM components", hr); + } + + // lite startup is faster and all what we need is included + hr = MFStartup(MF_VERSION, MFSTARTUP_LITE); + if (hr != S_OK) { + // Do you know you can't initialize MF in test mode or safe mode? + ReportError("Failed to initialize Media Foundation", hr); + } + + LOG_INFO(Audio_DSP, "Media Foundation activated"); + + // initialize transform + transform = MFDecoderInit(); + if (transform == nullptr) { + LOG_CRITICAL(Audio_DSP, "Can't initialize decoder"); + return; + } + + hr = transform->GetStreamIDs(1, &in_stream_id, 1, &out_stream_id); + if (hr == E_NOTIMPL) { + // if not implemented, it means this MFT does not assign stream ID for you + in_stream_id = 0; + out_stream_id = 0; + } else if (FAILED(hr)) { + ReportError("Decoder failed to initialize the stream ID", hr); + return; + } + transform_initialized = true; +} + +WMFDecoder::Impl::~Impl() { + if (transform_initialized) { + MFFlush(transform.get()); + // delete the transform object before shutting down MF + // otherwise access violation will occur + transform.reset(); + } + MFShutdown(); + CoUninitialize(); +} + +std::optional WMFDecoder::Impl::ProcessRequest(const BinaryRequest& request) { + if (request.codec != DecoderCodec::AAC) { + LOG_ERROR(Audio_DSP, "Got unknown codec {}", static_cast(request.codec)); + return {}; + } + + switch (request.cmd) { + case DecoderCommand::Init: { + LOG_INFO(Audio_DSP, "WMFDecoder initializing"); + return Initalize(request); + } + case DecoderCommand::Decode: { + return Decode(request); + } + case DecoderCommand::Unknown: { + BinaryResponse response; + std::memcpy(&response, &request, sizeof(response)); + response.unknown1 = 0x0; + return response; + } + default: + LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", static_cast(request.cmd)); + return {}; + } +} + +std::optional WMFDecoder::Impl::Initalize(const BinaryRequest& request) { + BinaryResponse response; + std::memcpy(&response, &request, sizeof(response)); + response.unknown1 = 0x0; + + format_selected = false; // select format again if application request initialize the DSP + return response; +} + +MFOutputState WMFDecoder::Impl::DecodingLoop(ADTSData adts_header, + std::array, 2>& out_streams) { + MFOutputState output_status = MFOutputState::OK; + std::optional> output_buffer; + unique_mfptr output; + + while (true) { + auto [output_status, output] = ReceiveSample(transform.get(), out_stream_id); + + // 0 -> okay; 3 -> okay but more data available (buffer too small) + if (output_status == MFOutputState::OK || output_status == MFOutputState::HaveMoreData) { + output_buffer = CopySampleToBuffer(output.get()); + + // the following was taken from ffmpeg version of the decoder + f32 val_f32; + for (std::size_t i = 0; i < output_buffer->size();) { + for (std::size_t channel = 0; channel < adts_header.channels; channel++) { + val_f32 = output_buffer->at(i); + s16 val = static_cast(0x7FFF * val_f32); + out_streams[channel].push_back(val & 0xFF); + out_streams[channel].push_back(val >> 8); + // i is incremented on per channel basis + i++; + } + } + } + + // in case of "ok" only, just return quickly + if (output_status == MFOutputState::OK) + return MFOutputState::OK; + + // for status = 2, reset MF + if (output_status == MFOutputState::NeedReconfig) { + format_selected = false; + return MFOutputState::NeedReconfig; + } + + // for status = 3, try again with new buffer + if (output_status == MFOutputState::HaveMoreData) + continue; + + // according to MS document, this is not an error (?!) + if (output_status == MFOutputState::NeedMoreInput) + return MFOutputState::NeedMoreInput; + + return MFOutputState::FatalError; // return on other status + } + + return MFOutputState::FatalError; +} + +std::optional WMFDecoder::Impl::Decode(const BinaryRequest& request) { + BinaryResponse response; + response.codec = request.codec; + response.cmd = request.cmd; + response.size = request.size; + response.num_channels = 2; + response.num_samples = 1024; + + if (!transform_initialized) { + LOG_DEBUG(Audio_DSP, "Decoder not initialized"); + // This is a hack to continue games when decoder failed to initialize + return response; + } + + if (request.src_addr < Memory::FCRAM_PADDR || + request.src_addr + request.size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { + 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; + unique_mfptr sample; + MFInputState input_status = MFInputState::OK; + MFOutputState output_status = MFOutputState::OK; + std::optional adts_meta = DetectMediaType((char*)data, request.size); + + if (!adts_meta) { + LOG_ERROR(Audio_DSP, "Unable to deduce decoding parameters from ADTS stream"); + return response; + } + + response.num_channels = adts_meta->ADTSHeader.channels; + + if (!format_selected) { + LOG_DEBUG(Audio_DSP, "New ADTS stream: channels = {}, sample rate = {}", + adts_meta->ADTSHeader.channels, adts_meta->ADTSHeader.samplerate); + SelectInputMediaType(transform.get(), in_stream_id, adts_meta->ADTSHeader, + adts_meta->AACTag, 14); + SelectOutputMediaType(transform.get(), out_stream_id); + SendSample(transform.get(), in_stream_id, nullptr); + // cache the result from detect_mediatype and call select_*_mediatype only once + // This could increase performance very slightly + transform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0); + format_selected = true; + } + + sample = CreateSample((void*)data, request.size, 1, 0); + sample->SetUINT32(MFSampleExtension_CleanPoint, 1); + + while (true) { + input_status = SendSample(transform.get(), in_stream_id, sample.get()); + output_status = DecodingLoop(adts_meta->ADTSHeader, out_streams); + + if (output_status == MFOutputState::FatalError) { + // if the decode issues are caused by MFT not accepting new samples, try again + // NOTICE: you are required to check the output even if you already knew/guessed + // MFT didn't accept the input sample + if (input_status == MFInputState::NotAccepted) { + // try again + continue; + } + + LOG_ERROR(Audio_DSP, "Errors occurred when receiving output"); + return response; + } else if (output_status == MFOutputState::NeedReconfig) { + // flush the transform + MFFlush(transform.get()); + // decode again + return this->Decode(request); + } + + break; // jump out of the loop if at least we don't have obvious issues + } + + if (out_streams[0].size() != 0) { + if (request.dst_addr_ch0 < Memory::FCRAM_PADDR || + 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.dst_addr_ch0); + return {}; + } + std::memcpy(memory.GetFCRAMPointer(request.dst_addr_ch0 - Memory::FCRAM_PADDR), + out_streams[0].data(), out_streams[0].size()); + } + + if (out_streams[1].size() != 0) { + if (request.dst_addr_ch1 < Memory::FCRAM_PADDR || + 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.dst_addr_ch1); + return {}; + } + std::memcpy(memory.GetFCRAMPointer(request.dst_addr_ch1 - Memory::FCRAM_PADDR), + out_streams[1].data(), out_streams[1].size()); + } + + return response; +} + +WMFDecoder::WMFDecoder(Memory::MemorySystem& memory) : impl(std::make_unique(memory)) {} + +WMFDecoder::~WMFDecoder() = default; + +std::optional WMFDecoder::ProcessRequest(const BinaryRequest& request) { + return impl->ProcessRequest(request); +} + +} // namespace AudioCore::HLE diff --git a/src/audio_core/hle/wmf_decoder.h b/src/audio_core/hle/wmf_decoder.h new file mode 100644 index 000000000..34e223740 --- /dev/null +++ b/src/audio_core/hle/wmf_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 WMFDecoder final : public DecoderBase { +public: + explicit WMFDecoder(Memory::MemorySystem& memory); + ~WMFDecoder() 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/wmf_decoder_utils.cpp b/src/audio_core/hle/wmf_decoder_utils.cpp new file mode 100644 index 000000000..21dd8a950 --- /dev/null +++ b/src/audio_core/hle/wmf_decoder_utils.cpp @@ -0,0 +1,349 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +#include "common/logging/log.h" +#include "common/string_util.h" +#include "wmf_decoder_utils.h" + +// utility functions +void ReportError(std::string msg, HRESULT hr) { + if (SUCCEEDED(hr)) { + return; + } + LPWSTR err; + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, hr, + // hardcode to use en_US because if any user had problems with this + // we can help them w/o translating anything + // default is to use the language currently active on the operating system + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPWSTR)&err, 0, nullptr); + if (err != nullptr) { + LOG_CRITICAL(Audio_DSP, "{}: {}", msg, Common::UTF16ToUTF8(err)); + LocalFree(err); + } + LOG_CRITICAL(Audio_DSP, "{}: {:08x}", msg, hr); +} + +unique_mfptr MFDecoderInit(GUID audio_format) { + HRESULT hr = S_OK; + MFT_REGISTER_TYPE_INFO reg = {0}; + GUID category = MFT_CATEGORY_AUDIO_DECODER; + IMFActivate** activate; + unique_mfptr transform; + UINT32 num_activate; + + reg.guidMajorType = MFMediaType_Audio; + reg.guidSubtype = audio_format; + + hr = MFTEnumEx(category, + MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_LOCALMFT | MFT_ENUM_FLAG_SORTANDFILTER, + ®, nullptr, &activate, &num_activate); + if (FAILED(hr) || num_activate < 1) { + ReportError("Failed to enumerate decoders", hr); + CoTaskMemFree(activate); + return nullptr; + } + LOG_INFO(Audio_DSP, "Windows(R) Media Foundation found {} suitable decoder(s)", num_activate); + for (unsigned int n = 0; n < num_activate; n++) { + hr = activate[n]->ActivateObject( + IID_IMFTransform, + reinterpret_cast(static_cast(Amp(transform)))); + if (FAILED(hr)) + transform = nullptr; + activate[n]->Release(); + if (SUCCEEDED(hr)) + break; + } + if (transform == nullptr) { + ReportError("Failed to initialize MFT", hr); + CoTaskMemFree(activate); + return nullptr; + } + CoTaskMemFree(activate); + return transform; +} + +unique_mfptr CreateSample(const void* data, DWORD len, DWORD alignment, + LONGLONG duration) { + HRESULT hr = S_OK; + unique_mfptr buf; + unique_mfptr sample; + + hr = MFCreateSample(Amp(sample)); + if (FAILED(hr)) { + ReportError("Unable to allocate a sample", hr); + return nullptr; + } + // Yes, the argument for alignment is the actual alignment - 1 + hr = MFCreateAlignedMemoryBuffer(len, alignment - 1, Amp(buf)); + if (FAILED(hr)) { + ReportError("Unable to allocate a memory buffer for sample", hr); + return nullptr; + } + if (data) { + BYTE* buffer; + // lock the MediaBuffer + // this is actually not a thread-safe lock + hr = buf->Lock(&buffer, nullptr, nullptr); + if (FAILED(hr)) { + ReportError("Unable to lock down MediaBuffer", hr); + return nullptr; + } + + std::memcpy(buffer, data, len); + + buf->SetCurrentLength(len); + buf->Unlock(); + } + + sample->AddBuffer(buf.get()); + hr = sample->SetSampleDuration(duration); + if (FAILED(hr)) { + // MFT will take a guess for you in this case + ReportError("Unable to set sample duration, but continuing anyway", hr); + } + + return sample; +} + +bool SelectInputMediaType(IMFTransform* transform, int in_stream_id, const ADTSData& adts, + const UINT8* user_data, UINT32 user_data_len, GUID audio_format) { + HRESULT hr = S_OK; + unique_mfptr t; + + // actually you can get rid of the whole block of searching and filtering mess + // if you know the exact parameters of your media stream + hr = MFCreateMediaType(Amp(t)); + if (FAILED(hr)) { + ReportError("Unable to create an empty MediaType", hr); + return false; + } + + // basic definition + t->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + t->SetGUID(MF_MT_SUBTYPE, audio_format); + + t->SetUINT32(MF_MT_AAC_PAYLOAD_TYPE, 1); + t->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, adts.channels); + t->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, adts.samplerate); + // 0xfe = 254 = "unspecified" + t->SetUINT32(MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION, 254); + t->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 1); + t->SetBlob(MF_MT_USER_DATA, user_data, user_data_len); + hr = transform->SetInputType(in_stream_id, t.get(), 0); + if (FAILED(hr)) { + ReportError("failed to select input types for MFT", hr); + return false; + } + + return true; +} + +bool SelectOutputMediaType(IMFTransform* transform, int out_stream_id, GUID audio_format) { + HRESULT hr = S_OK; + UINT32 tmp; + unique_mfptr type; + + // If you know what you need and what you are doing, you can specify the conditions instead of + // searching but it's better to use search since MFT may or may not support your output + // parameters + for (DWORD i = 0;; i++) { + hr = transform->GetOutputAvailableType(out_stream_id, i, Amp(type)); + if (hr == MF_E_NO_MORE_TYPES || hr == E_NOTIMPL) { + return true; + } + if (FAILED(hr)) { + ReportError("failed to get output types for MFT", hr); + return false; + } + + hr = type->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &tmp); + + if (FAILED(hr)) + continue; + // select PCM-16 format + if (tmp == 32) { + hr = type->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 1); + if (FAILED(hr)) { + ReportError("failed to set MF_MT_AUDIO_BLOCK_ALIGNMENT for MFT on output stream", + hr); + return false; + } + hr = transform->SetOutputType(out_stream_id, type.get(), 0); + if (FAILED(hr)) { + ReportError("failed to select output types for MFT", hr); + return false; + } + return true; + } else { + continue; + } + + return false; + } + + ReportError("MFT: Unable to find preferred output format", E_NOTIMPL); + return false; +} + +std::optional DetectMediaType(char* buffer, std::size_t len) { + if (len < 7) { + return std::nullopt; + } + + ADTSData tmp; + ADTSMeta result; + // see https://docs.microsoft.com/en-us/windows/desktop/api/mmreg/ns-mmreg-heaacwaveinfo_tag + // for the meaning of the byte array below + + // it might be a good idea to wrap the parameters into a struct + // and pass that struct into the function but doing that will lead to messier code + // const UINT8 aac_data[] = { 0x01, 0x00, 0xfe, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x11, 0x90 + // }; first byte: 0: raw aac 1: adts 2: adif 3: latm/laos + UINT8 aac_tmp[] = {0x01, 0x00, 0xfe, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x00, 0x00}; + uint16_t tag = 0; + + tmp = ParseADTS(buffer); + if (tmp.length == 0) { + return std::nullopt; + } + + tag = MFGetAACTag(tmp); + aac_tmp[12] |= (tag & 0xff00) >> 8; + aac_tmp[13] |= (tag & 0x00ff); + std::memcpy(&(result.ADTSHeader), &tmp, sizeof(ADTSData)); + std::memcpy(&(result.AACTag), aac_tmp, 14); + return result; +} + +void MFFlush(IMFTransform* transform) { + HRESULT hr = transform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); + if (FAILED(hr)) { + ReportError("MFT: Flush command failed", hr); + } + hr = transform->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0); + if (FAILED(hr)) { + ReportError("Failed to end streaming for MFT", hr); + } +} + +MFInputState SendSample(IMFTransform* transform, DWORD in_stream_id, IMFSample* in_sample) { + HRESULT hr = S_OK; + + if (in_sample) { + hr = transform->ProcessInput(in_stream_id, in_sample, 0); + if (hr == MF_E_NOTACCEPTING) { + return MFInputState::NotAccepted; // try again + } else if (FAILED(hr)) { + ReportError("MFT: Failed to process input", hr); + return MFInputState::FatalError; + } // FAILED(hr) + } else { + hr = transform->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0); + if (FAILED(hr)) { + ReportError("MFT: Failed to drain when processing input", hr); + } + } + + return MFInputState::OK; +} + +std::tuple> ReceiveSample(IMFTransform* transform, + DWORD out_stream_id) { + HRESULT hr; + MFT_OUTPUT_DATA_BUFFER out_buffers; + MFT_OUTPUT_STREAM_INFO out_info; + DWORD status = 0; + unique_mfptr sample; + bool mft_create_sample = false; + + hr = transform->GetOutputStreamInfo(out_stream_id, &out_info); + + if (FAILED(hr)) { + ReportError("MFT: Failed to get stream info", hr); + return std::make_tuple(MFOutputState::FatalError, std::move(sample)); + } + mft_create_sample = (out_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) || + (out_info.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES); + + while (true) { + status = 0; + + if (!mft_create_sample) { + sample = CreateSample(nullptr, out_info.cbSize, out_info.cbAlignment); + if (!sample.get()) { + ReportError("MFT: Unable to allocate memory for samples", hr); + return std::make_tuple(MFOutputState::FatalError, std::move(sample)); + } + } + + out_buffers.dwStreamID = out_stream_id; + out_buffers.pSample = sample.get(); + + hr = transform->ProcessOutput(0, 1, &out_buffers, &status); + + if (!FAILED(hr)) { + break; + } + + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + // Most likely reasons: data corrupted; your actions not expected by MFT + return std::make_tuple(MFOutputState::NeedMoreInput, std::move(sample)); + } + + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { + ReportError("MFT: stream format changed, re-configuration required", hr); + return std::make_tuple(MFOutputState::NeedReconfig, std::move(sample)); + } + + break; + } + + if (out_buffers.dwStatus & MFT_OUTPUT_DATA_BUFFER_INCOMPLETE) { + // this status is also unreliable but whatever + return std::make_tuple(MFOutputState::HaveMoreData, std::move(sample)); + } + + if (out_buffers.pSample == nullptr) { + ReportError("MFT: decoding failure", hr); + return std::make_tuple(MFOutputState::FatalError, std::move(sample)); + } + + return std::make_tuple(MFOutputState::OK, std::move(sample)); +} + +std::optional> CopySampleToBuffer(IMFSample* sample) { + unique_mfptr buffer; + HRESULT hr = S_OK; + std::optional> output; + std::vector output_buffer; + BYTE* data; + DWORD len = 0; + + hr = sample->GetTotalLength(&len); + if (FAILED(hr)) { + ReportError("Failed to get the length of sample buffer", hr); + return std::nullopt; + } + + hr = sample->ConvertToContiguousBuffer(Amp(buffer)); + if (FAILED(hr)) { + ReportError("Failed to get sample buffer", hr); + return std::nullopt; + } + + hr = buffer->Lock(&data, nullptr, nullptr); + if (FAILED(hr)) { + ReportError("Failed to lock the buffer", hr); + return std::nullopt; + } + + output_buffer.resize(len / sizeof(f32)); + std::memcpy(output_buffer.data(), data, len); + output = output_buffer; + + // if buffer unlock fails, then... whatever, we have already got data + buffer->Unlock(); + return output; +} diff --git a/src/audio_core/hle/wmf_decoder_utils.h b/src/audio_core/hle/wmf_decoder_utils.h new file mode 100644 index 000000000..26e1217a2 --- /dev/null +++ b/src/audio_core/hle/wmf_decoder_utils.h @@ -0,0 +1,89 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +#pragma once + +// AAC decoder related APIs are only available with WIN7+ +#define WINVER _WIN32_WINNT_WIN7 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adts.h" + +enum class MFOutputState { FatalError, OK, NeedMoreInput, NeedReconfig, HaveMoreData }; +enum class MFInputState { FatalError, OK, NotAccepted }; + +// utility functions / templates +template +struct MFRelease { + void operator()(T* pointer) const { + pointer->Release(); + }; +}; + +template <> +struct MFRelease { + void operator()(IMFTransform* pointer) const { + MFShutdownObject(pointer); + pointer->Release(); + }; +}; + +// wrapper facilities for dealing with pointers +template +using unique_mfptr = std::unique_ptr>; + +template +class AmpImpl { +public: + AmpImpl(SmartPtr& smart_ptr) : smart_ptr(smart_ptr) {} + ~AmpImpl() { + smart_ptr.reset(raw_ptr); + } + + operator RawPtr*() { + return &raw_ptr; + } + +private: + SmartPtr& smart_ptr; + RawPtr raw_ptr = nullptr; +}; + +template +auto Amp(SmartPtr& smart_ptr) { + return AmpImpl(smart_ptr); +} + +// convient function for formatting error messages +void ReportError(std::string msg, HRESULT hr); + +// data type for transferring ADTS metadata between functions +struct ADTSMeta { + ADTSData ADTSHeader; + u8 AACTag[14]; +}; + +// exported functions +unique_mfptr MFDecoderInit(GUID audio_format = MFAudioFormat_AAC); +unique_mfptr CreateSample(const void* data, DWORD len, DWORD alignment = 1, + LONGLONG duration = 0); +bool SelectInputMediaType(IMFTransform* transform, int in_stream_id, const ADTSData& adts, + const UINT8* user_data, UINT32 user_data_len, + GUID audio_format = MFAudioFormat_AAC); +std::optional DetectMediaType(char* buffer, std::size_t len); +bool SelectOutputMediaType(IMFTransform* transform, int out_stream_id, + GUID audio_format = MFAudioFormat_PCM); +void MFFlush(IMFTransform* transform); +MFInputState SendSample(IMFTransform* transform, DWORD in_stream_id, IMFSample* in_sample); +std::tuple> ReceiveSample(IMFTransform* transform, + DWORD out_stream_id); +std::optional> CopySampleToBuffer(IMFSample* sample); diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 908225ac8..4942c02ab 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -38,6 +38,7 @@ #define SYSDATA_DIR "sysdata" #define LOG_DIR "log" #define CHEATS_DIR "cheats" +#define DLL_DIR "external_dlls" // Filenames // Files in the directory returned by GetUserPath(UserPath::LogDir) diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 4b95174d6..0ae9e2c5f 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -711,6 +711,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { // TODO: Put the logs in a better location for each OS paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); paths.emplace(UserPath::CheatsDir, user_path + CHEATS_DIR DIR_SEP); + paths.emplace(UserPath::DLLDir, user_path + DLL_DIR DIR_SEP); } if (!new_path.empty()) { diff --git a/src/common/file_util.h b/src/common/file_util.h index 96a30675c..d30780b97 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -24,6 +24,7 @@ enum class UserPath { CacheDir, CheatsDir, ConfigDir, + DLLDir, LogDir, NANDDir, RootDir, diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 0cb36a2ce..c6b222ef0 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -9,6 +9,8 @@ add_executable(tests core/hle/kernel/hle_ipc.cpp core/memory/memory.cpp core/memory/vm_manager.cpp + audio_core/audio_fixures.h + audio_core/decoder_tests.cpp tests.cpp ) @@ -21,7 +23,7 @@ endif() create_target_directory_groups(tests) -target_link_libraries(tests PRIVATE common core video_core) +target_link_libraries(tests PRIVATE common core video_core audio_core) target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include nihstro-headers Threads::Threads) add_test(NAME tests COMMAND tests) diff --git a/src/tests/audio_core/audio_fixures.h b/src/tests/audio_core/audio_fixures.h new file mode 100644 index 000000000..58147b6fd --- /dev/null +++ b/src/tests/audio_core/audio_fixures.h @@ -0,0 +1,12 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once +#include + +constexpr int fixure_buffer_size = 41; +constexpr std::array fixure_buffer[41] = { + 0xff, 0xf1, 0x4c, 0x80, 0x05, 0x3f, 0xfc, 0x21, 0x1a, 0x4e, 0xb0, 0x00, 0x00, 0x00, + 0x05, 0xfc, 0x4e, 0x1f, 0x08, 0x88, 0x00, 0x00, 0x00, 0xc4, 0x1a, 0x03, 0xfc, 0x9c, + 0x3e, 0x1d, 0x08, 0x84, 0x03, 0xd8, 0x3f, 0xe4, 0xe1, 0x20, 0x00, 0x0b, 0x38}; diff --git a/src/tests/audio_core/decoder_tests.cpp b/src/tests/audio_core/decoder_tests.cpp new file mode 100644 index 000000000..d8f31127d --- /dev/null +++ b/src/tests/audio_core/decoder_tests.cpp @@ -0,0 +1,58 @@ +// Copyright 2019 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. +#if defined(HAVE_MF) || defined(HAVE_FFMPEG) + +#include +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/memory.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/shared_page.h" +#include "core/memory.h" + +#include "audio_core/hle/decoder.h" +#ifdef HAVE_MF +#include "audio_core/hle/wmf_decoder.h" +#elif HAVE_FFMPEG +#include "audio_core/hle/ffmpeg_decoder.h" +#endif +#include "audio_fixures.h" + +TEST_CASE("DSP HLE Audio Decoder", "[audio_core]") { + // HACK: see comments of member timing + Core::System::GetInstance().timing = std::make_unique(); + Core::System::GetInstance().memory = std::make_unique(); + Kernel::KernelSystem kernel(*Core::System::GetInstance().memory, 0); + SECTION("decoder should produce correct samples") { + auto process = kernel.CreateProcess(kernel.CreateCodeSet("", 0)); + auto decoder = +#ifdef HAVE_MF + std::make_unique(*Core::System::GetInstance().memory); +#elif HAVE_FFMPEG + std::make_unique(*Core::System::GetInstance().memory); +#endif + AudioCore::HLE::BinaryRequest request; + + request.codec = AudioCore::HLE::DecoderCodec::AAC; + request.cmd = AudioCore::HLE::DecoderCommand::Init; + // initialize decoder + std::optional response = decoder->ProcessRequest(request); + + request.cmd = AudioCore::HLE::DecoderCommand::Decode; + u8* fcram = Core::System::GetInstance().memory->GetFCRAMPointer(0); + + memcpy(fcram, fixure_buffer, fixure_buffer_size); + request.src_addr = Memory::FCRAM_PADDR; + request.dst_addr_ch0 = Memory::FCRAM_PADDR + 1024; + request.dst_addr_ch1 = Memory::FCRAM_PADDR + 1048576; // 1 MB + request.size = fixure_buffer_size; + + response = decoder->ProcessRequest(request); + response = decoder->ProcessRequest(request); + // remove this line + request.src_addr = Memory::FCRAM_PADDR; + } +} + +#endif