Merge pull request #236 from adriadam10/feat/audiodec

feat(libSceAudiodec): decode MP3, M4AAC and ATRAC9
This commit is contained in:
boykopovar 2026-10-02 10:34:11 +03:00 committed by GitHub
commit 409c41cc6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 872 additions and 69 deletions

View File

@ -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;

View File

@ -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
}

View File

@ -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:$<TARGET_FILE_DIR:libSceAudiodec>")
endif()
endif()

View File

@ -1,46 +1,250 @@
#include <cstdint>
#include <cstddef>
#include <array>
#include <memory>
#include <mutex>
#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<std::int32_t>(0x807F0000);
constexpr std::int32_t ERROR_INVALID_TYPE = static_cast<std::int32_t>(0x807F0001);
constexpr std::int32_t ERROR_ARG = static_cast<std::int32_t>(0x807F0002);
constexpr std::int32_t ERROR_INVALID_PARAM_SIZE = static_cast<std::int32_t>(0x807F0004);
constexpr std::int32_t ERROR_INVALID_BSI_INFO_SIZE = static_cast<std::int32_t>(0x807F0005);
constexpr std::int32_t ERROR_INVALID_AU_INFO_SIZE = static_cast<std::int32_t>(0x807F0006);
constexpr std::int32_t ERROR_INVALID_PCM_ITEM_SIZE = static_cast<std::int32_t>(0x807F0007);
constexpr std::int32_t ERROR_INVALID_CTRL_POINTER = static_cast<std::int32_t>(0x807F0008);
constexpr std::int32_t ERROR_INVALID_PARAM_POINTER = static_cast<std::int32_t>(0x807F0009);
constexpr std::int32_t ERROR_INVALID_BSI_INFO_POINTER = static_cast<std::int32_t>(0x807F000A);
constexpr std::int32_t ERROR_INVALID_AU_INFO_POINTER = static_cast<std::int32_t>(0x807F000B);
constexpr std::int32_t ERROR_INVALID_PCM_ITEM_POINTER = static_cast<std::int32_t>(0x807F000C);
constexpr std::int32_t ERROR_INVALID_AU_POINTER = static_cast<std::int32_t>(0x807F000D);
constexpr std::int32_t ERROR_INVALID_PCM_POINTER = static_cast<std::int32_t>(0x807F000E);
constexpr std::int32_t ERROR_INVALID_HANDLE = static_cast<std::int32_t>(0x807F000F);
constexpr std::int32_t ERROR_INVALID_WORD_LENGTH = static_cast<std::int32_t>(0x807F0010);
constexpr std::int32_t ERROR_INVALID_AU_SIZE = static_cast<std::int32_t>(0x807F0011);
constexpr std::int32_t ERROR_INVALID_PCM_SIZE = static_cast<std::int32_t>(0x807F0012);
constexpr std::int32_t ERROR_M4AAC_INVALID_SAMPLING_FREQ = static_cast<std::int32_t>(0x807F0300);
constexpr std::int32_t ERROR_M4AAC_INVALID_ENABLE_HEAAC = static_cast<std::int32_t>(0x807F0302);
constexpr std::int32_t ERROR_M4AAC_INVALID_CONFIG_NUMBER = static_cast<std::int32_t>(0x807F0303);
constexpr std::int32_t ERROR_M4AAC_INVALID_MAX_CHANNELS = static_cast<std::int32_t>(0x807F0304);
constexpr std::int32_t ERROR_AT9_INVALID_CONFIG_DATA = static_cast<std::int32_t>(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<Audiodec::Decoder> decoder;
Audiodec::At9Format at9{};
std::uint32_t channels = 0;
std::uint32_t sampleRate = 0;
};
std::mutex g_mutex;
std::array<std::uint32_t, 4> g_initialized{};
std::array<Instance, 64> 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<const AudiodecParamMp3*>(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<const std::uint32_t*>(ctrl->pParam);
const std::uint32_t infoSize = *static_cast<const std::uint32_t*>(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<std::uint32_t>(static_cast<std::uint64_t>(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<std::size_t>(handle) > g_instances.size()) return nullptr;
Instance& instance = g_instances[static_cast<std::size_t>(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<const AudiodecParamAt9*>(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<AudiodecAt9Info*>(ctrl->pBsiInfo), instance.at9);
} else if (codec_type == TYPE_MP3) {
instance.decoder = Audiodec::CreateMp3(static_cast<const AudiodecParamMp3*>(ctrl->pParam)->i_bw_pcm);
} else if (const std::int32_t result = createAac(static_cast<const AudiodecParamM4aac*>(ctrl->pParam), static_cast<AudiodecM4aacInfo*>(ctrl->pBsiInfo), instance); result != 0) {
instance = {};
return result;
}
return static_cast<int32_t>(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<const std::uint8_t*>(ctrl->pAuInfo->p_au_addr), ctrl->pAuInfo->ui_au_size,
static_cast<std::uint8_t*>(ctrl->pPcmItem->p_pcm_addr), ctrl->pPcmItem->ui_pcm_size);
if (instance->codecType == TYPE_M4AAC) {
auto* info = static_cast<AudiodecM4aacInfo*>(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<AudiodecMp3Info*>(ctrl->pBsiInfo), decoded.mp3);
if (instance->codecType == TYPE_AT9) fillAt9Info(static_cast<AudiodecAt9Info*>(ctrl->pBsiInfo), instance->at9);
ctrl->pAuInfo->ui_au_size = static_cast<std::uint32_t>(decoded.consumed);
ctrl->pPcmItem->ui_pcm_size = static_cast<std::uint32_t>(decoded.produced);
return 0;
}
}

View File

@ -0,0 +1,252 @@
#include "prx/libSceAudiodec/src/Codecs.hpp"
#include "libatrac9.h"
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/log.h>
}
#include <algorithm>
#include <cmath>
#include <cstring>
#include <stdexcept>
#include <string>
#include <vector>
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::int16_t>(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<std::uint8_t> 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<std::uint8_t*>(data);
packet->size = static_cast<int>(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<AVSampleFormat>(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<std::size_t>(frame->ch_layout.nb_channels);
const auto samples = static_cast<std::size_t>(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<const float*>(frame->extended_data[channel])[sample]
: reinterpret_cast<const std::int16_t*>(frame->extended_data[channel])[sample] / 32768.0f;
writeSample(value, wordSize, pcm + result.produced);
result.produced += sampleBytes;
}
}
}
result.channels = static_cast<std::uint32_t>(channels);
result.sampleRate = static_cast<std::uint32_t>(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<std::uint8_t*>(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<int>(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<std::uint8_t> 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<std::uint8_t> 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<std::size_t>((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<std::size_t>(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<std::size_t>(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<float*>(pcm + result.produced), &used, 0)
: Atrac9Decode(handle, data + offset, reinterpret_cast<short*>(pcm + result.produced), &used, 0);
if (status != 0 || used <= 0 || offset + static_cast<std::size_t>(used) > superframeSize) {
Reset();
return {DecodeStatus::InvalidData};
}
offset += static_cast<std::size_t>(used);
result.produced += frameBytes;
}
result.consumed = superframeSize;
result.channels = static_cast<std::uint32_t>(info.channels);
result.sampleRate = static_cast<std::uint32_t>(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<std::uint8_t>(word >> 6 & 3);
header.modeExtension = static_cast<std::uint8_t>(word >> 4 & 3);
header.copyright = static_cast<std::uint8_t>(word >> 3 & 1);
header.original = static_cast<std::uint8_t>(word >> 2 & 1);
header.emphasis = static_cast<std::uint8_t>(word & 3);
return true;
}
std::uint32_t AacSampleRate(std::uint32_t index) {
return index < 13 ? AAC_SAMPLE_RATES[index] : 0;
}
std::unique_ptr<Decoder> CreateMp3(std::int32_t wordSize) {
return std::make_unique<Mp3Decoder>(wordSize);
}
std::unique_ptr<Decoder> CreateAac(std::int32_t wordSize, bool adts, std::uint32_t samplingFreqIndex, std::uint32_t channels) {
std::vector<std::uint8_t> config;
if (!adts) {
const std::uint32_t lowComplexity = 2;
config = {static_cast<std::uint8_t>(lowComplexity << 3 | samplingFreqIndex >> 1),
static_cast<std::uint8_t>((samplingFreqIndex & 1) << 7 | channels << 3)};
}
return std::make_unique<AacDecoder>(wordSize, adts, std::move(config));
}
std::unique_ptr<Decoder> CreateAt9(std::int32_t wordSize, const std::uint8_t (&config)[4], At9Format& format) {
if (!ValidAt9Config(config)) return nullptr;
auto decoder = std::make_unique<At9Decoder>(wordSize, config);
const auto& info = decoder->Info();
format = {static_cast<std::uint32_t>(info.channels), static_cast<std::uint32_t>(info.samplingRate), static_cast<std::uint32_t>(info.superframeSize),
static_cast<std::uint32_t>(info.framesInSuperframe), static_cast<std::uint32_t>(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;
}
}

View File

@ -0,0 +1,65 @@
#ifndef CORE_LIBS_PRX_LIBSCEAUDIODEC_SRC_CODECS_HPP
#define CORE_LIBS_PRX_LIBSCEAUDIODEC_SRC_CODECS_HPP
#include <cstddef>
#include <cstdint>
#include <memory>
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<Decoder> CreateMp3(std::int32_t wordSize);
std::unique_ptr<Decoder> CreateAac(std::int32_t wordSize, bool adts, std::uint32_t samplingFreqIndex, std::uint32_t channels);
std::unique_ptr<Decoder> CreateAt9(std::int32_t wordSize, const std::uint8_t (&config)[4], At9Format& format);
}
#endif

View File

@ -0,0 +1,235 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <vector>
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<std::int32_t>(0x807F0000);
constexpr std::int32_t errorInvalidType = static_cast<std::int32_t>(0x807F0001);
constexpr std::int32_t errorArg = static_cast<std::int32_t>(0x807F0002);
constexpr std::int32_t errorInvalidParamSize = static_cast<std::int32_t>(0x807F0004);
constexpr std::int32_t errorInvalidBsiInfoSize = static_cast<std::int32_t>(0x807F0005);
constexpr std::int32_t errorInvalidCtrlPointer = static_cast<std::int32_t>(0x807F0008);
constexpr std::int32_t errorInvalidAuInfoPointer = static_cast<std::int32_t>(0x807F000B);
constexpr std::int32_t errorInvalidHandle = static_cast<std::int32_t>(0x807F000F);
constexpr std::int32_t errorInvalidWordLength = static_cast<std::int32_t>(0x807F0010);
constexpr std::int32_t errorInvalidAuSize = static_cast<std::int32_t>(0x807F0011);
constexpr std::int32_t errorInvalidPcmSize = static_cast<std::int32_t>(0x807F0012);
constexpr std::int32_t errorInvalidConfigNumber = static_cast<std::int32_t>(0x807F0303);
constexpr std::int32_t errorInvalidAt9Config = static_cast<std::int32_t>(0x807F1000);
struct Streams {
AudiodecAuInfo au{sizeof(AudiodecAuInfo), nullptr, 0};
AudiodecPcmItem pcm{sizeof(AudiodecPcmItem), nullptr, 0};
std::vector<std::uint8_t> output = std::vector<std::uint8_t>(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<std::uint8_t*>(data);
streams.au.ui_au_size = static_cast<std::uint32_t>(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<std::size_t>((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{&param, &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<const std::int16_t*>(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{&param, &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<const float*>(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{&param, &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<std::uint8_t> 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);
}

View File

@ -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.