Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 96 additions & 2 deletions core/libs/prx/libSceAjm.native/src/Ajm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ struct Instance {
AVCodecContext* opus = nullptr;
std::uint32_t opusChannels = 0;
std::uint32_t opusMappingFamily = 0;
std::uint32_t opusStreams = 0;
std::uint32_t opusCoupled = 0;
bool opusLayoutPending = false;
std::uint32_t opusSampleRate = 0;
std::vector<std::uint8_t> opusPending;
Resampler resampler;
Expand Down Expand Up @@ -374,12 +377,13 @@ std::int32_t InitializeInstance(Instance& instance, const std::uint8_t* paramete
std::uint32_t mappingFamily = 0;
if (size >= 12) std::memcpy(&mappingFamily, parameters + 8, sizeof(mappingFamily));
if (sampleRate != 48000) NotImplemented_nid_no_patch("sceAjmBatchJobInitialize (Opus sample rate other than 48000)");
if (mappingFamily > 1) NotImplemented_nid_no_patch("sceAjmBatchJobInitialize (Opus mapping family other than 0 or 1)");
if (mappingFamily > 1 && mappingFamily != 255) NotImplemented_nid_no_patch("sceAjmBatchJobInitialize (Opus mapping family other than 0, 1 or 255)");
if (mappingFamily == 0 && channels > 2) return AJM_RESULT_INVALID_PARAMETER;
instance.opusChannels = channels;
instance.opusMappingFamily = mappingFamily;
instance.opusSampleRate = sampleRate;
OpenOpus(instance);
instance.opusLayoutPending = mappingFamily == 255 && channels > 1;
if (!instance.opusLayoutPending) OpenOpus(instance);
instance.opusPending.clear();
instance.initialized = true;
instance.totalDecodedSamples = 0;
Expand Down Expand Up @@ -953,6 +957,21 @@ void OpenOpus(Instance& instance) {
if (!instance.opus) throw std::runtime_error("AJM: cannot allocate the FFmpeg Opus decoder");
av_channel_layout_default(&instance.opus->ch_layout, static_cast<int>(instance.opusChannels));
instance.opus->sample_rate = 48000;
if (instance.opusMappingFamily == 255 && instance.opusChannels > 1) {
instance.opus->extradata_size = static_cast<int>(21 + instance.opusChannels);
instance.opus->extradata = static_cast<std::uint8_t*>(av_mallocz(instance.opus->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE));
if (!instance.opus->extradata) throw std::bad_alloc();
auto* header = instance.opus->extradata;
std::memcpy(header, "OpusHead", 8);
header[8] = 1;
header[9] = static_cast<std::uint8_t>(instance.opusChannels);
header[12] = 0x80;
header[13] = 0xbb;
header[18] = 255;
header[19] = static_cast<std::uint8_t>(instance.opusStreams);
header[20] = static_cast<std::uint8_t>(instance.opusCoupled);
for (std::uint32_t channel = 0; channel < instance.opusChannels; ++channel) header[21 + channel] = static_cast<std::uint8_t>(channel);
}
if (instance.opusMappingFamily == 1) {
static constexpr std::uint8_t streams[8] = {1, 1, 2, 2, 3, 4, 4, 5};
static constexpr std::uint8_t coupled[8] = {0, 1, 1, 2, 2, 2, 3, 3};
Expand Down Expand Up @@ -999,6 +1018,80 @@ std::size_t OpusPacketSamples(const std::uint8_t* packet, std::size_t size) {
return frames * frameSamples;
}


std::size_t OpusLength(const std::uint8_t* data, std::size_t size, std::size_t& at) {
if (at >= size) return SIZE_MAX;
const std::size_t first = data[at++];
if (first < 252) return first;
if (at >= size) return SIZE_MAX;
return first + 4u * data[at++];
}

std::size_t OpusSelfDelimitedBytes(const std::uint8_t* data, std::size_t size) {
if (size < 1) return SIZE_MAX;
const std::uint32_t code = data[0] & 3u;
std::size_t at = 1;
if (code == 0) {
const std::size_t frame = OpusLength(data, size, at);
return frame == SIZE_MAX ? SIZE_MAX : at + frame;
}
if (code == 1) {
const std::size_t frame = OpusLength(data, size, at);
return frame == SIZE_MAX ? SIZE_MAX : at + 2 * frame;
}
if (code == 2) {
const std::size_t first = OpusLength(data, size, at);
const std::size_t second = OpusLength(data, size, at);
return first == SIZE_MAX || second == SIZE_MAX ? SIZE_MAX : at + first + second;
}
if (at >= size) return SIZE_MAX;
const std::uint8_t count = data[at++];
const std::size_t frames = count & 0x3fu;
if (frames == 0) return SIZE_MAX;
std::size_t padding = 0;
if ((count & 0x40u) != 0) {
for (;;) {
if (at >= size) return SIZE_MAX;
const std::uint8_t value = data[at++];
padding += value == 255 ? 254 : value;
if (value != 255) break;
}
}
std::size_t total = 0;
if ((count & 0x80u) != 0) {
for (std::size_t frame = 0; frame < frames; ++frame) {
const std::size_t bytes = OpusLength(data, size, at);
if (bytes == SIZE_MAX) return SIZE_MAX;
total += bytes;
}
} else {
const std::size_t bytes = OpusLength(data, size, at);
if (bytes == SIZE_MAX) return SIZE_MAX;
total = bytes * frames;
}
return at + total + padding;
}

void OpenOpusFromPacket(Instance& instance, const std::uint8_t* data, std::size_t size) {
std::uint32_t streams = 0;
std::uint32_t coupled = 0;
std::size_t offset = 0;
while (offset < size) {
if ((data[offset] & 0x04u) != 0) ++coupled;
++streams;
const std::uint32_t channels = coupled * 2 + (streams - coupled);
if (channels >= instance.opusChannels) break;
const std::size_t bytes = OpusSelfDelimitedBytes(data + offset, size - offset);
if (bytes == SIZE_MAX || bytes >= size - offset) break;
offset += bytes;
}
if (coupled * 2 + (streams - coupled) != instance.opusChannels) throw std::runtime_error("AJM Opus: a mapping family 255 packet's " + std::to_string(streams) + " streams (" + std::to_string(coupled) + " stereo) do not make its " + std::to_string(instance.opusChannels) + " channels");
instance.opusStreams = streams;
instance.opusCoupled = coupled;
instance.opusLayoutPending = false;
OpenOpus(instance);
}

void RunOpus(Instance& instance, const JobHeader& job, const AjmBuffer* inputs, const AjmBuffer* outputs) {
const JobInput input(job, inputs);
PcmOutputs pcmOutputs(outputs, job.outputCount);
Expand Down Expand Up @@ -1048,6 +1141,7 @@ void RunOpus(Instance& instance, const JobHeader& job, const AjmBuffer* inputs,
if (frames == 0 && pcmOutputs.produced == 0) result |= AJM_RESULT_NOT_ENOUGH_ROOM;
break;
}
if (instance.opusLayoutPending) OpenOpusFromPacket(instance, data, bytes);
packet->data = const_cast<std::uint8_t*>(data);
packet->size = static_cast<int>(bytes);
if (avcodec_send_packet(instance.opus, packet.get()) < 0) {
Expand Down
112 changes: 112 additions & 0 deletions core/libs/tests/GuestAjm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,116 @@ bool Refused(std::uint32_t context, const AjmBatchInfo& info) {
return false;
}

void AppendOpusLength(std::vector<std::uint8_t>& out, std::size_t length) {
if (length < 252) {
out.push_back(static_cast<std::uint8_t>(length));
return;
}
out.push_back(static_cast<std::uint8_t>(252 + (length - 252) % 4));
out.push_back(static_cast<std::uint8_t>((length - 252) / 4));
}

std::vector<std::uint8_t> SelfDelimited(const std::uint8_t* packet, std::size_t bytes) {
const std::uint32_t code = packet[0] & 3u;
Require(code <= 1);
std::vector<std::uint8_t> out{packet[0]};
AppendOpusLength(out, code == 0 ? bytes - 1 : (bytes - 1) / 2);
out.insert(out.end(), packet + 1, packet + bytes);
return out;
}

void TestOpusMultistreamFamily255(std::uint32_t context) {
const bool stereo = (OPUS_STEREO[2] & 0x04u) != 0;
const std::uint32_t channels = stereo ? 4 : 2;
std::uint32_t mono = 0;
std::uint32_t multi = 0;
const std::uint32_t parameters[2][3] = {{stereo ? 2u : 1u, 48000, 0}, {channels, 48000, 255}};
std::uint32_t* instances[2] = {&mono, &multi};
for (std::size_t index = 0; index < 2; ++index) {
Require(sceAjmInstanceCreate(context, 24, 0, instances[index]) == 0);
std::vector<std::uint8_t> batch(4096);
AjmBatchInfo info{};
std::int64_t initResult[2] = {-1, -1};
Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0);
Require(sceAjmBatchJobInitialize(&info, *instances[index], parameters[index], sizeof(parameters[index]), initResult) == 0);
Submit(context, info);
Require(initResult[0] == 0);
}
const std::uint32_t streamChannels = channels / 2;
std::size_t offset = 0;
for (int packet = 0; packet < 3; ++packet) {
const std::size_t bytes = OPUS_STEREO[offset] | (std::size_t{OPUS_STEREO[offset + 1]} << 8u);
const std::uint8_t* data = OPUS_STEREO + offset + 2;
std::vector<std::uint8_t> joined = SelfDelimited(data, bytes);
joined.insert(joined.end(), data, data + bytes);
std::vector<std::uint8_t> framed{static_cast<std::uint8_t>(joined.size()), static_cast<std::uint8_t>(joined.size() >> 8u)};
framed.insert(framed.end(), joined.begin(), joined.end());
std::vector<std::int16_t> single(960 * streamChannels, -12345);
std::vector<std::int16_t> both(960 * channels, -12345);
DecodeSideband sideband{};
RunDecode(context, mono, OPUS_STEREO + offset, 2 + bytes, single.data(), single.size() * sizeof(std::int16_t), sideband);
Require(sideband.result == 0);
RunDecode(context, multi, framed.data(), framed.size(), both.data(), both.size() * sizeof(std::int16_t), sideband);
Require(sideband.result == 0 && static_cast<std::size_t>(sideband.inputConsumed) == framed.size() && sideband.outputWritten == 960 * channels * 2);
for (std::size_t sample = 0; sample < 960; ++sample) {
for (std::uint32_t channel = 0; channel < streamChannels; ++channel) {
Require(both[sample * channels + channel] == single[sample * streamChannels + channel]);
Require(both[sample * channels + streamChannels + channel] == single[sample * streamChannels + channel]);
}
}
offset += 2 + bytes;
}
Require(sceAjmInstanceDestroy(context, mono) == 0);
Require(sceAjmInstanceDestroy(context, multi) == 0);
}

void TestOpusMonoFamily255(std::uint32_t context) {
std::uint32_t instances[2] = {};
for (std::size_t index = 0; index < 2; ++index) {
Require(sceAjmInstanceCreate(context, 24, 0, &instances[index]) == 0);
const std::uint32_t parameters[3] = {1, 48000, index == 0 ? 0u : 255u};
std::vector<std::uint8_t> batch(4096);
AjmBatchInfo info{};
std::int64_t initResult[2] = {-1, -1};
Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0);
Require(sceAjmBatchJobInitialize(&info, instances[index], parameters, sizeof(parameters), initResult) == 0);
Submit(context, info);
Require(initResult[0] == 0);
}
std::size_t offset = 0;
int peak = 0;
for (int packet = 0; packet < 3; ++packet) {
const std::size_t bytes = OPUS_STEREO[offset] | (std::size_t{OPUS_STEREO[offset + 1]} << 8u);
std::vector<std::int16_t> pcm[2] = {std::vector<std::int16_t>(960, -12345), std::vector<std::int16_t>(960, -12345)};
for (std::size_t index = 0; index < 2; ++index) {
DecodeSideband sideband{};
RunDecode(context, instances[index], OPUS_STEREO + offset, 2 + bytes, pcm[index].data(), pcm[index].size() * sizeof(std::int16_t), sideband);
Require(sideband.result == 0 && static_cast<std::size_t>(sideband.inputConsumed) == 2 + bytes && sideband.outputWritten == 960 * 2);
Require(sideband.totalDecodedSamples == static_cast<std::uint64_t>(packet + 1) * 960);
}
Require(pcm[0] == pcm[1]);
if (packet > 0) {
for (const std::int16_t sample : pcm[1]) peak = std::max(peak, std::abs(static_cast<int>(sample)));
}
offset += 2 + bytes;
}
Require(peak > 900 && peak < 1200);

const std::uint32_t refusedParameters[1][3] = {{1, 48000, 2}};
for (const auto& parameters : refusedParameters) {
std::uint32_t instance = 0;
Require(sceAjmInstanceCreate(context, 24, 0, &instance) == 0);
std::vector<std::uint8_t> batch(4096);
AjmBatchInfo info{};
std::int64_t initResult[2] = {-1, -1};
Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0);
Require(sceAjmBatchJobInitialize(&info, instance, parameters, sizeof(parameters), initResult) == 0);
Require(Refused(context, info));
Require(sceAjmInstanceDestroy(context, instance) == 0);
}
for (const std::uint32_t instance : instances) Require(sceAjmInstanceDestroy(context, instance) == 0);
}

void TestDecodeSingle(std::uint32_t context) {
std::uint32_t instance = 0;
Require(sceAjmInstanceCreate(context, 0, 0, &instance) == 0);
Expand Down Expand Up @@ -1245,6 +1355,8 @@ int main() {
TestMp3(context);
TestOpus(context);
TestOpus(context, 1);
TestOpusMonoFamily255(context);
TestOpusMultistreamFamily255(context);
TestOpusFamilyZeroChannels(context);
TestOpusSurround(context, 3, 0);
for (std::size_t stream = 0; stream < 2; ++stream) {
Expand Down
Loading
Loading