Merge pull request #3669 from MarcZX100/carry/audioin-open-variants-3340
feat(libSceAudioIn): implement sceAudioInHqOpen and sceAudioInAsyncOpen
This commit is contained in:
commit
2605ad6c76
|
|
@ -21,6 +21,7 @@ constexpr int AUDIO_IN_ERROR_PORT_FULL = static_cast<int>(0x80260107);
|
|||
constexpr int AUDIO_IN_ERROR_BUSY = static_cast<int>(0x8026010A);
|
||||
constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 1;
|
||||
constexpr std::uint32_t MAX_QUEUED_BLOCKS = 4;
|
||||
constexpr std::uint32_t MAX_ASYNC_LEN = 384;
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
|
|
@ -75,6 +76,32 @@ Port* find(int handle) {
|
|||
return port.used ? &port : nullptr;
|
||||
}
|
||||
|
||||
int checkArguments(std::uint32_t type, std::uint32_t index, std::uint32_t len, std::uint32_t freq) {
|
||||
if (type != 0 && type != 1) return AUDIO_IN_ERROR_INVALID_TYPE;
|
||||
if (index != 0) return AUDIO_IN_ERROR_INVALID_PARAM;
|
||||
if (len != 128 && len != 256) return AUDIO_IN_ERROR_INVALID_SIZE;
|
||||
if (freq != 48000 && freq != 16000) return AUDIO_IN_ERROR_INVALID_FREQ;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int openPort(std::uint32_t len, std::uint32_t freq, std::uint32_t param, const char* function) {
|
||||
if (param == 0x10) {
|
||||
NotImplemented_nid_no_patch(function);
|
||||
return 0;
|
||||
}
|
||||
Format format{};
|
||||
if (!formatOf(param, format)) return AUDIO_IN_ERROR_INVALID_PARAM;
|
||||
std::lock_guard lock(g_mutex);
|
||||
for (std::size_t i = 0; i < g_ports.size(); ++i) {
|
||||
if (g_ports[i].used) continue;
|
||||
const auto frameBytes = static_cast<std::uint32_t>(SDL_AUDIO_BITSIZE(format.format) / 8 * format.channels);
|
||||
const auto grain = std::chrono::duration_cast<Clock::duration>(std::chrono::microseconds(1000000ull * len / freq));
|
||||
g_ports[i] = {true, false, len, frameBytes, openDevice(freq, len, format), grain, {}};
|
||||
return static_cast<int>(i + 1);
|
||||
}
|
||||
return AUDIO_IN_ERROR_PORT_FULL;
|
||||
}
|
||||
|
||||
std::size_t capture(SDL_AudioDeviceID device, std::uint8_t* dest, std::size_t bytes, Clock::time_point deadline) {
|
||||
while (SDL_GetQueuedAudioSize(device) < bytes && Clock::now() < deadline) PreciseSleepUs(1000);
|
||||
const std::size_t queued = SDL_GetQueuedAudioSize(device);
|
||||
|
|
@ -136,25 +163,8 @@ int APS5_VABI sceAudioInInput(int handle, void* dest) {
|
|||
|
||||
int APS5_VABI sceAudioInOpen(int user_id, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) {
|
||||
(void)user_id;
|
||||
if (type != 0 && type != 1) return AUDIO_IN_ERROR_INVALID_TYPE;
|
||||
if (index != 0) return AUDIO_IN_ERROR_INVALID_PARAM;
|
||||
if (len != 128 && len != 256) return AUDIO_IN_ERROR_INVALID_SIZE;
|
||||
if (freq != 48000 && freq != 16000) return AUDIO_IN_ERROR_INVALID_FREQ;
|
||||
if (param == 0x10) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
Format format{};
|
||||
if (!formatOf(param, format)) return AUDIO_IN_ERROR_INVALID_PARAM;
|
||||
std::lock_guard lock(g_mutex);
|
||||
for (std::size_t i = 0; i < g_ports.size(); ++i) {
|
||||
if (g_ports[i].used) continue;
|
||||
const auto frameBytes = static_cast<std::uint32_t>(SDL_AUDIO_BITSIZE(format.format) / 8 * format.channels);
|
||||
const auto grain = std::chrono::duration_cast<Clock::duration>(std::chrono::microseconds(1000000ull * len / freq));
|
||||
g_ports[i] = {true, false, len, frameBytes, openDevice(freq, len, format), grain, {}};
|
||||
return static_cast<int>(i + 1);
|
||||
}
|
||||
return AUDIO_IN_ERROR_PORT_FULL;
|
||||
if (const int error = checkArguments(type, index, len, freq); error != 0) return error;
|
||||
return openPort(len, freq, param, __func__);
|
||||
}
|
||||
|
||||
int32_t APS5_VABI sceAudioInClose(int32_t handle) {
|
||||
|
|
@ -168,25 +178,24 @@ int32_t APS5_VABI sceAudioInClose(int32_t handle) {
|
|||
}
|
||||
|
||||
int32_t APS5_VABI sceAudioInHqOpen(int32_t user_id, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) {
|
||||
(void)user_id;
|
||||
(void)type;
|
||||
(void)index;
|
||||
(void)len;
|
||||
(void)freq;
|
||||
(void)param;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
(void)user_id;
|
||||
if (const int error = checkArguments(type, index, len, freq); error != 0) return error;
|
||||
if (len != 128 || freq != 48000) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
return openPort(len, freq, param, __func__);
|
||||
}
|
||||
|
||||
int32_t APS5_VABI sceAudioInAsyncOpen(int32_t user_id, uint32_t type, uint32_t index, uint32_t len, uint32_t freq, uint32_t param) {
|
||||
(void)user_id;
|
||||
(void)type;
|
||||
(void)index;
|
||||
(void)len;
|
||||
(void)freq;
|
||||
(void)param;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
(void)user_id;
|
||||
const int error = checkArguments(type, index, len, freq);
|
||||
if (error == AUDIO_IN_ERROR_INVALID_SIZE && len <= MAX_ASYNC_LEN) {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
}
|
||||
if (error != 0) return error;
|
||||
return openPort(len, freq, param, __func__);
|
||||
}
|
||||
|
||||
int32_t APS5_VABI sceAudioInIsPadEmbeddedMic(int32_t handle) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
|
@ -6,12 +7,15 @@
|
|||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI sceAudioInInit();
|
||||
int APS5_VABI sceAudioInOpen(int, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t);
|
||||
int APS5_VABI sceAudioInHqOpen(int, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t);
|
||||
int APS5_VABI sceAudioInAsyncOpen(int, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t);
|
||||
int APS5_VABI sceAudioInInput(int, void*);
|
||||
int APS5_VABI sceAudioInGetSilentState(int);
|
||||
int APS5_VABI sceAudioInClose(int);
|
||||
|
|
@ -19,6 +23,16 @@ int APS5_VABI sceAudioInClose(int);
|
|||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
template <typename TCall>
|
||||
static bool ReportsNotImplemented(const char* name, TCall call) {
|
||||
try {
|
||||
call();
|
||||
} catch (const std::runtime_error& error) {
|
||||
return std::string(error.what()) == std::string(name) + " not implemented";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void SetEnvironment(const char* name, const std::string& value) {
|
||||
#ifdef _WIN32
|
||||
_putenv_s(name, value.c_str());
|
||||
|
|
@ -42,6 +56,22 @@ void TestValidation() {
|
|||
Require(sceAudioInOpen(user, 0, 0, 256, 44100, 2) == invalidFreq);
|
||||
Require(sceAudioInOpen(user, 0, 0, 256, 48000, 3) == invalidParam);
|
||||
Require(sceAudioInOpen(user, 0, 0, 256, 48000, 0) == invalidParam);
|
||||
Require(sceAudioInHqOpen(user, 2, 0, 128, 48000, 2) == invalidType);
|
||||
Require(sceAudioInHqOpen(user, 0, 1, 128, 48000, 2) == invalidParam);
|
||||
Require(sceAudioInHqOpen(user, 0, 0, 512, 48000, 2) == invalidSize);
|
||||
Require(sceAudioInHqOpen(user, 0, 0, 128, 44100, 2) == invalidFreq);
|
||||
Require(sceAudioInHqOpen(user, 0, 0, 128, 48000, 3) == invalidParam);
|
||||
Require(sceAudioInAsyncOpen(user, 2, 0, 128, 48000, 2) == invalidType);
|
||||
Require(sceAudioInAsyncOpen(user, 0, 1, 128, 48000, 2) == invalidParam);
|
||||
Require(sceAudioInAsyncOpen(user, 0, 0, 385, 48000, 2) == invalidSize);
|
||||
Require(sceAudioInAsyncOpen(user, 0, 0, 256, 44100, 2) == invalidFreq);
|
||||
Require(sceAudioInAsyncOpen(user, 0, 0, 256, 48000, 0) == invalidParam);
|
||||
Require(ReportsNotImplemented("sceAudioInHqOpen", [] { sceAudioInHqOpen(user, 0, 0, 256, 48000, 2); }));
|
||||
Require(ReportsNotImplemented("sceAudioInHqOpen", [] { sceAudioInHqOpen(user, 0, 0, 128, 16000, 2); }));
|
||||
Require(ReportsNotImplemented("sceAudioInAsyncOpen", [] { sceAudioInAsyncOpen(user, 0, 0, 384, 48000, 2); }));
|
||||
Require(ReportsNotImplemented("sceAudioInOpen", [] { sceAudioInOpen(user, 0, 0, 256, 48000, 0x10); }));
|
||||
Require(ReportsNotImplemented("sceAudioInHqOpen", [] { sceAudioInHqOpen(user, 0, 0, 128, 48000, 0x10); }));
|
||||
Require(ReportsNotImplemented("sceAudioInAsyncOpen", [] { sceAudioInAsyncOpen(user, 0, 0, 128, 48000, 0x10); }));
|
||||
}
|
||||
|
||||
void TestCapture() {
|
||||
|
|
@ -66,6 +96,11 @@ void TestCapture() {
|
|||
Require(sceAudioInInput(handle, block.data()) == 256);
|
||||
for (std::size_t i = 0; i < 256 * 2; ++i) Require(block[i] == 0);
|
||||
Require(sceAudioInClose(handle) == 0);
|
||||
const int async = sceAudioInAsyncOpen(user, 0, 0, 256, 48000, 2);
|
||||
Require(async == handle && sceAudioInGetSilentState(async) == 0);
|
||||
Require(sceAudioInInput(async, block.data()) == 256);
|
||||
Require(std::equal(block.begin(), block.end() - 1, recorded.begin()) && block.back() == 0x5555);
|
||||
Require(sceAudioInClose(async) == 0);
|
||||
std::filesystem::remove(path);
|
||||
}
|
||||
|
||||
|
|
@ -87,8 +122,30 @@ void TestNoDevice() {
|
|||
for (std::size_t i = 0; i < 256 * 8; ++i) Require(buffer[i] == 0);
|
||||
Require(buffer[256 * 8] == 0xCC);
|
||||
Require(sceAudioInInput(handle, nullptr) == 0);
|
||||
for (int port = 1; port < 8; ++port) Require(sceAudioInOpen(user, 1, 0, 128, 48000, 1) > 0);
|
||||
const int hq = sceAudioInHqOpen(user, 0, 0, 128, 48000, 2);
|
||||
const int async = sceAudioInAsyncOpen(user, 1, 0, 256, 16000, 0x11);
|
||||
Require(hq > 0 && async > 0 && hq != handle && async != handle && hq != async);
|
||||
Require(sceAudioInGetSilentState(hq) == 1 && sceAudioInGetSilentState(async) == 1);
|
||||
std::fill(buffer.begin(), buffer.end(), 0xCC);
|
||||
Require(sceAudioInInput(hq, buffer.data()) == 128);
|
||||
for (std::size_t i = 0; i < 128 * 4; ++i) Require(buffer[i] == 0);
|
||||
Require(buffer[128 * 4] == 0xCC);
|
||||
std::fill(buffer.begin(), buffer.end(), 0xCC);
|
||||
Require(sceAudioInInput(async, buffer.data()) == 256);
|
||||
for (std::size_t i = 0; i < 256 * 4; ++i) Require(buffer[i] == 0);
|
||||
Require(buffer[256 * 4] == 0xCC);
|
||||
Require(sceAudioInInput(async, nullptr) == 0);
|
||||
for (int port = 3; port < 8; ++port) Require(sceAudioInOpen(user, 1, 0, 128, 48000, 1) > 0);
|
||||
Require(sceAudioInOpen(user, 1, 0, 128, 48000, 1) == portFull);
|
||||
Require(sceAudioInHqOpen(user, 1, 0, 128, 48000, 1) == portFull);
|
||||
Require(sceAudioInAsyncOpen(user, 1, 0, 128, 48000, 1) == portFull);
|
||||
Require(sceAudioInClose(hq) == 0);
|
||||
Require(sceAudioInClose(hq) == invalidHandle);
|
||||
Require(sceAudioInGetSilentState(hq) == invalidHandle);
|
||||
Require(sceAudioInAsyncOpen(user, 0, 0, 128, 48000, 2) == hq);
|
||||
Require(sceAudioInClose(async) == 0);
|
||||
Require(sceAudioInInput(async, buffer.data()) == invalidHandle);
|
||||
Require(sceAudioInHqOpen(user, 1, 0, 128, 48000, 0x12) == async);
|
||||
Require(sceAudioInClose(handle) == 0);
|
||||
Require(sceAudioInClose(handle) == invalidHandle);
|
||||
Require(sceAudioInInput(handle, buffer.data()) == invalidHandle);
|
||||
|
|
|
|||
|
|
@ -354,6 +354,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
|
||||
### Functional
|
||||
|
||||
- [sceAudioInHqOpen, sceAudioInAsyncOpen](../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) open the same port as `sceAudioInOpen`, so `sceAudioInInput`, `sceAudioInGetSilentState` and `sceAudioInClose` accept their handles. The signatures agree across [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/fc5d2cc219c43c8c8f354153e7b3d0c04a5d506e/src/core/libraries/audio/audioin.cpp), [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/60977b29103452b9681ae10c7a9011d44965bf16/src/SharpProspero/Interop/Audio/AudioIn.cs) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/c86c8b4d9333a766cc8c4495f89f893bf35e6199/src/libs/audio.cpp). HqOpen is limited to 128 samples at 48 kHz; AsyncOpen rejects sizes above 384 and throws for currently unverified inputs. Host capture pacing and console behavior remain unverified.
|
||||
- [getc_unlocked, putc_unlocked](../../core/libs/prx/libc/src/StandardStreams.cpp) (libc) - byte I/O uses the host unlocked operations; guest EOF/error flags and macro counters follow `FileStream`. Linux status queries use `feof_unlocked`/`ferror_unlocked` because [glibc `feof`](https://github.com/bminor/glibc/blob/d2097651cc57834dbfcaa102ddfacae0d86cfb66/libio/feof.c) and [`ferror`](https://github.com/bminor/glibc/blob/d2097651cc57834dbfcaa102ddfacae0d86cfb66/libio/ferror.c) can acquire the stream lock. Windows uses [the CRT no-lock byte operations](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fgetc-nolock-fgetwc-nolock) and `feof`/`ferror`, verified under a held stream lock on UCRT. Console behavior and other host CRTs remain unverified.
|
||||
|
||||
- [ftrylockfile](../../core/libs/prx/libc/src/RuntimeSupport.cpp) (libc) - tries the host CRT stream lock that `flockfile`, `funlockfile` and each locking host stdio call on the stream take, so it returns -1 while another thread holds the stream through `flockfile` or is inside such a call, and 0 when it acquires the lock or already owns it (recursively, released by as many `funlockfile` calls), as FreeBSD 11 [`_ftrylockfile`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/stdio/_flock_stub.c#L89-L104) does. A libc export holds the lock only for each host call it makes, not across several host calls or while it updates the guest `FILE` prefix. Linux uses glibc `ftrylockfile`. UCRT has no try variant of `_lock_file`, so Windows calls `TryEnterCriticalSection` on the `CRITICAL_SECTION` that `_lock_file` enters, found through the private UCRT stream layout (`__crt_stdio_stream_data` in `ucrt/inc/corecrt_internal_stdio.h`, unchanged from Windows SDK 10.0.19041 to 10.0.26100); another CRT or a change of that layout breaks it. A null or closed stream throws where FreeBSD dereferences it
|
||||
|
|
|
|||
Loading…
Reference in New Issue