feat(libSceAudiodec): decode M4AAC with non-delay output enabled

The 28- and 32-byte M4AAC parameter blocks carry two fields at offsets
24 and 28 whose range errors fpPS4 names
SCE_AUDIODEC_ERROR_M4AAC_INVALID_ENABLE_NONDELAY_OUTPUT (0x807F0305) and
SCE_AUDIODEC_ERROR_M4AAC_INVALID_SURROUND_CHANNEL_INTERLEAVE_ORDER
(0x807F0306). shadPS4's AJM AAC decoder outputs every decoded frame from
the first one on when non-delay output is enabled, which is what the
decoder here does, so a decoder created with non-delay output 1 is
accepted and decodes as before. Surround channel interleave orders 1
and 2 still throw, now naming the order: no source says which order
each value selects. Name both errors and test that every access unit
yields its frame from the first one with non-delay output enabled.
This commit is contained in:
Yunus Shaikh committed 2026-10-28 16:03:59 +00:00
1 parent f8f5b24b77
commit b3b6178347
3 files changed
+32 -27

No files matched your search

+10 -7
View File
@@ -6,6 +6,7 @@
#include <stdexcept>
#include <memory>
#include <mutex>
#include <string>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libSceAudiodec/src/Codecs.hpp"
@@ -34,6 +35,8 @@ constexpr std::int32_t ERROR_M4AAC_INVALID_SAMPLING_FREQ = static_cast<std::int3
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_M4AAC_INVALID_ENABLE_NONDELAY_OUTPUT = static_cast<std::int32_t>(0x807F0305);
constexpr std::int32_t ERROR_M4AAC_INVALID_SURROUND_CHANNEL_INTERLEAVE_ORDER = static_cast<std::int32_t>(0x807F0306);
constexpr std::int32_t ERROR_AT9_INVALID_CONFIG_DATA = static_cast<std::int32_t>(0x807F1000);
constexpr std::uint32_t TYPE_AT9 = 1;
@@ -136,14 +139,14 @@ void fillMp3Info(AudiodecMp3Info* info, const Audiodec::Mp3Header& header) {
}
std::int32_t createAac(const AudiodecParamM4aac* param, AudiodecM4aacInfo* info, Instance& instance) {
std::uint32_t extensionMode = 0;
std::uint32_t outputMode = 0;
std::uint32_t enableNondelayOutput = 0;
std::uint32_t surroundChannelInterleaveOrder = 0;
const auto* bytes = reinterpret_cast<const std::byte*>(param);
if (param->ui_size >= 28) std::memcpy(&extensionMode, bytes + 24, sizeof(extensionMode));
if (extensionMode > 1) return static_cast<std::int32_t>(0x807F0305);
if (param->ui_size == 32) std::memcpy(&outputMode, bytes + 28, sizeof(outputMode));
if (outputMode > 2) return static_cast<std::int32_t>(0x807F0306);
if (extensionMode != 0 || outputMode != 0) throw std::runtime_error("M4AAC extended decoding modes are not implemented");
if (param->ui_size >= 28) std::memcpy(&enableNondelayOutput, bytes + 24, sizeof(enableNondelayOutput));
if (enableNondelayOutput > 1) return ERROR_M4AAC_INVALID_ENABLE_NONDELAY_OUTPUT;
if (param->ui_size == 32) std::memcpy(&surroundChannelInterleaveOrder, bytes + 28, sizeof(surroundChannelInterleaveOrder));
if (surroundChannelInterleaveOrder > 2) return ERROR_M4AAC_INVALID_SURROUND_CHANNEL_INTERLEAVE_ORDER;
if (surroundChannelInterleaveOrder != 0) throw std::runtime_error("M4AAC surround channel interleave order " + std::to_string(surroundChannelInterleaveOrder) + " is not implemented");
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;
+21 -19
View File
@@ -6,7 +6,6 @@
#include <cstdlib>
#include <cstring>
#include <numbers>
#include <stdexcept>
#include <vector>
static void Require(bool value) { if (!value) std::abort(); }
@@ -231,8 +230,14 @@ 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, std::uint32_t maxChannels = 2) {
AudiodecParamM4aac param{sizeof(param), 1, adts ? 1u : 2u, 3, maxChannels, 0};
struct M4aacExtendedParam {
AudiodecParamM4aac base;
std::uint32_t enableNondelayOutput;
std::uint32_t surroundChannelInterleaveOrder;
};
void TestAac(bool adts, std::uint32_t maxChannels = 2, std::uint32_t paramSize = sizeof(AudiodecParamM4aac), std::uint32_t enableNondelayOutput = 0) {
M4aacExtendedParam param{{paramSize, 1, adts ? 1u : 2u, 3, maxChannels, 0}, enableNondelayOutput, 0};
AudiodecM4aacInfo info{sizeof(info), 0, 0, 0, 0};
AudiodecCtrl ctrl{&param, &info, nullptr, nullptr};
const std::int32_t handle = sceAudiodecCreateDecoder(&ctrl, 3);
@@ -419,11 +424,7 @@ int main() {
aac.ui_max_channels = 6;
aac.ui_enable_heaac = 1;
Require(sceAudiodecCreateDecoder(&aacCtrl, 3) == static_cast<std::int32_t>(0x807F0302));
struct ExtendedParam {
AudiodecParamM4aac base;
std::uint32_t extensionMode;
std::uint32_t outputMode;
} extended{{32, 1, 1, 3, 2, 0}, 0, 0};
M4aacExtendedParam extended{{32, 1, 1, 3, 2, 0}, 0, 0};
aacCtrl.pParam = &extended;
for (const std::uint32_t size : {25u, 27u, 29u, 31u, 33u}) {
extended.base.ui_size = size;
@@ -431,19 +432,17 @@ int main() {
}
for (const std::uint32_t size : {28u, 32u}) {
extended.base.ui_size = size;
extended.extensionMode = 2;
extended.enableNondelayOutput = 2;
Require(sceAudiodecCreateDecoder(&aacCtrl, 3) == static_cast<std::int32_t>(0x807F0305));
extended.extensionMode = 1;
bool threw = false;
try { sceAudiodecCreateDecoder(&aacCtrl, 3); }
catch (const std::runtime_error&) { threw = true; }
Require(threw);
extended.extensionMode = 0;
const auto extendedHandle = sceAudiodecCreateDecoder(&aacCtrl, 3);
Require(extendedHandle > 0);
Require(sceAudiodecDeleteDecoder(extendedHandle) == 0);
for (const std::uint32_t enableNondelayOutput : {0u, 1u}) {
extended.enableNondelayOutput = enableNondelayOutput;
const auto extendedHandle = sceAudiodecCreateDecoder(&aacCtrl, 3);
Require(extendedHandle > 0);
Require(sceAudiodecDeleteDecoder(extendedHandle) == 0);
}
extended.enableNondelayOutput = 0;
}
extended.outputMode = 3;
extended.surroundChannelInterleaveOrder = 3;
Require(sceAudiodecCreateDecoder(&aacCtrl, 3) == static_cast<std::int32_t>(0x807F0306));
const std::int32_t handle = sceAudiodecCreateDecoder(&ctrl, 2);
@@ -461,6 +460,9 @@ int main() {
TestAac(false);
TestAac(false, 6);
TestAac(false, 8);
TestAac(true, 2, 28, 1);
TestAac(false, 2, 32, 1);
TestAac(false, 8, 28, 1);
TestAacRaw51(6);
TestAacRaw51(8);
for (const bool adts : {true, false}) {
+1 -1
View File
@@ -2,7 +2,7 @@
### Unknown function info
- [M4AAC extended parameters](../../../core/libs/prx/libSceAudiodec/Export.cpp): 28-byte and 32-byte parameter variants add fields at offsets 24 and 28. Their accepted ranges are 0..1 and 0..2; nonzero modes throw until the corresponding decoder configuration is implemented. Zero extensions use the base decoding configuration.
- [M4AAC extended parameters](../../../core/libs/prx/libSceAudiodec/Export.cpp): 28-byte and 32-byte parameter variants add fields at offsets 24 and 28, named by their range errors (`0x807F0305` `SCE_AUDIODEC_ERROR_M4AAC_INVALID_ENABLE_NONDELAY_OUTPUT`, `0x807F0306` `SCE_AUDIODEC_ERROR_M4AAC_INVALID_SURROUND_CHANNEL_INTERLEAVE_ORDER`, in fpPS4's `ps4_libsceaudiodec.pas`). Non-delay output 1 decodes every access unit to its own PCM from the first one on, as shadPS4's AJM AAC decoder does with `EnableNondelayOutput`; without that flag shadPS4 holds back the first 2 frames. Non-delay output 0 still decodes the same way, with no frame held back: no measurement shows whether the console delays it. Surround channel interleave orders 1 and 2 throw: shadPS4 names two AJM orders (`ExtlExtrLsRs`, `LsRsExtlExtr`) but uses neither, and no source says which value selects which order or which order 0 is.
- [sceAudiodecDecode](../../../core/libs/prx/libSceAudiodec/src/Codecs.cpp) (libSceAudiodec) - a raw M4AAC stream carries no channel configuration, so the decoder tries the mono/stereo configuration and then configurations 3 to 7 within `ui_max_channels` until one decodes the first access unit, and keeps that one. No reference documents how the library picks the layout (shadPS4 maps the channel count straight to the configuration; SharpProspero only describes the maximum). With the stereo configuration a mono raw stream decodes as 2 channels, so 2 is reported, not 1
- [sceAudiodecDecode](../../../core/libs/prx/libSceAudiodec/src/Codecs.cpp) (libSceAudiodec) - the HE-AAC handling follows one console measurement of an HE-AAC v2 ADTS stream: SBR output at 48 kHz in one channel with `ui_enable_heaac` 1, the 24 kHz mono core with 0. Not measured: whether the console, like FFmpeg, decodes a stream without SBR data in its first access unit as AAC-LC with `ui_enable_heaac` 1, and what it reports in `ui_sampling_freq` and `ui_heaac` with `ui_enable_heaac` 0 (here the rate of the returned PCM and 0)