mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	audio_core: Replace AAC decoders with single FAAD2-based decoder. (#7098)
This commit is contained in:
		
							parent
							
								
									1570aeffcb
								
							
						
					
					
						commit
						27bad3a699
					
				
					 28 changed files with 304 additions and 2403 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							|  | @ -79,6 +79,9 @@ | ||||||
| [submodule "sirit"] | [submodule "sirit"] | ||||||
|     path = externals/sirit |     path = externals/sirit | ||||||
|     url = https://github.com/yuzu-emu/sirit |     url = https://github.com/yuzu-emu/sirit | ||||||
|  | [submodule "faad2"] | ||||||
|  |     path = externals/faad2/faad2 | ||||||
|  |     url = https://github.com/knik0/faad2 | ||||||
| [submodule "library-headers"] | [submodule "library-headers"] | ||||||
|     path = externals/library-headers |     path = externals/library-headers | ||||||
|     url = https://github.com/citra-emu/ext-library-headers.git |     url = https://github.com/citra-emu/ext-library-headers.git | ||||||
|  |  | ||||||
|  | @ -81,9 +81,6 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support | ||||||
| 
 | 
 | ||||||
| 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 (preferred over FFmpeg)" ON "WIN32" OFF) |  | ||||||
| CMAKE_DEPENDENT_OPTION(ENABLE_AUDIOTOOLBOX "Use AudioToolbox decoder (preferred over FFmpeg)" ON "APPLE" OFF) |  | ||||||
| 
 |  | ||||||
| CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF) | CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF) | ||||||
| 
 | 
 | ||||||
| # Compile options | # Compile options | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								externals/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								externals/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							|  | @ -156,24 +156,12 @@ endif() | ||||||
| # Open Source Archives | # Open Source Archives | ||||||
| add_subdirectory(open_source_archives) | add_subdirectory(open_source_archives) | ||||||
| 
 | 
 | ||||||
|  | # faad2 | ||||||
|  | add_subdirectory(faad2 EXCLUDE_FROM_ALL) | ||||||
|  | 
 | ||||||
| # Dynamic library headers | # Dynamic library headers | ||||||
| add_library(library-headers INTERFACE) | add_library(library-headers INTERFACE) | ||||||
| 
 | 
 | ||||||
| if (USE_SYSTEM_FDK_AAC_HEADERS) |  | ||||||
|     find_path(SYSTEM_FDK_AAC_INCLUDES NAMES fdk-aac/aacdecoder_lib.h) |  | ||||||
|     if (SYSTEM_FDK_AAC_INCLUDES STREQUAL "SYSTEM_FDK_AAC_INCLUDES-NOTFOUND") |  | ||||||
|         message(WARNING "System fdk-aac headers not found. Falling back on bundled headers.") |  | ||||||
|     else() |  | ||||||
|         message(STATUS "Using system fdk_aac headers.") |  | ||||||
|         target_include_directories(library-headers SYSTEM INTERFACE ${SYSTEM_FDK_AAC_INCLUDES}) |  | ||||||
|         set(FOUND_FDK_AAC_HEADERS ON) |  | ||||||
|     endif() |  | ||||||
| endif() |  | ||||||
| if (NOT FOUND_FDK_AAC_HEADERS) |  | ||||||
|     message(STATUS "Using bundled fdk_aac headers.") |  | ||||||
|     target_include_directories(library-headers SYSTEM INTERFACE ./library-headers/fdk-aac/include) |  | ||||||
| endif() |  | ||||||
| 
 |  | ||||||
| if (USE_SYSTEM_FFMPEG_HEADERS) | if (USE_SYSTEM_FFMPEG_HEADERS) | ||||||
|     find_path(SYSTEM_FFMPEG_INCLUDES NAMES libavutil/avutil.h) |     find_path(SYSTEM_FFMPEG_INCLUDES NAMES libavutil/avutil.h) | ||||||
|     if (SYSTEM_FFMPEG_INCLUDES STREQUAL "SYSTEM_FFMPEG_INCLUDES-NOTFOUND") |     if (SYSTEM_FFMPEG_INCLUDES STREQUAL "SYSTEM_FFMPEG_INCLUDES-NOTFOUND") | ||||||
|  |  | ||||||
|  | @ -15,7 +15,6 @@ option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one) | ||||||
| option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF) | option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF) | ||||||
| option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF) | option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF) | ||||||
| option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF) | option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF) | ||||||
| option(USE_SYSTEM_FDK_AAC_HEADERS "Use the system fdk-aac headers (instead of the bundled one)" OFF) |  | ||||||
| option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF) | option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF) | ||||||
| option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF) | option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF) | ||||||
| option(USE_SYSTEM_ZSTD "Use the system Zstandard library (instead of the bundled one)" OFF) | option(USE_SYSTEM_ZSTD "Use the system Zstandard library (instead of the bundled one)" OFF) | ||||||
|  | @ -36,7 +35,6 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "US | ||||||
| CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF) | CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF) | ||||||
| CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF) | CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF) | ||||||
| CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF) | CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF) | ||||||
| CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FDK_AAC_HEADERS "Disable system fdk_aac" OFF "USE_SYSTEM_LIBS" OFF) |  | ||||||
| CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF) | CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF) | ||||||
| CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF) | CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF) | ||||||
| CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_ZSTD "Disable system Zstandard" OFF "USE_SYSTEM_LIBS" OFF) | CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_ZSTD "Disable system Zstandard" OFF "USE_SYSTEM_LIBS" OFF) | ||||||
|  | @ -57,7 +55,6 @@ set(LIB_VAR_LIST | ||||||
|     FMT |     FMT | ||||||
|     XBYAK |     XBYAK | ||||||
|     INIH |     INIH | ||||||
|     FDK_AAC_HEADERS |  | ||||||
|     FFMPEG_HEADERS |     FFMPEG_HEADERS | ||||||
|     GLSLANG |     GLSLANG | ||||||
|     ZSTD |     ZSTD | ||||||
|  |  | ||||||
							
								
								
									
										102
									
								
								externals/faad2/CMakeLists.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								externals/faad2/CMakeLists.txt
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | ||||||
|  | # Copy source to build directory for some modifications. | ||||||
|  | set(FAAD2_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/faad2/libfaad") | ||||||
|  | if (NOT EXISTS "${FAAD2_SOURCE_DIR}") | ||||||
|  |     file(COPY faad2/libfaad/ DESTINATION "${FAAD2_SOURCE_DIR}/") | ||||||
|  | 
 | ||||||
|  |     # These are fixed defines for some reason and not controllable with compile flags. | ||||||
|  |     file(READ "${FAAD2_SOURCE_DIR}/common.h" FAAD2_COMMON_H) | ||||||
|  |     # Disable SBR decoding since we don't want it for AAC-LC. | ||||||
|  |     string(REGEX REPLACE "#define SBR_DEC" "" FAAD2_COMMON_H "${FAAD2_COMMON_H}") | ||||||
|  |     # Disable PS decoding. This can cause mono to be upmixed to stereo, which we don't want. | ||||||
|  |     string(REGEX REPLACE "#define PS_DEC" "" FAAD2_COMMON_H "${FAAD2_COMMON_H}") | ||||||
|  |     file(WRITE "${FAAD2_SOURCE_DIR}/common.h" "${FAAD2_COMMON_H}") | ||||||
|  | endif() | ||||||
|  | 
 | ||||||
|  | # Source list from faad2/libfaad/Makefile.am, cut down to just what we need for AAC-LC. | ||||||
|  | add_library(faad2 STATIC EXCLUDE_FROM_ALL | ||||||
|  |     "${FAAD2_SOURCE_DIR}/bits.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/cfft.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/common.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/decoder.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/drc.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/error.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/filtbank.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/huffman.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/is.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/mdct.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/mp4.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/ms.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/output.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/pns.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/pulse.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/specrec.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/syntax.c" | ||||||
|  |     "${FAAD2_SOURCE_DIR}/tns.c" | ||||||
|  | ) | ||||||
|  | target_include_directories(faad2 PUBLIC faad2/include PRIVATE "${FAAD2_SOURCE_DIR}") | ||||||
|  | 
 | ||||||
|  | # Configure compile definitions. | ||||||
|  | 
 | ||||||
|  | # Read version from autoconf script for configuring constant. | ||||||
|  | file(READ faad2/configure.ac CONFIGURE_SCRIPT) | ||||||
|  | string(REGEX MATCH "AC_INIT\\(faad2, ([0-9.]+)\\)" _ ${CONFIGURE_SCRIPT}) | ||||||
|  | set(FAAD_VERSION ${CMAKE_MATCH_1}) | ||||||
|  | message(STATUS "Building faad2 version ${FAAD_VERSION}") | ||||||
|  | 
 | ||||||
|  | # Check for functions and headers. | ||||||
|  | include(CheckFunctionExists) | ||||||
|  | include(CheckIncludeFiles) | ||||||
|  | check_function_exists(getpwuid HAVE_GETPWUID) | ||||||
|  | check_function_exists(lrintf HAVE_LRINTF) | ||||||
|  | check_function_exists(memcpy HAVE_MEMCPY) | ||||||
|  | check_function_exists(strchr HAVE_STRCHR) | ||||||
|  | check_function_exists(strsep HAVE_STRSEP) | ||||||
|  | check_include_files(dlfcn.h HAVE_DLFCN_H) | ||||||
|  | check_include_files(errno.h HAVE_ERRNO_H) | ||||||
|  | check_include_files(float.h HAVE_FLOAT_H) | ||||||
|  | check_include_files(inttypes.h HAVE_INTTYPES_H) | ||||||
|  | check_include_files(IOKit/IOKitLib.h HAVE_IOKIT_IOKITLIB_H) | ||||||
|  | check_include_files(limits.h HAVE_LIMITS_H) | ||||||
|  | check_include_files(mathf.h HAVE_MATHF_H) | ||||||
|  | check_include_files(stdint.h HAVE_STDINT_H) | ||||||
|  | check_include_files(stdio.h HAVE_STDIO_H) | ||||||
|  | check_include_files(stdlib.h HAVE_STDLIB_H) | ||||||
|  | check_include_files(strings.h HAVE_STRINGS_H) | ||||||
|  | check_include_files(string.h HAVE_STRING_H) | ||||||
|  | check_include_files(sysfs/libsysfs.h HAVE_SYSFS_LIBSYSFS_H) | ||||||
|  | check_include_files(sys/stat.h HAVE_SYS_STAT_H) | ||||||
|  | check_include_files(sys/time.h HAVE_SYS_TIME_H) | ||||||
|  | check_include_files(sys/types.h HAVE_SYS_TYPES_H) | ||||||
|  | check_include_files(unistd.h HAVE_UNISTD_H) | ||||||
|  | 
 | ||||||
|  | # faad2 uses a relative include for its config.h which breaks under CMake. | ||||||
|  | # We can use target_compile_definitions to pass on the configuration instead. | ||||||
|  | target_compile_definitions(faad2 PRIVATE | ||||||
|  |     -DFAAD_VERSION=${FAAD_VERSION} | ||||||
|  |     -DPACKAGE_VERSION=\"${FAAD_VERSION}\" | ||||||
|  |     -DSTDC_HEADERS | ||||||
|  |     -DHAVE_GETPWUID=${HAVE_GETPWUID} | ||||||
|  |     -DHAVE_LRINTF=${HAVE_LRINTF} | ||||||
|  |     -DHAVE_MEMCPY=${HAVE_MEMCPY} | ||||||
|  |     -DHAVE_STRCHR=${HAVE_STRCHR} | ||||||
|  |     -DHAVE_STRSEP=${HAVE_STRSEP} | ||||||
|  |     -DHAVE_DLFCN_H=${HAVE_DLFCN_H} | ||||||
|  |     -DHAVE_ERRNO_H=${HAVE_ERRNO_H} | ||||||
|  |     -DHAVE_FLOAT_H=${HAVE_FLOAT_H} | ||||||
|  |     -DHAVE_INTTYPES_H=${HAVE_INTTYPES_H} | ||||||
|  |     -DHAVE_IOKIT_IOKITLIB_H=${HAVE_IOKIT_IOKITLIB_H} | ||||||
|  |     -DHAVE_LIMITS_H=${HAVE_LIMITS_H} | ||||||
|  |     -DHAVE_MATHF_H=${HAVE_MATHF_H} | ||||||
|  |     -DHAVE_STDINT_H=${HAVE_STDINT_H} | ||||||
|  |     -DHAVE_STDIO_H=${HAVE_STDIO_H} | ||||||
|  |     -DHAVE_STDLIB_H=${HAVE_STDLIB_H} | ||||||
|  |     -DHAVE_STRINGS_H=${HAVE_STRINGS_H} | ||||||
|  |     -DHAVE_STRING_H=${HAVE_STRING_H} | ||||||
|  |     -DHAVE_SYSFS_LIBSYSFS_H=${HAVE_SYSFS_LIBSYSFS_H} | ||||||
|  |     -DHAVE_SYS_STAT_H=${HAVE_SYS_STAT_H} | ||||||
|  |     -DHAVE_SYS_TIME_H=${HAVE_SYS_TIME_H} | ||||||
|  |     -DHAVE_SYS_TYPES_H=${HAVE_SYS_TYPES_H} | ||||||
|  |     -DHAVE_UNISTD_H=${HAVE_UNISTD_H} | ||||||
|  |     # Only compile for AAC-LC decoding. | ||||||
|  |     -DLC_ONLY_DECODER | ||||||
|  | ) | ||||||
							
								
								
									
										1
									
								
								externals/faad2/faad2
									
										
									
									
										vendored
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								externals/faad2/faad2
									
										
									
									
										vendored
									
									
										Submodule
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | Subproject commit 3918dee56063500d0aa23d6c3c94b211ac471a8c | ||||||
|  | @ -4,15 +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.cpp | ||||||
|     hle/decoder.h |     hle/decoder.h | ||||||
|     hle/fdk_decoder.cpp |     hle/faad2_decoder.cpp | ||||||
|     hle/fdk_decoder.h |     hle/faad2_decoder.h | ||||||
|     hle/ffmpeg_decoder.cpp |  | ||||||
|     hle/ffmpeg_decoder.h |  | ||||||
|     hle/filter.cpp |     hle/filter.cpp | ||||||
|     hle/filter.h |     hle/filter.h | ||||||
|     hle/hle.cpp |     hle/hle.cpp | ||||||
|  | @ -48,36 +44,7 @@ add_library(audio_core STATIC | ||||||
| create_target_directory_groups(audio_core) | create_target_directory_groups(audio_core) | ||||||
| 
 | 
 | ||||||
| target_link_libraries(audio_core PUBLIC citra_common citra_core) | target_link_libraries(audio_core PUBLIC citra_common citra_core) | ||||||
| target_link_libraries(audio_core PRIVATE SoundTouch teakra) | target_link_libraries(audio_core PRIVATE faad2 SoundTouch teakra) | ||||||
| 
 |  | ||||||
| if(ENABLE_MF) |  | ||||||
|     target_sources(audio_core PRIVATE |  | ||||||
|         hle/wmf_decoder.cpp |  | ||||||
|         hle/wmf_decoder.h |  | ||||||
|         hle/wmf_decoder_utils.cpp |  | ||||||
|         hle/wmf_decoder_utils.h |  | ||||||
|     ) |  | ||||||
|     # We dynamically load the required symbols from mf.dll and mfplat.dll but mfuuid is not a dll |  | ||||||
|     # just a static library of GUIDS so include that one directly. |  | ||||||
|     target_link_libraries(audio_core PRIVATE mfuuid.lib) |  | ||||||
|     target_compile_definitions(audio_core PUBLIC HAVE_MF) |  | ||||||
| elseif(ENABLE_AUDIOTOOLBOX) |  | ||||||
|     target_sources(audio_core PRIVATE |  | ||||||
|         hle/audiotoolbox_decoder.cpp |  | ||||||
|         hle/audiotoolbox_decoder.h |  | ||||||
|     ) |  | ||||||
|     find_library(AUDIOTOOLBOX AudioToolbox) |  | ||||||
|     target_link_libraries(audio_core PRIVATE ${AUDIOTOOLBOX}) |  | ||||||
|     target_compile_definitions(audio_core PUBLIC HAVE_AUDIOTOOLBOX) |  | ||||||
| endif() |  | ||||||
| 
 |  | ||||||
| if(ANDROID) |  | ||||||
|     target_sources(audio_core PRIVATE |  | ||||||
|         hle/mediandk_decoder.cpp |  | ||||||
|         hle/mediandk_decoder.h |  | ||||||
|     ) |  | ||||||
|     target_link_libraries(audio_core PRIVATE mediandk) |  | ||||||
| endif() |  | ||||||
| 
 | 
 | ||||||
| if(ENABLE_SDL2) | if(ENABLE_SDL2) | ||||||
|     target_link_libraries(audio_core PRIVATE SDL2::SDL2) |     target_link_libraries(audio_core PRIVATE SDL2::SDL2) | ||||||
|  |  | ||||||
|  | @ -1,28 +0,0 @@ | ||||||
| // 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" |  | ||||||
| 
 |  | ||||||
| namespace AudioCore { |  | ||||||
| 
 |  | ||||||
| struct ADTSData { |  | ||||||
|     u8 header_length = 0; |  | ||||||
|     bool mpeg2 = false; |  | ||||||
|     u8 profile = 0; |  | ||||||
|     u8 channels = 0; |  | ||||||
|     u8 channel_idx = 0; |  | ||||||
|     u8 framecount = 0; |  | ||||||
|     u8 samplerate_idx = 0; |  | ||||||
|     u32 length = 0; |  | ||||||
|     u32 samplerate = 0; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| ADTSData ParseADTS(const u8* 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); |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore
 |  | ||||||
|  | @ -1,79 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| #include <array> |  | ||||||
| #include "adts.h" |  | ||||||
| #include "common/bit_field.h" |  | ||||||
| 
 |  | ||||||
| namespace AudioCore { |  | ||||||
| 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}; |  | ||||||
| 
 |  | ||||||
| struct ADTSHeader { |  | ||||||
|     union { |  | ||||||
|         std::array<u8, 7> raw{}; |  | ||||||
|         BitFieldBE<52, 12, u64> sync_word; |  | ||||||
|         BitFieldBE<51, 1, u64> mpeg2; |  | ||||||
|         BitFieldBE<49, 2, u64> layer; |  | ||||||
|         BitFieldBE<48, 1, u64> protection_absent; |  | ||||||
|         BitFieldBE<46, 2, u64> profile; |  | ||||||
|         BitFieldBE<42, 4, u64> samplerate_idx; |  | ||||||
|         BitFieldBE<41, 1, u64> private_bit; |  | ||||||
|         BitFieldBE<38, 3, u64> channel_idx; |  | ||||||
|         BitFieldBE<37, 1, u64> originality; |  | ||||||
|         BitFieldBE<36, 1, u64> home; |  | ||||||
|         BitFieldBE<35, 1, u64> copyright_id; |  | ||||||
|         BitFieldBE<34, 1, u64> copyright_id_start; |  | ||||||
|         BitFieldBE<21, 13, u64> frame_length; |  | ||||||
|         BitFieldBE<10, 11, u64> buffer_fullness; |  | ||||||
|         BitFieldBE<8, 2, u64> frame_count; |  | ||||||
|     }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| ADTSData ParseADTS(const u8* buffer) { |  | ||||||
|     ADTSHeader header; |  | ||||||
|     memcpy(header.raw.data(), buffer, sizeof(header.raw)); |  | ||||||
| 
 |  | ||||||
|     // sync word 0xfff
 |  | ||||||
|     if (header.sync_word != 0xfff) { |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     ADTSData out{}; |  | ||||||
|     // bit 16 = no CRC
 |  | ||||||
|     out.header_length = header.protection_absent ? 7 : 9; |  | ||||||
|     out.mpeg2 = static_cast<bool>(header.mpeg2); |  | ||||||
|     // bit 17 to 18
 |  | ||||||
|     out.profile = static_cast<u8>(header.profile) + 1; |  | ||||||
|     // bit 19 to 22
 |  | ||||||
|     out.samplerate_idx = static_cast<u8>(header.samplerate_idx); |  | ||||||
|     out.samplerate = header.samplerate_idx > 15 ? 0 : freq_table[header.samplerate_idx]; |  | ||||||
|     // bit 24 to 26
 |  | ||||||
|     out.channel_idx = static_cast<u8>(header.channel_idx); |  | ||||||
|     out.channels = (header.channel_idx > 7) ? 0 : channel_table[header.channel_idx]; |  | ||||||
|     // bit 55 to 56
 |  | ||||||
|     out.framecount = static_cast<u8>(header.frame_count + 1); |  | ||||||
|     // bit 31 to 43
 |  | ||||||
|     out.length = static_cast<u32>(header.frame_length); |  | ||||||
| 
 |  | ||||||
|     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; |  | ||||||
| } |  | ||||||
| } // namespace AudioCore
 |  | ||||||
|  | @ -1,264 +0,0 @@ | ||||||
| // Copyright 2023 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <AudioToolbox/AudioToolbox.h> |  | ||||||
| #include "audio_core/audio_types.h" |  | ||||||
| #include "audio_core/hle/adts.h" |  | ||||||
| #include "audio_core/hle/audiotoolbox_decoder.h" |  | ||||||
| 
 |  | ||||||
| namespace AudioCore::HLE { |  | ||||||
| 
 |  | ||||||
| static constexpr auto bytes_per_sample = sizeof(s16); |  | ||||||
| static constexpr auto aac_frames_per_packet = 1024; |  | ||||||
| static constexpr auto error_out_of_data = -1932; |  | ||||||
| 
 |  | ||||||
| class AudioToolboxDecoder::Impl { |  | ||||||
| public: |  | ||||||
|     explicit Impl(Memory::MemorySystem& memory); |  | ||||||
|     ~Impl(); |  | ||||||
|     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::optional<BinaryMessage> Initalize(const BinaryMessage& request); |  | ||||||
|     std::optional<BinaryMessage> Decode(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
|     void Clear(); |  | ||||||
|     bool InitializeDecoder(AudioCore::ADTSData& adts_header); |  | ||||||
| 
 |  | ||||||
|     static OSStatus DataFunc(AudioConverterRef in_audio_converter, u32* io_number_data_packets, |  | ||||||
|                              AudioBufferList* io_data, |  | ||||||
|                              AudioStreamPacketDescription** out_data_packet_description, |  | ||||||
|                              void* in_user_data); |  | ||||||
| 
 |  | ||||||
|     Memory::MemorySystem& memory; |  | ||||||
| 
 |  | ||||||
|     AudioCore::ADTSData adts_config; |  | ||||||
|     AudioStreamBasicDescription output_format = {}; |  | ||||||
|     AudioConverterRef converter = nullptr; |  | ||||||
| 
 |  | ||||||
|     u8* curr_data = nullptr; |  | ||||||
|     u32 curr_data_len = 0; |  | ||||||
| 
 |  | ||||||
|     AudioStreamPacketDescription packet_description; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| AudioToolboxDecoder::Impl::Impl(Memory::MemorySystem& memory_) : memory(memory_) {} |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> AudioToolboxDecoder::Impl::Initalize(const BinaryMessage& request) { |  | ||||||
|     BinaryMessage response = request; |  | ||||||
|     response.header.result = ResultStatus::Success; |  | ||||||
| 
 |  | ||||||
|     Clear(); |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| AudioToolboxDecoder::Impl::~Impl() { |  | ||||||
|     Clear(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void AudioToolboxDecoder::Impl::Clear() { |  | ||||||
|     curr_data = nullptr; |  | ||||||
|     curr_data_len = 0; |  | ||||||
| 
 |  | ||||||
|     adts_config = {}; |  | ||||||
|     output_format = {}; |  | ||||||
| 
 |  | ||||||
|     if (converter) { |  | ||||||
|         AudioConverterDispose(converter); |  | ||||||
|         converter = nullptr; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> AudioToolboxDecoder::Impl::ProcessRequest( |  | ||||||
|     const BinaryMessage& request) { |  | ||||||
|     if (request.header.codec != DecoderCodec::DecodeAAC) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "AudioToolbox AAC Decoder cannot handle such codec: {}", |  | ||||||
|                   static_cast<u16>(request.header.codec)); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     switch (request.header.cmd) { |  | ||||||
|     case DecoderCommand::Init: { |  | ||||||
|         return Initalize(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::EncodeDecode: { |  | ||||||
|         return Decode(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::Shutdown: |  | ||||||
|     case DecoderCommand::SaveState: |  | ||||||
|     case DecoderCommand::LoadState: { |  | ||||||
|         LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}", |  | ||||||
|                     static_cast<u16>(request.header.cmd)); |  | ||||||
|         BinaryMessage response = request; |  | ||||||
|         response.header.result = ResultStatus::Success; |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
|     default: |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", |  | ||||||
|                   static_cast<u16>(request.header.cmd)); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool AudioToolboxDecoder::Impl::InitializeDecoder(AudioCore::ADTSData& adts_header) { |  | ||||||
|     if (converter) { |  | ||||||
|         if (adts_config.channels == adts_header.channels && |  | ||||||
|             adts_config.samplerate == adts_header.samplerate) { |  | ||||||
|             return true; |  | ||||||
|         } else { |  | ||||||
|             Clear(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     AudioStreamBasicDescription input_format = { |  | ||||||
|         .mSampleRate = static_cast<Float64>(adts_header.samplerate), |  | ||||||
|         .mFormatID = kAudioFormatMPEG4AAC, |  | ||||||
|         .mFramesPerPacket = aac_frames_per_packet, |  | ||||||
|         .mChannelsPerFrame = adts_header.channels, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     u32 bytes_per_frame = input_format.mChannelsPerFrame * bytes_per_sample; |  | ||||||
|     output_format = { |  | ||||||
|         .mSampleRate = input_format.mSampleRate, |  | ||||||
|         .mFormatID = kAudioFormatLinearPCM, |  | ||||||
|         .mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked, |  | ||||||
|         .mBytesPerPacket = bytes_per_frame, |  | ||||||
|         .mFramesPerPacket = 1, |  | ||||||
|         .mBytesPerFrame = bytes_per_frame, |  | ||||||
|         .mChannelsPerFrame = input_format.mChannelsPerFrame, |  | ||||||
|         .mBitsPerChannel = bytes_per_sample * 8, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     auto status = AudioConverterNew(&input_format, &output_format, &converter); |  | ||||||
|     if (status != noErr) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Could not create AAC audio converter: {}", status); |  | ||||||
|         Clear(); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     adts_config = adts_header; |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| OSStatus AudioToolboxDecoder::Impl::DataFunc( |  | ||||||
|     AudioConverterRef in_audio_converter, u32* io_number_data_packets, AudioBufferList* io_data, |  | ||||||
|     AudioStreamPacketDescription** out_data_packet_description, void* in_user_data) { |  | ||||||
|     auto impl = reinterpret_cast<Impl*>(in_user_data); |  | ||||||
|     if (!impl || !impl->curr_data || impl->curr_data_len == 0) { |  | ||||||
|         *io_number_data_packets = 0; |  | ||||||
|         return error_out_of_data; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     io_data->mNumberBuffers = 1; |  | ||||||
|     io_data->mBuffers[0].mNumberChannels = 0; |  | ||||||
|     io_data->mBuffers[0].mDataByteSize = impl->curr_data_len; |  | ||||||
|     io_data->mBuffers[0].mData = impl->curr_data; |  | ||||||
|     *io_number_data_packets = 1; |  | ||||||
| 
 |  | ||||||
|     if (out_data_packet_description != nullptr) { |  | ||||||
|         impl->packet_description.mStartOffset = 0; |  | ||||||
|         impl->packet_description.mVariableFramesInPacket = 0; |  | ||||||
|         impl->packet_description.mDataByteSize = impl->curr_data_len; |  | ||||||
|         *out_data_packet_description = &impl->packet_description; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     impl->curr_data = nullptr; |  | ||||||
|     impl->curr_data_len = 0; |  | ||||||
| 
 |  | ||||||
|     return noErr; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> AudioToolboxDecoder::Impl::Decode(const BinaryMessage& request) { |  | ||||||
|     BinaryMessage response{}; |  | ||||||
|     response.header.codec = request.header.codec; |  | ||||||
|     response.header.cmd = request.header.cmd; |  | ||||||
|     response.decode_aac_response.size = request.decode_aac_request.size; |  | ||||||
| 
 |  | ||||||
|     if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR || |  | ||||||
|         request.decode_aac_request.src_addr + request.decode_aac_request.size > |  | ||||||
|             Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", |  | ||||||
|                   request.decode_aac_request.src_addr); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const auto data = |  | ||||||
|         memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR); |  | ||||||
|     auto adts_header = AudioCore::ParseADTS(data); |  | ||||||
|     curr_data = data + adts_header.header_length; |  | ||||||
|     curr_data_len = request.decode_aac_request.size - adts_header.header_length; |  | ||||||
| 
 |  | ||||||
|     if (!InitializeDecoder(adts_header)) { |  | ||||||
|         return std::nullopt; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Up to 2048 samples, up to 2 channels each
 |  | ||||||
|     s16 decoder_output[4096]; |  | ||||||
|     AudioBufferList out_buffer{1, |  | ||||||
|                                {{ |  | ||||||
|                                    output_format.mChannelsPerFrame, |  | ||||||
|                                    sizeof(decoder_output), |  | ||||||
|                                    decoder_output, |  | ||||||
|                                }}}; |  | ||||||
| 
 |  | ||||||
|     u32 num_packets = sizeof(decoder_output) / output_format.mBytesPerPacket; |  | ||||||
|     auto status = AudioConverterFillComplexBuffer(converter, DataFunc, this, &num_packets, |  | ||||||
|                                                   &out_buffer, nullptr); |  | ||||||
|     if (status != noErr && status != error_out_of_data) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Could not decode AAC data: {}", status); |  | ||||||
|         Clear(); |  | ||||||
|         return std::nullopt; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // De-interleave samples.
 |  | ||||||
|     std::array<std::vector<s16>, 2> out_streams; |  | ||||||
|     auto num_frames = num_packets * output_format.mFramesPerPacket; |  | ||||||
|     for (u32 frame = 0; frame < num_frames; frame++) { |  | ||||||
|         for (u32 ch = 0; ch < output_format.mChannelsPerFrame; ch++) { |  | ||||||
|             out_streams[ch].push_back( |  | ||||||
|                 decoder_output[(frame * output_format.mChannelsPerFrame) + ch]); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     curr_data = nullptr; |  | ||||||
|     curr_data_len = 0; |  | ||||||
| 
 |  | ||||||
|     response.decode_aac_response.sample_rate = |  | ||||||
|         GetSampleRateEnum(static_cast<u32>(output_format.mSampleRate)); |  | ||||||
|     response.decode_aac_response.num_channels = output_format.mChannelsPerFrame; |  | ||||||
|     response.decode_aac_response.num_samples = num_frames; |  | ||||||
| 
 |  | ||||||
|     // transfer the decoded buffer from vector to the FCRAM
 |  | ||||||
|     for (std::size_t ch = 0; ch < out_streams.size(); ch++) { |  | ||||||
|         if (!out_streams[ch].empty()) { |  | ||||||
|             auto byte_size = out_streams[ch].size() * bytes_per_sample; |  | ||||||
|             auto dst = ch == 0 ? request.decode_aac_request.dst_addr_ch0 |  | ||||||
|                                : request.decode_aac_request.dst_addr_ch1; |  | ||||||
|             if (dst < Memory::FCRAM_PADDR || |  | ||||||
|                 dst + byte_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|                 LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch{} {:08x}", ch, dst); |  | ||||||
|                 return {}; |  | ||||||
|             } |  | ||||||
|             std::memcpy(memory.GetFCRAMPointer(dst - Memory::FCRAM_PADDR), out_streams[ch].data(), |  | ||||||
|                         byte_size); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| AudioToolboxDecoder::AudioToolboxDecoder(Memory::MemorySystem& memory) |  | ||||||
|     : impl(std::make_unique<Impl>(memory)) {} |  | ||||||
| 
 |  | ||||||
| AudioToolboxDecoder::~AudioToolboxDecoder() = default; |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> AudioToolboxDecoder::ProcessRequest(const BinaryMessage& request) { |  | ||||||
|     return impl->ProcessRequest(request); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool AudioToolboxDecoder::IsValid() const { |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore::HLE
 |  | ||||||
							
								
								
									
										186
									
								
								src/audio_core/hle/faad2_decoder.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/audio_core/hle/faad2_decoder.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,186 @@ | ||||||
|  | // Copyright 2023 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #include <neaacdec.h> | ||||||
|  | #include "audio_core/hle/faad2_decoder.h" | ||||||
|  | 
 | ||||||
|  | namespace AudioCore::HLE { | ||||||
|  | 
 | ||||||
|  | class FAAD2Decoder::Impl { | ||||||
|  | public: | ||||||
|  |     explicit Impl(Memory::MemorySystem& memory); | ||||||
|  |     ~Impl(); | ||||||
|  |     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request); | ||||||
|  |     bool IsValid() const { | ||||||
|  |         return decoder != nullptr; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     std::optional<BinaryMessage> Initalize(const BinaryMessage& request); | ||||||
|  | 
 | ||||||
|  |     std::optional<BinaryMessage> Decode(const BinaryMessage& request); | ||||||
|  | 
 | ||||||
|  |     Memory::MemorySystem& memory; | ||||||
|  | 
 | ||||||
|  |     NeAACDecHandle decoder = nullptr; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | FAAD2Decoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { | ||||||
|  |     decoder = NeAACDecOpen(); | ||||||
|  |     if (decoder == nullptr) { | ||||||
|  |         LOG_CRITICAL(Audio_DSP, "Could not open FAAD2 decoder."); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     auto config = NeAACDecGetCurrentConfiguration(decoder); | ||||||
|  |     config->defObjectType = LC; | ||||||
|  |     config->outputFormat = FAAD_FMT_16BIT; | ||||||
|  |     if (!NeAACDecSetConfiguration(decoder, config)) { | ||||||
|  |         LOG_CRITICAL(Audio_DSP, "Could not configure FAAD2 decoder."); | ||||||
|  |         NeAACDecClose(decoder); | ||||||
|  |         decoder = nullptr; | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     LOG_INFO(Audio_DSP, "Created FAAD2 AAC decoder."); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FAAD2Decoder::Impl::~Impl() { | ||||||
|  |     if (decoder) { | ||||||
|  |         NeAACDecClose(decoder); | ||||||
|  |         decoder = nullptr; | ||||||
|  | 
 | ||||||
|  |         LOG_INFO(Audio_DSP, "Destroyed FAAD2 AAC decoder."); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<BinaryMessage> FAAD2Decoder::Impl::ProcessRequest(const BinaryMessage& request) { | ||||||
|  |     if (request.header.codec != DecoderCodec::DecodeAAC) { | ||||||
|  |         LOG_ERROR(Audio_DSP, "FAAD2 AAC Decoder cannot handle such codec: {}", | ||||||
|  |                   static_cast<u16>(request.header.codec)); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     switch (request.header.cmd) { | ||||||
|  |     case DecoderCommand::Init: { | ||||||
|  |         return Initalize(request); | ||||||
|  |     } | ||||||
|  |     case DecoderCommand::EncodeDecode: { | ||||||
|  |         return Decode(request); | ||||||
|  |     } | ||||||
|  |     case DecoderCommand::Shutdown: | ||||||
|  |     case DecoderCommand::SaveState: | ||||||
|  |     case DecoderCommand::LoadState: { | ||||||
|  |         LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}", | ||||||
|  |                     static_cast<u16>(request.header.cmd)); | ||||||
|  |         BinaryMessage response = request; | ||||||
|  |         response.header.result = ResultStatus::Success; | ||||||
|  |         return response; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |         LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", | ||||||
|  |                   static_cast<u16>(request.header.cmd)); | ||||||
|  |         return {}; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<BinaryMessage> FAAD2Decoder::Impl::Initalize(const BinaryMessage& request) { | ||||||
|  |     BinaryMessage response = request; | ||||||
|  |     response.header.result = ResultStatus::Success; | ||||||
|  |     return response; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<BinaryMessage> FAAD2Decoder::Impl::Decode(const BinaryMessage& request) { | ||||||
|  |     BinaryMessage response{}; | ||||||
|  |     response.header.codec = request.header.codec; | ||||||
|  |     response.header.cmd = request.header.cmd; | ||||||
|  |     response.decode_aac_response.size = request.decode_aac_request.size; | ||||||
|  |     // This is a hack to continue games when a failure occurs.
 | ||||||
|  |     response.decode_aac_response.sample_rate = DecoderSampleRate::Rate48000; | ||||||
|  |     response.decode_aac_response.num_channels = 2; | ||||||
|  |     response.decode_aac_response.num_samples = 1024; | ||||||
|  | 
 | ||||||
|  |     if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR || | ||||||
|  |         request.decode_aac_request.src_addr + request.decode_aac_request.size > | ||||||
|  |             Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { | ||||||
|  |         LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", | ||||||
|  |                   request.decode_aac_request.src_addr); | ||||||
|  |         return response; | ||||||
|  |     } | ||||||
|  |     u8* data = memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR); | ||||||
|  |     u32 data_len = request.decode_aac_request.size; | ||||||
|  | 
 | ||||||
|  |     unsigned long sample_rate; | ||||||
|  |     u8 num_channels; | ||||||
|  |     auto init_result = NeAACDecInit(decoder, data, data_len, &sample_rate, &num_channels); | ||||||
|  |     if (init_result < 0) { | ||||||
|  |         LOG_ERROR(Audio_DSP, "Could not initialize FAAD2 AAC decoder for request: {}", init_result); | ||||||
|  |         return response; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Advance past the frame header if needed.
 | ||||||
|  |     data += init_result; | ||||||
|  |     data_len -= init_result; | ||||||
|  | 
 | ||||||
|  |     std::array<std::vector<s16>, 2> out_streams; | ||||||
|  | 
 | ||||||
|  |     while (data_len > 0) { | ||||||
|  |         NeAACDecFrameInfo frame_info; | ||||||
|  |         auto curr_sample_buffer = | ||||||
|  |             static_cast<s16*>(NeAACDecDecode(decoder, &frame_info, data, data_len)); | ||||||
|  |         if (curr_sample_buffer == nullptr || frame_info.error != 0) { | ||||||
|  |             LOG_ERROR(Audio_DSP, "Failed to decode AAC buffer using FAAD2: {}", frame_info.error); | ||||||
|  |             return response; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Split the decode result into channels.
 | ||||||
|  |         u32 num_samples = frame_info.samples / frame_info.channels; | ||||||
|  |         for (u32 sample = 0; sample < num_samples; sample++) { | ||||||
|  |             for (u32 ch = 0; ch < frame_info.channels; ch++) { | ||||||
|  |                 out_streams[ch].push_back(curr_sample_buffer[(sample * frame_info.channels) + ch]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         data += frame_info.bytesconsumed; | ||||||
|  |         data_len -= frame_info.bytesconsumed; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Transfer the decoded buffer from vector to the FCRAM.
 | ||||||
|  |     for (std::size_t ch = 0; ch < out_streams.size(); ch++) { | ||||||
|  |         if (out_streams[ch].empty()) { | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |         auto byte_size = out_streams[ch].size() * sizeof(s16); | ||||||
|  |         auto dst = ch == 0 ? request.decode_aac_request.dst_addr_ch0 | ||||||
|  |                            : request.decode_aac_request.dst_addr_ch1; | ||||||
|  |         if (dst < Memory::FCRAM_PADDR || | ||||||
|  |             dst + byte_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { | ||||||
|  |             LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch{} {:08x}", ch, dst); | ||||||
|  |             return response; | ||||||
|  |         } | ||||||
|  |         std::memcpy(memory.GetFCRAMPointer(dst - Memory::FCRAM_PADDR), out_streams[ch].data(), | ||||||
|  |                     byte_size); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Set the output frame info.
 | ||||||
|  |     response.decode_aac_response.sample_rate = GetSampleRateEnum(sample_rate); | ||||||
|  |     response.decode_aac_response.num_channels = num_channels; | ||||||
|  |     response.decode_aac_response.num_samples = static_cast<u32_le>(out_streams[0].size()); | ||||||
|  | 
 | ||||||
|  |     return response; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | FAAD2Decoder::FAAD2Decoder(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(memory)) {} | ||||||
|  | 
 | ||||||
|  | FAAD2Decoder::~FAAD2Decoder() = default; | ||||||
|  | 
 | ||||||
|  | std::optional<BinaryMessage> FAAD2Decoder::ProcessRequest(const BinaryMessage& request) { | ||||||
|  |     return impl->ProcessRequest(request); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool FAAD2Decoder::IsValid() const { | ||||||
|  |     return impl->IsValid(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace AudioCore::HLE
 | ||||||
|  | @ -8,10 +8,10 @@ | ||||||
| 
 | 
 | ||||||
| namespace AudioCore::HLE { | namespace AudioCore::HLE { | ||||||
| 
 | 
 | ||||||
| class AudioToolboxDecoder final : public DecoderBase { | class FAAD2Decoder final : public DecoderBase { | ||||||
| public: | public: | ||||||
|     explicit AudioToolboxDecoder(Memory::MemorySystem& memory); |     explicit FAAD2Decoder(Memory::MemorySystem& memory); | ||||||
|     ~AudioToolboxDecoder() override; |     ~FAAD2Decoder() override; | ||||||
|     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request) override; |     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request) override; | ||||||
|     bool IsValid() const override; |     bool IsValid() const override; | ||||||
| 
 | 
 | ||||||
|  | @ -1,236 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include "audio_core/hle/fdk_decoder.h" |  | ||||||
| #include "common/dynamic_library/fdk-aac.h" |  | ||||||
| 
 |  | ||||||
| using namespace DynamicLibrary; |  | ||||||
| 
 |  | ||||||
| namespace AudioCore::HLE { |  | ||||||
| 
 |  | ||||||
| class FDKDecoder::Impl { |  | ||||||
| public: |  | ||||||
|     explicit Impl(Memory::MemorySystem& memory); |  | ||||||
|     ~Impl(); |  | ||||||
|     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request); |  | ||||||
|     bool IsValid() const { |  | ||||||
|         return decoder != nullptr; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::optional<BinaryMessage> Initalize(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
|     std::optional<BinaryMessage> Decode(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
|     void Clear(); |  | ||||||
| 
 |  | ||||||
|     Memory::MemorySystem& memory; |  | ||||||
| 
 |  | ||||||
|     HANDLE_AACDECODER decoder = nullptr; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| FDKDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { |  | ||||||
|     if (!FdkAac::LoadFdkAac()) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // allocate an array of LIB_INFO structures
 |  | ||||||
|     // if we don't pre-fill the whole segment with zeros, when we call `aacDecoder_GetLibInfo`
 |  | ||||||
|     // it will segfault, upon investigation, there is some code in fdk_aac depends on your initial
 |  | ||||||
|     // values in this array
 |  | ||||||
|     LIB_INFO decoder_info[FDK_MODULE_LAST] = {}; |  | ||||||
|     // get library information and fill the struct
 |  | ||||||
|     if (FdkAac::aacDecoder_GetLibInfo(decoder_info) != 0) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Failed to retrieve fdk_aac library information!"); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     LOG_INFO(Audio_DSP, "Using fdk_aac version {} (build date: {})", decoder_info[0].versionStr, |  | ||||||
|              decoder_info[0].build_date); |  | ||||||
| 
 |  | ||||||
|     // choose the input format when initializing: 1 layer of ADTS
 |  | ||||||
|     decoder = FdkAac::aacDecoder_Open(TRANSPORT_TYPE::TT_MP4_ADTS, 1); |  | ||||||
|     // set maximum output channel to two (stereo)
 |  | ||||||
|     // if the input samples have more channels, fdk_aac will perform a downmix
 |  | ||||||
|     AAC_DECODER_ERROR ret = FdkAac::aacDecoder_SetParam(decoder, AAC_PCM_MAX_OUTPUT_CHANNELS, 2); |  | ||||||
|     if (ret != AAC_DEC_OK) { |  | ||||||
|         // unable to set this parameter reflects the decoder implementation might be broken
 |  | ||||||
|         // we'd better shuts down everything
 |  | ||||||
|         FdkAac::aacDecoder_Close(decoder); |  | ||||||
|         decoder = nullptr; |  | ||||||
|         LOG_ERROR(Audio_DSP, "Unable to set downmix parameter: {}", ret); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> FDKDecoder::Impl::Initalize(const BinaryMessage& request) { |  | ||||||
|     BinaryMessage response = request; |  | ||||||
|     response.header.result = ResultStatus::Success; |  | ||||||
| 
 |  | ||||||
|     if (decoder) { |  | ||||||
|         LOG_INFO(Audio_DSP, "FDK Decoder initialized"); |  | ||||||
|         Clear(); |  | ||||||
|     } else { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Decoder not initialized"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FDKDecoder::Impl::~Impl() { |  | ||||||
|     if (decoder) { |  | ||||||
|         FdkAac::aacDecoder_Close(decoder); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void FDKDecoder::Impl::Clear() { |  | ||||||
|     s16 decoder_output[8192]; |  | ||||||
|     // flush and re-sync the decoder, discarding the internal buffer
 |  | ||||||
|     // we actually don't care if this succeeds or not
 |  | ||||||
|     // FLUSH - flush internal buffer
 |  | ||||||
|     // INTR - treat the current internal buffer as discontinuous
 |  | ||||||
|     // CONCEAL - try to interpolate and smooth out the samples
 |  | ||||||
|     if (decoder) { |  | ||||||
|         FdkAac::aacDecoder_DecodeFrame(decoder, decoder_output, 8192, |  | ||||||
|                                        AACDEC_FLUSH & AACDEC_INTR & AACDEC_CONCEAL); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> FDKDecoder::Impl::ProcessRequest(const BinaryMessage& request) { |  | ||||||
|     if (request.header.codec != DecoderCodec::DecodeAAC) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "FDK AAC Decoder cannot handle such codec: {}", |  | ||||||
|                   static_cast<u16>(request.header.codec)); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     switch (request.header.cmd) { |  | ||||||
|     case DecoderCommand::Init: { |  | ||||||
|         return Initalize(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::EncodeDecode: { |  | ||||||
|         return Decode(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::Shutdown: |  | ||||||
|     case DecoderCommand::SaveState: |  | ||||||
|     case DecoderCommand::LoadState: { |  | ||||||
|         LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}", |  | ||||||
|                     static_cast<u16>(request.header.cmd)); |  | ||||||
|         BinaryMessage response = request; |  | ||||||
|         response.header.result = ResultStatus::Success; |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
|     default: |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", |  | ||||||
|                   static_cast<u16>(request.header.cmd)); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> FDKDecoder::Impl::Decode(const BinaryMessage& request) { |  | ||||||
|     BinaryMessage response{}; |  | ||||||
|     response.header.codec = request.header.codec; |  | ||||||
|     response.header.cmd = request.header.cmd; |  | ||||||
|     response.decode_aac_response.size = request.decode_aac_request.size; |  | ||||||
| 
 |  | ||||||
|     if (!decoder) { |  | ||||||
|         LOG_DEBUG(Audio_DSP, "Decoder not initalized"); |  | ||||||
|         // This is a hack to continue games that are not compiled with the aac codec
 |  | ||||||
|         response.decode_aac_response.num_channels = 2; |  | ||||||
|         response.decode_aac_response.num_samples = 1024; |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR || |  | ||||||
|         request.decode_aac_request.src_addr + request.decode_aac_request.size > |  | ||||||
|             Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", |  | ||||||
|                   request.decode_aac_request.src_addr); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
|     u8* data = memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR); |  | ||||||
| 
 |  | ||||||
|     std::array<std::vector<s16>, 2> out_streams; |  | ||||||
| 
 |  | ||||||
|     u32 data_size = request.decode_aac_request.size; |  | ||||||
| 
 |  | ||||||
|     // decoding loops
 |  | ||||||
|     AAC_DECODER_ERROR result = AAC_DEC_OK; |  | ||||||
|     // Up to 2048 samples, up to 2 channels each
 |  | ||||||
|     s16 decoder_output[4096]; |  | ||||||
|     // note that we don't free this pointer as it is automatically freed by fdk_aac
 |  | ||||||
|     CStreamInfo* stream_info; |  | ||||||
|     // how many bytes to be queued into the decoder, decrementing from the buffer size
 |  | ||||||
|     u32 buffer_remaining = data_size; |  | ||||||
|     // alias the data_size as an u32
 |  | ||||||
|     u32 input_size = data_size; |  | ||||||
| 
 |  | ||||||
|     while (buffer_remaining) { |  | ||||||
|         // queue the input buffer, fdk_aac will automatically slice out the buffer it needs
 |  | ||||||
|         // from the input buffer
 |  | ||||||
|         result = FdkAac::aacDecoder_Fill(decoder, &data, &input_size, &buffer_remaining); |  | ||||||
|         if (result != AAC_DEC_OK) { |  | ||||||
|             // there are some issues when queuing the input buffer
 |  | ||||||
|             LOG_ERROR(Audio_DSP, "Failed to enqueue the input samples"); |  | ||||||
|             return std::nullopt; |  | ||||||
|         } |  | ||||||
|         // get output from decoder
 |  | ||||||
|         result = FdkAac::aacDecoder_DecodeFrame(decoder, decoder_output, |  | ||||||
|                                                 sizeof(decoder_output) / sizeof(s16), 0); |  | ||||||
|         if (result == AAC_DEC_OK) { |  | ||||||
|             // get the stream information
 |  | ||||||
|             stream_info = FdkAac::aacDecoder_GetStreamInfo(decoder); |  | ||||||
|             // fill the stream information for binary response
 |  | ||||||
|             response.decode_aac_response.sample_rate = GetSampleRateEnum(stream_info->sampleRate); |  | ||||||
|             response.decode_aac_response.num_channels = stream_info->numChannels; |  | ||||||
|             response.decode_aac_response.num_samples = stream_info->frameSize; |  | ||||||
|             // fill the output
 |  | ||||||
|             // the sample size = frame_size * channel_counts
 |  | ||||||
|             for (int sample = 0; sample < stream_info->frameSize; sample++) { |  | ||||||
|                 for (int ch = 0; ch < stream_info->numChannels; ch++) { |  | ||||||
|                     out_streams[ch].push_back( |  | ||||||
|                         decoder_output[(sample * stream_info->numChannels) + ch]); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } else if (result == AAC_DEC_TRANSPORT_SYNC_ERROR) { |  | ||||||
|             // decoder has some synchronization problems, try again with new samples,
 |  | ||||||
|             // using old samples might trigger this error again
 |  | ||||||
|             continue; |  | ||||||
|         } else { |  | ||||||
|             LOG_ERROR(Audio_DSP, "Error decoding the sample: {}", result); |  | ||||||
|             return std::nullopt; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // transfer the decoded buffer from vector to the FCRAM
 |  | ||||||
|     for (std::size_t ch = 0; ch < out_streams.size(); ch++) { |  | ||||||
|         if (!out_streams[ch].empty()) { |  | ||||||
|             auto byte_size = out_streams[ch].size() * sizeof(s16); |  | ||||||
|             auto dst = ch == 0 ? request.decode_aac_request.dst_addr_ch0 |  | ||||||
|                                : request.decode_aac_request.dst_addr_ch1; |  | ||||||
|             if (dst < Memory::FCRAM_PADDR || |  | ||||||
|                 dst + byte_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|                 LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch{} {:08x}", ch, dst); |  | ||||||
|                 return {}; |  | ||||||
|             } |  | ||||||
|             std::memcpy(memory.GetFCRAMPointer(dst - Memory::FCRAM_PADDR), out_streams[ch].data(), |  | ||||||
|                         byte_size); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FDKDecoder::FDKDecoder(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(memory)) {} |  | ||||||
| 
 |  | ||||||
| FDKDecoder::~FDKDecoder() = default; |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> FDKDecoder::ProcessRequest(const BinaryMessage& request) { |  | ||||||
|     return impl->ProcessRequest(request); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool FDKDecoder::IsValid() const { |  | ||||||
|     return impl->IsValid(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore::HLE
 |  | ||||||
|  | @ -1,23 +0,0 @@ | ||||||
| // Copyright 2019 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 FDKDecoder final : public DecoderBase { |  | ||||||
| public: |  | ||||||
|     explicit FDKDecoder(Memory::MemorySystem& memory); |  | ||||||
|     ~FDKDecoder() override; |  | ||||||
|     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request) override; |  | ||||||
|     bool IsValid() const override; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     class Impl; |  | ||||||
|     std::unique_ptr<Impl> impl; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore::HLE
 |  | ||||||
|  | @ -1,290 +0,0 @@ | ||||||
| // Copyright 2018 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include "audio_core/hle/ffmpeg_decoder.h" |  | ||||||
| #include "common/dynamic_library/ffmpeg.h" |  | ||||||
| 
 |  | ||||||
| using namespace DynamicLibrary; |  | ||||||
| 
 |  | ||||||
| namespace AudioCore::HLE { |  | ||||||
| 
 |  | ||||||
| class FFMPEGDecoder::Impl { |  | ||||||
| public: |  | ||||||
|     explicit Impl(Memory::MemorySystem& memory); |  | ||||||
|     ~Impl(); |  | ||||||
|     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request); |  | ||||||
|     bool IsValid() const { |  | ||||||
|         return have_ffmpeg_dl; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::optional<BinaryMessage> Initalize(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
|     void Clear(); |  | ||||||
| 
 |  | ||||||
|     std::optional<BinaryMessage> Decode(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
|     struct AVPacketDeleter { |  | ||||||
|         void operator()(AVPacket* packet) const { |  | ||||||
|             FFmpeg::av_packet_free(&packet); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     struct AVCodecContextDeleter { |  | ||||||
|         void operator()(AVCodecContext* context) const { |  | ||||||
|             FFmpeg::avcodec_free_context(&context); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     struct AVCodecParserContextDeleter { |  | ||||||
|         void operator()(AVCodecParserContext* parser) const { |  | ||||||
|             FFmpeg::av_parser_close(parser); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     struct AVFrameDeleter { |  | ||||||
|         void operator()(AVFrame* frame) const { |  | ||||||
|             FFmpeg::av_frame_free(&frame); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     bool initalized = false; |  | ||||||
|     bool have_ffmpeg_dl; |  | ||||||
| 
 |  | ||||||
|     Memory::MemorySystem& memory; |  | ||||||
| 
 |  | ||||||
|     const AVCodec* codec; |  | ||||||
|     std::unique_ptr<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 = FFmpeg::LoadFFmpeg(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FFMPEGDecoder::Impl::~Impl() = default; |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> FFMPEGDecoder::Impl::ProcessRequest(const BinaryMessage& request) { |  | ||||||
|     if (request.header.codec != DecoderCodec::DecodeAAC) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got wrong codec {}", static_cast<u16>(request.header.codec)); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     switch (request.header.cmd) { |  | ||||||
|     case DecoderCommand::Init: { |  | ||||||
|         return Initalize(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::EncodeDecode: { |  | ||||||
|         return Decode(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::Shutdown: |  | ||||||
|     case DecoderCommand::SaveState: |  | ||||||
|     case DecoderCommand::LoadState: { |  | ||||||
|         LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}", |  | ||||||
|                     static_cast<u16>(request.header.cmd)); |  | ||||||
|         BinaryMessage response = request; |  | ||||||
|         response.header.result = ResultStatus::Success; |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
|     default: |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", |  | ||||||
|                   static_cast<u16>(request.header.cmd)); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> FFMPEGDecoder::Impl::Initalize(const BinaryMessage& request) { |  | ||||||
|     if (initalized) { |  | ||||||
|         Clear(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     BinaryMessage response = request; |  | ||||||
|     response.header.result = ResultStatus::Success; |  | ||||||
| 
 |  | ||||||
|     if (!have_ffmpeg_dl) { |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     av_packet.reset(FFmpeg::av_packet_alloc()); |  | ||||||
| 
 |  | ||||||
|     codec = FFmpeg::avcodec_find_decoder(AV_CODEC_ID_AAC); |  | ||||||
|     if (!codec) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Codec not found\n"); |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     parser.reset(FFmpeg::av_parser_init(codec->id)); |  | ||||||
|     if (!parser) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Parser not found\n"); |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     av_context.reset(FFmpeg::avcodec_alloc_context3(codec)); |  | ||||||
|     if (!av_context) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Could not allocate audio codec context\n"); |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (FFmpeg::avcodec_open2(av_context.get(), codec, nullptr) < 0) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Could not open codec\n"); |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     initalized = true; |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void FFMPEGDecoder::Impl::Clear() { |  | ||||||
|     if (!have_ffmpeg_dl) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     av_context.reset(); |  | ||||||
|     parser.reset(); |  | ||||||
|     decoded_frame.reset(); |  | ||||||
|     av_packet.reset(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> FFMPEGDecoder::Impl::Decode(const BinaryMessage& request) { |  | ||||||
|     BinaryMessage response{}; |  | ||||||
|     response.header.codec = request.header.codec; |  | ||||||
|     response.header.cmd = request.header.cmd; |  | ||||||
|     response.decode_aac_response.size = request.decode_aac_request.size; |  | ||||||
| 
 |  | ||||||
|     if (!initalized) { |  | ||||||
|         LOG_DEBUG(Audio_DSP, "Decoder not initalized"); |  | ||||||
|         // This is a hack to continue games that are not compiled with the aac codec
 |  | ||||||
|         response.decode_aac_response.num_channels = 2; |  | ||||||
|         response.decode_aac_response.num_samples = 1024; |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR || |  | ||||||
|         request.decode_aac_request.src_addr + request.decode_aac_request.size > |  | ||||||
|             Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", |  | ||||||
|                   request.decode_aac_request.src_addr); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
|     u8* data = memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR); |  | ||||||
| 
 |  | ||||||
|     std::array<std::vector<u8>, 2> out_streams; |  | ||||||
| 
 |  | ||||||
|     std::size_t data_size = request.decode_aac_request.size; |  | ||||||
|     while (data_size > 0) { |  | ||||||
|         if (!decoded_frame) { |  | ||||||
|             decoded_frame.reset(FFmpeg::av_frame_alloc()); |  | ||||||
|             if (!decoded_frame) { |  | ||||||
|                 LOG_ERROR(Audio_DSP, "Could not allocate audio frame"); |  | ||||||
|                 return {}; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         int ret = FFmpeg::av_parser_parse2(parser.get(), av_context.get(), &av_packet->data, |  | ||||||
|                                            &av_packet->size, data, static_cast<int>(data_size), |  | ||||||
|                                            AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); |  | ||||||
|         if (ret < 0) { |  | ||||||
|             LOG_ERROR(Audio_DSP, "Error while parsing"); |  | ||||||
|             return {}; |  | ||||||
|         } |  | ||||||
|         data += ret; |  | ||||||
|         data_size -= ret; |  | ||||||
| 
 |  | ||||||
|         ret = FFmpeg::avcodec_send_packet(av_context.get(), av_packet.get()); |  | ||||||
|         if (ret < 0) { |  | ||||||
|             LOG_ERROR(Audio_DSP, "Error submitting the packet to the decoder"); |  | ||||||
|             return {}; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (av_packet->size) { |  | ||||||
|             while (ret >= 0) { |  | ||||||
|                 ret = FFmpeg::avcodec_receive_frame(av_context.get(), decoded_frame.get()); |  | ||||||
|                 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) |  | ||||||
|                     break; |  | ||||||
|                 else if (ret < 0) { |  | ||||||
|                     LOG_ERROR(Audio_DSP, "Error during decoding"); |  | ||||||
|                     return {}; |  | ||||||
|                 } |  | ||||||
|                 int bytes_per_sample = FFmpeg::av_get_bytes_per_sample(av_context->sample_fmt); |  | ||||||
|                 if (bytes_per_sample < 0) { |  | ||||||
|                     LOG_ERROR(Audio_DSP, "Failed to calculate data size"); |  | ||||||
|                     return {}; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
| #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100) |  | ||||||
|                 auto num_channels = static_cast<u32>(decoded_frame->ch_layout.nb_channels); |  | ||||||
| #else |  | ||||||
|                 auto num_channels = static_cast<u32>(decoded_frame->channels); |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|                 ASSERT(num_channels <= out_streams.size()); |  | ||||||
| 
 |  | ||||||
|                 std::size_t size = bytes_per_sample * (decoded_frame->nb_samples); |  | ||||||
| 
 |  | ||||||
|                 response.decode_aac_response.sample_rate = |  | ||||||
|                     GetSampleRateEnum(decoded_frame->sample_rate); |  | ||||||
|                 response.decode_aac_response.num_channels = num_channels; |  | ||||||
|                 response.decode_aac_response.num_samples += decoded_frame->nb_samples; |  | ||||||
| 
 |  | ||||||
|                 // FFmpeg converts to 32 signed floating point PCM, we need s16 PCM so we need to
 |  | ||||||
|                 // convert it
 |  | ||||||
|                 f32 val_float; |  | ||||||
|                 for (std::size_t current_pos(0); current_pos < size;) { |  | ||||||
|                     for (std::size_t channel(0); channel < num_channels; channel++) { |  | ||||||
|                         std::memcpy(&val_float, decoded_frame->data[channel] + current_pos, |  | ||||||
|                                     sizeof(val_float)); |  | ||||||
|                         val_float = std::clamp(val_float, -1.0f, 1.0f); |  | ||||||
|                         s16 val = static_cast<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.decode_aac_request.dst_addr_ch0 < Memory::FCRAM_PADDR || |  | ||||||
|             request.decode_aac_request.dst_addr_ch0 + out_streams[0].size() > |  | ||||||
|                 Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|             LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}", |  | ||||||
|                       request.decode_aac_request.dst_addr_ch0); |  | ||||||
|             return {}; |  | ||||||
|         } |  | ||||||
|         std::memcpy( |  | ||||||
|             memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch0 - Memory::FCRAM_PADDR), |  | ||||||
|             out_streams[0].data(), out_streams[0].size()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (out_streams[1].size() != 0) { |  | ||||||
|         if (request.decode_aac_request.dst_addr_ch1 < Memory::FCRAM_PADDR || |  | ||||||
|             request.decode_aac_request.dst_addr_ch1 + out_streams[1].size() > |  | ||||||
|                 Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|             LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}", |  | ||||||
|                       request.decode_aac_request.dst_addr_ch1); |  | ||||||
|             return {}; |  | ||||||
|         } |  | ||||||
|         std::memcpy( |  | ||||||
|             memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch1 - Memory::FCRAM_PADDR), |  | ||||||
|             out_streams[1].data(), out_streams[1].size()); |  | ||||||
|     } |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| FFMPEGDecoder::FFMPEGDecoder(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(memory)) {} |  | ||||||
| 
 |  | ||||||
| FFMPEGDecoder::~FFMPEGDecoder() = default; |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> FFMPEGDecoder::ProcessRequest(const BinaryMessage& request) { |  | ||||||
|     return impl->ProcessRequest(request); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool FFMPEGDecoder::IsValid() const { |  | ||||||
|     return impl->IsValid(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore::HLE
 |  | ||||||
|  | @ -1,23 +0,0 @@ | ||||||
| // 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<BinaryMessage> ProcessRequest(const BinaryMessage& request) override; |  | ||||||
|     bool IsValid() const override; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     class Impl; |  | ||||||
|     std::unique_ptr<Impl> impl; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore::HLE
 |  | ||||||
|  | @ -8,23 +8,15 @@ | ||||||
| #include <boost/serialization/vector.hpp> | #include <boost/serialization/vector.hpp> | ||||||
| #include <boost/serialization/weak_ptr.hpp> | #include <boost/serialization/weak_ptr.hpp> | ||||||
| #include "audio_core/audio_types.h" | #include "audio_core/audio_types.h" | ||||||
| #include "common/archives.h" |  | ||||||
| #ifdef HAVE_MF |  | ||||||
| #include "audio_core/hle/wmf_decoder.h" |  | ||||||
| #elif HAVE_AUDIOTOOLBOX |  | ||||||
| #include "audio_core/hle/audiotoolbox_decoder.h" |  | ||||||
| #elif ANDROID |  | ||||||
| #include "audio_core/hle/mediandk_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/decoder.h" | ||||||
| #include "audio_core/hle/fdk_decoder.h" | #include "audio_core/hle/faad2_decoder.h" | ||||||
| #include "audio_core/hle/ffmpeg_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" | ||||||
| #include "audio_core/hle/source.h" | #include "audio_core/hle/source.h" | ||||||
| #include "audio_core/sink.h" | #include "audio_core/sink.h" | ||||||
|  | #include "common/archives.h" | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "common/hash.h" | #include "common/hash.h" | ||||||
|  | @ -121,26 +113,8 @@ private: | ||||||
| 
 | 
 | ||||||
| static std::vector<std::function<std::unique_ptr<HLE::DecoderBase>(Memory::MemorySystem&)>> | static std::vector<std::function<std::unique_ptr<HLE::DecoderBase>(Memory::MemorySystem&)>> | ||||||
|     decoder_backends = { |     decoder_backends = { | ||||||
| #if defined(HAVE_MF) |  | ||||||
|         [](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> { |         [](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> { | ||||||
|             return std::make_unique<HLE::WMFDecoder>(memory); |             return std::make_unique<HLE::FAAD2Decoder>(memory); | ||||||
|         }, |  | ||||||
| #endif |  | ||||||
| #if defined(HAVE_AUDIOTOOLBOX) |  | ||||||
|         [](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> { |  | ||||||
|             return std::make_unique<HLE::AudioToolboxDecoder>(memory); |  | ||||||
|         }, |  | ||||||
| #endif |  | ||||||
| #if ANDROID |  | ||||||
|         [](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> { |  | ||||||
|             return std::make_unique<HLE::MediaNDKDecoder>(memory); |  | ||||||
|         }, |  | ||||||
| #endif |  | ||||||
|         [](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> { |  | ||||||
|             return std::make_unique<HLE::FDKDecoder>(memory); |  | ||||||
|         }, |  | ||||||
|         [](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> { |  | ||||||
|             return std::make_unique<HLE::FFMPEGDecoder>(memory); |  | ||||||
|         }, |         }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,253 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <media/NdkMediaCodec.h> |  | ||||||
| #include <media/NdkMediaError.h> |  | ||||||
| #include <media/NdkMediaFormat.h> |  | ||||||
| 
 |  | ||||||
| #include <memory> |  | ||||||
| #include <vector> |  | ||||||
| 
 |  | ||||||
| #include "audio_core/hle/adts.h" |  | ||||||
| #include "audio_core/hle/mediandk_decoder.h" |  | ||||||
| 
 |  | ||||||
| namespace AudioCore::HLE { |  | ||||||
| 
 |  | ||||||
| struct AMediaCodecRelease { |  | ||||||
|     void operator()(AMediaCodec* codec) const { |  | ||||||
|         AMediaCodec_stop(codec); |  | ||||||
|         AMediaCodec_delete(codec); |  | ||||||
|     }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class MediaNDKDecoder::Impl { |  | ||||||
| public: |  | ||||||
|     explicit Impl(Memory::MemorySystem& memory); |  | ||||||
|     ~Impl(); |  | ||||||
|     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
|     bool SetMediaType(const AudioCore::ADTSData& adts_data); |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::optional<BinaryMessage> Initalize(const BinaryMessage& request); |  | ||||||
|     std::optional<BinaryMessage> Decode(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
|     Memory::MemorySystem& memory; |  | ||||||
|     std::unique_ptr<AMediaCodec, AMediaCodecRelease> decoder; |  | ||||||
|     // default: 2 channles, 48000 samplerate
 |  | ||||||
|     AudioCore::ADTSData mADTSData{ |  | ||||||
|         /*header_length*/ 7,  /*mpeg2*/ false,   /*profile*/ 2, |  | ||||||
|         /*channels*/ 2,       /*channel_idx*/ 2, /*framecount*/ 0, |  | ||||||
|         /*samplerate_idx*/ 3, /*length*/ 0,      /*samplerate*/ 48000}; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| MediaNDKDecoder::Impl::Impl(Memory::MemorySystem& memory_) : memory(memory_) { |  | ||||||
|     SetMediaType(mADTSData); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MediaNDKDecoder::Impl::~Impl() = default; |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> MediaNDKDecoder::Impl::Initalize(const BinaryMessage& request) { |  | ||||||
|     BinaryMessage response = request; |  | ||||||
|     response.header.result = ResultStatus::Success; |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool MediaNDKDecoder::Impl::SetMediaType(const AudioCore::ADTSData& adts_data) { |  | ||||||
|     const char* mime = "audio/mp4a-latm"; |  | ||||||
|     if (decoder && mADTSData.profile == adts_data.profile && |  | ||||||
|         mADTSData.channel_idx == adts_data.channel_idx && |  | ||||||
|         mADTSData.samplerate_idx == adts_data.samplerate_idx) { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|     decoder.reset(AMediaCodec_createDecoderByType(mime)); |  | ||||||
|     if (decoder == nullptr) { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     u8 csd_0[2]; |  | ||||||
|     csd_0[0] = static_cast<u8>((adts_data.profile << 3) | (adts_data.samplerate_idx >> 1)); |  | ||||||
|     csd_0[1] = |  | ||||||
|         static_cast<u8>(((adts_data.samplerate_idx << 7) & 0x80) | (adts_data.channel_idx << 3)); |  | ||||||
|     AMediaFormat* format = AMediaFormat_new(); |  | ||||||
|     AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mime); |  | ||||||
|     AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, adts_data.samplerate); |  | ||||||
|     AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, adts_data.channels); |  | ||||||
|     AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_IS_ADTS, 1); |  | ||||||
|     AMediaFormat_setBuffer(format, "csd-0", csd_0, sizeof(csd_0)); |  | ||||||
| 
 |  | ||||||
|     media_status_t status = AMediaCodec_configure(decoder.get(), format, NULL, NULL, 0); |  | ||||||
|     if (status != AMEDIA_OK) { |  | ||||||
|         AMediaFormat_delete(format); |  | ||||||
|         decoder.reset(); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     status = AMediaCodec_start(decoder.get()); |  | ||||||
|     if (status != AMEDIA_OK) { |  | ||||||
|         AMediaFormat_delete(format); |  | ||||||
|         decoder.reset(); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     AMediaFormat_delete(format); |  | ||||||
|     mADTSData = adts_data; |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> MediaNDKDecoder::Impl::ProcessRequest(const BinaryMessage& request) { |  | ||||||
|     if (request.header.codec != DecoderCodec::DecodeAAC) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "AAC Decoder cannot handle such codec: {}", |  | ||||||
|                   static_cast<u16>(request.header.codec)); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     switch (request.header.cmd) { |  | ||||||
|     case DecoderCommand::Init: { |  | ||||||
|         return Initalize(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::EncodeDecode: { |  | ||||||
|         return Decode(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::Shutdown: |  | ||||||
|     case DecoderCommand::SaveState: |  | ||||||
|     case DecoderCommand::LoadState: { |  | ||||||
|         LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}", |  | ||||||
|                     static_cast<u16>(request.header.cmd)); |  | ||||||
|         BinaryMessage response = request; |  | ||||||
|         response.header.result = ResultStatus::Success; |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
|     default: |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", |  | ||||||
|                   static_cast<u16>(request.header.cmd)); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> MediaNDKDecoder::Impl::Decode(const BinaryMessage& request) { |  | ||||||
|     BinaryMessage response{}; |  | ||||||
|     response.header.codec = request.header.codec; |  | ||||||
|     response.header.cmd = request.header.cmd; |  | ||||||
|     response.decode_aac_response.size = request.decode_aac_request.size; |  | ||||||
|     response.decode_aac_response.num_samples = 1024; |  | ||||||
| 
 |  | ||||||
|     if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR || |  | ||||||
|         request.decode_aac_request.src_addr + request.decode_aac_request.size > |  | ||||||
|             Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", |  | ||||||
|                   request.decode_aac_request.src_addr); |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const u8* data = |  | ||||||
|         memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR); |  | ||||||
|     ADTSData adts_data = AudioCore::ParseADTS(data); |  | ||||||
|     SetMediaType(adts_data); |  | ||||||
|     response.decode_aac_response.sample_rate = GetSampleRateEnum(adts_data.samplerate); |  | ||||||
|     response.decode_aac_response.num_channels = adts_data.channels; |  | ||||||
|     if (!decoder) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Missing decoder for profile: {}, channels: {}, samplerate: {}", |  | ||||||
|                   adts_data.profile, adts_data.channels, adts_data.samplerate); |  | ||||||
|         return {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // input
 |  | ||||||
|     constexpr int timeout = 160; |  | ||||||
|     std::size_t buffer_size = 0; |  | ||||||
|     u8* buffer = nullptr; |  | ||||||
|     ssize_t buffer_index = AMediaCodec_dequeueInputBuffer(decoder.get(), timeout); |  | ||||||
|     if (buffer_index < 0) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Failed to enqueue the input samples: {}", buffer_index); |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
|     buffer = AMediaCodec_getInputBuffer(decoder.get(), buffer_index, &buffer_size); |  | ||||||
|     if (buffer_size < request.decode_aac_request.size) { |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
|     std::memcpy(buffer, data, request.decode_aac_request.size); |  | ||||||
|     media_status_t status = AMediaCodec_queueInputBuffer(decoder.get(), buffer_index, 0, |  | ||||||
|                                                          request.decode_aac_request.size, 0, 0); |  | ||||||
|     if (status != AMEDIA_OK) { |  | ||||||
|         LOG_WARNING(Audio_DSP, "Try queue input buffer again later!"); |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // output
 |  | ||||||
|     AMediaCodecBufferInfo info; |  | ||||||
|     std::array<std::vector<u16>, 2> out_streams; |  | ||||||
|     buffer_index = AMediaCodec_dequeueOutputBuffer(decoder.get(), &info, timeout); |  | ||||||
|     switch (buffer_index) { |  | ||||||
|     case AMEDIACODEC_INFO_TRY_AGAIN_LATER: |  | ||||||
|         LOG_WARNING(Audio_DSP, "Failed to dequeue output buffer: timeout!"); |  | ||||||
|         break; |  | ||||||
|     case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED: |  | ||||||
|         LOG_WARNING(Audio_DSP, "Failed to dequeue output buffer: buffers changed!"); |  | ||||||
|         break; |  | ||||||
|     case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED: { |  | ||||||
|         AMediaFormat* format = AMediaCodec_getOutputFormat(decoder.get()); |  | ||||||
|         LOG_WARNING(Audio_DSP, "output format: {}", AMediaFormat_toString(format)); |  | ||||||
|         AMediaFormat_delete(format); |  | ||||||
|         buffer_index = AMediaCodec_dequeueOutputBuffer(decoder.get(), &info, timeout); |  | ||||||
|     } |  | ||||||
|     default: { |  | ||||||
|         int offset = info.offset; |  | ||||||
|         buffer = AMediaCodec_getOutputBuffer(decoder.get(), buffer_index, &buffer_size); |  | ||||||
|         while (offset < info.size) { |  | ||||||
|             for (int channel = 0; channel < response.decode_aac_response.num_channels; channel++) { |  | ||||||
|                 u16 pcm_data; |  | ||||||
|                 std::memcpy(&pcm_data, buffer + offset, sizeof(pcm_data)); |  | ||||||
|                 out_streams[channel].push_back(pcm_data); |  | ||||||
|                 offset += sizeof(pcm_data); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         AMediaCodec_releaseOutputBuffer(decoder.get(), buffer_index, info.size != 0); |  | ||||||
|     } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // transfer the decoded buffer from vector to the FCRAM
 |  | ||||||
|     size_t stream0_size = out_streams[0].size() * sizeof(u16); |  | ||||||
|     if (stream0_size != 0) { |  | ||||||
|         if (request.decode_aac_request.dst_addr_ch0 < Memory::FCRAM_PADDR || |  | ||||||
|             request.decode_aac_request.dst_addr_ch0 + stream0_size > |  | ||||||
|                 Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|             LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}", |  | ||||||
|                       request.decode_aac_request.dst_addr_ch0); |  | ||||||
|             return response; |  | ||||||
|         } |  | ||||||
|         std::memcpy( |  | ||||||
|             memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch0 - Memory::FCRAM_PADDR), |  | ||||||
|             out_streams[0].data(), stream0_size); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     size_t stream1_size = out_streams[1].size() * sizeof(u16); |  | ||||||
|     if (stream1_size != 0) { |  | ||||||
|         if (request.decode_aac_request.dst_addr_ch1 < Memory::FCRAM_PADDR || |  | ||||||
|             request.decode_aac_request.dst_addr_ch1 + stream1_size > |  | ||||||
|                 Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|             LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}", |  | ||||||
|                       request.decode_aac_request.dst_addr_ch1); |  | ||||||
|             return response; |  | ||||||
|         } |  | ||||||
|         std::memcpy( |  | ||||||
|             memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch1 - Memory::FCRAM_PADDR), |  | ||||||
|             out_streams[1].data(), stream1_size); |  | ||||||
|     } |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MediaNDKDecoder::MediaNDKDecoder(Memory::MemorySystem& memory) |  | ||||||
|     : impl(std::make_unique<Impl>(memory)) {} |  | ||||||
| 
 |  | ||||||
| MediaNDKDecoder::~MediaNDKDecoder() = default; |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> MediaNDKDecoder::ProcessRequest(const BinaryMessage& request) { |  | ||||||
|     return impl->ProcessRequest(request); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool MediaNDKDecoder::IsValid() const { |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore::HLE
 |  | ||||||
|  | @ -1,22 +0,0 @@ | ||||||
| // Copyright 2019 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 MediaNDKDecoder final : public DecoderBase { |  | ||||||
| public: |  | ||||||
|     explicit MediaNDKDecoder(Memory::MemorySystem& memory); |  | ||||||
|     ~MediaNDKDecoder() override; |  | ||||||
|     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request) override; |  | ||||||
|     bool IsValid() const override; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     class Impl; |  | ||||||
|     std::unique_ptr<Impl> impl; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore::HLE
 |  | ||||||
|  | @ -1,313 +0,0 @@ | ||||||
| // 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 { |  | ||||||
| 
 |  | ||||||
| using namespace MFDecoder; |  | ||||||
| 
 |  | ||||||
| class WMFDecoder::Impl { |  | ||||||
| public: |  | ||||||
|     explicit Impl(Memory::MemorySystem& memory); |  | ||||||
|     ~Impl(); |  | ||||||
|     std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request); |  | ||||||
|     bool IsValid() const { |  | ||||||
|         return is_valid; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     std::optional<BinaryMessage> Initalize(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
|     std::optional<BinaryMessage> Decode(const BinaryMessage& request); |  | ||||||
| 
 |  | ||||||
|     MFOutputState DecodingLoop(AudioCore::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; |  | ||||||
|     bool is_valid = false; |  | ||||||
|     bool mf_started = false; |  | ||||||
|     bool coinited = false; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| WMFDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) { |  | ||||||
|     // Attempt to load the symbols for mf.dll
 |  | ||||||
|     if (!InitMFDLL()) { |  | ||||||
|         LOG_CRITICAL(Audio_DSP, |  | ||||||
|                      "Unable to load mf.dll. AAC audio through media foundation unavailable"); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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); |  | ||||||
|     } else { |  | ||||||
|         coinited = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // lite startup is faster and all what we need is included
 |  | ||||||
|     hr = MFDecoder::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); |  | ||||||
|     } else { |  | ||||||
|         mf_started = true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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; |  | ||||||
|     is_valid = 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(); |  | ||||||
|     } |  | ||||||
|     if (mf_started) { |  | ||||||
|         MFDecoder::MFShutdown(); |  | ||||||
|     } |  | ||||||
|     if (coinited) { |  | ||||||
|         CoUninitialize(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> WMFDecoder::Impl::ProcessRequest(const BinaryMessage& request) { |  | ||||||
|     if (request.header.codec != DecoderCodec::DecodeAAC) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got unknown codec {}", static_cast<u16>(request.header.codec)); |  | ||||||
|         return std::nullopt; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     switch (request.header.cmd) { |  | ||||||
|     case DecoderCommand::Init: { |  | ||||||
|         LOG_INFO(Audio_DSP, "WMFDecoder initializing"); |  | ||||||
|         return Initalize(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::EncodeDecode: { |  | ||||||
|         return Decode(request); |  | ||||||
|     } |  | ||||||
|     case DecoderCommand::Shutdown: |  | ||||||
|     case DecoderCommand::SaveState: |  | ||||||
|     case DecoderCommand::LoadState: { |  | ||||||
|         LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}", |  | ||||||
|                     static_cast<u16>(request.header.cmd)); |  | ||||||
|         BinaryMessage response = request; |  | ||||||
|         response.header.result = ResultStatus::Success; |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
|     default: |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got unknown binary request: {}", |  | ||||||
|                   static_cast<u16>(request.header.cmd)); |  | ||||||
|         return std::nullopt; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| std::optional<BinaryMessage> WMFDecoder::Impl::Initalize(const BinaryMessage& request) { |  | ||||||
|     BinaryMessage response = request; |  | ||||||
|     response.header.result = ResultStatus::Success; |  | ||||||
| 
 |  | ||||||
|     format_selected = false; // select format again if application request initialize the DSP
 |  | ||||||
|     return response; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| MFOutputState WMFDecoder::Impl::DecodingLoop(AudioCore::ADTSData adts_header, |  | ||||||
|                                              std::array<std::vector<u8>, 2>& out_streams) { |  | ||||||
|     std::optional<std::vector<f32>> output_buffer; |  | ||||||
| 
 |  | ||||||
|     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 = std::clamp(output_buffer->at(i), -1.0f, 1.0f); |  | ||||||
|                     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++; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // If we return OK here, the decoder won't be in a state to receive new data and will fail
 |  | ||||||
|         // on the next call; instead treat it like the HaveMoreData case
 |  | ||||||
|         if (output_status == MFOutputState::OK) |  | ||||||
|             continue; |  | ||||||
| 
 |  | ||||||
|         // 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<BinaryMessage> WMFDecoder::Impl::Decode(const BinaryMessage& request) { |  | ||||||
|     BinaryMessage response{}; |  | ||||||
|     response.header.codec = request.header.codec; |  | ||||||
|     response.header.cmd = request.header.cmd; |  | ||||||
|     response.decode_aac_response.size = request.decode_aac_request.size; |  | ||||||
|     response.decode_aac_response.num_channels = 2; |  | ||||||
|     response.decode_aac_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.decode_aac_request.src_addr < Memory::FCRAM_PADDR || |  | ||||||
|         request.decode_aac_request.src_addr + request.decode_aac_request.size > |  | ||||||
|             Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}", |  | ||||||
|                   request.decode_aac_request.src_addr); |  | ||||||
|         return std::nullopt; |  | ||||||
|     } |  | ||||||
|     const u8* data = |  | ||||||
|         memory.GetFCRAMPointer(request.decode_aac_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(data, request.decode_aac_request.size); |  | ||||||
| 
 |  | ||||||
|     if (!adts_meta) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Unable to deduce decoding parameters from ADTS stream"); |  | ||||||
|         return response; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     response.decode_aac_response.sample_rate = GetSampleRateEnum(adts_meta->ADTSHeader.samplerate); |  | ||||||
|     response.decode_aac_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(data, request.decode_aac_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.decode_aac_request.dst_addr_ch0 < Memory::FCRAM_PADDR || |  | ||||||
|             request.decode_aac_request.dst_addr_ch0 + out_streams[0].size() > |  | ||||||
|                 Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|             LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}", |  | ||||||
|                       request.decode_aac_request.dst_addr_ch0); |  | ||||||
|             return std::nullopt; |  | ||||||
|         } |  | ||||||
|         std::memcpy( |  | ||||||
|             memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch0 - Memory::FCRAM_PADDR), |  | ||||||
|             out_streams[0].data(), out_streams[0].size()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (out_streams[1].size() != 0) { |  | ||||||
|         if (request.decode_aac_request.dst_addr_ch1 < Memory::FCRAM_PADDR || |  | ||||||
|             request.decode_aac_request.dst_addr_ch1 + out_streams[1].size() > |  | ||||||
|                 Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) { |  | ||||||
|             LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}", |  | ||||||
|                       request.decode_aac_request.dst_addr_ch1); |  | ||||||
|             return std::nullopt; |  | ||||||
|         } |  | ||||||
|         std::memcpy( |  | ||||||
|             memory.GetFCRAMPointer(request.decode_aac_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<BinaryMessage> WMFDecoder::ProcessRequest(const BinaryMessage& request) { |  | ||||||
|     return impl->ProcessRequest(request); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool WMFDecoder::IsValid() const { |  | ||||||
|     return impl->IsValid(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore::HLE
 |  | ||||||
|  | @ -1,23 +0,0 @@ | ||||||
| // 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<BinaryMessage> ProcessRequest(const BinaryMessage& request) override; |  | ||||||
|     bool IsValid() const override; |  | ||||||
| 
 |  | ||||||
| private: |  | ||||||
|     class Impl; |  | ||||||
|     std::unique_ptr<Impl> impl; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| } // namespace AudioCore::HLE
 |  | ||||||
|  | @ -1,464 +0,0 @@ | ||||||
| // 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" |  | ||||||
| 
 |  | ||||||
| namespace MFDecoder { |  | ||||||
| 
 |  | ||||||
| // 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{}; |  | ||||||
|     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 AudioCore::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(const u8* buffer, std::size_t len) { |  | ||||||
|     if (len < 7) { |  | ||||||
|         return std::nullopt; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     AudioCore::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 = AudioCore::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(AudioCore::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; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| namespace { |  | ||||||
| 
 |  | ||||||
| struct LibraryDeleter { |  | ||||||
|     using pointer = HMODULE; |  | ||||||
|     void operator()(HMODULE h) const { |  | ||||||
|         if (h != nullptr) |  | ||||||
|             FreeLibrary(h); |  | ||||||
|     } |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| std::unique_ptr<HMODULE, LibraryDeleter> mf_dll{nullptr}; |  | ||||||
| std::unique_ptr<HMODULE, LibraryDeleter> mfplat_dll{nullptr}; |  | ||||||
| 
 |  | ||||||
| } // namespace
 |  | ||||||
| 
 |  | ||||||
| bool InitMFDLL() { |  | ||||||
| 
 |  | ||||||
|     mf_dll.reset(LoadLibrary(TEXT("mf.dll"))); |  | ||||||
|     if (!mf_dll) { |  | ||||||
|         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 mf.dll: {}", message); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     mfplat_dll.reset(LoadLibrary(TEXT("mfplat.dll"))); |  | ||||||
|     if (!mfplat_dll) { |  | ||||||
|         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 mfplat.dll: {}", message); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MFStartup = Symbol<HRESULT(ULONG, DWORD)>(mfplat_dll.get(), "MFStartup"); |  | ||||||
|     if (!MFStartup) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Cannot load function MFStartup"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MFShutdown = Symbol<HRESULT(void)>(mfplat_dll.get(), "MFShutdown"); |  | ||||||
|     if (!MFShutdown) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Cannot load function MFShutdown"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MFShutdownObject = Symbol<HRESULT(IUnknown*)>(mf_dll.get(), "MFShutdownObject"); |  | ||||||
|     if (!MFShutdownObject) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Cannot load function MFShutdownObject"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MFCreateAlignedMemoryBuffer = Symbol<HRESULT(DWORD, DWORD, IMFMediaBuffer**)>( |  | ||||||
|         mfplat_dll.get(), "MFCreateAlignedMemoryBuffer"); |  | ||||||
|     if (!MFCreateAlignedMemoryBuffer) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Cannot load function MFCreateAlignedMemoryBuffer"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MFCreateSample = Symbol<HRESULT(IMFSample**)>(mfplat_dll.get(), "MFCreateSample"); |  | ||||||
|     if (!MFCreateSample) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Cannot load function MFCreateSample"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MFTEnumEx = |  | ||||||
|         Symbol<HRESULT(GUID, UINT32, const MFT_REGISTER_TYPE_INFO*, const MFT_REGISTER_TYPE_INFO*, |  | ||||||
|                        IMFActivate***, UINT32*)>(mfplat_dll.get(), "MFTEnumEx"); |  | ||||||
|     if (!MFTEnumEx) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Cannot load function MFTEnumEx"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     MFCreateMediaType = Symbol<HRESULT(IMFMediaType**)>(mfplat_dll.get(), "MFCreateMediaType"); |  | ||||||
|     if (!MFCreateMediaType) { |  | ||||||
|         LOG_ERROR(Audio_DSP, "Cannot load function MFCreateMediaType"); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Symbol<HRESULT(ULONG, DWORD)> MFStartup; |  | ||||||
| Symbol<HRESULT(void)> MFShutdown; |  | ||||||
| Symbol<HRESULT(IUnknown*)> MFShutdownObject; |  | ||||||
| Symbol<HRESULT(DWORD, DWORD, IMFMediaBuffer**)> MFCreateAlignedMemoryBuffer; |  | ||||||
| Symbol<HRESULT(IMFSample**)> MFCreateSample; |  | ||||||
| Symbol<HRESULT(GUID, UINT32, const MFT_REGISTER_TYPE_INFO*, const MFT_REGISTER_TYPE_INFO*, |  | ||||||
|                IMFActivate***, UINT32*)> |  | ||||||
|     MFTEnumEx; |  | ||||||
| Symbol<HRESULT(IMFMediaType**)> MFCreateMediaType; |  | ||||||
| 
 |  | ||||||
| } // namespace MFDecoder
 |  | ||||||
|  | @ -1,125 +0,0 @@ | ||||||
| // Copyright 2019 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| #include <memory> |  | ||||||
| #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" |  | ||||||
| 
 |  | ||||||
| namespace MFDecoder { |  | ||||||
| 
 |  | ||||||
| template <typename T> |  | ||||||
| struct Symbol { |  | ||||||
|     Symbol() = default; |  | ||||||
|     Symbol(HMODULE dll, const char* name) { |  | ||||||
|         if (dll) { |  | ||||||
|             ptr_symbol = reinterpret_cast<T*>(GetProcAddress(dll, name)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     operator T*() const { |  | ||||||
|         return ptr_symbol; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     explicit operator bool() const { |  | ||||||
|         return ptr_symbol != nullptr; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     T* ptr_symbol = nullptr; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // Runtime load the MF symbols to prevent mf.dll not found errors on citra load
 |  | ||||||
| extern Symbol<HRESULT(ULONG, DWORD)> MFStartup; |  | ||||||
| extern Symbol<HRESULT(void)> MFShutdown; |  | ||||||
| extern Symbol<HRESULT(IUnknown*)> MFShutdownObject; |  | ||||||
| extern Symbol<HRESULT(DWORD, DWORD, IMFMediaBuffer**)> MFCreateAlignedMemoryBuffer; |  | ||||||
| extern Symbol<HRESULT(IMFSample**)> MFCreateSample; |  | ||||||
| extern Symbol<HRESULT(GUID, UINT32, const MFT_REGISTER_TYPE_INFO*, const MFT_REGISTER_TYPE_INFO*, |  | ||||||
|                       IMFActivate***, UINT32*)> |  | ||||||
|     MFTEnumEx; |  | ||||||
| extern Symbol<HRESULT(IMFMediaType**)> MFCreateMediaType; |  | ||||||
| 
 |  | ||||||
| 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 { |  | ||||||
|     AudioCore::ADTSData ADTSHeader; |  | ||||||
|     u8 AACTag[14]; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // exported functions
 |  | ||||||
| 
 |  | ||||||
| /// Loads the symbols from mf.dll at runtime. Returns false if the symbols can't be loaded
 |  | ||||||
| bool InitMFDLL(); |  | ||||||
| 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 AudioCore::ADTSData& adts, const UINT8* user_data, |  | ||||||
|                           UINT32 user_data_len, GUID audio_format = MFAudioFormat_AAC); |  | ||||||
| std::optional<ADTSMeta> DetectMediaType(const u8* 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); |  | ||||||
| 
 |  | ||||||
| } // namespace MFDecoder
 |  | ||||||
|  | @ -76,8 +76,6 @@ add_library(citra_common STATIC | ||||||
|     construct.h |     construct.h | ||||||
|     dynamic_library/dynamic_library.cpp |     dynamic_library/dynamic_library.cpp | ||||||
|     dynamic_library/dynamic_library.h |     dynamic_library/dynamic_library.h | ||||||
|     dynamic_library/fdk-aac.cpp |  | ||||||
|     dynamic_library/fdk-aac.h |  | ||||||
|     dynamic_library/ffmpeg.cpp |     dynamic_library/ffmpeg.cpp | ||||||
|     dynamic_library/ffmpeg.h |     dynamic_library/ffmpeg.h | ||||||
|     error.cpp |     error.cpp | ||||||
|  |  | ||||||
|  | @ -1,57 +0,0 @@ | ||||||
| // Copyright 2023 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <memory> |  | ||||||
| 
 |  | ||||||
| #include "common/dynamic_library/dynamic_library.h" |  | ||||||
| #include "common/dynamic_library/fdk-aac.h" |  | ||||||
| #include "common/logging/log.h" |  | ||||||
| 
 |  | ||||||
| namespace DynamicLibrary::FdkAac { |  | ||||||
| 
 |  | ||||||
| aacDecoder_GetLibInfo_func aacDecoder_GetLibInfo; |  | ||||||
| aacDecoder_Open_func aacDecoder_Open; |  | ||||||
| aacDecoder_Close_func aacDecoder_Close; |  | ||||||
| aacDecoder_SetParam_func aacDecoder_SetParam; |  | ||||||
| aacDecoder_GetStreamInfo_func aacDecoder_GetStreamInfo; |  | ||||||
| aacDecoder_DecodeFrame_func aacDecoder_DecodeFrame; |  | ||||||
| aacDecoder_Fill_func aacDecoder_Fill; |  | ||||||
| 
 |  | ||||||
| static std::unique_ptr<Common::DynamicLibrary> fdk_aac; |  | ||||||
| 
 |  | ||||||
| #define LOAD_SYMBOL(library, name)                                                                 \ |  | ||||||
|     any_failed = any_failed || (name = library->GetSymbol<name##_func>(#name)) == nullptr |  | ||||||
| 
 |  | ||||||
| bool LoadFdkAac() { |  | ||||||
|     if (fdk_aac) { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fdk_aac = std::make_unique<Common::DynamicLibrary>("fdk-aac", 2); |  | ||||||
|     if (!fdk_aac->IsLoaded()) { |  | ||||||
|         LOG_WARNING(Common, "Could not dynamically load libfdk-aac: {}", fdk_aac->GetLoadError()); |  | ||||||
|         fdk_aac.reset(); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     auto any_failed = false; |  | ||||||
|     LOAD_SYMBOL(fdk_aac, aacDecoder_GetLibInfo); |  | ||||||
|     LOAD_SYMBOL(fdk_aac, aacDecoder_Open); |  | ||||||
|     LOAD_SYMBOL(fdk_aac, aacDecoder_Close); |  | ||||||
|     LOAD_SYMBOL(fdk_aac, aacDecoder_SetParam); |  | ||||||
|     LOAD_SYMBOL(fdk_aac, aacDecoder_GetStreamInfo); |  | ||||||
|     LOAD_SYMBOL(fdk_aac, aacDecoder_DecodeFrame); |  | ||||||
|     LOAD_SYMBOL(fdk_aac, aacDecoder_Fill); |  | ||||||
| 
 |  | ||||||
|     if (any_failed) { |  | ||||||
|         LOG_WARNING(Common, "Could not find all required functions in libfdk-aac."); |  | ||||||
|         fdk_aac.reset(); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     LOG_INFO(Common, "Successfully loaded libfdk-aac."); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| } // namespace DynamicLibrary::FdkAac
 |  | ||||||
|  | @ -1,34 +0,0 @@ | ||||||
| // Copyright 2023 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #pragma once |  | ||||||
| 
 |  | ||||||
| extern "C" { |  | ||||||
| #include <fdk-aac/aacdecoder_lib.h> |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| namespace DynamicLibrary::FdkAac { |  | ||||||
| 
 |  | ||||||
| typedef INT (*aacDecoder_GetLibInfo_func)(LIB_INFO* info); |  | ||||||
| typedef HANDLE_AACDECODER (*aacDecoder_Open_func)(TRANSPORT_TYPE transportFmt, UINT nrOfLayers); |  | ||||||
| typedef void (*aacDecoder_Close_func)(HANDLE_AACDECODER self); |  | ||||||
| typedef AAC_DECODER_ERROR (*aacDecoder_SetParam_func)(const HANDLE_AACDECODER self, |  | ||||||
|                                                       const AACDEC_PARAM param, const INT value); |  | ||||||
| typedef CStreamInfo* (*aacDecoder_GetStreamInfo_func)(HANDLE_AACDECODER self); |  | ||||||
| typedef AAC_DECODER_ERROR (*aacDecoder_DecodeFrame_func)(HANDLE_AACDECODER self, INT_PCM* pTimeData, |  | ||||||
|                                                          const INT timeDataSize, const UINT flags); |  | ||||||
| typedef AAC_DECODER_ERROR (*aacDecoder_Fill_func)(HANDLE_AACDECODER self, UCHAR* pBuffer[], |  | ||||||
|                                                   const UINT bufferSize[], UINT* bytesValid); |  | ||||||
| 
 |  | ||||||
| extern aacDecoder_GetLibInfo_func aacDecoder_GetLibInfo; |  | ||||||
| extern aacDecoder_Open_func aacDecoder_Open; |  | ||||||
| extern aacDecoder_Close_func aacDecoder_Close; |  | ||||||
| extern aacDecoder_SetParam_func aacDecoder_SetParam; |  | ||||||
| extern aacDecoder_GetStreamInfo_func aacDecoder_GetStreamInfo; |  | ||||||
| extern aacDecoder_DecodeFrame_func aacDecoder_DecodeFrame; |  | ||||||
| extern aacDecoder_Fill_func aacDecoder_Fill; |  | ||||||
| 
 |  | ||||||
| bool LoadFdkAac(); |  | ||||||
| 
 |  | ||||||
| } // namespace DynamicLibrary::FdkAac
 |  | ||||||
|  | @ -12,7 +12,6 @@ add_executable(tests | ||||||
|     core/memory/vm_manager.cpp |     core/memory/vm_manager.cpp | ||||||
|     precompiled_headers.h |     precompiled_headers.h | ||||||
|     audio_core/hle/hle.cpp |     audio_core/hle/hle.cpp | ||||||
|     audio_core/hle/adts_reader.cpp |  | ||||||
|     audio_core/lle/lle.cpp |     audio_core/lle/lle.cpp | ||||||
|     audio_core/audio_fixures.h |     audio_core/audio_fixures.h | ||||||
|     audio_core/decoder_tests.cpp |     audio_core/decoder_tests.cpp | ||||||
|  |  | ||||||
|  | @ -1,77 +0,0 @@ | ||||||
| // Copyright 2023 Citra Emulator Project
 |  | ||||||
| // Licensed under GPLv2 or any later version
 |  | ||||||
| // Refer to the license.txt file included.
 |  | ||||||
| 
 |  | ||||||
| #include <catch2/catch_test_macros.hpp> |  | ||||||
| #include <fmt/format.h> |  | ||||||
| 
 |  | ||||||
| #include "audio_core/hle/adts.h" |  | ||||||
| 
 |  | ||||||
| namespace { |  | ||||||
| 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}; |  | ||||||
| 
 |  | ||||||
| AudioCore::ADTSData ParseADTS_Old(const unsigned char* buffer) { |  | ||||||
|     u32 tmp = 0; |  | ||||||
|     AudioCore::ADTSData out{}; |  | ||||||
| 
 |  | ||||||
|     // sync word 0xfff
 |  | ||||||
|     tmp = (buffer[0] << 8) | (buffer[1] & 0xf0); |  | ||||||
|     if ((tmp & 0xffff) != 0xfff0) { |  | ||||||
|         out.length = 0; |  | ||||||
|         return out; |  | ||||||
|     } |  | ||||||
|     // bit 16 = no CRC
 |  | ||||||
|     out.header_length = (buffer[1] & 0x1) ? 7 : 9; |  | ||||||
|     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; |  | ||||||
| } |  | ||||||
| } // namespace
 |  | ||||||
| 
 |  | ||||||
| TEST_CASE("ParseADTS fuzz", "[audio_core][hle]") { |  | ||||||
|     for (u32 i = 0; i < 0x10000; i++) { |  | ||||||
|         std::array<u8, 7> adts_header; |  | ||||||
|         std::string adts_header_string = "ADTS Header: "; |  | ||||||
|         for (auto& it : adts_header) { |  | ||||||
|             it = static_cast<u8>(rand()); |  | ||||||
|             adts_header_string.append(fmt::format("{:2X} ", it)); |  | ||||||
|         } |  | ||||||
|         INFO(adts_header_string); |  | ||||||
| 
 |  | ||||||
|         AudioCore::ADTSData out_old_impl = |  | ||||||
|             ParseADTS_Old(reinterpret_cast<const unsigned char*>(adts_header.data())); |  | ||||||
|         AudioCore::ADTSData out = AudioCore::ParseADTS(adts_header.data()); |  | ||||||
| 
 |  | ||||||
|         REQUIRE(out_old_impl.length == out.length); |  | ||||||
|         REQUIRE(out_old_impl.channels == out.channels); |  | ||||||
|         REQUIRE(out_old_impl.channel_idx == out.channel_idx); |  | ||||||
|         REQUIRE(out_old_impl.framecount == out.framecount); |  | ||||||
|         REQUIRE(out_old_impl.header_length == out.header_length); |  | ||||||
|         REQUIRE(out_old_impl.mpeg2 == out.mpeg2); |  | ||||||
|         REQUIRE(out_old_impl.profile == out.profile); |  | ||||||
|         REQUIRE(out_old_impl.samplerate == out.samplerate); |  | ||||||
|         REQUIRE(out_old_impl.samplerate_idx == out.samplerate_idx); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue