From 569c2ec56c7afe3246560432f0f28e47d903c358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Thu, 1 Oct 2026 19:37:38 +0200 Subject: [PATCH] feat(libSceAudiodec): decode MP3, M4AAC and ATRAC9 All six exports are implemented: MP3 and AAC (ADTS or raw blocks) are decoded with the bundled FFmpeg and ATRAC9 with LibAtrac9, one unit per call, into interleaved S16 or float PCM, with the libSceAudiodec validation and error codes. libSceAudiodec.native is built from the same sources. Co-Authored-By: Claude --- core/libs/SceTypes.hpp | 53 ++++ .../libs/prx/libSceAudiodec.native/Export.cpp | 38 +-- core/libs/prx/libSceAudiodec/CMakeLists.txt | 29 ++ core/libs/prx/libSceAudiodec/Export.cpp | 268 +++++++++++++++--- core/libs/prx/libSceAudiodec/src/Codecs.cpp | 252 ++++++++++++++++ core/libs/prx/libSceAudiodec/src/Codecs.hpp | 65 +++++ core/libs/tests/GuestAudiodec.cpp | 235 +++++++++++++++ docs/dev/TechnicalDebt.md | 1 + 8 files changed, 872 insertions(+), 69 deletions(-) create mode 100644 core/libs/prx/libSceAudiodec/CMakeLists.txt create mode 100644 core/libs/prx/libSceAudiodec/src/Codecs.cpp create mode 100644 core/libs/prx/libSceAudiodec/src/Codecs.hpp create mode 100644 core/libs/tests/GuestAudiodec.cpp diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 14a4e72d5..21ee63e4d 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -798,6 +798,59 @@ struct AudiodecCtrl { AudiodecPcmItem* pPcmItem; }; +struct AudiodecParamAt9 { + std::uint32_t ui_size; + std::int32_t i_bw_pcm; + std::uint8_t ui_config_data[4]; +}; + +struct AudiodecAt9Info { + std::uint32_t ui_size; + std::uint32_t ui_channel; + std::uint32_t ui_bitrate; + std::uint32_t ui_sampling_rate; + std::uint32_t ui_super_frame_size; + std::uint32_t ui_frames_in_super_frame; + std::uint32_t ui_next_frame_size; + std::uint32_t ui_frame_samples; + std::int32_t i_result; +}; + +struct AudiodecParamMp3 { + std::uint32_t ui_size; + std::int32_t i_bw_pcm; +}; + +struct AudiodecMp3Info { + std::uint32_t ui_size; + std::uint32_t ui_header; + std::uint8_t uc_crc; + std::uint8_t uc_mode; + std::uint8_t uc_mode_extension; + std::uint8_t uc_copyright; + std::uint8_t uc_original; + std::uint8_t uc_emphasis; + std::uint8_t uc_reserved[2]; + std::int32_t i_result; +}; + +struct AudiodecParamM4aac { + std::uint32_t ui_size; + std::int32_t i_bw_pcm; + std::uint32_t ui_config_number; + std::uint32_t ui_sampling_freq_index; + std::uint32_t ui_max_channels; + std::uint32_t ui_enable_heaac; +}; + +struct AudiodecM4aacInfo { + std::uint32_t ui_size; + std::uint32_t ui_sampling_freq; + std::uint32_t ui_number_of_channels; + std::uint32_t ui_heaac; + std::int32_t i_result; +}; + struct VoiceInitParam { std::int32_t app_type; std::uint64_t on_event; diff --git a/core/libs/prx/libSceAudiodec.native/Export.cpp b/core/libs/prx/libSceAudiodec.native/Export.cpp index f2e908905..fb34206c2 100644 --- a/core/libs/prx/libSceAudiodec.native/Export.cpp +++ b/core/libs/prx/libSceAudiodec.native/Export.cpp @@ -5,42 +5,6 @@ extern "C" { -int32_t APS5_VABI sceAudiodecClearContext(int32_t handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int32_t APS5_VABI sceAudiodecCreateDecoder(AudiodecCtrl* ctrl, uint32_t codec_type) { - (void)ctrl; - (void)codec_type; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int32_t APS5_VABI sceAudiodecDecode(int32_t handle, AudiodecCtrl* ctrl) { - (void)handle; - (void)ctrl; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int32_t APS5_VABI sceAudiodecDeleteDecoder(int32_t handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int32_t APS5_VABI sceAudiodecInitLibrary(uint32_t codec_type) { - (void)codec_type; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int32_t APS5_VABI sceAudiodecTermLibrary(uint32_t codec_type) { - (void)codec_type; - NotImplemented_nid_no_patch(__func__); - return 0; -} +APS5_DUMMY_FUN } diff --git a/core/libs/prx/libSceAudiodec/CMakeLists.txt b/core/libs/prx/libSceAudiodec/CMakeLists.txt new file mode 100644 index 000000000..0283a14a3 --- /dev/null +++ b/core/libs/prx/libSceAudiodec/CMakeLists.txt @@ -0,0 +1,29 @@ +add_library(libSceAudiodec SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Codecs.cpp +) +target_include_directories(libSceAudiodec PRIVATE + ${LIBS_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src +) +target_link_libraries(libSceAudiodec PRIVATE atrac9 ffmpeg libc libkernel) +set_target_properties(libSceAudiodec PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(libSceAudiodec) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_audiodec_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudiodec.cpp) + target_include_directories(guest_audiodec_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_audiodec_tests PRIVATE libSceAudiodec libc) + configure_windows_unwind(guest_audiodec_tests) + add_test(NAME guest_audiodec COMMAND guest_audiodec_tests) + if(WIN32) + set_tests_properties(guest_audiodec PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAudiodec/Export.cpp b/core/libs/prx/libSceAudiodec/Export.cpp index f2e908905..76722d355 100644 --- a/core/libs/prx/libSceAudiodec/Export.cpp +++ b/core/libs/prx/libSceAudiodec/Export.cpp @@ -1,46 +1,250 @@ #include #include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libSceAudiodec/src/Codecs.hpp" + +namespace { + +constexpr std::int32_t ERROR_API_FAIL = static_cast(0x807F0000); +constexpr std::int32_t ERROR_INVALID_TYPE = static_cast(0x807F0001); +constexpr std::int32_t ERROR_ARG = static_cast(0x807F0002); +constexpr std::int32_t ERROR_INVALID_PARAM_SIZE = static_cast(0x807F0004); +constexpr std::int32_t ERROR_INVALID_BSI_INFO_SIZE = static_cast(0x807F0005); +constexpr std::int32_t ERROR_INVALID_AU_INFO_SIZE = static_cast(0x807F0006); +constexpr std::int32_t ERROR_INVALID_PCM_ITEM_SIZE = static_cast(0x807F0007); +constexpr std::int32_t ERROR_INVALID_CTRL_POINTER = static_cast(0x807F0008); +constexpr std::int32_t ERROR_INVALID_PARAM_POINTER = static_cast(0x807F0009); +constexpr std::int32_t ERROR_INVALID_BSI_INFO_POINTER = static_cast(0x807F000A); +constexpr std::int32_t ERROR_INVALID_AU_INFO_POINTER = static_cast(0x807F000B); +constexpr std::int32_t ERROR_INVALID_PCM_ITEM_POINTER = static_cast(0x807F000C); +constexpr std::int32_t ERROR_INVALID_AU_POINTER = static_cast(0x807F000D); +constexpr std::int32_t ERROR_INVALID_PCM_POINTER = static_cast(0x807F000E); +constexpr std::int32_t ERROR_INVALID_HANDLE = static_cast(0x807F000F); +constexpr std::int32_t ERROR_INVALID_WORD_LENGTH = static_cast(0x807F0010); +constexpr std::int32_t ERROR_INVALID_AU_SIZE = static_cast(0x807F0011); +constexpr std::int32_t ERROR_INVALID_PCM_SIZE = static_cast(0x807F0012); +constexpr std::int32_t ERROR_M4AAC_INVALID_SAMPLING_FREQ = static_cast(0x807F0300); +constexpr std::int32_t ERROR_M4AAC_INVALID_ENABLE_HEAAC = static_cast(0x807F0302); +constexpr std::int32_t ERROR_M4AAC_INVALID_CONFIG_NUMBER = static_cast(0x807F0303); +constexpr std::int32_t ERROR_M4AAC_INVALID_MAX_CHANNELS = static_cast(0x807F0304); +constexpr std::int32_t ERROR_AT9_INVALID_CONFIG_DATA = static_cast(0x807F1000); + +constexpr std::uint32_t TYPE_AT9 = 1; +constexpr std::uint32_t TYPE_MP3 = 2; +constexpr std::uint32_t TYPE_M4AAC = 3; +constexpr std::uint32_t M4AAC_CONFIG_ADTS = 1; +constexpr std::uint32_t M4AAC_CONFIG_RAW = 2; +constexpr std::int32_t M4AAC_RESULT_DECODE_ERROR = -4; +constexpr std::int32_t M4AAC_RESULT_INSUFFICIENT_DATA = -5; + +static_assert(sizeof(AudiodecAuInfo) == 24 && sizeof(AudiodecPcmItem) == 24 && sizeof(AudiodecCtrl) == 32); +static_assert(sizeof(AudiodecParamAt9) == 12 && sizeof(AudiodecAt9Info) == 36); +static_assert(sizeof(AudiodecParamMp3) == 8 && sizeof(AudiodecMp3Info) == 20); +static_assert(sizeof(AudiodecParamM4aac) == 24 && sizeof(AudiodecM4aacInfo) == 20); + +struct Instance { + std::uint32_t codecType = 0; + std::unique_ptr decoder; + Audiodec::At9Format at9{}; + std::uint32_t channels = 0; + std::uint32_t sampleRate = 0; +}; + +std::mutex g_mutex; +std::array g_initialized{}; +std::array g_instances{}; + +bool validType(std::uint32_t codecType) { + return codecType == TYPE_AT9 || codecType == TYPE_MP3 || codecType == TYPE_M4AAC; +} + +std::int32_t wordSizeOf(const void* param) { + return static_cast(param)->i_bw_pcm; +} + +std::int32_t validateWordSize(std::int32_t wordSize) { + if (wordSize == Audiodec::WORD_SIZE_16BIT || wordSize == Audiodec::WORD_SIZE_FLOAT) return 0; + if (wordSize == 0) NotImplemented_nid_no_patch("libSceAudiodec 24-bit PCM output"); + return ERROR_INVALID_WORD_LENGTH; +} + +std::int32_t validateCtrl(const AudiodecCtrl* ctrl, std::uint32_t codecType) { + if (!ctrl) return ERROR_INVALID_CTRL_POINTER; + if (!ctrl->pParam) return ERROR_INVALID_PARAM_POINTER; + if (!ctrl->pBsiInfo) return ERROR_INVALID_BSI_INFO_POINTER; + const std::uint32_t paramSize = *static_cast(ctrl->pParam); + const std::uint32_t infoSize = *static_cast(ctrl->pBsiInfo); + switch (codecType) { + case TYPE_AT9: + if (paramSize != sizeof(AudiodecParamAt9)) return ERROR_INVALID_PARAM_SIZE; + if (infoSize != sizeof(AudiodecAt9Info)) return ERROR_INVALID_BSI_INFO_SIZE; + break; + case TYPE_MP3: + if (paramSize != sizeof(AudiodecParamMp3)) return ERROR_INVALID_PARAM_SIZE; + if (infoSize != sizeof(AudiodecMp3Info)) return ERROR_INVALID_BSI_INFO_SIZE; + break; + default: + if (paramSize < sizeof(AudiodecParamM4aac)) return ERROR_INVALID_PARAM_SIZE; + if (infoSize != sizeof(AudiodecM4aacInfo)) return ERROR_INVALID_BSI_INFO_SIZE; + break; + } + return validateWordSize(wordSizeOf(ctrl->pParam)); +} + +std::int32_t validateBuffers(const AudiodecCtrl* ctrl) { + if (!ctrl->pAuInfo) return ERROR_INVALID_AU_INFO_POINTER; + if (!ctrl->pPcmItem) return ERROR_INVALID_PCM_ITEM_POINTER; + if (ctrl->pAuInfo->ui_size != sizeof(AudiodecAuInfo)) return ERROR_INVALID_AU_INFO_SIZE; + if (ctrl->pPcmItem->ui_size != sizeof(AudiodecPcmItem)) return ERROR_INVALID_PCM_ITEM_SIZE; + if (!ctrl->pAuInfo->p_au_addr) return ERROR_INVALID_AU_POINTER; + if (!ctrl->pPcmItem->p_pcm_addr) return ERROR_INVALID_PCM_POINTER; + if (ctrl->pAuInfo->ui_au_size == 0) return ERROR_INVALID_AU_SIZE; + if (ctrl->pPcmItem->ui_pcm_size == 0) return ERROR_INVALID_PCM_SIZE; + return 0; +} + +void fillAt9Info(AudiodecAt9Info* info, const Audiodec::At9Format& format) { + const std::uint32_t superframeSamples = format.frameSamples * format.framesInSuperframe; + info->ui_channel = format.channels; + info->ui_bitrate = static_cast(static_cast(format.superframeSize) * 8 * format.sampleRate / superframeSamples); + info->ui_sampling_rate = format.sampleRate; + info->ui_super_frame_size = format.superframeSize; + info->ui_frames_in_super_frame = format.framesInSuperframe; + info->ui_next_frame_size = format.superframeSize; + info->ui_frame_samples = format.frameSamples; + info->i_result = 0; +} + +void fillMp3Info(AudiodecMp3Info* info, const Audiodec::Mp3Header& header) { + info->ui_header = header.word; + info->uc_crc = header.crc; + info->uc_mode = header.mode; + info->uc_mode_extension = header.modeExtension; + info->uc_copyright = header.copyright; + info->uc_original = header.original; + info->uc_emphasis = header.emphasis; + info->uc_reserved[0] = 0; + info->uc_reserved[1] = 0; + info->i_result = 0; +} + +std::int32_t createAac(const AudiodecParamM4aac* param, AudiodecM4aacInfo* info, Instance& instance) { + if (param->ui_config_number != M4AAC_CONFIG_ADTS && param->ui_config_number != M4AAC_CONFIG_RAW) return ERROR_M4AAC_INVALID_CONFIG_NUMBER; + const std::uint32_t sampleRate = Audiodec::AacSampleRate(param->ui_sampling_freq_index); + if (param->ui_config_number == M4AAC_CONFIG_RAW && sampleRate == 0) return ERROR_M4AAC_INVALID_SAMPLING_FREQ; + if (param->ui_max_channels > 8) return ERROR_M4AAC_INVALID_MAX_CHANNELS; + if (param->ui_enable_heaac > 1) return ERROR_M4AAC_INVALID_ENABLE_HEAAC; + const std::uint32_t channels = param->ui_max_channels == 0 ? 2 : param->ui_max_channels; + instance.decoder = Audiodec::CreateAac(param->i_bw_pcm, param->ui_config_number == M4AAC_CONFIG_ADTS, param->ui_sampling_freq_index, channels); + instance.channels = channels; + instance.sampleRate = sampleRate; + info->ui_sampling_freq = sampleRate; + info->ui_number_of_channels = channels; + info->ui_heaac = 0; + info->i_result = 0; + return 0; +} + +Instance* find(std::int32_t handle) { + if (handle <= 0 || static_cast(handle) > g_instances.size()) return nullptr; + Instance& instance = g_instances[static_cast(handle - 1)]; + return instance.decoder ? &instance : nullptr; +} + +} extern "C" { -int32_t APS5_VABI sceAudiodecClearContext(int32_t handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int32_t APS5_VABI sceAudiodecCreateDecoder(AudiodecCtrl* ctrl, uint32_t codec_type) { - (void)ctrl; - (void)codec_type; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int32_t APS5_VABI sceAudiodecDecode(int32_t handle, AudiodecCtrl* ctrl) { - (void)handle; - (void)ctrl; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int32_t APS5_VABI sceAudiodecDeleteDecoder(int32_t handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - int32_t APS5_VABI sceAudiodecInitLibrary(uint32_t codec_type) { - (void)codec_type; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!validType(codec_type)) return ERROR_INVALID_TYPE; + std::lock_guard lock(g_mutex); + ++g_initialized[codec_type]; + return 0; } int32_t APS5_VABI sceAudiodecTermLibrary(uint32_t codec_type) { - (void)codec_type; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!validType(codec_type)) return ERROR_INVALID_TYPE; + std::lock_guard lock(g_mutex); + if (g_initialized[codec_type] > 0) --g_initialized[codec_type]; + return 0; +} + +int32_t APS5_VABI sceAudiodecCreateDecoder(AudiodecCtrl* ctrl, uint32_t codec_type) { + if (!validType(codec_type)) return ERROR_INVALID_TYPE; + if (const std::int32_t result = validateCtrl(ctrl, codec_type); result != 0) return result; + std::lock_guard lock(g_mutex); + if (g_initialized[codec_type] == 0) return ERROR_ARG; + for (std::size_t i = 0; i < g_instances.size(); ++i) { + Instance& instance = g_instances[i]; + if (instance.decoder) continue; + instance = {}; + instance.codecType = codec_type; + if (codec_type == TYPE_AT9) { + const auto* param = static_cast(ctrl->pParam); + instance.decoder = Audiodec::CreateAt9(param->i_bw_pcm, param->ui_config_data, instance.at9); + if (!instance.decoder) return ERROR_AT9_INVALID_CONFIG_DATA; + fillAt9Info(static_cast(ctrl->pBsiInfo), instance.at9); + } else if (codec_type == TYPE_MP3) { + instance.decoder = Audiodec::CreateMp3(static_cast(ctrl->pParam)->i_bw_pcm); + } else if (const std::int32_t result = createAac(static_cast(ctrl->pParam), static_cast(ctrl->pBsiInfo), instance); result != 0) { + instance = {}; + return result; + } + return static_cast(i + 1); + } + return ERROR_API_FAIL; +} + +int32_t APS5_VABI sceAudiodecDeleteDecoder(int32_t handle) { + std::lock_guard lock(g_mutex); + Instance* instance = find(handle); + if (!instance) return ERROR_INVALID_HANDLE; + *instance = {}; + return 0; +} + +int32_t APS5_VABI sceAudiodecClearContext(int32_t handle) { + std::lock_guard lock(g_mutex); + Instance* instance = find(handle); + if (!instance) return ERROR_INVALID_HANDLE; + instance->decoder->Reset(); + return 0; +} + +int32_t APS5_VABI sceAudiodecDecode(int32_t handle, AudiodecCtrl* ctrl) { + std::lock_guard lock(g_mutex); + Instance* instance = find(handle); + if (!instance) return ERROR_INVALID_HANDLE; + if (const std::int32_t result = validateCtrl(ctrl, instance->codecType); result != 0) return result; + if (const std::int32_t result = validateBuffers(ctrl); result != 0) return result; + + const Audiodec::DecodeResult decoded = instance->decoder->Decode(static_cast(ctrl->pAuInfo->p_au_addr), ctrl->pAuInfo->ui_au_size, + static_cast(ctrl->pPcmItem->p_pcm_addr), ctrl->pPcmItem->ui_pcm_size); + if (instance->codecType == TYPE_M4AAC) { + auto* info = static_cast(ctrl->pBsiInfo); + info->i_result = decoded.status == Audiodec::DecodeStatus::InvalidData ? M4AAC_RESULT_DECODE_ERROR + : decoded.status == Audiodec::DecodeStatus::PartialInput ? M4AAC_RESULT_INSUFFICIENT_DATA : 0; + if (decoded.channels != 0) { + info->ui_sampling_freq = decoded.sampleRate; + info->ui_number_of_channels = decoded.channels; + info->ui_heaac = decoded.highEfficiency; + } + } + if (decoded.status == Audiodec::DecodeStatus::NotEnoughRoom) return ERROR_INVALID_PCM_SIZE; + if (decoded.status != Audiodec::DecodeStatus::Ok) { + ctrl->pAuInfo->ui_au_size = 0; + ctrl->pPcmItem->ui_pcm_size = 0; + return ERROR_API_FAIL; + } + if (instance->codecType == TYPE_MP3) fillMp3Info(static_cast(ctrl->pBsiInfo), decoded.mp3); + if (instance->codecType == TYPE_AT9) fillAt9Info(static_cast(ctrl->pBsiInfo), instance->at9); + ctrl->pAuInfo->ui_au_size = static_cast(decoded.consumed); + ctrl->pPcmItem->ui_pcm_size = static_cast(decoded.produced); + return 0; } } diff --git a/core/libs/prx/libSceAudiodec/src/Codecs.cpp b/core/libs/prx/libSceAudiodec/src/Codecs.cpp new file mode 100644 index 000000000..36d021763 --- /dev/null +++ b/core/libs/prx/libSceAudiodec/src/Codecs.cpp @@ -0,0 +1,252 @@ +#include "prx/libSceAudiodec/src/Codecs.hpp" + +#include "libatrac9.h" +extern "C" { +#include +#include +} + +#include +#include +#include +#include +#include +#include + +namespace Audiodec { + +namespace { + +constexpr std::uint32_t AAC_SAMPLE_RATES[13] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350}; + +void writeSample(float value, std::int32_t wordSize, std::uint8_t* out) { + if (wordSize == WORD_SIZE_FLOAT) { + std::memcpy(out, &value, sizeof(value)); + return; + } + const auto sample = static_cast(std::clamp(std::lrint(value * 32768.0), -32768L, 32767L)); + std::memcpy(out, &sample, sizeof(sample)); +} + +class FfmpegDecoder : public Decoder { +public: + FfmpegDecoder(AVCodecID id, std::int32_t wordSize, std::vector extradata = {}) : wordSize(wordSize), extradata(std::move(extradata)), id(id) { + static const bool quiet = (av_log_set_level(AV_LOG_QUIET), true); + (void)quiet; + open(); + } + + ~FfmpegDecoder() override { avcodec_free_context(&context); } + + void Reset() override { avcodec_flush_buffers(context); } + +protected: + DecodeResult decodePacket(const std::uint8_t* data, std::size_t size, std::uint8_t* pcm, std::size_t pcmSize) { + AVPacket* packet = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + if (!packet || !frame) { + av_packet_free(&packet); + av_frame_free(&frame); + throw std::bad_alloc(); + } + packet->data = const_cast(data); + packet->size = static_cast(size); + DecodeResult result{}; + result.consumed = size; + if (avcodec_send_packet(context, packet) < 0) result.status = DecodeStatus::InvalidData; + while (result.status == DecodeStatus::Ok && avcodec_receive_frame(context, frame) == 0) { + const auto format = static_cast(frame->format); + if (format != AV_SAMPLE_FMT_FLTP && format != AV_SAMPLE_FMT_S16P) throw std::runtime_error("libSceAudiodec: FFmpeg sample format " + std::to_string(frame->format) + " is not converted"); + const auto channels = static_cast(frame->ch_layout.nb_channels); + const auto samples = static_cast(frame->nb_samples); + const std::size_t sampleBytes = wordSize == WORD_SIZE_FLOAT ? 4 : 2; + if (result.produced + samples * channels * sampleBytes > pcmSize) { + result.status = DecodeStatus::NotEnoughRoom; + } else { + for (std::size_t sample = 0; sample < samples; ++sample) { + for (std::size_t channel = 0; channel < channels; ++channel) { + const float value = format == AV_SAMPLE_FMT_FLTP ? reinterpret_cast(frame->extended_data[channel])[sample] + : reinterpret_cast(frame->extended_data[channel])[sample] / 32768.0f; + writeSample(value, wordSize, pcm + result.produced); + result.produced += sampleBytes; + } + } + } + result.channels = static_cast(channels); + result.sampleRate = static_cast(frame->sample_rate); + result.highEfficiency = context->profile == AV_PROFILE_AAC_HE || context->profile == AV_PROFILE_AAC_HE_V2; + av_frame_unref(frame); + } + av_packet_free(&packet); + av_frame_free(&frame); + return result; + } + +private: + void open() { + const AVCodec* codec = avcodec_find_decoder(id); + context = codec ? avcodec_alloc_context3(codec) : nullptr; + if (!context) throw std::runtime_error("libSceAudiodec: cannot allocate an FFmpeg decoder"); + if (!extradata.empty()) { + context->extradata = static_cast(av_mallocz(extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE)); + if (!context->extradata) throw std::bad_alloc(); + std::memcpy(context->extradata, extradata.data(), extradata.size()); + context->extradata_size = static_cast(extradata.size()); + } + if (avcodec_open2(context, codec, nullptr) < 0) throw std::runtime_error("libSceAudiodec: cannot open an FFmpeg decoder"); + } + + std::int32_t wordSize; + std::vector extradata; + AVCodecID id; + AVCodecContext* context = nullptr; +}; + +class Mp3Decoder final : public FfmpegDecoder { +public: + explicit Mp3Decoder(std::int32_t wordSize) : FfmpegDecoder(AV_CODEC_ID_MP3, wordSize) {} + + DecodeResult Decode(const std::uint8_t* data, std::size_t size, std::uint8_t* pcm, std::size_t pcmSize) override { + Mp3Header header{}; + if (!ParseMp3Header(data, size, header)) return {DecodeStatus::InvalidData}; + if (header.frameBytes > size) return {DecodeStatus::PartialInput}; + DecodeResult result = decodePacket(data, header.frameBytes, pcm, pcmSize); + result.mp3 = header; + return result; + } +}; + +class AacDecoder final : public FfmpegDecoder { +public: + AacDecoder(std::int32_t wordSize, bool adts, std::vector extradata) : FfmpegDecoder(AV_CODEC_ID_AAC, wordSize, std::move(extradata)), adts(adts) {} + + DecodeResult Decode(const std::uint8_t* data, std::size_t size, std::uint8_t* pcm, std::size_t pcmSize) override { + std::size_t frameBytes = size; + if (adts) { + if (size < 7 || data[0] != 0xFF || (data[1] & 0xF6) != 0xF0) return {DecodeStatus::InvalidData}; + frameBytes = static_cast((data[3] & 3) << 11 | data[4] << 3 | data[5] >> 5); + if (frameBytes < 7) return {DecodeStatus::InvalidData}; + if (frameBytes > size) return {DecodeStatus::PartialInput}; + } + return decodePacket(data, frameBytes, pcm, pcmSize); + } + +private: + bool adts; +}; + +class At9Decoder final : public Decoder { +public: + At9Decoder(std::int32_t wordSize, const std::uint8_t (&config)[4]) : wordSize(wordSize) { + std::memcpy(this->config, config, sizeof(this->config)); + Reset(); + } + + ~At9Decoder() override { Atrac9ReleaseHandle(handle); } + + void Reset() override { + if (handle) Atrac9ReleaseHandle(handle); + handle = Atrac9GetHandle(); + if (!handle || Atrac9InitDecoder(handle, config) != 0) throw std::runtime_error("libSceAudiodec: cannot initialize the ATRAC9 decoder"); + Atrac9GetCodecInfo(handle, &info); + } + + DecodeResult Decode(const std::uint8_t* data, std::size_t size, std::uint8_t* pcm, std::size_t pcmSize) override { + const auto superframeSize = static_cast(info.superframeSize); + if (size < superframeSize) return {DecodeStatus::PartialInput}; + const std::size_t sampleBytes = wordSize == WORD_SIZE_FLOAT ? 4 : 2; + const std::size_t frameBytes = static_cast(info.frameSamples) * info.channels * sampleBytes; + if (frameBytes * info.framesInSuperframe > pcmSize) return {DecodeStatus::NotEnoughRoom}; + DecodeResult result{}; + std::size_t offset = 0; + for (int frame = 0; frame < info.framesInSuperframe; ++frame) { + int used = 0; + const int status = wordSize == WORD_SIZE_FLOAT + ? Atrac9DecodeF32(handle, data + offset, reinterpret_cast(pcm + result.produced), &used, 0) + : Atrac9Decode(handle, data + offset, reinterpret_cast(pcm + result.produced), &used, 0); + if (status != 0 || used <= 0 || offset + static_cast(used) > superframeSize) { + Reset(); + return {DecodeStatus::InvalidData}; + } + offset += static_cast(used); + result.produced += frameBytes; + } + result.consumed = superframeSize; + result.channels = static_cast(info.channels); + result.sampleRate = static_cast(info.samplingRate); + return result; + } + + const Atrac9CodecInfo& Info() const { return info; } + +private: + std::int32_t wordSize; + std::uint8_t config[4]; + void* handle = nullptr; + Atrac9CodecInfo info{}; +}; + +} + +bool ParseMp3Header(const std::uint8_t* data, std::size_t size, Mp3Header& header) { + if (size < 4) return false; + const std::uint32_t word = std::uint32_t{data[0]} << 24 | std::uint32_t{data[1]} << 16 | std::uint32_t{data[2]} << 8 | data[3]; + const auto version = word >> 19 & 3; + const auto layer = word >> 17 & 3; + const auto bitrateIndex = word >> 12 & 15; + const auto rateIndex = word >> 10 & 3; + if ((word & 0xFFE00000u) != 0xFFE00000u || version == 1 || layer != 1 || bitrateIndex == 0 || bitrateIndex == 15 || rateIndex == 3) return false; + static constexpr std::uint32_t rates[4][3] = {{11025, 12000, 8000}, {0, 0, 0}, {22050, 24000, 16000}, {44100, 48000, 32000}}; + static constexpr std::uint32_t mpeg1Kbps[16] = {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}; + static constexpr std::uint32_t mpeg2Kbps[16] = {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}; + const bool mpeg1 = version == 3; + const std::uint32_t sampleRate = rates[version][rateIndex]; + const std::uint32_t bitrate = (mpeg1 ? mpeg1Kbps : mpeg2Kbps)[bitrateIndex] * 1000; + header.word = word; + header.frameBytes = (mpeg1 ? 144 : 72) * bitrate / sampleRate + (word >> 9 & 1); + header.crc = (word >> 16 & 1) == 0; + header.mode = static_cast(word >> 6 & 3); + header.modeExtension = static_cast(word >> 4 & 3); + header.copyright = static_cast(word >> 3 & 1); + header.original = static_cast(word >> 2 & 1); + header.emphasis = static_cast(word & 3); + return true; +} + +std::uint32_t AacSampleRate(std::uint32_t index) { + return index < 13 ? AAC_SAMPLE_RATES[index] : 0; +} + +std::unique_ptr CreateMp3(std::int32_t wordSize) { + return std::make_unique(wordSize); +} + +std::unique_ptr CreateAac(std::int32_t wordSize, bool adts, std::uint32_t samplingFreqIndex, std::uint32_t channels) { + std::vector config; + if (!adts) { + const std::uint32_t lowComplexity = 2; + config = {static_cast(lowComplexity << 3 | samplingFreqIndex >> 1), + static_cast((samplingFreqIndex & 1) << 7 | channels << 3)}; + } + return std::make_unique(wordSize, adts, std::move(config)); +} + +std::unique_ptr CreateAt9(std::int32_t wordSize, const std::uint8_t (&config)[4], At9Format& format) { + if (!ValidAt9Config(config)) return nullptr; + auto decoder = std::make_unique(wordSize, config); + const auto& info = decoder->Info(); + format = {static_cast(info.channels), static_cast(info.samplingRate), static_cast(info.superframeSize), + static_cast(info.framesInSuperframe), static_cast(info.frameSamples)}; + return decoder; +} + +bool ValidAt9Config(const std::uint8_t (&config)[4]) { + void* handle = Atrac9GetHandle(); + std::uint8_t copy[4]; + std::memcpy(copy, config, sizeof(copy)); + const bool valid = handle && Atrac9InitDecoder(handle, copy) == 0; + if (handle) Atrac9ReleaseHandle(handle); + return valid; +} + +} diff --git a/core/libs/prx/libSceAudiodec/src/Codecs.hpp b/core/libs/prx/libSceAudiodec/src/Codecs.hpp new file mode 100644 index 000000000..ebf8434eb --- /dev/null +++ b/core/libs/prx/libSceAudiodec/src/Codecs.hpp @@ -0,0 +1,65 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAUDIODEC_SRC_CODECS_HPP +#define CORE_LIBS_PRX_LIBSCEAUDIODEC_SRC_CODECS_HPP + +#include +#include +#include + +namespace Audiodec { + +constexpr std::int32_t WORD_SIZE_16BIT = 1; +constexpr std::int32_t WORD_SIZE_FLOAT = 2; + +enum class DecodeStatus { + Ok, + InvalidData, + PartialInput, + NotEnoughRoom, +}; + +struct Mp3Header { + std::uint32_t word; + std::size_t frameBytes; + bool crc; + std::uint8_t mode; + std::uint8_t modeExtension; + std::uint8_t copyright; + std::uint8_t original; + std::uint8_t emphasis; +}; + +struct DecodeResult { + DecodeStatus status = DecodeStatus::Ok; + std::size_t consumed = 0; + std::size_t produced = 0; + std::uint32_t channels = 0; + std::uint32_t sampleRate = 0; + bool highEfficiency = false; + Mp3Header mp3{}; +}; + +struct At9Format { + std::uint32_t channels; + std::uint32_t sampleRate; + std::uint32_t superframeSize; + std::uint32_t framesInSuperframe; + std::uint32_t frameSamples; +}; + +class Decoder { +public: + virtual ~Decoder() = default; + virtual DecodeResult Decode(const std::uint8_t* data, std::size_t size, std::uint8_t* pcm, std::size_t pcmSize) = 0; + virtual void Reset() = 0; +}; + +bool ParseMp3Header(const std::uint8_t* data, std::size_t size, Mp3Header& header); +std::uint32_t AacSampleRate(std::uint32_t index); +bool ValidAt9Config(const std::uint8_t (&config)[4]); +std::unique_ptr CreateMp3(std::int32_t wordSize); +std::unique_ptr CreateAac(std::int32_t wordSize, bool adts, std::uint32_t samplingFreqIndex, std::uint32_t channels); +std::unique_ptr CreateAt9(std::int32_t wordSize, const std::uint8_t (&config)[4], At9Format& format); + +} + +#endif diff --git a/core/libs/tests/GuestAudiodec.cpp b/core/libs/tests/GuestAudiodec.cpp new file mode 100644 index 000000000..fb9a4b3e5 --- /dev/null +++ b/core/libs/tests/GuestAudiodec.cpp @@ -0,0 +1,235 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" { +std::int32_t APS5_VABI sceAudiodecInitLibrary(std::uint32_t); +std::int32_t APS5_VABI sceAudiodecTermLibrary(std::uint32_t); +std::int32_t APS5_VABI sceAudiodecCreateDecoder(AudiodecCtrl*, std::uint32_t); +std::int32_t APS5_VABI sceAudiodecDeleteDecoder(std::int32_t); +std::int32_t APS5_VABI sceAudiodecDecode(std::int32_t, AudiodecCtrl*); +std::int32_t APS5_VABI sceAudiodecClearContext(std::int32_t); +} + +static void Require(bool value) { if (!value) std::abort(); } + +namespace { + +const std::uint8_t AAC_ADTS[] = { + 0xFF, 0xF1, 0x4C, 0x80, 0x0F, 0x9F, 0xFC, 0xDC, 0x00, 0x4C, 0x61, 0x76, 0x63, 0x36, 0x33, 0x2E, 0x31, 0x2E, 0x31, 0x30, 0x32, 0x00, 0x42, 0x4D, + 0x9F, 0xFF, 0xFF, 0xE1, 0x40, 0xC9, 0xA1, 0xA2, 0xF4, 0x44, 0x23, 0x8E, 0xEA, 0x91, 0x95, 0xA9, 0x2E, 0xE5, 0xDC, 0x92, 0x3B, 0x65, 0xC0, 0xCE, + 0xCF, 0x8E, 0x0C, 0xEC, 0xEC, 0xEC, 0xEC, 0xED, 0x59, 0x49, 0x61, 0xBC, 0xA3, 0x44, 0xEA, 0x53, 0xA7, 0x08, 0x22, 0x06, 0x36, 0x6C, 0xD9, 0xB0, + 0x64, 0x48, 0xA5, 0x06, 0x36, 0x6C, 0x18, 0x19, 0x10, 0x30, 0x31, 0xB3, 0x66, 0xCD, 0x83, 0x22, 0x45, 0x29, 0xB3, 0x72, 0xCB, 0x14, 0x51, 0x45, + 0x14, 0x51, 0x45, 0x14, 0x51, 0x45, 0x40, 0x37, 0xD1, 0x78, 0x8E, 0x3B, 0xAA, 0x46, 0x56, 0xA4, 0xBB, 0x97, 0x72, 0x48, 0xED, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xF1, 0x4C, 0x80, 0x18, 0xFF, 0xFC, 0x21, 0x48, 0x6C, 0xFE, 0x7E, 0x7E, 0x7E, 0x47, 0x51, 0x4B, 0xAA, 0xC9, 0x71, + 0x64, 0xB8, 0xB2, 0x5C, 0x3F, 0xFE, 0xD7, 0xFD, 0xFE, 0xB8, 0xD3, 0xBF, 0x5D, 0x7F, 0xFD, 0xFF, 0xFA, 0xFD, 0xFA, 0xE3, 0x8D, 0x7C, 0x7F, 0xFE, + 0x4F, 0xFA, 0xFD, 0xFA, 0xE3, 0x8D, 0x7E, 0xFF, 0xFF, 0x5F, 0xFD, 0xBE, 0xFD, 0x71, 0xD7, 0x01, 0xB7, 0x49, 0xCD, 0xA0, 0x90, 0x90, 0x96, 0xAF, + 0x1B, 0x1C, 0xBC, 0xFB, 0x50, 0x49, 0xFB, 0x71, 0x11, 0x44, 0x26, 0x28, 0x81, 0xA2, 0x53, 0x71, 0x2B, 0xE9, 0x11, 0x4E, 0x3E, 0x9C, 0xF1, 0x14, + 0xE3, 0xE9, 0xF4, 0x88, 0xA7, 0x1F, 0x4F, 0xA5, 0x8B, 0x0C, 0x9E, 0x62, 0x69, 0x10, 0xD3, 0x20, 0xBE, 0x58, 0x4A, 0x6D, 0x72, 0x38, 0xAE, 0x2C, + 0x4C, 0x51, 0x03, 0x44, 0xA6, 0xE2, 0x57, 0xD2, 0x22, 0x9C, 0x7D, 0x39, 0xE2, 0x29, 0xC7, 0xD3, 0xE9, 0x11, 0x4E, 0x3E, 0x9F, 0x42, 0x38, 0x42, + 0x93, 0xE5, 0x39, 0x62, 0x28, 0xA2, 0x88, 0xA2, 0x8A, 0x28, 0xA3, 0x87, 0x6A, 0x1D, 0xE8, 0x77, 0xA1, 0xDE, 0x9F, 0xFF, 0x6B, 0xFE, 0xFF, 0x5C, + 0x69, 0xDF, 0xAE, 0xBF, 0xFE, 0xFF, 0xFD, 0x7E, 0xFD, 0x70, 0x7F, 0x8F, 0xFF, 0xC9, 0xFF, 0x5F, 0xBF, 0x5C, 0x1F, 0xF3, 0xFF, 0xF5, 0xFF, 0xDB, + 0xEF, 0xD7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xF1, 0x4C, 0x80, 0x0B, 0xFF, 0xFC, 0x21, 0x77, 0x4F, 0xFF, 0xFF, 0xF0, + 0x24, 0x0A, 0xFA, 0x67, 0x34, 0xD7, 0x8E, 0xF7, 0xBC, 0xFF, 0xB7, 0xFE, 0xFA, 0xFA, 0x74, 0xD5, 0xAE, 0xE3, 0xBE, 0x4B, 0x40, 0xEF, 0xBF, 0xBF, + 0xBC, 0x2F, 0xFC, 0x7C, 0x7C, 0x66, 0x40, 0xF8, 0xF8, 0xF8, 0x03, 0xDF, 0xDE, 0x11, 0xF1, 0xF0, 0xD8, 0xCF, 0xEF, 0xA0, 0x64, 0xF8, 0x6C, 0x67, + 0xF7, 0x84, 0x64, 0xF8, 0x6C, 0x67, 0xF7, 0x29, 0x01, 0xBE, 0x99, 0xCD, 0x35, 0xE3, 0xBD, 0xEF, 0x3F, 0xED, 0xFF, 0xBE, 0xBE, 0x9D, 0x35, 0x6B, + 0xB8, 0xEF, 0x92, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xF1, 0x4C, 0x80, 0x0C, 0xFF, 0xFC, 0x21, 0x17, 0x4F, 0xFF, 0xFF, 0xF8, 0x26, + 0x0A, 0x88, 0x5D, 0x20, 0xA5, 0x56, 0x67, 0x3F, 0xF6, 0xFF, 0xDF, 0xAE, 0x2E, 0xE5, 0xC9, 0x72, 0x48, 0xE9, 0x90, 0x31, 0xAF, 0x1A, 0xF1, 0xAF, + 0xBA, 0xBE, 0xE5, 0xE0, 0x23, 0x2A, 0xCA, 0xBC, 0x6B, 0xC6, 0xBC, 0x6B, 0xC6, 0xBC, 0x6B, 0xC6, 0xBC, 0x6B, 0xC6, 0xB4, 0x69, 0x28, 0xA4, 0x46, + 0x92, 0x8A, 0x44, 0x69, 0x28, 0xA4, 0x46, 0x91, 0x1D, 0x24, 0x69, 0x11, 0xA4, 0x47, 0x30, 0x38, 0xF0, 0xE9, 0x05, 0x2A, 0xB3, 0x39, 0xFF, 0xB7, + 0xFE, 0xFD, 0x71, 0x77, 0x2E, 0x4B, 0x92, 0x1D, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0xFF, 0xF1, 0x4C, 0x80, 0x0C, 0xFF, 0xFC, + 0x21, 0x17, 0x4F, 0xFF, 0xFF, 0xF8, 0x28, 0x59, 0xC4, 0xAD, 0x28, 0x99, 0x29, 0xAE, 0xF7, 0xF5, 0x6A, 0xB9, 0x2D, 0x25, 0xC7, 0x5C, 0x92, 0x02, + 0x13, 0x42, 0x16, 0x62, 0x16, 0x67, 0x76, 0x6D, 0xDA, 0xA9, 0x9A, 0xBF, 0xE3, 0xC9, 0x27, 0x5E, 0x8A, 0xE4, 0xA6, 0x6A, 0xE4, 0xA5, 0x1C, 0x73, + 0x35, 0x9B, 0x46, 0x6D, 0x19, 0xB4, 0x39, 0xA3, 0x36, 0x8C, 0xCD, 0x38, 0xD3, 0x8D, 0x66, 0x69, 0xC6, 0xB3, 0x68, 0xCD, 0xA3, 0x36, 0x8C, 0xDA, + 0x33, 0x50, 0x8E, 0x34, 0xA2, 0x64, 0xA6, 0xBB, 0xDF, 0xD5, 0xAA, 0xE4, 0xB4, 0x97, 0x1D, 0x72, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, +}; + +const std::uint8_t MP3_MONO[] = { + 0xFF, 0xFB, 0x14, 0xC4, 0x00, 0x00, 0x03, 0xB0, 0x21, 0x5E, 0xF4, 0x30, 0x80, 0x30, 0x98, 0x88, 0xA9, 0x83, 0x34, 0x70, 0x00, 0x18, 0x89, 0xCB, + 0x20, 0x00, 0x3B, 0xBB, 0xB9, 0x9F, 0xD7, 0x0E, 0x06, 0x06, 0x06, 0x2C, 0x3E, 0xEF, 0x83, 0xE7, 0xF1, 0x00, 0x63, 0x83, 0xFD, 0x4E, 0xFC, 0xFF, + 0x29, 0xE1, 0xF7, 0xD9, 0x87, 0x7E, 0xFF, 0x65, 0x9E, 0x21, 0xAF, 0x46, 0x58, 0xBB, 0x46, 0x58, 0xBA, 0x64, 0x40, 0x21, 0xB2, 0x6C, 0xFA, 0x6C, + 0xF8, 0x42, 0x0E, 0x7C, 0x1E, 0x8F, 0x0D, 0xBF, 0x1B, 0x02, 0xBC, 0x24, 0x0D, 0x37, 0xCA, 0x9D, 0x52, 0xFD, 0x77, 0xE0, 0x16, 0xBA, 0x02, 0x04, + 0xFF, 0xFB, 0x14, 0xC4, 0x03, 0x83, 0xC4, 0xE0, 0x2B, 0x28, 0x1D, 0xE4, 0x80, 0x20, 0x5D, 0x95, 0x60, 0x41, 0x70, 0x16, 0xC8, 0xD3, 0x02, 0x00, + 0x2F, 0x12, 0x02, 0xD0, 0x40, 0x1D, 0x98, 0x4D, 0x06, 0x99, 0x84, 0xF2, 0x68, 0x98, 0xBF, 0x86, 0xD1, 0x84, 0xC8, 0x18, 0x2A, 0xA9, 0x79, 0x50, + 0x85, 0xDE, 0x14, 0x88, 0x9A, 0xF8, 0x44, 0x02, 0xB0, 0x60, 0x62, 0x5E, 0x11, 0xE9, 0xDC, 0x18, 0x65, 0x8F, 0xFF, 0x7F, 0xFC, 0xB3, 0xF8, 0x58, + 0xEF, 0xFF, 0xFF, 0xE1, 0x95, 0xF8, 0x44, 0x02, 0xB0, 0x60, 0x62, 0x5E, 0x11, 0xE9, 0xDC, 0x18, 0x65, 0x8F, 0xFF, 0x7F, 0xFC, 0xB3, 0xF8, 0x58, + 0xFF, 0xFB, 0x14, 0xC4, 0x09, 0x83, 0xC2, 0xEC, 0xAB, 0x02, 0x0B, 0x80, 0xB6, 0x40, 0x5D, 0x95, 0x60, 0x41, 0x70, 0x16, 0xC8, 0xEF, 0xFF, 0xFF, + 0xE1, 0x9F, 0x08, 0x80, 0x56, 0x0C, 0x0C, 0x4B, 0xC2, 0x3D, 0x3B, 0x83, 0x0C, 0xB1, 0xFF, 0xEF, 0xFF, 0x96, 0x7F, 0x0B, 0x1D, 0xFF, 0xFF, 0xFC, + 0x31, 0xF8, 0x44, 0x02, 0xB0, 0x60, 0x62, 0x5E, 0x11, 0xE9, 0xDC, 0x18, 0x65, 0x8F, 0xFF, 0x7F, 0xFC, 0xB3, 0xF8, 0x58, 0xEF, 0xFF, 0xFF, 0xE1, + 0x9F, 0x08, 0x80, 0x56, 0x0C, 0x0C, 0x4B, 0xC2, 0x3D, 0x3B, 0x83, 0x0C, 0xB1, 0xFF, 0xEF, 0xFF, 0x96, 0x7F, 0x0B, 0x1D, 0xFF, 0xFF, 0xFC, 0x31, + 0xFF, 0xFB, 0x14, 0xC4, 0x17, 0x83, 0xC2, 0xEC, 0xAB, 0x02, 0x0B, 0x80, 0xB6, 0x40, 0x5D, 0x95, 0x60, 0x41, 0x70, 0x16, 0xC8, 0x96, 0x55, 0x8D, + 0xBF, 0xE9, 0xD8, 0x50, 0x01, 0xA6, 0x17, 0x80, 0x34, 0x61, 0x00, 0x17, 0xC6, 0x15, 0x40, 0xD4, 0x61, 0x56, 0x25, 0x66, 0x3E, 0xA4, 0x92, 0x62, + 0xC3, 0xED, 0x26, 0x23, 0xA5, 0x5C, 0x63, 0x10, 0x01, 0xC6, 0x0F, 0x20, 0x9E, 0x60, 0x62, 0x08, 0x26, 0x06, 0xE0, 0x12, 0xD6, 0x22, 0x02, 0x6C, + 0x93, 0xC7, 0x04, 0x3E, 0xBF, 0xFE, 0xBF, 0xFF, 0x72, 0x62, 0x28, 0x22, 0xD1, 0x2F, 0xEF, 0xFD, 0xF1, 0x15, 0x09, 0x83, 0x08, 0x8C, 0x00, 0xFE, + 0xFF, 0xFB, 0x14, 0xC4, 0x25, 0x80, 0x07, 0x74, 0x2D, 0x16, 0x15, 0xE7, 0x80, 0x00, 0xF2, 0x0C, 0xE8, 0x83, 0x36, 0xB0, 0x00, 0xC4, 0x42, 0x82, + 0xC0, 0x86, 0x24, 0x2A, 0x02, 0x11, 0xFF, 0x02, 0x70, 0xF6, 0x07, 0x80, 0x9B, 0xCB, 0xCD, 0x0D, 0x00, 0x7C, 0x49, 0x1D, 0xBF, 0xD3, 0xDE, 0x3B, + 0x52, 0x36, 0xFE, 0xFA, 0xF3, 0x66, 0xB5, 0x13, 0xDF, 0x06, 0x84, 0xA1, 0x2F, 0xCB, 0x05, 0x41, 0x5F, 0xF5, 0x2A, 0x4C, 0x41, 0x4D, 0x45, 0x34, + 0x2E, 0x30, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xFF, 0xFB, 0x14, 0xC4, 0x0E, 0x83, 0xC0, 0x00, 0x01, 0xA4, 0x1C, 0x00, 0x00, 0x20, 0x00, 0x00, 0x34, 0x80, 0x00, 0x00, 0x04, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, +}; + +constexpr std::int32_t errorApiFail = static_cast(0x807F0000); +constexpr std::int32_t errorInvalidType = static_cast(0x807F0001); +constexpr std::int32_t errorArg = static_cast(0x807F0002); +constexpr std::int32_t errorInvalidParamSize = static_cast(0x807F0004); +constexpr std::int32_t errorInvalidBsiInfoSize = static_cast(0x807F0005); +constexpr std::int32_t errorInvalidCtrlPointer = static_cast(0x807F0008); +constexpr std::int32_t errorInvalidAuInfoPointer = static_cast(0x807F000B); +constexpr std::int32_t errorInvalidHandle = static_cast(0x807F000F); +constexpr std::int32_t errorInvalidWordLength = static_cast(0x807F0010); +constexpr std::int32_t errorInvalidAuSize = static_cast(0x807F0011); +constexpr std::int32_t errorInvalidPcmSize = static_cast(0x807F0012); +constexpr std::int32_t errorInvalidConfigNumber = static_cast(0x807F0303); +constexpr std::int32_t errorInvalidAt9Config = static_cast(0x807F1000); + +struct Streams { + AudiodecAuInfo au{sizeof(AudiodecAuInfo), nullptr, 0}; + AudiodecPcmItem pcm{sizeof(AudiodecPcmItem), nullptr, 0}; + std::vector output = std::vector(16384); +}; + +std::int32_t Decode(std::int32_t handle, AudiodecCtrl& ctrl, Streams& streams, const std::uint8_t* data, std::size_t size, std::uint32_t room = 16384) { + streams.au.p_au_addr = const_cast(data); + streams.au.ui_au_size = static_cast(size); + streams.pcm.p_pcm_addr = streams.output.data(); + streams.pcm.ui_pcm_size = room; + ctrl.pAuInfo = &streams.au; + ctrl.pPcmItem = &streams.pcm; + return sceAudiodecDecode(handle, &ctrl); +} + +int ZeroCrossings(const std::int16_t* samples, std::size_t count, std::size_t stride) { + int crossings = 0; + for (std::size_t i = stride; i < count * stride; i += stride) crossings += (samples[i - stride] < 0) != (samples[i] < 0); + return crossings; +} + +std::size_t AdtsLength(const std::uint8_t* frame) { + return static_cast((frame[3] & 3) << 11 | frame[4] << 3 | frame[5] >> 5); +} + +void TestAac(bool adts) { + AudiodecParamM4aac param{sizeof(param), 1, adts ? 1u : 2u, 3, 2, 0}; + AudiodecM4aacInfo info{sizeof(info), 0, 0, 0, 0}; + AudiodecCtrl ctrl{¶m, &info, nullptr, nullptr}; + const std::int32_t handle = sceAudiodecCreateDecoder(&ctrl, 3); + Require(handle > 0 && info.ui_sampling_freq == 48000 && info.ui_number_of_channels == 2); + Streams streams; + int crossings = 0; + for (std::size_t offset = 0, frame = 0; offset < sizeof(AAC_ADTS); offset += AdtsLength(AAC_ADTS + offset), ++frame) { + const std::size_t length = AdtsLength(AAC_ADTS + offset); + const std::uint8_t* data = adts ? AAC_ADTS + offset : AAC_ADTS + offset + 7; + const std::size_t size = adts ? sizeof(AAC_ADTS) - offset : length - 7; + Require(Decode(handle, ctrl, streams, data, size) == 0); + Require(streams.au.ui_au_size == (adts ? length : length - 7)); + Require(streams.pcm.ui_pcm_size == 1024 * 2 * 2 && info.i_result == 0); + Require(info.ui_sampling_freq == 48000 && info.ui_number_of_channels == 2 && info.ui_heaac == 0); + if (frame == 2) crossings = ZeroCrossings(reinterpret_cast(streams.output.data()), 1024, 2); + } + Require(crossings >= 38 && crossings <= 46); + Require(Decode(handle, ctrl, streams, AAC_ADTS, sizeof(AAC_ADTS), 1024) == errorInvalidPcmSize); + if (adts) { + Require(Decode(handle, ctrl, streams, AAC_ADTS, 50) == errorApiFail && info.i_result == -5); + Require(Decode(handle, ctrl, streams, AAC_ADTS + 1, 50) == errorApiFail && info.i_result == -4); + } + Require(sceAudiodecClearContext(handle) == 0); + Require(sceAudiodecDeleteDecoder(handle) == 0); +} + +void TestMp3() { + AudiodecParamMp3 param{sizeof(param), 2}; + AudiodecMp3Info info{}; + info.ui_size = sizeof(info); + AudiodecCtrl ctrl{¶m, &info, nullptr, nullptr}; + const std::int32_t handle = sceAudiodecCreateDecoder(&ctrl, 2); + Require(handle > 0); + Streams streams; + std::size_t offset = 0; + int frames = 0; + float peak = 0; + while (offset < sizeof(MP3_MONO)) { + Require(Decode(handle, ctrl, streams, MP3_MONO + offset, sizeof(MP3_MONO) - offset) == 0); + Require(streams.au.ui_au_size == 144 * 32000 / 48000 + (MP3_MONO[offset + 2] >> 1 & 1)); + Require(streams.pcm.ui_pcm_size == 1152 * 4); + Require(info.ui_header >> 21 == 0x7FF && info.uc_mode == 3 && info.i_result == 0); + for (std::size_t i = 0; i < 1152; ++i) peak = std::fmax(peak, std::fabs(reinterpret_cast(streams.output.data())[i])); + offset += streams.au.ui_au_size; + ++frames; + } + Require(offset == sizeof(MP3_MONO) && frames == 6 && peak > 0.1f && peak < 0.15f); + Require(Decode(handle, ctrl, streams, MP3_MONO, 10) == errorApiFail); + Require(Decode(handle, ctrl, streams, MP3_MONO + 1, 100) == errorApiFail); + Require(sceAudiodecDeleteDecoder(handle) == 0); +} + +void TestAt9() { + AudiodecParamAt9 param{sizeof(param), 1, {0xFE, 0x72, 0x1F, 0xF0}}; + AudiodecAt9Info info{}; + info.ui_size = sizeof(info); + AudiodecCtrl ctrl{¶m, &info, nullptr, nullptr}; + const std::int32_t handle = sceAudiodecCreateDecoder(&ctrl, 1); + Require(handle > 0); + Require(info.ui_channel == 2 && info.ui_sampling_rate == 48000 && info.ui_frame_samples == 256); + Require(info.ui_super_frame_size == 1024 && info.ui_frames_in_super_frame == 4 && info.ui_bitrate == 384000); + Streams streams; + const std::vector silence(1024); + Require(Decode(handle, ctrl, streams, silence.data(), 1023) == errorApiFail); + Require(Decode(handle, ctrl, streams, silence.data(), silence.size(), 4095) == errorInvalidPcmSize); + Require(Decode(handle, ctrl, streams, silence.data(), silence.size()) == errorApiFail); + Require(sceAudiodecDeleteDecoder(handle) == 0); + param.ui_config_data[0] = 0xFD; + Require(sceAudiodecCreateDecoder(&ctrl, 1) == errorInvalidAt9Config); +} + +} + +int main() { + Require(sceAudiodecInitLibrary(4) == errorInvalidType); + Require(sceAudiodecTermLibrary(0) == errorInvalidType); + + AudiodecParamMp3 mp3{sizeof(mp3), 1}; + AudiodecMp3Info mp3Info{}; + mp3Info.ui_size = sizeof(mp3Info); + AudiodecCtrl ctrl{&mp3, &mp3Info, nullptr, nullptr}; + Require(sceAudiodecCreateDecoder(&ctrl, 2) == errorArg); + for (std::uint32_t type = 1; type <= 3; ++type) Require(sceAudiodecInitLibrary(type) == 0); + Require(sceAudiodecCreateDecoder(nullptr, 2) == errorInvalidCtrlPointer); + Require(sceAudiodecCreateDecoder(&ctrl, 5) == errorInvalidType); + mp3.ui_size = 12; + Require(sceAudiodecCreateDecoder(&ctrl, 2) == errorInvalidParamSize); + mp3.ui_size = sizeof(mp3); + mp3Info.ui_size = 16; + Require(sceAudiodecCreateDecoder(&ctrl, 2) == errorInvalidBsiInfoSize); + mp3Info.ui_size = sizeof(mp3Info); + mp3.i_bw_pcm = 3; + Require(sceAudiodecCreateDecoder(&ctrl, 2) == errorInvalidWordLength); + mp3.i_bw_pcm = 1; + AudiodecParamM4aac aac{sizeof(aac), 1, 3, 3, 2, 0}; + AudiodecM4aacInfo aacInfo{sizeof(aacInfo), 0, 0, 0, 0}; + AudiodecCtrl aacCtrl{&aac, &aacInfo, nullptr, nullptr}; + Require(sceAudiodecCreateDecoder(&aacCtrl, 3) == errorInvalidConfigNumber); + + const std::int32_t handle = sceAudiodecCreateDecoder(&ctrl, 2); + Require(handle > 0); + Streams streams; + Require(sceAudiodecDecode(handle, &ctrl) == errorInvalidAuInfoPointer); + Require(Decode(handle, ctrl, streams, MP3_MONO, 0) == errorInvalidAuSize); + Require(Decode(handle, ctrl, streams, MP3_MONO, sizeof(MP3_MONO), 0) == errorInvalidPcmSize); + Require(Decode(handle + 1, ctrl, streams, MP3_MONO, sizeof(MP3_MONO)) == errorInvalidHandle); + Require(sceAudiodecDeleteDecoder(handle) == 0); + Require(sceAudiodecDeleteDecoder(handle) == errorInvalidHandle); + Require(sceAudiodecClearContext(handle) == errorInvalidHandle); + + TestAac(true); + TestAac(false); + TestMp3(); + TestAt9(); + for (std::uint32_t type = 1; type <= 3; ++type) Require(sceAudiodecTermLibrary(type) == 0); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 443d743e1..7edcb5578 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -46,4 +46,5 @@ Throughout the project, every function at every stage either **does exactly what - The libc [SSE4a trap emulation](../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. - `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows. - [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character. +- [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified.