feat(libSceAudioOut): implement sceAudioOutSetMixLevelPadSpk
The mix level is a linear gain on the pad speaker port, 32768 being 0 dB. A pad speaker port opens at 11626 (-9 dB). An unknown handle, a port of another type and a level above 0 dB return the AudioOut error codes; a negative level throws, as its effect is unknown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2cf901bcff
commit
2452ffcb3f
|
|
@ -32,4 +32,14 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(audio_out2_pad_mix PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
|
||||
add_test_executable(audio_out_mix_level_pad_spk_tests tests/AudioOutMixLevelPadSpk.cpp)
|
||||
target_include_directories(audio_out_mix_level_pad_spk_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(audio_out_mix_level_pad_spk_tests PRIVATE libSceAudioOut libc)
|
||||
configure_windows_unwind(audio_out_mix_level_pad_spk_tests)
|
||||
add_test(NAME audio_out_mix_level_pad_spk COMMAND audio_out_mix_level_pad_spk_tests)
|
||||
if(WIN32)
|
||||
set_tests_properties(audio_out_mix_level_pad_spk PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAudioOut>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ static constexpr int PORT_TYPE_AUX = 127;
|
|||
|
||||
static constexpr int PORTS_MAX = 32;
|
||||
static constexpr int DEFAULT_VOLUME = 32768;
|
||||
static constexpr int DEFAULT_PADSPK_MIX_LEVEL = 11626;
|
||||
static constexpr std::uint32_t FORMAT_MASK = 0xFFu;
|
||||
static constexpr std::uint64_t TARGET_LATENCY_US = 40000;
|
||||
static constexpr std::uint64_t DRAIN_TIMEOUT_US = 200000;
|
||||
|
|
@ -86,6 +87,7 @@ struct Port {
|
|||
Format format = Format::Unknown;
|
||||
int channels = 0;
|
||||
int volume[8] = {};
|
||||
int mixLevel = DEFAULT_VOLUME;
|
||||
std::uint64_t lastOutputTime = 0;
|
||||
SDL_AudioDeviceID device = 0;
|
||||
SDL_AudioSpec spec = {};
|
||||
|
|
@ -143,7 +145,7 @@ static const void* prepareBuffer(const Port& port, const void* data, std::vector
|
|||
const auto bps = bytesPerSample(port.format);
|
||||
const auto size = frames * ch * bps;
|
||||
|
||||
bool volumeChanged = false;
|
||||
bool volumeChanged = port.mixLevel != DEFAULT_VOLUME;
|
||||
for (std::uint32_t i = 0; i < ch; i++) {
|
||||
if (port.volume[i] != DEFAULT_VOLUME) {
|
||||
volumeChanged = true;
|
||||
|
|
@ -161,11 +163,12 @@ static const void* prepareBuffer(const Port& port, const void* data, std::vector
|
|||
if (formatIsFloat(port.format)) {
|
||||
auto* dst = reinterpret_cast<float*>(buf.data());
|
||||
const auto* src = static_cast<const float*>(data);
|
||||
const float mixLevel = static_cast<float>(port.mixLevel) / static_cast<float>(DEFAULT_VOLUME);
|
||||
for (std::uint32_t fr = 0; fr < frames; fr++) {
|
||||
for (std::uint32_t c = 0; c < ch; c++) {
|
||||
const auto srcCh = isStd ? STD_8CH_MAP[c] : c;
|
||||
dst[fr * ch + c] = src[fr * ch + srcCh] *
|
||||
(static_cast<float>(port.volume[c]) / static_cast<float>(DEFAULT_VOLUME));
|
||||
(static_cast<float>(port.volume[c]) / static_cast<float>(DEFAULT_VOLUME)) * mixLevel;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -175,7 +178,7 @@ static const void* prepareBuffer(const Port& port, const void* data, std::vector
|
|||
for (std::uint32_t c = 0; c < ch; c++) {
|
||||
const auto srcCh = isStd ? STD_8CH_MAP[c] : c;
|
||||
std::int64_t s = static_cast<std::int64_t>(src[fr * ch + srcCh]) *
|
||||
port.volume[c] / DEFAULT_VOLUME;
|
||||
port.volume[c] * port.mixLevel / (static_cast<std::int64_t>(DEFAULT_VOLUME) * DEFAULT_VOLUME);
|
||||
s = std::clamp(s,
|
||||
static_cast<std::int64_t>(std::numeric_limits<std::int16_t>::min()),
|
||||
static_cast<std::int64_t>(std::numeric_limits<std::int16_t>::max()));
|
||||
|
|
@ -325,6 +328,7 @@ int APS5_VABI sceAudioOutOpen(int userId, int type, int index, std::uint32_t len
|
|||
port.format = format;
|
||||
port.channels = channelsForFormat(format);
|
||||
port.lastOutputTime = 0;
|
||||
port.mixLevel = type == PORT_TYPE_PADSPK ? DEFAULT_PADSPK_MIX_LEVEL : DEFAULT_VOLUME;
|
||||
for (int c = 0; c < port.channels; c++) {
|
||||
port.volume[c] = DEFAULT_VOLUME;
|
||||
}
|
||||
|
|
@ -494,9 +498,23 @@ int APS5_VABI sceAudioOutGetPortState(int handle, AudioOutPortState* state) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceAudioOutSetMixLevelPadSpk(void) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
int APS5_VABI sceAudioOutSetMixLevelPadSpk(int handle, int mixLevel) {
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
Port* port = getPort(handle);
|
||||
if (port == nullptr) {
|
||||
return -2144993277;
|
||||
}
|
||||
if (port->type != PORT_TYPE_PADSPK) {
|
||||
return -2144993270;
|
||||
}
|
||||
if (mixLevel > DEFAULT_VOLUME) {
|
||||
return -2144993260;
|
||||
}
|
||||
if (mixLevel < 0) {
|
||||
throw std::runtime_error("sceAudioOutSetMixLevelPadSpk: negative mix level not supported");
|
||||
}
|
||||
port->mixLevel = mixLevel;
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t);
|
||||
int APS5_VABI sceAudioOutClose(int);
|
||||
int APS5_VABI sceAudioOutOutput(int, const void*);
|
||||
int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*);
|
||||
int APS5_VABI sceAudioOutSetMixLevelPadSpk(int, int);
|
||||
}
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
static void SetEnvironment(const char* name, const std::string& value) {
|
||||
#ifdef _WIN32
|
||||
_putenv_s(name, value.c_str());
|
||||
#else
|
||||
setenv(name, value.c_str(), 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename TFunction>
|
||||
static bool ThrowsRuntimeError(TFunction function) {
|
||||
try {
|
||||
function();
|
||||
} catch (const std::runtime_error&) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int user = 0x10000000;
|
||||
constexpr int portTypeMain = 0;
|
||||
constexpr int portTypePadSpeaker = 4;
|
||||
constexpr std::uint32_t formatS16Mono = 0;
|
||||
constexpr std::uint32_t formatFloatStereo = 4;
|
||||
constexpr std::uint32_t frames = 256;
|
||||
constexpr std::uint32_t frequency = 48000;
|
||||
constexpr int unity = 32768;
|
||||
constexpr int defaultMixLevel = 11626;
|
||||
constexpr int invalidPort = static_cast<int>(0x80260003);
|
||||
constexpr int invalidPortType = static_cast<int>(0x8026000A);
|
||||
constexpr int invalidMixLevel = static_cast<int>(0x80260014);
|
||||
|
||||
template<typename TSample, typename TSetup>
|
||||
std::vector<TSample> Play(int type, std::uint32_t format, const std::vector<TSample>& block, TSetup setup) {
|
||||
const auto path = std::filesystem::temp_directory_path() / "anyps5_audio_out_mix_level_pad_spk.raw";
|
||||
std::filesystem::remove(path);
|
||||
SetEnvironment("SDL_DISKAUDIOFILE", path.string());
|
||||
|
||||
const int handle = sceAudioOutOpen(user, type, 0, frames, frequency, format);
|
||||
Require(handle > 0);
|
||||
setup(handle);
|
||||
Require(sceAudioOutOutput(handle, block.data()) == static_cast<int>(frames));
|
||||
Require(sceAudioOutOutput(handle, nullptr) == static_cast<int>(frames));
|
||||
Require(sceAudioOutClose(handle) == 0);
|
||||
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
const std::vector<char> bytes{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
|
||||
file.close();
|
||||
std::filesystem::remove(path);
|
||||
Require(bytes.size() >= block.size() * sizeof(TSample) && bytes.size() % sizeof(TSample) == 0);
|
||||
|
||||
const auto* samples = reinterpret_cast<const TSample*>(bytes.data());
|
||||
std::size_t first = 0;
|
||||
std::size_t last = bytes.size() / sizeof(TSample);
|
||||
while (first < last && samples[first] == TSample{}) first++;
|
||||
while (last > first && samples[last - 1] == TSample{}) last--;
|
||||
return {samples + first, samples + last};
|
||||
}
|
||||
|
||||
std::vector<std::int16_t> Ramp() {
|
||||
std::vector<std::int16_t> block(frames);
|
||||
for (std::uint32_t frame = 0; frame < frames; frame++) {
|
||||
block[frame] = static_cast<std::int16_t>((frame % 2 == 0 ? 1 : -1) * 256 * (static_cast<int>(frame % 100) + 1));
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
std::vector<std::int16_t> Scaled(const std::vector<std::int16_t>& block, int level) {
|
||||
std::vector<std::int16_t> scaled;
|
||||
for (const std::int16_t sample : block) {
|
||||
Require(sample * level % unity == 0);
|
||||
scaled.push_back(static_cast<std::int16_t>(sample * level / unity));
|
||||
}
|
||||
return scaled;
|
||||
}
|
||||
|
||||
void TestDefaultLevel() {
|
||||
std::vector<std::int16_t> block(frames);
|
||||
for (std::uint32_t frame = 0; frame < frames; frame++) block[frame] = frame % 2 == 0 ? 16384 : -16384;
|
||||
const auto played = Play(portTypePadSpeaker, formatS16Mono, block, [](int) {});
|
||||
Require(played == Scaled(block, defaultMixLevel));
|
||||
Require(played[0] == 5813 && played[1] == -5813);
|
||||
}
|
||||
|
||||
void TestLevels() {
|
||||
const auto block = Ramp();
|
||||
Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) {
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity) == 0);
|
||||
}) == block);
|
||||
Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) {
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == 0);
|
||||
}) == Scaled(block, unity / 2));
|
||||
Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) {
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity) == 0);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 4) == 0);
|
||||
}) == Scaled(block, unity / 4));
|
||||
Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) {
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, 0) == 0);
|
||||
}).empty());
|
||||
}
|
||||
|
||||
void TestLevelWithVolume() {
|
||||
const auto block = Ramp();
|
||||
Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) {
|
||||
int volume = unity / 2;
|
||||
Require(sceAudioOutSetVolume(handle, 1, &volume) == 0);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == 0);
|
||||
}) == Scaled(block, unity / 4));
|
||||
|
||||
std::vector<float> stereo(frames * 2);
|
||||
for (std::uint32_t frame = 0; frame < frames; frame++) {
|
||||
stereo[frame * 2] = (frame % 2 == 0 ? 0.5f : -0.25f);
|
||||
stereo[frame * 2 + 1] = (frame % 2 == 0 ? -1.0f : 0.125f);
|
||||
}
|
||||
const auto played = Play(portTypePadSpeaker, formatFloatStereo, stereo, [](int handle) {
|
||||
int volume[2] = {unity, unity / 2};
|
||||
Require(sceAudioOutSetVolume(handle, 2, volume) == 0);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 4) == 0);
|
||||
});
|
||||
Require(played.size() == stereo.size());
|
||||
for (std::uint32_t frame = 0; frame < frames; frame++) {
|
||||
Require(played[frame * 2] == stereo[frame * 2] * 0.25f);
|
||||
Require(played[frame * 2 + 1] == stereo[frame * 2 + 1] * 0.125f);
|
||||
}
|
||||
}
|
||||
|
||||
void TestOtherPortType() {
|
||||
const auto block = Ramp();
|
||||
Require(Play(portTypeMain, formatS16Mono, block, [](int) {}) == block);
|
||||
Require(Play(portTypeMain, formatS16Mono, block, [](int handle) {
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == invalidPortType);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidPortType);
|
||||
}) == block);
|
||||
}
|
||||
|
||||
void TestRejectedLevels() {
|
||||
const auto block = Ramp();
|
||||
int closed = 0;
|
||||
Require(Play(portTypePadSpeaker, formatS16Mono, block, [&closed](int handle) {
|
||||
closed = handle;
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == 0);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidMixLevel);
|
||||
Require(ThrowsRuntimeError([handle] { sceAudioOutSetMixLevelPadSpk(handle, -1); }));
|
||||
Require(sceAudioOutSetMixLevelPadSpk(0, unity) == invalidPort);
|
||||
Require(sceAudioOutSetMixLevelPadSpk(handle + 1, unity) == invalidPort);
|
||||
}) == Scaled(block, unity / 2));
|
||||
Require(sceAudioOutSetMixLevelPadSpk(closed, unity) == invalidPort);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
SetEnvironment("SDL_AUDIODRIVER", "disk");
|
||||
TestDefaultLevel();
|
||||
TestLevels();
|
||||
TestLevelWithVolume();
|
||||
TestOtherPortType();
|
||||
TestRejectedLevels();
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceSslClose](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature
|
||||
- [sceSslGetSerialNumber](../../core/libs/prx/libSceSsl/Export.cpp) (libSceSsl) - unknown signature
|
||||
- [X+4jdIS75P0](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown name, signature
|
||||
- [sceAudioOutSetMixLevelPadSpk](../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - effect of a negative mix level unknown; only 0 to 32768 (0 dB) is accepted
|
||||
- [AOWqIYsgVHs](../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - unknown name, signature
|
||||
- [GQTObcITIXI](../../core/libs/prx/libSceShare/Export.cpp) (libSceShare) - unknown name, signature
|
||||
- [BnMAMrsfVWo](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libc) - unknown name, signature
|
||||
|
|
|
|||
Loading…
Reference in New Issue