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
10 changes: 10 additions & 0 deletions core/libs/prx/libSceAudioOut/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ target_link_libraries(libSceAudioOut PRIVATE libc libkernel SDL2-static)
configure_windows_unwind(libSceAudioOut)

if(BUILD_TESTING)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_test_executable(audio_out_resampling_tests tests/AudioOutResampling.cpp)
target_include_directories(audio_out_resampling_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR})
target_link_libraries(audio_out_resampling_tests PRIVATE libSceAudioOut libc)
set_target_properties(audio_out_resampling_tests PROPERTIES ENABLE_EXPORTS ON)
add_test(NAME audio_out_resampling COMMAND audio_out_resampling_tests)
set_tests_properties(audio_out_resampling PROPERTIES TIMEOUT 10)

endif()

add_test_executable(audio_out_initialization_tests tests/AudioOutInitialization.cpp src/AudioOutSdl.cpp)
target_include_directories(audio_out_initialization_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR})
target_link_libraries(audio_out_initialization_tests PRIVATE libc)
Expand Down
27 changes: 2 additions & 25 deletions core/libs/prx/libSceAudioOut/src/AudioOut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ static bool openDevice(Port& port) {
desired.samples = static_cast<Uint16>(port.samplesNum);
desired.callback = nullptr;
SDL_AudioSpec obtained{};
port.device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE);
port.device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0);
if (port.device == 0) {
return false;
}
Expand Down Expand Up @@ -221,31 +221,8 @@ static void queueAudio(Port& port, const void* data, std::unique_lock<std::mutex
const std::uint32_t preparedSize = port.samplesNum *
static_cast<std::uint32_t>(port.channels) * bytesPerSample(port.format);

SDL_AudioCVT cvt{};
const int cvtResult = SDL_BuildAudioCVT(
&cvt,
sdlFormat(port.format), static_cast<Uint8>(port.channels), static_cast<int>(port.freq),
port.spec.format, port.spec.channels, port.spec.freq);

if (cvtResult < 0) {
throw std::runtime_error(std::string("SDL_BuildAudioCVT: ") + SDL_GetError());
}

const void* queueData = prepared;
std::uint32_t queueSize = preparedSize;
std::vector<std::uint8_t> convertBuf;

if (cvtResult > 0) {
convertBuf.resize(static_cast<std::size_t>(preparedSize) * cvt.len_mult);
std::memcpy(convertBuf.data(), prepared, preparedSize);
cvt.buf = convertBuf.data();
cvt.len = static_cast<int>(preparedSize);
if (SDL_ConvertAudio(&cvt) < 0) {
throw std::runtime_error(std::string("SDL_ConvertAudio: ") + SDL_GetError());
}
queueData = cvt.buf;
queueSize = static_cast<std::uint32_t>(cvt.len_cvt);
}
const std::uint32_t queueSize = preparedSize;

const std::uint64_t bufferUs = port.freq != 0
? (1000000ULL * port.samplesNum) / port.freq
Expand Down
103 changes: 103 additions & 0 deletions core/libs/prx/libSceAudioOut/tests/AudioOutResampling.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include "SDL.h"
#include "prx/libc/include/general/VabiMacros.hpp"

#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <vector>

extern "C" {
int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t);
int APS5_VABI sceAudioOutOutput(int, const void*);
int APS5_VABI sceAudioOutClose(int);
}

namespace {

constexpr std::uint32_t grain = 256;
constexpr int hostRate = 48000;
SDL_AudioStream* conversion = nullptr;
std::vector<float> played;

void Require(bool value, const char* message) {
if (value) return;
std::fprintf(stderr, "%s\n", message);
std::abort();
}

void Collect() {
const int bytes = SDL_AudioStreamAvailable(conversion);
Require(bytes >= 0 && bytes % static_cast<int>(sizeof(float)) == 0, "invalid converted size");
const auto first = played.size();
played.resize(first + bytes / sizeof(float));
if (bytes != 0) Require(SDL_AudioStreamGet(conversion, played.data() + first, bytes) == bytes, "stream read failed");
}

void TestBoundary(std::uint32_t sourceRate) {
played.clear();
const int port = sceAudioOutOpen(0, 0, 0, grain, sourceRate, 4);
Require(port > 0, "port creation failed");
std::vector<float> data(grain * 2, 0.0f);
data[(grain - 1) * 2] = 0.5f;
data[(grain - 1) * 2 + 1] = 0.25f;
Require(sceAudioOutOutput(port, data.data()) == static_cast<int>(grain), "first output failed");
data.assign(grain * 2, 0.0f);
for (int block = 0; block < 8; block++) Require(sceAudioOutOutput(port, data.data()) == static_cast<int>(grain), "trailing output failed");
Require(sceAudioOutOutput(port, nullptr) == static_cast<int>(grain), "drain failed");
Require(sceAudioOutClose(port) == 0, "close failed");
const auto boundary = static_cast<std::size_t>(std::ceil(static_cast<double>(grain) * hostRate / sourceRate));
Require(played.size() / 2 > boundary + 16, "resampled tail missing");
double tailEnergy = 0.0;
for (std::size_t frame = boundary; frame < boundary + 16; frame++) {
const float left = played[frame * 2];
const float right = played[frame * 2 + 1];
Require(std::isfinite(left) && std::isfinite(right), "nonfinite resampled output");
Require(std::abs(left - 2.0f * right) < 1e-6f, "stereo channels changed during conversion");
tailEnergy += static_cast<double>(left) * left;
}
Require(tailEnergy > 1e-7, "resampling discarded the impulse response at the grain boundary");
}

}

extern "C" int SDLCALL SDL_InitSubSystem(Uint32) { return 0; }
extern "C" Uint32 SDLCALL SDL_WasInit(Uint32) { return SDL_INIT_AUDIO; }
extern "C" void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID, int) {}
extern "C" Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID) { return 0; }
extern "C" void SDLCALL SDL_ClearQueuedAudio(SDL_AudioDeviceID) {}

extern "C" SDL_AudioDeviceID SDLCALL SDL_OpenAudioDevice(const char*, int, const SDL_AudioSpec* desired, SDL_AudioSpec* obtained, int changes) {
*obtained = *desired;
if ((changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) != 0) obtained->freq = hostRate;
else {
conversion = SDL_NewAudioStream(desired->format, desired->channels, desired->freq, AUDIO_F32SYS, 2, hostRate);
Require(conversion != nullptr, "host conversion stream creation failed");
}
return 2;
}

extern "C" int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID, const void* data, Uint32 size) {
if (conversion != nullptr) {
if (SDL_AudioStreamPut(conversion, data, static_cast<int>(size)) < 0) return -1;
Collect();
} else {
const auto* samples = static_cast<const float*>(data);
played.insert(played.end(), samples, samples + size / sizeof(float));
}
return 0;
}

extern "C" void SDLCALL SDL_CloseAudioDevice(SDL_AudioDeviceID) {
if (conversion == nullptr) return;
Require(SDL_AudioStreamFlush(conversion) == 0, "host conversion flush failed");
Collect();
SDL_FreeAudioStream(conversion);
conversion = nullptr;
}

int main() {
TestBoundary(32000);
TestBoundary(44100);
return 0;
}
Loading