mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									106364e01e
								
							
						
					
					
						commit
						aa6a29d7e1
					
				
					 5 changed files with 388 additions and 4 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										379
									
								
								src/tests/audio_core/hle/source.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										379
									
								
								src/tests/audio_core/hle/source.cpp
									
										
									
									
									
										Normal 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); | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue