AudioCore/HLE/source: Partially implement last_buffer_id (#7397)

* AudioCore/HLE/source: Partially implement last_buffer_id

shared_memory.h: fix typo

* tests\audio_core\hle\source.cpp: Add test cases to verify last_buffer_id
This commit is contained in:
SachinVin 2024-02-05 23:24:13 +05:30 committed by GitHub
parent 106364e01e
commit aa6a29d7e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 388 additions and 4 deletions

View file

@ -316,7 +316,7 @@ struct SourceStatus {
u16_le sync_count; ///< Is set by the DSP to the value of SourceConfiguration::sync_count
u32_dsp buffer_position; ///< Number of samples into the current buffer
u16_le current_buffer_id; ///< Updated when a buffer finishes playing
INSERT_PADDING_DSPWORDS(1);
u16_le last_buffer_id; ///< Updated when all buffers in the queue finish playing
};
Status status[num_sources];

View file

@ -324,6 +324,7 @@ void Source::GenerateFrame() {
if (state.current_buffer.empty() && !DequeueBuffer()) {
state.enabled = false;
state.buffer_update = true;
state.last_buffer_id = state.current_buffer_id;
state.current_buffer_id = 0;
return;
}
@ -411,6 +412,7 @@ bool Source::DequeueBuffer() {
state.next_sample_number = state.current_sample_number;
state.current_buffer_physical_address = buf.physical_address;
state.current_buffer_id = buf.buffer_id;
state.last_buffer_id = 0;
state.buffer_update = buf.from_queue && !buf.has_played;
if (buf.is_looping) {
@ -432,9 +434,10 @@ SourceStatus::Status Source::GetCurrentStatus() {
ret.is_enabled = state.enabled;
ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
state.buffer_update = false;
ret.current_buffer_id = state.current_buffer_id;
ret.buffer_position = state.current_sample_number;
ret.sync_count = state.sync_count;
ret.buffer_position = state.current_sample_number;
ret.current_buffer_id = state.current_buffer_id;
ret.last_buffer_id = state.last_buffer_id;
return ret;
}

View file

@ -143,7 +143,8 @@ private:
// buffer_id state
bool buffer_update = false;
u32 current_buffer_id = 0;
u16 last_buffer_id = 0;
u16 current_buffer_id = 0;
// Decoding state

View file

@ -9,6 +9,7 @@ add_executable(tests
core/memory/vm_manager.cpp
precompiled_headers.h
audio_core/hle/hle.cpp
audio_core/hle/source.cpp
audio_core/lle/lle.cpp
audio_core/audio_fixures.h
audio_core/decoder_tests.cpp

View file

@ -0,0 +1,379 @@
#include <cstdio>
#include <catch2/catch_template_test_macros.hpp>
#include "audio_core/hle/shared_memory.h"
#include "common/settings.h"
#include "tests/audio_core/merryhime_3ds_audio/merry_audio/merry_audio.h"
TEST_CASE_METHOD(MerryAudio::MerryAudioFixture, "Verify SourceStatus::Status::last_buffer_id 1",
"[audio_core][hle]") {
// World's worst triangle wave generator.
// Generates PCM16.
auto fillBuffer = [this](u32* audio_buffer, size_t size, unsigned freq) {
for (size_t i = 0; i < size; i++) {
u32 data = (i % freq) * 256;
audio_buffer[i] = (data << 16) | (data & 0xFFFF);
}
DSP_FlushDataCache(audio_buffer, size);
};
constexpr size_t NUM_SAMPLES = 160 * 1;
u32* audio_buffer = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer, NUM_SAMPLES, 160);
u32* audio_buffer2 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer2, NUM_SAMPLES, 80);
u32* audio_buffer3 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer3, NUM_SAMPLES, 40);
MerryAudio::AudioState state;
{
std::vector<u8> dspfirm;
SECTION("HLE") {
// The test case assumes HLE AudioCore doesn't require a valid firmware
InitDspCore(Settings::AudioEmulation::HLE);
dspfirm = {0};
}
SECTION("LLE Sanity") {
InitDspCore(Settings::AudioEmulation::LLE);
dspfirm = loadDspFirmFromFile();
}
if (!dspfirm.size()) {
SKIP("Couldn't load firmware\n");
return;
}
auto ret = audioInit(dspfirm);
if (!ret) {
INFO("Couldn't init audio\n");
goto end;
}
state = *ret;
}
state.waitForSync();
initSharedMem(state);
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
{
u16 buffer_id = 0;
size_t next_queue_position = 0;
state.write().source_configurations->config[0].play_position = 0;
state.write().source_configurations->config[0].physical_address =
osConvertVirtToPhys(audio_buffer3);
state.write().source_configurations->config[0].length = NUM_SAMPLES;
state.write().source_configurations->config[0].mono_or_stereo.Assign(
AudioCore::HLE::SourceConfiguration::Configuration::MonoOrStereo::Stereo);
state.write().source_configurations->config[0].format.Assign(
AudioCore::HLE::SourceConfiguration::Configuration::Format::PCM16);
state.write().source_configurations->config[0].fade_in.Assign(false);
state.write().source_configurations->config[0].adpcm_dirty.Assign(false);
state.write().source_configurations->config[0].is_looping.Assign(false);
state.write().source_configurations->config[0].buffer_id = ++buffer_id;
state.write().source_configurations->config[0].partial_reset_flag.Assign(true);
state.write().source_configurations->config[0].play_position_dirty.Assign(true);
state.write().source_configurations->config[0].embedded_buffer_dirty.Assign(true);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write().source_configurations->config[0].buffers[next_queue_position].length =
NUM_SAMPLES;
state.write().source_configurations->config[0].buffers[next_queue_position].adpcm_dirty =
false;
state.write().source_configurations->config[0].buffers[next_queue_position].is_looping =
false;
state.write().source_configurations->config[0].buffers[next_queue_position].buffer_id =
++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
state.notifyDsp();
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
if (!state.read().source_statuses->status[0].is_enabled) {
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
}
if (state.read().source_statuses->status[0].current_buffer_id_dirty) {
if (state.read().source_statuses->status[0].current_buffer_id == buffer_id ||
state.read().source_statuses->status[0].current_buffer_id == 0) {
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address =
osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.length = NUM_SAMPLES;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.adpcm_dirty = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.is_looping = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.buffer_id = ++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |=
1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
}
}
state.notifyDsp();
}
// current_buffer_id should be 0 if the queue is not empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0);
// Let the queue finish playing
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
state.notifyDsp();
}
// TODO: There seems to be some nuances with how the LLE firmware runs the buffer queue,
// that differs from the HLE implementation
// REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 5);
// current_buffer_id should be equal to buffer_id once the queue is empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id);
}
end:
audioExit(state);
}
TEST_CASE_METHOD(MerryAudio::MerryAudioFixture, "Verify SourceStatus::Status::last_buffer_id 2",
"[audio_core][hle]") {
// World's worst triangle wave generator.
// Generates PCM16.
auto fillBuffer = [this](u32* audio_buffer, size_t size, unsigned freq) {
for (size_t i = 0; i < size; i++) {
u32 data = (i % freq) * 256;
audio_buffer[i] = (data << 16) | (data & 0xFFFF);
}
DSP_FlushDataCache(audio_buffer, size);
};
constexpr size_t NUM_SAMPLES = 160 * 1;
u32* audio_buffer = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer, NUM_SAMPLES, 160);
u32* audio_buffer2 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer2, NUM_SAMPLES, 80);
u32* audio_buffer3 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer3, NUM_SAMPLES, 40);
MerryAudio::AudioState state;
{
std::vector<u8> dspfirm;
SECTION("HLE") {
// The test case assumes HLE AudioCore doesn't require a valid firmware
InitDspCore(Settings::AudioEmulation::HLE);
dspfirm = {0};
}
SECTION("LLE Sanity") {
InitDspCore(Settings::AudioEmulation::LLE);
dspfirm = loadDspFirmFromFile();
}
if (!dspfirm.size()) {
SKIP("Couldn't load firmware\n");
return;
}
auto ret = audioInit(dspfirm);
if (!ret) {
INFO("Couldn't init audio\n");
goto end;
}
state = *ret;
}
state.waitForSync();
initSharedMem(state);
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
{
u16 buffer_id = 0;
size_t next_queue_position = 0;
state.write().source_configurations->config[0].play_position = 0;
state.write().source_configurations->config[0].physical_address =
osConvertVirtToPhys(audio_buffer3);
state.write().source_configurations->config[0].length = NUM_SAMPLES;
state.write().source_configurations->config[0].mono_or_stereo.Assign(
AudioCore::HLE::SourceConfiguration::Configuration::MonoOrStereo::Stereo);
state.write().source_configurations->config[0].format.Assign(
AudioCore::HLE::SourceConfiguration::Configuration::Format::PCM16);
state.write().source_configurations->config[0].fade_in.Assign(false);
state.write().source_configurations->config[0].adpcm_dirty.Assign(false);
state.write().source_configurations->config[0].is_looping.Assign(false);
state.write().source_configurations->config[0].buffer_id = ++buffer_id;
state.write().source_configurations->config[0].partial_reset_flag.Assign(true);
state.write().source_configurations->config[0].play_position_dirty.Assign(true);
state.write().source_configurations->config[0].embedded_buffer_dirty.Assign(true);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write().source_configurations->config[0].buffers[next_queue_position].length =
NUM_SAMPLES;
state.write().source_configurations->config[0].buffers[next_queue_position].adpcm_dirty =
false;
state.write().source_configurations->config[0].buffers[next_queue_position].is_looping =
false;
state.write().source_configurations->config[0].buffers[next_queue_position].buffer_id =
++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
state.notifyDsp();
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
if (!state.read().source_statuses->status[0].is_enabled) {
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
}
if (state.read().source_statuses->status[0].current_buffer_id_dirty) {
if (state.read().source_statuses->status[0].current_buffer_id == buffer_id ||
state.read().source_statuses->status[0].current_buffer_id == 0) {
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address =
osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.length = NUM_SAMPLES;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.adpcm_dirty = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.is_looping = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.buffer_id = ++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |=
1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
}
}
state.notifyDsp();
}
// current_buffer_id should be 0 if the queue is not empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0);
// Let the queue finish playing
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
state.notifyDsp();
}
// TODO: There seems to be some nuances with how the LLE firmware runs the buffer queue,
// that differs from the HLE implementation
// REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 5);
// current_buffer_id should be equal to buffer_id once the queue is empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id);
// Restart Playing
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
if (!state.read().source_statuses->status[0].is_enabled) {
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
}
if (state.read().source_statuses->status[0].current_buffer_id_dirty) {
if (state.read().source_statuses->status[0].current_buffer_id == buffer_id ||
state.read().source_statuses->status[0].current_buffer_id == 0) {
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address =
osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.length = NUM_SAMPLES;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.adpcm_dirty = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.is_looping = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.buffer_id = ++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |=
1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
}
}
state.notifyDsp();
}
// current_buffer_id should be 0 if the queue is not empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0);
// Let the queue finish playing
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
state.notifyDsp();
}
// current_buffer_id should be equal to buffer_id once the queue is empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id);
}
end:
audioExit(state);
}