diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 42ff74398..86bd95651 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -666,6 +666,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.