mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Merge pull request #4508 from B3n30/dsp_aac
CoreAudio::HLE: Add FFmpeg/WMF AAC decoder
This commit is contained in:
		
						commit
						1f38c53d8f
					
				
					 27 changed files with 1821 additions and 11 deletions
				
			
		|  | @ -5,7 +5,7 @@ cd /citra | ||||||
| echo 'max_size = 3.0G' > "$HOME/.ccache/ccache.conf" | echo 'max_size = 3.0G' > "$HOME/.ccache/ccache.conf" | ||||||
| 
 | 
 | ||||||
| mkdir build && cd build | 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 | make -j4 | ||||||
| 
 | 
 | ||||||
| echo "Tests skipped" | echo "Tests skipped" | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ export Qt5_DIR=$(brew --prefix)/opt/qt5 | ||||||
| export PATH="/usr/local/opt/ccache/libexec:$PATH" | export PATH="/usr/local/opt/ccache/libexec:$PATH" | ||||||
| 
 | 
 | ||||||
| mkdir build && cd build | 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 | make -j4 | ||||||
| 
 | 
 | ||||||
| ctest -VV -C Release | ctest -VV -C Release | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| #!/bin/sh -ex | #!/bin/sh -ex | ||||||
| 
 | 
 | ||||||
| brew update | brew update | ||||||
| brew install qt5 sdl2 dylibbundler p7zip ccache | brew install qt5 sdl2 dylibbundler p7zip ccache ffmpeg | ||||||
|  |  | ||||||
|  | @ -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/" | 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 | # 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_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_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_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_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_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_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/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" | 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. | # Make the citra-qt.app application launch a debugging terminal. | ||||||
|  |  | ||||||
|  | @ -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_CUBEB "Enables the cubeb audio backend" ON) | ||||||
| 
 | 
 | ||||||
|  | option(ENABLE_FFMPEG "Enable FFmpeg decoder/encoder" OFF) | ||||||
|  | 
 | ||||||
| option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" 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) | if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit) | ||||||
|     message(STATUS "Copying pre-commit hook") |     message(STATUS "Copying pre-commit hook") | ||||||
|     file(COPY hooks/pre-commit |     file(COPY hooks/pre-commit | ||||||
|  | @ -251,6 +255,31 @@ if (ENABLE_QT) | ||||||
|     endif() |     endif() | ||||||
| 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 | # Platform-specific library requirements | ||||||
| # ====================================== | # ====================================== | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ install: | ||||||
|   - git submodule update --init --recursive |   - git submodule update --init --recursive | ||||||
|   - ps: | |   - ps: | | ||||||
|         if ($env:BUILD_TYPE -eq 'mingw') { |         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 |           # redirect err to null to prevent warnings from becoming errors | ||||||
|           # workaround to prevent pacman from failing due to cyclical dependencies |           # 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 |           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} |         $COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING} | ||||||
|         if ($env:BUILD_TYPE -eq 'msvc') { |         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 |           # 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 { |         } 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 .. |   - cd .. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										183
									
								
								externals/cmake-modules/FindFFmpeg.cmake
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								externals/cmake-modules/FindFFmpeg.cmake
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,183 @@ | ||||||
|  | # FindFFmpeg | ||||||
|  | # ---------- | ||||||
|  | # | ||||||
|  | # Find the native FFmpeg includes and libraries | ||||||
|  | # | ||||||
|  | # This module defines the following variables: | ||||||
|  | # | ||||||
|  | #  FFmpeg_INCLUDE_<component>: where to find <component>.h | ||||||
|  | #  FFmpeg_LIBRARY_<component>: where to find the <component> 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::<component>: Target to use <component> 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...). | ||||||
|  | # | ||||||
|  | # <component> 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) | ||||||
|  | @ -4,7 +4,11 @@ add_library(audio_core STATIC | ||||||
|     codec.h |     codec.h | ||||||
|     dsp_interface.cpp |     dsp_interface.cpp | ||||||
|     dsp_interface.h |     dsp_interface.h | ||||||
|  |     hle/adts.h | ||||||
|  |     hle/adts_reader.cpp | ||||||
|     hle/common.h |     hle/common.h | ||||||
|  |     hle/decoder.cpp | ||||||
|  |     hle/decoder.h | ||||||
|     hle/filter.cpp |     hle/filter.cpp | ||||||
|     hle/filter.h |     hle/filter.h | ||||||
|     hle/hle.cpp |     hle/hle.cpp | ||||||
|  | @ -27,6 +31,8 @@ add_library(audio_core STATIC | ||||||
| 
 | 
 | ||||||
|     $<$<BOOL:${SDL2_FOUND}>:sdl2_sink.cpp sdl2_sink.h> |     $<$<BOOL:${SDL2_FOUND}>:sdl2_sink.cpp sdl2_sink.h> | ||||||
|     $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> |     $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> | ||||||
|  |     $<$<BOOL:${FFMPEG_FOUND}>:hle/ffmpeg_decoder.cpp hle/ffmpeg_decoder.h hle/ffmpeg_dl.cpp hle/ffmpeg_dl.h> | ||||||
|  |     $<$<BOOL:${ENABLE_MF}>:hle/wmf_decoder.cpp hle/wmf_decoder.h hle/wmf_decoder_utils.cpp hle/wmf_decoder_utils.h> | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| create_target_directory_groups(audio_core) | 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 PUBLIC common core) | ||||||
| target_link_libraries(audio_core PRIVATE SoundTouch teakra) | 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) | if(SDL2_FOUND) | ||||||
|     target_link_libraries(audio_core PRIVATE SDL2) |     target_link_libraries(audio_core PRIVATE SDL2) | ||||||
|     target_compile_definitions(audio_core PRIVATE HAVE_SDL2) |     target_compile_definitions(audio_core PRIVATE HAVE_SDL2) | ||||||
|  | @ -43,3 +63,4 @@ if(ENABLE_CUBEB) | ||||||
|     target_link_libraries(audio_core PRIVATE cubeb) |     target_link_libraries(audio_core PRIVATE cubeb) | ||||||
|     add_definitions(-DHAVE_CUBEB=1) |     add_definitions(-DHAVE_CUBEB=1) | ||||||
| endif() | endif() | ||||||
|  | 
 | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								src/audio_core/hle/adts.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/audio_core/hle/adts.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||||
							
								
								
									
										61
									
								
								src/audio_core/hle/adts_reader.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/audio_core/hle/adts_reader.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | ||||||
|  | // Copyright 2019 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | #include <array> | ||||||
|  | #include "adts.h" | ||||||
|  | 
 | ||||||
|  | constexpr std::array<u32, 16> freq_table = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, | ||||||
|  |                                             16000, 12000, 11025, 8000,  7350,  0,     0,     0}; | ||||||
|  | constexpr std::array<u8, 8> 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; | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								src/audio_core/hle/decoder.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/audio_core/hle/decoder.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<BinaryResponse> 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<u16>(request.cmd)); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | } // namespace AudioCore::HLE
 | ||||||
							
								
								
									
										68
									
								
								src/audio_core/hle/decoder.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/audio_core/hle/decoder.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <memory> | ||||||
|  | #include <optional> | ||||||
|  | #include <vector> | ||||||
|  | #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<DecoderCodec> codec = | ||||||
|  |         DecoderCodec::None; // this is a guess. until now only 0x1 was observed here
 | ||||||
|  |     enum_le<DecoderCommand> 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<DecoderCodec> codec = | ||||||
|  |         DecoderCodec::None; // this could be something else. until now only 0x1 was observed here
 | ||||||
|  |     enum_le<DecoderCommand> 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<BinaryResponse> ProcessRequest(const BinaryRequest& request) = 0; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | class NullDecoder final : public DecoderBase { | ||||||
|  | public: | ||||||
|  |     NullDecoder(); | ||||||
|  |     ~NullDecoder() override; | ||||||
|  |     std::optional<BinaryResponse> ProcessRequest(const BinaryRequest& request) override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace AudioCore::HLE
 | ||||||
							
								
								
									
										263
									
								
								src/audio_core/hle/ffmpeg_decoder.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								src/audio_core/hle/ffmpeg_decoder.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<BinaryResponse> ProcessRequest(const BinaryRequest& request); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::optional<BinaryResponse> Initalize(const BinaryRequest& request); | ||||||
|  | 
 | ||||||
|  |     void Clear(); | ||||||
|  | 
 | ||||||
|  |     std::optional<BinaryResponse> 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<AVCodecContext, AVCodecContextDeleter> av_context; | ||||||
|  |     std::unique_ptr<AVCodecParserContext, AVCodecParserContextDeleter> parser; | ||||||
|  |     std::unique_ptr<AVPacket, AVPacketDeleter> av_packet; | ||||||
|  |     std::unique_ptr<AVFrame, AVFrameDeleter> decoded_frame; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | FFMPEGDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { | ||||||
|  |     have_ffmpeg_dl = InitFFmpegDL(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FFMPEGDecoder::Impl::~Impl() = default; | ||||||
|  | 
 | ||||||
|  | std::optional<BinaryResponse> FFMPEGDecoder::Impl::ProcessRequest(const BinaryRequest& request) { | ||||||
|  |     if (request.codec != DecoderCodec::AAC) { | ||||||
|  |         LOG_ERROR(Audio_DSP, "Got wrong codec {}", static_cast<u16>(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<u16>(request.cmd)); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<BinaryResponse> 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<BinaryResponse> 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<std::vector<u8>, 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<s16>(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<Impl>(memory)) {} | ||||||
|  | 
 | ||||||
|  | FFMPEGDecoder::~FFMPEGDecoder() = default; | ||||||
|  | 
 | ||||||
|  | std::optional<BinaryResponse> FFMPEGDecoder::ProcessRequest(const BinaryRequest& request) { | ||||||
|  |     return impl->ProcessRequest(request); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace AudioCore::HLE
 | ||||||
							
								
								
									
										22
									
								
								src/audio_core/hle/ffmpeg_decoder.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/audio_core/hle/ffmpeg_decoder.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<BinaryResponse> ProcessRequest(const BinaryRequest& request) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     class Impl; | ||||||
|  |     std::unique_ptr<Impl> impl; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace AudioCore::HLE
 | ||||||
							
								
								
									
										178
									
								
								src/audio_core/hle/ffmpeg_dl.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/audio_core/hle/ffmpeg_dl.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <memory> | ||||||
|  | #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<HMODULE, LibraryDeleter> dll_util{nullptr}; | ||||||
|  | std::unique_ptr<HMODULE, LibraryDeleter> dll_codec{nullptr}; | ||||||
|  | 
 | ||||||
|  | } // namespace
 | ||||||
|  | 
 | ||||||
|  | FuncDL<int(AVSampleFormat)> av_get_bytes_per_sample_dl; | ||||||
|  | FuncDL<AVFrame*(void)> av_frame_alloc_dl; | ||||||
|  | FuncDL<void(AVFrame**)> av_frame_free_dl; | ||||||
|  | FuncDL<AVCodecContext*(const AVCodec*)> avcodec_alloc_context3_dl; | ||||||
|  | FuncDL<void(AVCodecContext**)> avcodec_free_context_dl; | ||||||
|  | FuncDL<int(AVCodecContext*, const AVCodec*, AVDictionary**)> avcodec_open2_dl; | ||||||
|  | FuncDL<AVPacket*(void)> av_packet_alloc_dl; | ||||||
|  | FuncDL<void(AVPacket**)> av_packet_free_dl; | ||||||
|  | FuncDL<AVCodec*(AVCodecID)> avcodec_find_decoder_dl; | ||||||
|  | FuncDL<int(AVCodecContext*, const AVPacket*)> avcodec_send_packet_dl; | ||||||
|  | FuncDL<int(AVCodecContext*, AVFrame*)> avcodec_receive_frame_dl; | ||||||
|  | FuncDL<AVCodecParserContext*(int)> av_parser_init_dl; | ||||||
|  | FuncDL<int(AVCodecParserContext*, AVCodecContext*, uint8_t**, int*, const uint8_t*, int, int64_t, | ||||||
|  |            int64_t, int64_t)> | ||||||
|  |     av_parser_parse2_dl; | ||||||
|  | FuncDL<void(AVCodecParserContext*)> 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<LPSTR>(&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<LPSTR>(&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<int(AVSampleFormat)>(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<AVFrame*()>(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<void(AVFrame**)>(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<AVCodecContext*(const AVCodec*)>(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<void(AVCodecContext**)>(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<int(AVCodecContext*, const AVCodec*, AVDictionary**)>( | ||||||
|  |         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<AVPacket*(void)>(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<void(AVPacket**)>(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<AVCodec*(AVCodecID)>(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<int(AVCodecContext*, const AVPacket*)>(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<int(AVCodecContext*, AVFrame*)>(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<AVCodecParserContext*(int)>(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<int(AVCodecParserContext*, AVCodecContext*, uint8_t**, int*, const uint8_t*, int, | ||||||
|  |                    int64_t, int64_t, int64_t)>(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<void(AVCodecParserContext*)>(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
 | ||||||
							
								
								
									
										79
									
								
								src/audio_core/hle/ffmpeg_dl.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/audio_core/hle/ffmpeg_dl.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <windows.h> | ||||||
|  | #endif // _WIN32
 | ||||||
|  | 
 | ||||||
|  | extern "C" { | ||||||
|  | #include <libavcodec/avcodec.h> | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #ifdef _WIN32 | ||||||
|  | 
 | ||||||
|  | template <typename T> | ||||||
|  | struct FuncDL { | ||||||
|  |     FuncDL() = default; | ||||||
|  |     FuncDL(HMODULE dll, const char* name) { | ||||||
|  |         if (dll) { | ||||||
|  |             ptr_function = reinterpret_cast<T*>(GetProcAddress(dll, name)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     operator T*() const { | ||||||
|  |         return ptr_function; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     explicit operator bool() const { | ||||||
|  |         return ptr_function != nullptr; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     T* ptr_function = nullptr; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | extern FuncDL<int(AVSampleFormat)> av_get_bytes_per_sample_dl; | ||||||
|  | extern FuncDL<AVFrame*(void)> av_frame_alloc_dl; | ||||||
|  | extern FuncDL<void(AVFrame**)> av_frame_free_dl; | ||||||
|  | extern FuncDL<AVCodecContext*(const AVCodec*)> avcodec_alloc_context3_dl; | ||||||
|  | extern FuncDL<void(AVCodecContext**)> avcodec_free_context_dl; | ||||||
|  | extern FuncDL<int(AVCodecContext*, const AVCodec*, AVDictionary**)> avcodec_open2_dl; | ||||||
|  | extern FuncDL<AVPacket*(void)> av_packet_alloc_dl; | ||||||
|  | extern FuncDL<void(AVPacket**)> av_packet_free_dl; | ||||||
|  | extern FuncDL<AVCodec*(AVCodecID)> avcodec_find_decoder_dl; | ||||||
|  | extern FuncDL<int(AVCodecContext*, const AVPacket*)> avcodec_send_packet_dl; | ||||||
|  | extern FuncDL<int(AVCodecContext*, AVFrame*)> avcodec_receive_frame_dl; | ||||||
|  | extern FuncDL<AVCodecParserContext*(int)> av_parser_init_dl; | ||||||
|  | extern FuncDL<int(AVCodecParserContext*, AVCodecContext*, uint8_t**, int*, const uint8_t*, int, | ||||||
|  |                   int64_t, int64_t, int64_t)> | ||||||
|  |     av_parser_parse2_dl; | ||||||
|  | extern FuncDL<void(AVCodecParserContext*)> 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
 | ||||||
|  | @ -3,7 +3,13 @@ | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include "audio_core/audio_types.h" | #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/common.h" | ||||||
|  | #include "audio_core/hle/decoder.h" | ||||||
| #include "audio_core/hle/hle.h" | #include "audio_core/hle/hle.h" | ||||||
| #include "audio_core/hle/mixers.h" | #include "audio_core/hle/mixers.h" | ||||||
| #include "audio_core/hle/shared_memory.h" | #include "audio_core/hle/shared_memory.h" | ||||||
|  | @ -69,6 +75,8 @@ private: | ||||||
|     DspHle& parent; |     DspHle& parent; | ||||||
|     Core::TimingEventType* tick_event; |     Core::TimingEventType* tick_event; | ||||||
| 
 | 
 | ||||||
|  |     std::unique_ptr<HLE::DecoderBase> decoder; | ||||||
|  | 
 | ||||||
|     std::weak_ptr<DSP_DSP> dsp_dsp; |     std::weak_ptr<DSP_DSP> dsp_dsp; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -79,6 +87,15 @@ DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory) : parent(paren | ||||||
|         source.SetMemory(memory); |         source.SetMemory(memory); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | #ifdef HAVE_MF | ||||||
|  |     decoder = std::make_unique<HLE::WMFDecoder>(memory); | ||||||
|  | #elif HAVE_FFMPEG | ||||||
|  |     decoder = std::make_unique<HLE::FFMPEGDecoder>(memory); | ||||||
|  | #else | ||||||
|  |     LOG_WARNING(Audio_DSP, "No decoder found, this could lead to missing audio"); | ||||||
|  |     decoder = std::make_unique<HLE::NullDecoder>(); | ||||||
|  | #endif // HAVE_MF
 | ||||||
|  | 
 | ||||||
|     Core::Timing& timing = Core::System::GetInstance().CoreTiming(); |     Core::Timing& timing = Core::System::GetInstance().CoreTiming(); | ||||||
|     tick_event = |     tick_event = | ||||||
|         timing.RegisterEvent("AudioCore::DspHle::tick_event", [this](u64, s64 cycles_late) { |         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<u8>& buffer) | ||||||
| 
 | 
 | ||||||
|         return; |         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<u16>(request.codec)); | ||||||
|  |             UNIMPLEMENTED(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         std::optional<HLE::BinaryResponse> response = decoder->ProcessRequest(request); | ||||||
|  |         if (response) { | ||||||
|  |             const HLE::BinaryResponse& value = *response; | ||||||
|  |             pipe_data[static_cast<u32>(pipe_number)].resize(sizeof(value)); | ||||||
|  |             std::memcpy(pipe_data[static_cast<u32>(pipe_number)].data(), &value, sizeof(value)); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|         LOG_CRITICAL(Audio_DSP, "pipe_number = {} unimplemented", |         LOG_CRITICAL(Audio_DSP, "pipe_number = {} unimplemented", | ||||||
|                      static_cast<std::size_t>(pipe_number)); |                      static_cast<std::size_t>(pipe_number)); | ||||||
|  |  | ||||||
							
								
								
									
										274
									
								
								src/audio_core/hle/wmf_decoder.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								src/audio_core/hle/wmf_decoder.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<BinaryResponse> ProcessRequest(const BinaryRequest& request); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::optional<BinaryResponse> Initalize(const BinaryRequest& request); | ||||||
|  | 
 | ||||||
|  |     std::optional<BinaryResponse> Decode(const BinaryRequest& request); | ||||||
|  | 
 | ||||||
|  |     MFOutputState DecodingLoop(ADTSData adts_header, std::array<std::vector<u8>, 2>& out_streams); | ||||||
|  | 
 | ||||||
|  |     bool transform_initialized = false; | ||||||
|  |     bool format_selected = false; | ||||||
|  | 
 | ||||||
|  |     Memory::MemorySystem& memory; | ||||||
|  | 
 | ||||||
|  |     unique_mfptr<IMFTransform> 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<BinaryResponse> WMFDecoder::Impl::ProcessRequest(const BinaryRequest& request) { | ||||||
|  |     if (request.codec != DecoderCodec::AAC) { | ||||||
|  |         LOG_ERROR(Audio_DSP, "Got unknown codec {}", static_cast<u16>(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<u16>(request.cmd)); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<BinaryResponse> 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<std::vector<u8>, 2>& out_streams) { | ||||||
|  |     MFOutputState output_status = MFOutputState::OK; | ||||||
|  |     std::optional<std::vector<f32>> output_buffer; | ||||||
|  |     unique_mfptr<IMFSample> 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<s16>(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<BinaryResponse> 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<std::vector<u8>, 2> out_streams; | ||||||
|  |     unique_mfptr<IMFSample> sample; | ||||||
|  |     MFInputState input_status = MFInputState::OK; | ||||||
|  |     MFOutputState output_status = MFOutputState::OK; | ||||||
|  |     std::optional<ADTSMeta> 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<Impl>(memory)) {} | ||||||
|  | 
 | ||||||
|  | WMFDecoder::~WMFDecoder() = default; | ||||||
|  | 
 | ||||||
|  | std::optional<BinaryResponse> WMFDecoder::ProcessRequest(const BinaryRequest& request) { | ||||||
|  |     return impl->ProcessRequest(request); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace AudioCore::HLE
 | ||||||
							
								
								
									
										22
									
								
								src/audio_core/hle/wmf_decoder.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/audio_core/hle/wmf_decoder.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<BinaryResponse> ProcessRequest(const BinaryRequest& request) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     class Impl; | ||||||
|  |     std::unique_ptr<Impl> impl; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace AudioCore::HLE
 | ||||||
							
								
								
									
										349
									
								
								src/audio_core/hle/wmf_decoder_utils.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										349
									
								
								src/audio_core/hle/wmf_decoder_utils.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<IMFTransform> MFDecoderInit(GUID audio_format) { | ||||||
|  |     HRESULT hr = S_OK; | ||||||
|  |     MFT_REGISTER_TYPE_INFO reg = {0}; | ||||||
|  |     GUID category = MFT_CATEGORY_AUDIO_DECODER; | ||||||
|  |     IMFActivate** activate; | ||||||
|  |     unique_mfptr<IMFTransform> 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<void**>(static_cast<IMFTransform**>(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<IMFSample> CreateSample(const void* data, DWORD len, DWORD alignment, | ||||||
|  |                                      LONGLONG duration) { | ||||||
|  |     HRESULT hr = S_OK; | ||||||
|  |     unique_mfptr<IMFMediaBuffer> buf; | ||||||
|  |     unique_mfptr<IMFSample> 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<IMFMediaType> 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<IMFMediaType> 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<ADTSMeta> 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<MFOutputState, unique_mfptr<IMFSample>> 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<IMFSample> 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<std::vector<f32>> CopySampleToBuffer(IMFSample* sample) { | ||||||
|  |     unique_mfptr<IMFMediaBuffer> buffer; | ||||||
|  |     HRESULT hr = S_OK; | ||||||
|  |     std::optional<std::vector<f32>> output; | ||||||
|  |     std::vector<f32> 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; | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								src/audio_core/hle/wmf_decoder_utils.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/audio_core/hle/wmf_decoder_utils.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <optional> | ||||||
|  | #include <string> | ||||||
|  | #include <tuple> | ||||||
|  | #include <vector> | ||||||
|  | #include <comdef.h> | ||||||
|  | #include <mfapi.h> | ||||||
|  | #include <mferror.h> | ||||||
|  | #include <mfidl.h> | ||||||
|  | #include <mftransform.h> | ||||||
|  | 
 | ||||||
|  | #include "adts.h" | ||||||
|  | 
 | ||||||
|  | enum class MFOutputState { FatalError, OK, NeedMoreInput, NeedReconfig, HaveMoreData }; | ||||||
|  | enum class MFInputState { FatalError, OK, NotAccepted }; | ||||||
|  | 
 | ||||||
|  | // utility functions / templates
 | ||||||
|  | template <class T> | ||||||
|  | struct MFRelease { | ||||||
|  |     void operator()(T* pointer) const { | ||||||
|  |         pointer->Release(); | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template <> | ||||||
|  | struct MFRelease<IMFTransform> { | ||||||
|  |     void operator()(IMFTransform* pointer) const { | ||||||
|  |         MFShutdownObject(pointer); | ||||||
|  |         pointer->Release(); | ||||||
|  |     }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // wrapper facilities for dealing with pointers
 | ||||||
|  | template <typename T> | ||||||
|  | using unique_mfptr = std::unique_ptr<T, MFRelease<T>>; | ||||||
|  | 
 | ||||||
|  | template <typename SmartPtr, typename RawPtr> | ||||||
|  | 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 <typename SmartPtr> | ||||||
|  | auto Amp(SmartPtr& smart_ptr) { | ||||||
|  |     return AmpImpl<SmartPtr, decltype(smart_ptr.get())>(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<IMFTransform> MFDecoderInit(GUID audio_format = MFAudioFormat_AAC); | ||||||
|  | unique_mfptr<IMFSample> 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<ADTSMeta> 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<MFOutputState, unique_mfptr<IMFSample>> ReceiveSample(IMFTransform* transform, | ||||||
|  |                                                                  DWORD out_stream_id); | ||||||
|  | std::optional<std::vector<f32>> CopySampleToBuffer(IMFSample* sample); | ||||||
|  | @ -38,6 +38,7 @@ | ||||||
| #define SYSDATA_DIR "sysdata" | #define SYSDATA_DIR "sysdata" | ||||||
| #define LOG_DIR "log" | #define LOG_DIR "log" | ||||||
| #define CHEATS_DIR "cheats" | #define CHEATS_DIR "cheats" | ||||||
|  | #define DLL_DIR "external_dlls" | ||||||
| 
 | 
 | ||||||
| // Filenames
 | // Filenames
 | ||||||
| // Files in the directory returned by GetUserPath(UserPath::LogDir)
 | // Files in the directory returned by GetUserPath(UserPath::LogDir)
 | ||||||
|  |  | ||||||
|  | @ -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
 |         // TODO: Put the logs in a better location for each OS
 | ||||||
|         paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); |         paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); | ||||||
|         paths.emplace(UserPath::CheatsDir, user_path + CHEATS_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()) { |     if (!new_path.empty()) { | ||||||
|  |  | ||||||
|  | @ -24,6 +24,7 @@ enum class UserPath { | ||||||
|     CacheDir, |     CacheDir, | ||||||
|     CheatsDir, |     CheatsDir, | ||||||
|     ConfigDir, |     ConfigDir, | ||||||
|  |     DLLDir, | ||||||
|     LogDir, |     LogDir, | ||||||
|     NANDDir, |     NANDDir, | ||||||
|     RootDir, |     RootDir, | ||||||
|  |  | ||||||
|  | @ -9,6 +9,8 @@ add_executable(tests | ||||||
|     core/hle/kernel/hle_ipc.cpp |     core/hle/kernel/hle_ipc.cpp | ||||||
|     core/memory/memory.cpp |     core/memory/memory.cpp | ||||||
|     core/memory/vm_manager.cpp |     core/memory/vm_manager.cpp | ||||||
|  |     audio_core/audio_fixures.h | ||||||
|  |     audio_core/decoder_tests.cpp | ||||||
|     tests.cpp |     tests.cpp | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -21,7 +23,7 @@ endif() | ||||||
| 
 | 
 | ||||||
| create_target_directory_groups(tests) | 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) | target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include nihstro-headers Threads::Threads) | ||||||
| 
 | 
 | ||||||
| add_test(NAME tests COMMAND tests) | add_test(NAME tests COMMAND tests) | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								src/tests/audio_core/audio_fixures.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tests/audio_core/audio_fixures.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <array> | ||||||
|  | 
 | ||||||
|  | constexpr int fixure_buffer_size = 41; | ||||||
|  | constexpr std::array<u8, 41> 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}; | ||||||
							
								
								
									
										58
									
								
								src/tests/audio_core/decoder_tests.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/tests/audio_core/decoder_tests.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <catch2/catch.hpp> | ||||||
|  | #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::Timing>(); | ||||||
|  |     Core::System::GetInstance().memory = std::make_unique<Memory::MemorySystem>(); | ||||||
|  |     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<AudioCore::HLE::WMFDecoder>(*Core::System::GetInstance().memory); | ||||||
|  | #elif HAVE_FFMPEG | ||||||
|  |             std::make_unique<AudioCore::HLE::FFMPEGDecoder>(*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<AudioCore::HLE::BinaryResponse> 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 | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue