mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-29 15:33:35 -06:00
Merge branch 'shadps4-emu:main' into fontlib
This commit is contained in:
commit
cfcd365158
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -120,3 +120,6 @@
|
||||
[submodule "externals/miniz"]
|
||||
path = externals/miniz
|
||||
url = https://github.com/richgel999/miniz
|
||||
[submodule "externals/aacdec/fdk-aac"]
|
||||
path = externals/aacdec/fdk-aac
|
||||
url = https://android.googlesource.com/platform/external/aac
|
||||
|
||||
@ -202,8 +202,8 @@ execute_process(
|
||||
|
||||
# Set Version
|
||||
set(EMULATOR_VERSION_MAJOR "0")
|
||||
set(EMULATOR_VERSION_MINOR "12")
|
||||
set(EMULATOR_VERSION_PATCH "6")
|
||||
set(EMULATOR_VERSION_MINOR "13")
|
||||
set(EMULATOR_VERSION_PATCH "1")
|
||||
|
||||
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
|
||||
|
||||
@ -262,6 +262,8 @@ include_directories(src)
|
||||
|
||||
set(AJM_LIB src/core/libraries/ajm/ajm.cpp
|
||||
src/core/libraries/ajm/ajm.h
|
||||
src/core/libraries/ajm/ajm_aac.cpp
|
||||
src/core/libraries/ajm/ajm_aac.h
|
||||
src/core/libraries/ajm/ajm_at9.cpp
|
||||
src/core/libraries/ajm/ajm_at9.h
|
||||
src/core/libraries/ajm/ajm_batch.cpp
|
||||
@ -1085,7 +1087,7 @@ create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac)
|
||||
|
||||
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
||||
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
||||
|
||||
4
dist/net.shadps4.shadPS4.metainfo.xml
vendored
4
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@ -11,6 +11,7 @@
|
||||
<project_license translate="no">GPL-2.0</project_license>
|
||||
<launchable type="desktop-id" translate="no">net.shadps4.shadPS4.desktop</launchable>
|
||||
<url type="homepage" translate="no">https://shadps4.net/</url>
|
||||
<url type="vcs-browser" translate="no">https://github.com/shadps4-emu/shadPS4</url>
|
||||
<description>
|
||||
<p>shadPS4 is an early PlayStation 4 emulator for Windows, Linux and macOS written in C++.</p>
|
||||
<p>The emulator is still early in development, so don't expect a flawless experience. Nonetheless, the emulator can already run a number of commercial games.</p>
|
||||
@ -37,6 +38,9 @@
|
||||
<category translate="no">Game</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release version="0.13.0" date="2025-12-24">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0</url>
|
||||
</release>
|
||||
<release version="0.12.5" date="2025-11-07">
|
||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5</url>
|
||||
</release>
|
||||
|
||||
4
externals/CMakeLists.txt
vendored
4
externals/CMakeLists.txt
vendored
@ -258,6 +258,10 @@ if (WIN32)
|
||||
add_subdirectory(ext-wepoll)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET fdk-aac)
|
||||
add_subdirectory(aacdec)
|
||||
endif()
|
||||
|
||||
#nlohmann json
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
add_subdirectory(json)
|
||||
|
||||
154
externals/aacdec/CMakeLists.txt
vendored
Normal file
154
externals/aacdec/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(AACDEC_SRC
|
||||
fdk-aac/libAACdec/src/FDK_delay.cpp
|
||||
fdk-aac/libAACdec/src/aac_ram.cpp
|
||||
fdk-aac/libAACdec/src/aac_rom.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_drc.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_hcr.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_hcr_bit.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_hcrs.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_pns.cpp
|
||||
fdk-aac/libAACdec/src/aacdec_tns.cpp
|
||||
fdk-aac/libAACdec/src/aacdecoder.cpp
|
||||
fdk-aac/libAACdec/src/aacdecoder_lib.cpp
|
||||
fdk-aac/libAACdec/src/block.cpp
|
||||
fdk-aac/libAACdec/src/channel.cpp
|
||||
fdk-aac/libAACdec/src/channelinfo.cpp
|
||||
fdk-aac/libAACdec/src/conceal.cpp
|
||||
fdk-aac/libAACdec/src/ldfiltbank.cpp
|
||||
fdk-aac/libAACdec/src/pulsedata.cpp
|
||||
fdk-aac/libAACdec/src/rvlc.cpp
|
||||
fdk-aac/libAACdec/src/rvlcbit.cpp
|
||||
fdk-aac/libAACdec/src/rvlcconceal.cpp
|
||||
fdk-aac/libAACdec/src/stereo.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_ace_d4t64.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_ace_ltp.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_acelp.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_fac.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_lpc.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_lpd.cpp
|
||||
fdk-aac/libAACdec/src/usacdec_rom.cpp
|
||||
)
|
||||
|
||||
set(FDK_SRC
|
||||
fdk-aac/libFDK/src/FDK_bitbuffer.cpp
|
||||
fdk-aac/libFDK/src/FDK_core.cpp
|
||||
fdk-aac/libFDK/src/FDK_crc.cpp
|
||||
fdk-aac/libFDK/src/FDK_decorrelate.cpp
|
||||
fdk-aac/libFDK/src/FDK_hybrid.cpp
|
||||
fdk-aac/libFDK/src/FDK_lpc.cpp
|
||||
fdk-aac/libFDK/src/FDK_matrixCalloc.cpp
|
||||
fdk-aac/libFDK/src/FDK_qmf_domain.cpp
|
||||
fdk-aac/libFDK/src/FDK_tools_rom.cpp
|
||||
fdk-aac/libFDK/src/FDK_trigFcts.cpp
|
||||
fdk-aac/libFDK/src/autocorr2nd.cpp
|
||||
fdk-aac/libFDK/src/dct.cpp
|
||||
fdk-aac/libFDK/src/fft.cpp
|
||||
fdk-aac/libFDK/src/fft_rad2.cpp
|
||||
fdk-aac/libFDK/src/fixpoint_math.cpp
|
||||
fdk-aac/libFDK/src/huff_nodes.cpp
|
||||
fdk-aac/libFDK/src/mdct.cpp
|
||||
fdk-aac/libFDK/src/nlc_dec.cpp
|
||||
fdk-aac/libFDK/src/qmf.cpp
|
||||
fdk-aac/libFDK/src/scale.cpp
|
||||
)
|
||||
|
||||
set(SYS_SRC
|
||||
fdk-aac/libSYS/src/genericStds.cpp
|
||||
fdk-aac/libSYS/src/syslib_channelMapDescr.cpp
|
||||
)
|
||||
|
||||
set(ARITHCODING_SRC
|
||||
fdk-aac/libArithCoding/src/ac_arith_coder.cpp
|
||||
)
|
||||
|
||||
set(MPEGTPDEC_SRC
|
||||
fdk-aac/libMpegTPDec/src/tpdec_adif.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_adts.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_asc.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_drm.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_latm.cpp
|
||||
fdk-aac/libMpegTPDec/src/tpdec_lib.cpp
|
||||
)
|
||||
|
||||
set(SBRDEC_SRC
|
||||
fdk-aac/libSBRdec/src/HFgen_preFlat.cpp
|
||||
fdk-aac/libSBRdec/src/env_calc.cpp
|
||||
fdk-aac/libSBRdec/src/env_dec.cpp
|
||||
fdk-aac/libSBRdec/src/env_extr.cpp
|
||||
fdk-aac/libSBRdec/src/hbe.cpp
|
||||
fdk-aac/libSBRdec/src/huff_dec.cpp
|
||||
fdk-aac/libSBRdec/src/lpp_tran.cpp
|
||||
fdk-aac/libSBRdec/src/psbitdec.cpp
|
||||
fdk-aac/libSBRdec/src/psdec.cpp
|
||||
fdk-aac/libSBRdec/src/psdec_drm.cpp
|
||||
fdk-aac/libSBRdec/src/psdecrom_drm.cpp
|
||||
fdk-aac/libSBRdec/src/pvc_dec.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_deb.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_dec.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_ram.cpp
|
||||
fdk-aac/libSBRdec/src/sbr_rom.cpp
|
||||
fdk-aac/libSBRdec/src/sbrdec_drc.cpp
|
||||
fdk-aac/libSBRdec/src/sbrdec_freq_sca.cpp
|
||||
fdk-aac/libSBRdec/src/sbrdecoder.cpp
|
||||
)
|
||||
|
||||
set(PCMUTILS_SRC
|
||||
fdk-aac/libPCMutils/src/limiter.cpp
|
||||
fdk-aac/libPCMutils/src/pcm_utils.cpp
|
||||
fdk-aac/libPCMutils/src/pcmdmx_lib.cpp
|
||||
)
|
||||
|
||||
set(DRCDEC_SRC
|
||||
fdk-aac/libDRCdec/src/FDK_drcDecLib.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_gainDecoder.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_reader.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_rom.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_selectionProcess.cpp
|
||||
fdk-aac/libDRCdec/src/drcDec_tools.cpp
|
||||
fdk-aac/libDRCdec/src/drcGainDec_init.cpp
|
||||
fdk-aac/libDRCdec/src/drcGainDec_preprocess.cpp
|
||||
fdk-aac/libDRCdec/src/drcGainDec_process.cpp
|
||||
)
|
||||
|
||||
set(SACDEC_SRC
|
||||
fdk-aac/libSACdec/src/sac_bitdec.cpp
|
||||
fdk-aac/libSACdec/src/sac_calcM1andM2.cpp
|
||||
fdk-aac/libSACdec/src/sac_dec.cpp
|
||||
fdk-aac/libSACdec/src/sac_dec_conceal.cpp
|
||||
fdk-aac/libSACdec/src/sac_dec_lib.cpp
|
||||
fdk-aac/libSACdec/src/sac_process.cpp
|
||||
fdk-aac/libSACdec/src/sac_qmf.cpp
|
||||
fdk-aac/libSACdec/src/sac_reshapeBBEnv.cpp
|
||||
fdk-aac/libSACdec/src/sac_rom.cpp
|
||||
fdk-aac/libSACdec/src/sac_smoothing.cpp
|
||||
fdk-aac/libSACdec/src/sac_stp.cpp
|
||||
fdk-aac/libSACdec/src/sac_tsd.cpp
|
||||
)
|
||||
|
||||
add_library(fdk-aac
|
||||
${AACDEC_SRC}
|
||||
${FDK_SRC}
|
||||
${SYS_SRC}
|
||||
${ARITHCODING_SRC}
|
||||
${MPEGTPDEC_SRC}
|
||||
${SBRDEC_SRC}
|
||||
${PCMUTILS_SRC}
|
||||
${DRCDEC_SRC}
|
||||
${SACDEC_SRC}
|
||||
)
|
||||
|
||||
target_include_directories(fdk-aac
|
||||
PUBLIC
|
||||
fdk-aac/libAACdec/include
|
||||
fdk-aac/libFDK/include
|
||||
fdk-aac/libSYS/include
|
||||
fdk-aac/libArithCoding/include
|
||||
fdk-aac/libMpegTPDec/include
|
||||
fdk-aac/libSBRdec/include
|
||||
fdk-aac/libPCMutils/include
|
||||
fdk-aac/libDRCdec/include
|
||||
fdk-aac/libSACdec/include
|
||||
)
|
||||
1
externals/aacdec/fdk-aac
vendored
Submodule
1
externals/aacdec/fdk-aac
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ee76460efbdb147e26d804c798949c23f174460b
|
||||
@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
@ -13,6 +14,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/logging/log_entry.h"
|
||||
#include "common/logging/text_formatter.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
namespace Common::Log {
|
||||
|
||||
@ -23,8 +25,9 @@ std::string FormatLogMessage(const Entry& entry) {
|
||||
const char* class_name = GetLogClassName(entry.log_class);
|
||||
const char* level_name = GetLevelName(entry.log_level);
|
||||
|
||||
return fmt::format("[{}] <{}> {}:{} {}: {}", class_name, level_name, entry.filename,
|
||||
entry.line_num, entry.function, entry.message);
|
||||
return fmt::format("[{}] <{}> ({}) {}:{} {}: {}", class_name, level_name,
|
||||
Common::GetCurrentThreadName(), entry.filename, entry.line_num,
|
||||
entry.function, entry.message);
|
||||
}
|
||||
|
||||
void PrintMessage(const Entry& entry) {
|
||||
|
||||
@ -51,14 +51,14 @@ std::string convertValueToHex(const std::string type, const std::string valueStr
|
||||
uint32_t i;
|
||||
} floatUnion;
|
||||
floatUnion.f = std::stof(valueStr);
|
||||
result = toHex(floatUnion.i, sizeof(floatUnion.i));
|
||||
result = toHex(std::byteswap(floatUnion.i), sizeof(floatUnion.i));
|
||||
} else if (type == "float64") {
|
||||
union {
|
||||
double d;
|
||||
uint64_t i;
|
||||
} doubleUnion;
|
||||
doubleUnion.d = std::stod(valueStr);
|
||||
result = toHex(doubleUnion.i, sizeof(doubleUnion.i));
|
||||
result = toHex(std::byteswap(doubleUnion.i), sizeof(doubleUnion.i));
|
||||
} else if (type == "utf8") {
|
||||
std::vector<unsigned char> byteArray =
|
||||
std::vector<unsigned char>(valueStr.begin(), valueStr.end());
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "core/libraries/kernel/threads/pthread.h"
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
@ -237,4 +240,22 @@ void AccurateTimer::End() {
|
||||
target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
|
||||
}
|
||||
|
||||
std::string GetCurrentThreadName() {
|
||||
using namespace Libraries::Kernel;
|
||||
if (g_curthread && !g_curthread->name.empty()) {
|
||||
return g_curthread->name;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
PWSTR name;
|
||||
GetThreadDescription(GetCurrentThread(), &name);
|
||||
return Common::UTF16ToUTF8(name);
|
||||
#else
|
||||
char name[256];
|
||||
if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0) {
|
||||
return "<unknown name>";
|
||||
}
|
||||
return std::string{name};
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -46,4 +47,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::string GetCurrentThreadName();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@ -144,9 +144,8 @@ int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmInstanceCodecType() {
|
||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id) {
|
||||
return static_cast<AjmCodecType>((instance_id >> 14) & 0x1F);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context_id, AjmCodecType codec_type,
|
||||
|
||||
@ -82,8 +82,6 @@ enum class AjmStatisticsFlags : u64 {
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmStatisticsFlags)
|
||||
|
||||
union AjmStatisticsJobFlags {
|
||||
AjmStatisticsJobFlags(AjmJobFlags job_flags) : raw(job_flags.raw) {}
|
||||
|
||||
u64 raw;
|
||||
struct {
|
||||
u64 version : 3;
|
||||
@ -133,7 +131,7 @@ struct AjmSidebandGaplessDecode {
|
||||
|
||||
struct AjmSidebandResampleParameters {
|
||||
float ratio;
|
||||
uint32_t flags;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
struct AjmDecAt9InitializeParameters {
|
||||
@ -217,7 +215,7 @@ int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* stream, u32 stream_size, int p
|
||||
AjmDecMp3ParseFrame* frame);
|
||||
int PS4_SYSV_ABI sceAjmFinalize();
|
||||
int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context);
|
||||
int PS4_SYSV_ABI sceAjmInstanceCodecType();
|
||||
AjmCodecType PS4_SYSV_ABI sceAjmInstanceCodecType(u32 instance_id);
|
||||
int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags,
|
||||
u32* instance);
|
||||
int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance);
|
||||
|
||||
194
src/core/libraries/ajm/ajm_aac.cpp
Normal file
194
src/core/libraries/ajm/ajm_aac.cpp
Normal file
@ -0,0 +1,194 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ajm.h"
|
||||
#include "ajm_aac.h"
|
||||
#include "ajm_result.h"
|
||||
|
||||
// using this internal header to manually configure the decoder in RAW mode
|
||||
#include "externals/aacdec/fdk-aac/libAACdec/src/aacdecoder.h"
|
||||
|
||||
#include <aacdecoder_lib.h>
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
AjmAacDecoder::AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels)
|
||||
: m_format(format), m_flags(flags), m_channels(channels), m_pcm_buffer(2048 * 8),
|
||||
m_skip_frames(True(flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2) {}
|
||||
|
||||
AjmAacDecoder::~AjmAacDecoder() {
|
||||
aacDecoder_Close(m_decoder);
|
||||
}
|
||||
|
||||
TRANSPORT_TYPE TransportTypeFromConfigType(ConfigType config_type) {
|
||||
switch (config_type) {
|
||||
case ConfigType::ADTS:
|
||||
return TT_MP4_ADTS;
|
||||
case ConfigType::RAW:
|
||||
return TT_MP4_RAW;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
static UINT g_freq[] = {
|
||||
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000,
|
||||
};
|
||||
|
||||
void AjmAacDecoder::Reset() {
|
||||
if (m_decoder) {
|
||||
aacDecoder_Close(m_decoder);
|
||||
}
|
||||
|
||||
m_decoder = aacDecoder_Open(TransportTypeFromConfigType(m_init_params.config_type), 1);
|
||||
if (m_init_params.config_type == ConfigType::RAW) {
|
||||
// Manually configure the decoder
|
||||
// Things may be incorrect due to limited documentation
|
||||
CSAudioSpecificConfig asc{};
|
||||
asc.m_aot = AOT_AAC_LC;
|
||||
asc.m_samplingFrequency = g_freq[m_init_params.sampling_freq_type];
|
||||
asc.m_samplingFrequencyIndex = m_init_params.sampling_freq_type;
|
||||
asc.m_samplesPerFrame = 1024;
|
||||
asc.m_epConfig = -1;
|
||||
switch (m_channels) {
|
||||
case 0:
|
||||
asc.m_channelConfiguration = 2;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
asc.m_channelConfiguration = m_channels;
|
||||
break;
|
||||
case 7:
|
||||
asc.m_channelConfiguration = 11;
|
||||
break;
|
||||
case 8:
|
||||
asc.m_channelConfiguration = 12; // 7, 12 or 14 ?
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
UCHAR changed = 1;
|
||||
CAacDecoder_Init(m_decoder, &asc, AC_CM_ALLOC_MEM, &changed);
|
||||
}
|
||||
m_skip_frames = True(m_flags & AjmAacCodecFlags::EnableNondelayOutput) ? 0 : 2;
|
||||
}
|
||||
|
||||
void AjmAacDecoder::Initialize(const void* buffer, u32 buffer_size) {
|
||||
ASSERT(buffer_size == 8);
|
||||
m_init_params = *reinterpret_cast<const InitializeParameters*>(buffer);
|
||||
Reset();
|
||||
}
|
||||
|
||||
void AjmAacDecoder::GetInfo(void* out_info) const {
|
||||
auto* codec_info = reinterpret_cast<AjmSidebandDecM4aacCodecInfo*>(out_info);
|
||||
*codec_info = {
|
||||
.heaac = True(m_flags & AjmAacCodecFlags::EnableSbrDecode),
|
||||
};
|
||||
}
|
||||
|
||||
AjmSidebandFormat AjmAacDecoder::GetFormat() const {
|
||||
const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
|
||||
return {
|
||||
.num_channels = static_cast<u32>(info->numChannels),
|
||||
.channel_mask = GetChannelMask(info->numChannels),
|
||||
.sampl_freq = static_cast<u32>(info->sampleRate),
|
||||
.sample_encoding = m_format, // AjmFormatEncoding
|
||||
.bitrate = static_cast<u32>(info->bitRate),
|
||||
};
|
||||
}
|
||||
|
||||
u32 AjmAacDecoder::GetMinimumInputSize() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 AjmAacDecoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
|
||||
if (info->aacSamplesPerFrame <= 0) {
|
||||
return 0;
|
||||
}
|
||||
const auto skip_samples = std::min<u32>(gapless.current.skip_samples, info->frameSize);
|
||||
const auto samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min<u32>(gapless.current.total_samples, info->frameSize - skip_samples)
|
||||
: info->frameSize - skip_samples;
|
||||
return samples * info->numChannels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
DecoderResult AjmAacDecoder::ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
DecoderResult result{};
|
||||
|
||||
// Discard the previous contents of the internal buffer and replace them with new ones
|
||||
aacDecoder_SetParam(m_decoder, AAC_TPDEC_CLEAR_BUFFER, 1);
|
||||
UCHAR* buffers[] = {input.data()};
|
||||
const UINT sizes[] = {static_cast<UINT>(input.size())};
|
||||
UINT valid = sizes[0];
|
||||
aacDecoder_Fill(m_decoder, buffers, sizes, &valid);
|
||||
auto ret = aacDecoder_DecodeFrame(m_decoder, reinterpret_cast<s16*>(m_pcm_buffer.data()),
|
||||
m_pcm_buffer.size() / 2, 0);
|
||||
|
||||
switch (ret) {
|
||||
case AAC_DEC_OK:
|
||||
break;
|
||||
case AAC_DEC_NOT_ENOUGH_BITS:
|
||||
result.result = ORBIS_AJM_RESULT_PARTIAL_INPUT;
|
||||
return result;
|
||||
default:
|
||||
LOG_ERROR(Lib_Ajm, "aacDecoder_DecodeFrame failed ret = {:#x}", static_cast<u32>(ret));
|
||||
result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
|
||||
result.internal_result = ret;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* const info = aacDecoder_GetStreamInfo(m_decoder);
|
||||
auto bytes_used = info->numTotalBytes;
|
||||
|
||||
result.frames_decoded += 1;
|
||||
input = input.subspan(bytes_used);
|
||||
|
||||
if (m_skip_frames > 0) {
|
||||
--m_skip_frames;
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 skip_samples = 0;
|
||||
if (gapless.current.skip_samples > 0) {
|
||||
skip_samples = std::min<u16>(info->frameSize, gapless.current.skip_samples);
|
||||
gapless.current.skip_samples -= skip_samples;
|
||||
}
|
||||
|
||||
const auto max_samples =
|
||||
gapless.init.total_samples != 0 ? gapless.current.total_samples : info->aacSamplesPerFrame;
|
||||
|
||||
size_t pcm_written = 0;
|
||||
switch (m_format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
pcm_written = WriteOutputSamples<s16>(output, skip_samples * info->numChannels,
|
||||
max_samples * info->numChannels);
|
||||
break;
|
||||
case AjmFormatEncoding::S32:
|
||||
UNREACHABLE_MSG("NOT IMPLEMENTED");
|
||||
break;
|
||||
case AjmFormatEncoding::Float:
|
||||
UNREACHABLE_MSG("NOT IMPLEMENTED");
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
result.samples_written = pcm_written / info->numChannels;
|
||||
gapless.current.skipped_samples += info->frameSize - result.samples_written;
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= result.samples_written;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
73
src/core/libraries/ajm/ajm_aac.h
Normal file
73
src/core/libraries/ajm/ajm_aac.h
Normal file
@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
struct AAC_DECODER_INSTANCE;
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
enum ConfigType : u32 {
|
||||
ADTS = 1,
|
||||
RAW = 2,
|
||||
};
|
||||
|
||||
enum AjmAacCodecFlags : u32 {
|
||||
EnableSbrDecode = 1 << 0,
|
||||
EnableNondelayOutput = 1 << 1,
|
||||
SurroundChannelInterleaveOrderExtlExtrLsRs = 1 << 2,
|
||||
SurroundChannelInterleaveOrderLsRsExtlExtr = 1 << 3,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmAacCodecFlags)
|
||||
|
||||
struct AjmSidebandDecM4aacCodecInfo {
|
||||
u32 heaac;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct AjmAacDecoder final : AjmCodec {
|
||||
explicit AjmAacDecoder(AjmFormatEncoding format, AjmAacCodecFlags flags, u32 channels);
|
||||
~AjmAacDecoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetMinimumInputSize() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
private:
|
||||
struct InitializeParameters {
|
||||
ConfigType config_type;
|
||||
u32 sampling_freq_type;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_pcm, u32 max_pcm) {
|
||||
std::span<T> pcm_data{reinterpret_cast<T*>(m_pcm_buffer.data()),
|
||||
m_pcm_buffer.size() / sizeof(T)};
|
||||
pcm_data = pcm_data.subspan(skipped_pcm);
|
||||
const auto pcm_size = std::min(u32(pcm_data.size()), max_pcm);
|
||||
return output.Write(pcm_data.subspan(0, pcm_size));
|
||||
}
|
||||
|
||||
const AjmFormatEncoding m_format;
|
||||
const AjmAacCodecFlags m_flags;
|
||||
const u32 m_channels;
|
||||
std::vector<u8> m_pcm_buffer;
|
||||
|
||||
u32 m_skip_frames = 0;
|
||||
InitializeParameters m_init_params = {};
|
||||
AAC_DECODER_INSTANCE* m_decoder = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ajm_result.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/ajm/ajm_at9.h"
|
||||
#include "error_codes.h"
|
||||
@ -53,7 +54,7 @@ struct RIFFHeader {
|
||||
};
|
||||
static_assert(sizeof(RIFFHeader) == 12);
|
||||
|
||||
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags)
|
||||
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32)
|
||||
: m_format(format), m_flags(flags), m_handle(Atrac9GetHandle()) {}
|
||||
|
||||
AjmAt9Decoder::~AjmAt9Decoder() {
|
||||
@ -85,8 +86,8 @@ void AjmAt9Decoder::GetInfo(void* out_info) const {
|
||||
auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
|
||||
info->super_frame_size = m_codec_info.superframeSize;
|
||||
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
|
||||
info->next_frame_size = m_superframe_bytes_remain;
|
||||
info->frame_samples = m_codec_info.frameSamples;
|
||||
info->next_frame_size = static_cast<Atrac9Handle*>(m_handle)->Config.FrameBytes;
|
||||
}
|
||||
|
||||
u8 g_at9_guid[] = {0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D,
|
||||
@ -133,18 +134,22 @@ void AjmAt9Decoder::ParseRIFFHeader(std::span<u8>& in_buf, AjmInstanceGapless& g
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
bool is_reset = false;
|
||||
u32 AjmAt9Decoder::GetMinimumInputSize() const {
|
||||
return m_superframe_bytes_remain;
|
||||
}
|
||||
|
||||
DecoderResult AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
DecoderResult result{};
|
||||
if (True(m_flags & AjmAt9CodecFlags::ParseRiffHeader) &&
|
||||
*reinterpret_cast<u32*>(in_buf.data()) == 'FFIR') {
|
||||
ParseRIFFHeader(in_buf, gapless);
|
||||
is_reset = true;
|
||||
result.is_reset = true;
|
||||
}
|
||||
|
||||
if (!m_is_initialized) {
|
||||
return {0, 0, is_reset};
|
||||
result.result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
|
||||
return result;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
@ -166,7 +171,14 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
|
||||
if (ret != At9Status::ERR_SUCCESS) {
|
||||
LOG_ERROR(Lib_Ajm, "Atrac9Decode failed ret = {:#x}", ret);
|
||||
result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
|
||||
result.internal_result = ret;
|
||||
return result;
|
||||
}
|
||||
|
||||
result.frames_decoded += 1;
|
||||
in_buf = in_buf.subspan(bytes_used);
|
||||
|
||||
m_superframe_bytes_remain -= bytes_used;
|
||||
@ -196,10 +208,10 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
const auto samples_written = pcm_written / m_codec_info.channels;
|
||||
gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written;
|
||||
result.samples_written = pcm_written / m_codec_info.channels;
|
||||
gapless.current.skipped_samples += m_codec_info.frameSamples - result.samples_written;
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= samples_written;
|
||||
gapless.current.total_samples -= result.samples_written;
|
||||
}
|
||||
|
||||
m_num_frames += 1;
|
||||
@ -209,9 +221,23 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
}
|
||||
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||
m_num_frames = 0;
|
||||
} else if (gapless.IsEnd()) {
|
||||
// Drain the remaining superframe
|
||||
std::vector<s16> buf(m_codec_info.frameSamples * m_codec_info.channels, 0);
|
||||
while ((m_num_frames % m_codec_info.framesInSuperframe) != 0) {
|
||||
ret = Atrac9Decode(m_handle, in_buf.data(), buf.data(), &bytes_used,
|
||||
True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
|
||||
in_buf = in_buf.subspan(bytes_used);
|
||||
m_superframe_bytes_remain -= bytes_used;
|
||||
result.frames_decoded += 1;
|
||||
m_num_frames += 1;
|
||||
}
|
||||
in_buf = in_buf.subspan(m_superframe_bytes_remain);
|
||||
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||
m_num_frames = 0;
|
||||
}
|
||||
|
||||
return {1, m_codec_info.frameSamples, is_reset};
|
||||
return result;
|
||||
}
|
||||
|
||||
AjmSidebandFormat AjmAt9Decoder::GetFormat() const {
|
||||
@ -232,7 +258,7 @@ u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min<u32>(gapless.current.total_samples, m_codec_info.frameSamples - skip_samples)
|
||||
: m_codec_info.frameSamples;
|
||||
: m_codec_info.frameSamples - skip_samples;
|
||||
return samples * m_codec_info.channels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
@ -13,8 +14,6 @@
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;
|
||||
|
||||
enum AjmAt9CodecFlags : u32 {
|
||||
ParseRiffHeader = 1 << 0,
|
||||
NonInterleavedOutput = 1 << 8,
|
||||
@ -29,16 +28,17 @@ struct AjmSidebandDecAt9CodecInfo {
|
||||
};
|
||||
|
||||
struct AjmAt9Decoder final : AjmCodec {
|
||||
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags);
|
||||
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags, u32 channels);
|
||||
~AjmAt9Decoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetMinimumInputSize() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
private:
|
||||
template <class T>
|
||||
|
||||
@ -165,7 +165,7 @@ AjmJob AjmStatisticsJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buf
|
||||
ASSERT(job_flags.has_value());
|
||||
job.flags = job_flags.value();
|
||||
|
||||
AjmStatisticsJobFlags flags(job.flags);
|
||||
AjmStatisticsJobFlags flags{.raw = job.flags.raw};
|
||||
if (input_control_buffer.has_value()) {
|
||||
AjmBatchBuffer input_batch(input_control_buffer.value());
|
||||
if (True(flags.statistics_flags & AjmStatisticsFlags::Engine)) {
|
||||
|
||||
@ -52,6 +52,7 @@ struct AjmBatch {
|
||||
u32 id{};
|
||||
std::atomic_bool waiting{};
|
||||
std::atomic_bool canceled{};
|
||||
std::atomic_bool processed{};
|
||||
std::binary_semaphore finished{0};
|
||||
boost::container::small_vector<AjmJob, 16> jobs;
|
||||
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
static constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
|
||||
constexpr u32 ORBIS_AJM_WAIT_INFINITE = -1;
|
||||
constexpr int INSTANCE_ID_MASK = 0x3FFF;
|
||||
|
||||
AjmContext::AjmContext() {
|
||||
worker_thread = std::jthread([this](std::stop_token stop) { this->WorkerThread(stop); });
|
||||
@ -39,7 +40,12 @@ s32 AjmContext::BatchCancel(const u32 batch_id) {
|
||||
batch = *p_batch;
|
||||
}
|
||||
|
||||
batch->canceled = true;
|
||||
if (batch->processed) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
bool expected = false;
|
||||
batch->canceled.compare_exchange_strong(expected, true);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -58,7 +64,9 @@ void AjmContext::WorkerThread(std::stop_token stop) {
|
||||
Common::SetCurrentThreadName("shadPS4:AjmWorker");
|
||||
while (!stop.stop_requested()) {
|
||||
auto batch = batch_queue.PopWait(stop);
|
||||
if (batch != nullptr) {
|
||||
if (batch != nullptr && !batch->canceled) {
|
||||
bool expected = false;
|
||||
batch->processed.compare_exchange_strong(expected, true);
|
||||
ProcessBatch(batch->id, batch->jobs);
|
||||
batch->finished.release();
|
||||
}
|
||||
@ -77,7 +85,7 @@ void AjmContext::ProcessBatch(u32 id, std::span<AjmJob> jobs) {
|
||||
std::shared_ptr<AjmInstance> instance;
|
||||
{
|
||||
std::shared_lock lock(instances_mutex);
|
||||
auto* p_instance = instances.Get(job.instance_id);
|
||||
auto* p_instance = instances.Get(job.instance_id & INSTANCE_ID_MASK);
|
||||
ASSERT_MSG(p_instance != nullptr, "Attempting to execute job on null instance");
|
||||
instance = *p_instance;
|
||||
}
|
||||
@ -169,15 +177,15 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags,
|
||||
if (!opt_index.has_value()) {
|
||||
return ORBIS_AJM_ERROR_OUT_OF_RESOURCES;
|
||||
}
|
||||
*out_instance = opt_index.value();
|
||||
*out_instance = opt_index.value() | (static_cast<u32>(codec_type) << 14);
|
||||
|
||||
LOG_INFO(Lib_Ajm, "instance = {}", *out_instance);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AjmContext::InstanceDestroy(u32 instance) {
|
||||
s32 AjmContext::InstanceDestroy(u32 instance_id) {
|
||||
std::unique_lock lock(instances_mutex);
|
||||
if (!instances.Destroy(instance)) {
|
||||
if (!instances.Destroy(instance_id & INSTANCE_ID_MASK)) {
|
||||
return ORBIS_AJM_ERROR_INVALID_INSTANCE;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
|
||||
@ -1,27 +1,16 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/libraries/ajm/ajm_at9.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
#include "core/libraries/ajm/ajm_mp3.h"
|
||||
#include "ajm_aac.h"
|
||||
#include "ajm_at9.h"
|
||||
#include "ajm_instance.h"
|
||||
#include "ajm_mp3.h"
|
||||
#include "ajm_result.h"
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
|
||||
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
|
||||
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
|
||||
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
|
||||
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
|
||||
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
|
||||
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
|
||||
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||
|
||||
u8 GetPCMSize(AjmFormatEncoding format) {
|
||||
switch (format) {
|
||||
case AjmFormatEncoding::S16:
|
||||
@ -38,13 +27,18 @@ u8 GetPCMSize(AjmFormatEncoding format) {
|
||||
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
|
||||
switch (codec_type) {
|
||||
case AjmCodecType::At9Dec: {
|
||||
m_codec = std::make_unique<AjmAt9Decoder>(AjmFormatEncoding(flags.format),
|
||||
AjmAt9CodecFlags(flags.codec));
|
||||
m_codec = std::make_unique<AjmAt9Decoder>(
|
||||
AjmFormatEncoding(flags.format), AjmAt9CodecFlags(flags.codec), u32(flags.channels));
|
||||
break;
|
||||
}
|
||||
case AjmCodecType::Mp3Dec: {
|
||||
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format),
|
||||
AjmMp3CodecFlags(flags.codec));
|
||||
m_codec = std::make_unique<AjmMp3Decoder>(
|
||||
AjmFormatEncoding(flags.format), AjmMp3CodecFlags(flags.codec), u32(flags.channels));
|
||||
break;
|
||||
}
|
||||
case AjmCodecType::M4aacDec: {
|
||||
m_codec = std::make_unique<AjmAacDecoder>(
|
||||
AjmFormatEncoding(flags.format), AjmAacCodecFlags(flags.codec), u32(flags.channels));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -60,6 +54,7 @@ void AjmInstance::Reset() {
|
||||
|
||||
void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
const auto control_flags = job.flags.control_flags;
|
||||
job.output.p_result->result = 0;
|
||||
if (True(control_flags & AjmJobControlFlags::Reset)) {
|
||||
LOG_TRACE(Lib_Ajm, "Resetting instance {}", job.instance_id);
|
||||
Reset();
|
||||
@ -91,8 +86,7 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
m_gapless.current.total_samples -= sample_difference;
|
||||
} else {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
return;
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,61 +100,59 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
m_gapless.current.skip_samples -= sample_difference;
|
||||
} else {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
return;
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
|
||||
std::span<u8> in_buf(job.input.buffer);
|
||||
SparseOutputBuffer out_buf(job.output.buffers);
|
||||
std::span<u8> in_buf(job.input.buffer);
|
||||
SparseOutputBuffer out_buf(job.output.buffers);
|
||||
auto in_size = in_buf.size();
|
||||
auto out_size = out_buf.Size();
|
||||
u32 frames_decoded = 0;
|
||||
|
||||
u32 frames_decoded = 0;
|
||||
auto in_size = in_buf.size();
|
||||
auto out_size = out_buf.Size();
|
||||
while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) {
|
||||
if (!HasEnoughSpace(out_buf)) {
|
||||
if (job.output.p_mframe == nullptr || frames_decoded == 0) {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})",
|
||||
out_buf.Size(), m_codec->GetNextFrameSize(m_gapless));
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto [nframes, nsamples, reset] =
|
||||
m_codec->ProcessData(in_buf, out_buf, m_gapless);
|
||||
if (reset) {
|
||||
if (!job.input.buffer.empty()) {
|
||||
for (;;) {
|
||||
if (m_flags.gapless_loop && m_gapless.IsEnd()) {
|
||||
m_gapless.Reset();
|
||||
m_total_samples = 0;
|
||||
}
|
||||
if (!nframes) {
|
||||
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_INITIALIZED");
|
||||
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
|
||||
if (!HasEnoughSpace(out_buf)) {
|
||||
LOG_TRACE(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})", out_buf.Size(),
|
||||
m_codec->GetNextFrameSize(m_gapless));
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
}
|
||||
if (in_buf.size() < m_codec->GetMinimumInputSize()) {
|
||||
job.output.p_result->result |= ORBIS_AJM_RESULT_PARTIAL_INPUT;
|
||||
}
|
||||
if (job.output.p_result->result != 0) {
|
||||
break;
|
||||
}
|
||||
const auto result = m_codec->ProcessData(in_buf, out_buf, m_gapless);
|
||||
if (result.is_reset) {
|
||||
m_total_samples = 0;
|
||||
} else {
|
||||
m_total_samples += result.samples_written;
|
||||
}
|
||||
frames_decoded += result.frames_decoded;
|
||||
if (result.result != 0) {
|
||||
job.output.p_result->result |= result.result;
|
||||
job.output.p_result->internal_result = result.internal_result;
|
||||
break;
|
||||
}
|
||||
frames_decoded += nframes;
|
||||
m_total_samples += nsamples;
|
||||
|
||||
if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto total_decoded_samples = m_total_samples;
|
||||
if (m_flags.gapless_loop && m_gapless.IsEnd()) {
|
||||
in_buf = in_buf.subspan(in_buf.size());
|
||||
m_gapless.Reset();
|
||||
m_codec->Reset();
|
||||
}
|
||||
if (job.output.p_mframe) {
|
||||
job.output.p_mframe->num_frames = frames_decoded;
|
||||
}
|
||||
if (job.output.p_stream) {
|
||||
job.output.p_stream->input_consumed = in_size - in_buf.size();
|
||||
job.output.p_stream->output_written = out_size - out_buf.Size();
|
||||
job.output.p_stream->total_decoded_samples = total_decoded_samples;
|
||||
}
|
||||
if (job.output.p_mframe) {
|
||||
job.output.p_mframe->num_frames = frames_decoded;
|
||||
}
|
||||
if (job.output.p_stream) {
|
||||
job.output.p_stream->input_consumed = in_size - in_buf.size();
|
||||
job.output.p_stream->output_written = out_size - out_buf.Size();
|
||||
job.output.p_stream->total_decoded_samples = m_total_samples;
|
||||
}
|
||||
|
||||
if (job.output.p_format != nullptr) {
|
||||
@ -175,6 +167,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
|
||||
}
|
||||
|
||||
bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const {
|
||||
if (m_gapless.IsEnd()) {
|
||||
return true;
|
||||
}
|
||||
return output.Size() >= m_codec->GetNextFrameSize(m_gapless);
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +73,14 @@ struct AjmInstanceGapless {
|
||||
}
|
||||
};
|
||||
|
||||
struct DecoderResult {
|
||||
s32 result = 0;
|
||||
s32 internal_result = 0;
|
||||
u32 frames_decoded = 0;
|
||||
u32 samples_written = 0;
|
||||
bool is_reset = false;
|
||||
};
|
||||
|
||||
class AjmCodec {
|
||||
public:
|
||||
virtual ~AjmCodec() = default;
|
||||
@ -81,9 +89,10 @@ public:
|
||||
virtual void Reset() = 0;
|
||||
virtual void GetInfo(void* out_info) const = 0;
|
||||
virtual AjmSidebandFormat GetFormat() const = 0;
|
||||
virtual u32 GetMinimumInputSize() const = 0;
|
||||
virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0;
|
||||
virtual std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) = 0;
|
||||
virtual DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) = 0;
|
||||
};
|
||||
|
||||
class AjmInstance {
|
||||
@ -94,7 +103,6 @@ public:
|
||||
|
||||
private:
|
||||
bool HasEnoughSpace(const SparseOutputBuffer& output) const;
|
||||
std::optional<u32> GetNumRemainingSamples() const;
|
||||
void Reset();
|
||||
|
||||
AjmInstanceFlags m_flags{};
|
||||
|
||||
@ -8,7 +8,7 @@ namespace Libraries::Ajm {
|
||||
|
||||
void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
|
||||
if (job.output.p_engine) {
|
||||
job.output.p_engine->usage_batch = 0.01;
|
||||
job.output.p_engine->usage_batch = 0.05;
|
||||
const auto ic = job.input.statistics_engine_parameters->interval_count;
|
||||
for (u32 idx = 0; idx < ic; ++idx) {
|
||||
job.output.p_engine->usage_interval[idx] = 0.01;
|
||||
@ -25,10 +25,12 @@ void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
|
||||
job.output.p_memory->batch_size = 0x4200;
|
||||
job.output.p_memory->input_size = 0x2000;
|
||||
job.output.p_memory->output_size = 0x2000;
|
||||
job.output.p_memory->small_size = 0x200;
|
||||
job.output.p_memory->small_size = 0x400;
|
||||
}
|
||||
}
|
||||
|
||||
void AjmInstanceStatistics::Reset() {}
|
||||
|
||||
AjmInstanceStatistics& AjmInstanceStatistics::Getinstance() {
|
||||
static AjmInstanceStatistics instance;
|
||||
return instance;
|
||||
|
||||
@ -10,6 +10,7 @@ namespace Libraries::Ajm {
|
||||
class AjmInstanceStatistics {
|
||||
public:
|
||||
void ExecuteJob(AjmJob& job);
|
||||
void Reset();
|
||||
|
||||
static AjmInstanceStatistics& Getinstance();
|
||||
};
|
||||
|
||||
@ -105,7 +105,7 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
|
||||
return new_frame;
|
||||
}
|
||||
|
||||
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags)
|
||||
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32)
|
||||
: m_format(format), m_flags(flags), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
|
||||
m_codec_context(avcodec_alloc_context3(m_codec)), m_parser(av_parser_init(m_codec->id)) {
|
||||
int ret = avcodec_open2(m_codec_context, m_codec, nullptr);
|
||||
@ -122,6 +122,7 @@ void AjmMp3Decoder::Reset() {
|
||||
avcodec_flush_buffers(m_codec_context);
|
||||
m_header.reset();
|
||||
m_frame_samples = 0;
|
||||
m_frame_size = 0;
|
||||
}
|
||||
|
||||
void AjmMp3Decoder::GetInfo(void* out_info) const {
|
||||
@ -138,16 +139,28 @@ void AjmMp3Decoder::GetInfo(void* out_info) const {
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
u32 AjmMp3Decoder::GetMinimumInputSize() const {
|
||||
// 4 bytes is for mp3 header that contains frame_size
|
||||
return std::max<u32>(m_frame_size, 4);
|
||||
}
|
||||
|
||||
DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) {
|
||||
DecoderResult result{};
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
|
||||
if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
|
||||
m_header = std::byteswap(*reinterpret_cast<u32*>(in_buf.data()));
|
||||
AjmDecMp3ParseFrame info{};
|
||||
ParseMp3Header(in_buf.data(), in_buf.size(), false, &info);
|
||||
ParseMp3Header(in_buf.data(), in_buf.size(), true, &info);
|
||||
m_frame_samples = info.samples_per_channel;
|
||||
m_frame_size = info.frame_size;
|
||||
gapless.init = {
|
||||
.total_samples = info.total_samples,
|
||||
.skip_samples = static_cast<u16>(info.encoder_delay),
|
||||
.skipped_samples = 0,
|
||||
};
|
||||
gapless.current = gapless.init;
|
||||
}
|
||||
|
||||
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
|
||||
@ -155,9 +168,6 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
|
||||
in_buf = in_buf.subspan(ret);
|
||||
|
||||
u32 frames_decoded = 0;
|
||||
u32 samples_decoded = 0;
|
||||
|
||||
if (pkt->size) {
|
||||
// Send the packet with the compressed data to the decoder
|
||||
pkt->pts = m_parser->pts;
|
||||
@ -177,9 +187,8 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
UNREACHABLE_MSG("Error during decoding");
|
||||
}
|
||||
frame = ConvertAudioFrame(frame);
|
||||
samples_decoded += u32(frame->nb_samples);
|
||||
|
||||
frames_decoded += 1;
|
||||
result.frames_decoded += 1;
|
||||
u32 skip_samples = 0;
|
||||
if (gapless.current.skip_samples > 0) {
|
||||
skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples);
|
||||
@ -211,6 +220,7 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
if (gapless.init.total_samples != 0) {
|
||||
gapless.current.total_samples -= samples;
|
||||
}
|
||||
result.samples_written += samples;
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
@ -218,16 +228,16 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
|
||||
|
||||
av_packet_free(&pkt);
|
||||
|
||||
return {frames_decoded, samples_decoded, false};
|
||||
return result;
|
||||
}
|
||||
|
||||
u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
|
||||
const auto max_samples = gapless.init.total_samples != 0
|
||||
? std::min(gapless.current.total_samples, m_frame_samples)
|
||||
: m_frame_samples;
|
||||
const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
|
||||
return (max_samples - skip_samples) * m_codec_context->ch_layout.nb_channels *
|
||||
GetPCMSize(m_format);
|
||||
const auto skip_samples = std::min<u32>(gapless.current.skip_samples, m_frame_samples);
|
||||
const auto samples =
|
||||
gapless.init.total_samples != 0
|
||||
? std::min<u32>(gapless.current.total_samples, m_frame_samples - skip_samples)
|
||||
: m_frame_samples - skip_samples;
|
||||
return samples * m_codec_context->ch_layout.nb_channels * GetPCMSize(m_format);
|
||||
}
|
||||
|
||||
class BitReader {
|
||||
@ -264,7 +274,7 @@ private:
|
||||
|
||||
int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl,
|
||||
AjmDecMp3ParseFrame* frame) {
|
||||
LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
|
||||
LOG_TRACE(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
|
||||
|
||||
if (p_begin == nullptr || stream_size < 4 || frame == nullptr) {
|
||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||
@ -301,7 +311,8 @@ int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_
|
||||
|
||||
BitReader reader(p_current);
|
||||
if (header->protection_type == 0) {
|
||||
reader.Skip(16); // crc = reader.Read<u16>(16);
|
||||
// crc = reader.Read<u16>(16);
|
||||
reader.Skip(16);
|
||||
}
|
||||
|
||||
if (header->version == Mp3AudioVersion::V1) {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
@ -63,16 +64,17 @@ struct AjmSidebandDecMp3CodecInfo {
|
||||
|
||||
class AjmMp3Decoder : public AjmCodec {
|
||||
public:
|
||||
explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags);
|
||||
explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags, u32 channels);
|
||||
~AjmMp3Decoder() override;
|
||||
|
||||
void Reset() override;
|
||||
void Initialize(const void* buffer, u32 buffer_size) override {}
|
||||
void GetInfo(void* out_info) const override;
|
||||
AjmSidebandFormat GetFormat() const override;
|
||||
u32 GetMinimumInputSize() const override;
|
||||
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
|
||||
std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
|
||||
AjmInstanceGapless& gapless) override;
|
||||
|
||||
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||
AjmDecMp3ParseFrame* frame);
|
||||
@ -97,6 +99,7 @@ private:
|
||||
SwrContext* m_swr_context = nullptr;
|
||||
std::optional<u32> m_header;
|
||||
u32 m_frame_samples = 0;
|
||||
u32 m_frame_size = 0;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
|
||||
17
src/core/libraries/ajm/ajm_result.h
Normal file
17
src/core/libraries/ajm/ajm_result.h
Normal file
@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
|
||||
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
|
||||
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
|
||||
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
|
||||
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
|
||||
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
|
||||
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
|
||||
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
|
||||
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
|
||||
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||
@ -48,7 +48,7 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
|
||||
title.resize(title_len * 4 + 1);
|
||||
title[title_len * 4] = '\0';
|
||||
|
||||
if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4)) {
|
||||
if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4 + 1)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding");
|
||||
}
|
||||
}
|
||||
@ -59,14 +59,14 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
|
||||
placeholder[placeholder_len * 4] = '\0';
|
||||
|
||||
if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, &placeholder[0],
|
||||
placeholder_len * 4)) {
|
||||
placeholder_len * 4 + 1)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding");
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t text_len = std::char_traits<char16_t>::length(text_buffer);
|
||||
if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(),
|
||||
ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) {
|
||||
ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4 + 1)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding");
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ bool ImeDialogState::CopyTextToOrbisBuffer() {
|
||||
}
|
||||
|
||||
return ConvertUTF8ToOrbis(current_text.begin(), current_text.capacity(), text_buffer,
|
||||
max_text_length);
|
||||
static_cast<std::size_t>(max_text_length) + 1);
|
||||
}
|
||||
|
||||
bool ImeDialogState::CallTextFilter() {
|
||||
@ -380,10 +380,12 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
|
||||
.timestamp = {0},
|
||||
};
|
||||
|
||||
if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) {
|
||||
char16_t tmp_char[2] = {0};
|
||||
if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, tmp_char, 2)) {
|
||||
LOG_ERROR(Lib_ImeDialog, "InputTextCallback: ConvertUTF8ToOrbis failed");
|
||||
return 0;
|
||||
}
|
||||
src_keycode.character = tmp_char[0];
|
||||
LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: converted to Orbis char={:#X}",
|
||||
static_cast<uint16_t>(src_keycode.character));
|
||||
src_keycode.keycode = src_keycode.character; // TODO set this to the correct value
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -110,6 +110,15 @@ enum OrbisNetSocketSoOption : u32 {
|
||||
ORBIS_NET_SO_PRIORITY = 0x1203
|
||||
};
|
||||
|
||||
enum OrbisNetFlags : u32 {
|
||||
ORBIS_NET_MSG_PEEK = 0x00000002,
|
||||
ORBIS_NET_MSG_WAITALL = 0x00000040,
|
||||
ORBIS_NET_MSG_DONTWAIT = 0x00000080,
|
||||
ORBIS_NET_MSG_USECRYPTO = 0x00100000,
|
||||
ORBIS_NET_MSG_USESIGNATURE = 0x00200000,
|
||||
ORBIS_NET_MSG_PEEKLEN = (0x00400000 | ORBIS_NET_MSG_PEEK)
|
||||
};
|
||||
|
||||
constexpr std::string_view NameOf(OrbisNetSocketSoOption o) {
|
||||
switch (o) {
|
||||
case ORBIS_NET_SO_REUSEADDR:
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <vector>
|
||||
@ -184,28 +184,103 @@ int PosixSocket::Listen(int backlog) {
|
||||
return ConvertReturnErrorCode(::listen(sock, backlog));
|
||||
}
|
||||
|
||||
static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) {
|
||||
int posix_flags = 0;
|
||||
|
||||
if (sce_flags & ORBIS_NET_MSG_PEEK)
|
||||
posix_flags |= MSG_PEEK;
|
||||
#ifndef _WIN32
|
||||
if (sce_flags & ORBIS_NET_MSG_DONTWAIT)
|
||||
posix_flags |= MSG_DONTWAIT;
|
||||
#endif
|
||||
// MSG_WAITALL is only valid for stream sockets
|
||||
if ((sce_flags & ORBIS_NET_MSG_WAITALL) &&
|
||||
((sock_type == ORBIS_NET_SOCK_STREAM) || (sock_type == ORBIS_NET_SOCK_STREAM_P2P)))
|
||||
posix_flags |= MSG_WAITALL;
|
||||
|
||||
return posix_flags;
|
||||
}
|
||||
|
||||
// On Windows, MSG_DONTWAIT is not handled natively by recv/send.
|
||||
// This function uses select() with zero timeout to simulate non-blocking behavior.
|
||||
static int socket_is_ready(int sock, bool is_read = true) {
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
timeval timeout{0, 0};
|
||||
int res =
|
||||
select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout);
|
||||
if (res == 0)
|
||||
return ORBIS_NET_ERROR_EWOULDBLOCK;
|
||||
else if (res < 0)
|
||||
return ConvertReturnErrorCode(res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD bytesSent = 0;
|
||||
LPFN_WSASENDMSG wsasendmsg = nullptr;
|
||||
GUID guid = WSAID_WSASENDMSG;
|
||||
DWORD bytes = 0;
|
||||
int totalSent = 0;
|
||||
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
|
||||
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
|
||||
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsasendmsg,
|
||||
sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
// stream socket with multiple buffers
|
||||
bool use_wsamsg =
|
||||
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
|
||||
msg->msg_iovlen > 1;
|
||||
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
char* buf = (char*)msg->msg_iov[i].iov_base;
|
||||
size_t remaining = msg->msg_iov[i].iov_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
if (dontWait) {
|
||||
int ready = socket_is_ready(sock, false);
|
||||
if (ready <= 0)
|
||||
return ready;
|
||||
}
|
||||
|
||||
int sent = 0;
|
||||
if (use_wsamsg) {
|
||||
// only call WSASendMsg if we have multiple buffers
|
||||
LPFN_WSASENDMSG wsasendmsg = nullptr;
|
||||
GUID guid = WSAID_WSASENDMSG;
|
||||
DWORD bytes = 0;
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
|
||||
&wsasendmsg, sizeof(wsasendmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
// fallback to send()
|
||||
sent = ::send(sock, buf, remaining, 0);
|
||||
} else {
|
||||
DWORD bytesSent = 0;
|
||||
int res = wsasendmsg(
|
||||
sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), 0,
|
||||
&bytesSent, nullptr, nullptr);
|
||||
if (res == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
sent = bytesSent;
|
||||
}
|
||||
} else {
|
||||
sent = ::send(sock, buf, remaining, 0);
|
||||
if (sent == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
}
|
||||
|
||||
totalSent += sent;
|
||||
remaining -= sent;
|
||||
buf += sent;
|
||||
|
||||
if (!waitAll)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int res = wsasendmsg(sock, reinterpret_cast<LPWSAMSG>(const_cast<OrbisNetMsghdr*>(msg)), flags,
|
||||
&bytesSent, nullptr, nullptr);
|
||||
return totalSent;
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesSent);
|
||||
#else
|
||||
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), flags);
|
||||
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
int res = sendmsg(sock, reinterpret_cast<const msghdr*>(msg), native_flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
@ -213,37 +288,92 @@ int PosixSocket::SendMessage(const OrbisNetMsghdr* msg, int flags) {
|
||||
int PosixSocket::SendPacket(const void* msg, u32 len, int flags, const OrbisNetSockaddr* to,
|
||||
u32 tolen) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (to != nullptr) {
|
||||
sockaddr addr;
|
||||
convertOrbisNetSockaddrToPosix(to, &addr);
|
||||
return ConvertReturnErrorCode(
|
||||
sendto(sock, (const char*)msg, len, flags, &addr, sizeof(sockaddr_in)));
|
||||
} else {
|
||||
return ConvertReturnErrorCode(send(sock, (const char*)msg, len, flags));
|
||||
int res = 0;
|
||||
#ifdef _WIN32
|
||||
if (flags & ORBIS_NET_MSG_DONTWAIT) {
|
||||
res = socket_is_ready(sock, false);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
if (to == nullptr) {
|
||||
res = send(sock, (const char*)msg, len, posix_flags);
|
||||
} else {
|
||||
sockaddr addr{};
|
||||
convertOrbisNetSockaddrToPosix(to, &addr);
|
||||
res = sendto(sock, (const char*)msg, len, posix_flags, &addr, tolen);
|
||||
}
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
|
||||
#ifdef _WIN32
|
||||
LPFN_WSARECVMSG wsarecvmsg = nullptr;
|
||||
GUID guid = WSAID_WSARECVMSG;
|
||||
DWORD bytes = 0;
|
||||
int totalReceived = 0;
|
||||
bool waitAll = (flags & ORBIS_NET_MSG_WAITALL) != 0;
|
||||
bool dontWait = (flags & ORBIS_NET_MSG_DONTWAIT) != 0;
|
||||
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &wsarecvmsg,
|
||||
sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
// stream socket with multiple buffers
|
||||
bool use_wsarecvmsg =
|
||||
(socket_type == ORBIS_NET_SOCK_STREAM || socket_type == ORBIS_NET_SOCK_STREAM_P2P) &&
|
||||
msg->msg_iovlen > 1;
|
||||
|
||||
for (int i = 0; i < msg->msg_iovlen; ++i) {
|
||||
char* buf = (char*)msg->msg_iov[i].iov_base;
|
||||
size_t remaining = msg->msg_iov[i].iov_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
// emulate DONTWAIT
|
||||
if (dontWait) {
|
||||
int ready = socket_is_ready(sock, true);
|
||||
if (ready <= 0)
|
||||
return ready; // returns ORBIS_NET_ERROR_EWOULDBLOCK or error
|
||||
}
|
||||
|
||||
int received = 0;
|
||||
if (use_wsarecvmsg) {
|
||||
// only call WSARecvMsg if multiple buffers + stream
|
||||
LPFN_WSARECVMSG wsarecvmsg = nullptr;
|
||||
GUID guid = WSAID_WSARECVMSG;
|
||||
DWORD bytes = 0;
|
||||
if (WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
|
||||
&wsarecvmsg, sizeof(wsarecvmsg), &bytes, nullptr, nullptr) != 0) {
|
||||
// fallback to recv()
|
||||
received = ::recv(sock, buf, remaining, 0);
|
||||
if (received == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
} else {
|
||||
DWORD bytesReceived = 0;
|
||||
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived,
|
||||
nullptr, nullptr);
|
||||
if (res == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
received = bytesReceived;
|
||||
}
|
||||
} else {
|
||||
// fallback to recv() for UDP or single-buffer
|
||||
received = ::recv(sock, buf, remaining, 0);
|
||||
if (received == SOCKET_ERROR)
|
||||
return ConvertReturnErrorCode(WSAGetLastError());
|
||||
}
|
||||
|
||||
totalReceived += received;
|
||||
remaining -= received;
|
||||
buf += received;
|
||||
|
||||
// stop after first receive if WAITALL is not set
|
||||
if (!waitAll)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD bytesReceived = 0;
|
||||
int res = wsarecvmsg(sock, reinterpret_cast<LPWSAMSG>(msg), &bytesReceived, nullptr, nullptr);
|
||||
return totalReceived;
|
||||
|
||||
if (res == SOCKET_ERROR) {
|
||||
return ConvertReturnErrorCode(-1);
|
||||
}
|
||||
return static_cast<int>(bytesReceived);
|
||||
#else
|
||||
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), flags);
|
||||
int native_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
int res = recvmsg(sock, reinterpret_cast<msghdr*>(msg), native_flags);
|
||||
return ConvertReturnErrorCode(res);
|
||||
#endif
|
||||
}
|
||||
@ -251,15 +381,27 @@ int PosixSocket::ReceiveMessage(OrbisNetMsghdr* msg, int flags) {
|
||||
int PosixSocket::ReceivePacket(void* buf, u32 len, int flags, OrbisNetSockaddr* from,
|
||||
u32* fromlen) {
|
||||
std::scoped_lock lock{receive_mutex};
|
||||
if (from != nullptr) {
|
||||
sockaddr addr;
|
||||
int res = recvfrom(sock, (char*)buf, len, flags, &addr, (socklen_t*)fromlen);
|
||||
convertPosixSockaddrToOrbis(&addr, from);
|
||||
*fromlen = sizeof(OrbisNetSockaddrIn);
|
||||
return ConvertReturnErrorCode(res);
|
||||
} else {
|
||||
return ConvertReturnErrorCode(recv(sock, (char*)buf, len, flags));
|
||||
int res = 0;
|
||||
#ifdef _WIN32
|
||||
if (flags & ORBIS_NET_MSG_DONTWAIT) {
|
||||
res = socket_is_ready(sock);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
const auto posix_flags = convertOrbisFlagsToPosix(socket_type, flags);
|
||||
if (from == nullptr) {
|
||||
res = recv(sock, (char*)buf, len, posix_flags);
|
||||
} else {
|
||||
sockaddr addr{};
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
res = recvfrom(sock, (char*)buf, len, posix_flags, &addr,
|
||||
(fromlen && *fromlen <= sizeof(addr) ? (socklen_t*)fromlen : &addrlen));
|
||||
if (res > 0)
|
||||
convertPosixSockaddrToOrbis(&addr, from);
|
||||
}
|
||||
|
||||
return ConvertReturnErrorCode(res);
|
||||
}
|
||||
|
||||
SocketPtr PosixSocket::Accept(OrbisNetSockaddr* addr, u32* addrlen) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -62,7 +62,7 @@ struct OrbisNetLinger {
|
||||
s32 l_linger;
|
||||
};
|
||||
struct Socket {
|
||||
explicit Socket(int domain, int type, int protocol) {}
|
||||
explicit Socket(int domain, int type, int protocol) : socket_type(type) {}
|
||||
virtual ~Socket() = default;
|
||||
virtual bool IsValid() const = 0;
|
||||
virtual int Close() = 0;
|
||||
@ -84,6 +84,7 @@ struct Socket {
|
||||
virtual std::optional<net_socket> Native() = 0;
|
||||
std::mutex m_mutex;
|
||||
std::mutex receive_mutex;
|
||||
int socket_type;
|
||||
};
|
||||
|
||||
struct PosixSocket : public Socket {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/arch.h"
|
||||
@ -42,14 +42,6 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
|
||||
|
||||
#else
|
||||
|
||||
static std::string GetThreadName() {
|
||||
char name[256];
|
||||
if (pthread_getname_np(pthread_self(), name, sizeof(name)) != 0) {
|
||||
return "<unknown name>";
|
||||
}
|
||||
return std::string{name};
|
||||
}
|
||||
|
||||
static std::string DisassembleInstruction(void* code_address) {
|
||||
char buffer[256] = "<unable to decode>";
|
||||
|
||||
@ -80,18 +72,16 @@ static void SignalHandler(int sig, siginfo_t* info, void* raw_context) {
|
||||
case SIGBUS: {
|
||||
const bool is_write = Common::IsWriteError(raw_context);
|
||||
if (!signals->DispatchAccessViolation(raw_context, info->si_addr)) {
|
||||
UNREACHABLE_MSG(
|
||||
"Unhandled access violation in thread '{}' at code address {}: {} address {}",
|
||||
GetThreadName(), fmt::ptr(code_address), is_write ? "Write to" : "Read from",
|
||||
fmt::ptr(info->si_addr));
|
||||
UNREACHABLE_MSG("Unhandled access violation at code address {}: {} address {}",
|
||||
fmt::ptr(code_address), is_write ? "Write to" : "Read from",
|
||||
fmt::ptr(info->si_addr));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SIGILL:
|
||||
if (!signals->DispatchIllegalInstruction(raw_context)) {
|
||||
UNREACHABLE_MSG("Unhandled illegal instruction in thread '{}' at code address {}: {}",
|
||||
GetThreadName(), fmt::ptr(code_address),
|
||||
DisassembleInstruction(code_address));
|
||||
UNREACHABLE_MSG("Unhandled illegal instruction at code address {}: {}",
|
||||
fmt::ptr(code_address), DisassembleInstruction(code_address));
|
||||
}
|
||||
break;
|
||||
case SIGUSR1: { // Sleep thread until signal is received
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#include "common/debug.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/ipc/ipc.h"
|
||||
#ifdef ENABLE_DISCORD_RPC
|
||||
#include "common/discord_rpc_handler.h"
|
||||
@ -75,6 +76,7 @@ Emulator::~Emulator() {}
|
||||
|
||||
void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
std::optional<std::filesystem::path> p_game_folder) {
|
||||
Common::SetCurrentThreadName("Main Thread");
|
||||
if (waitForDebuggerBeforeRun) {
|
||||
Debugger::WaitForDebuggerAttach();
|
||||
}
|
||||
|
||||
@ -188,7 +188,9 @@ int main(int argc, char* argv[]) {
|
||||
if (argc == 1) {
|
||||
if (!SDL_ShowSimpleMessageBox(
|
||||
SDL_MESSAGEBOX_INFORMATION, "shadPS4",
|
||||
"This is a CLI application. Please use the QTLauncher for a GUI.", nullptr))
|
||||
"This is a CLI application. Please use the QTLauncher for a GUI: "
|
||||
"https://github.com/shadps4-emu/shadps4-qtlauncher/releases",
|
||||
nullptr))
|
||||
std::cerr << "Could not display SDL message box! Error: " << SDL_GetError() << "\n";
|
||||
int dummy = 0; // one does not simply pass 0 directly
|
||||
arg_map.at("-h")(dummy);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user