Add launcher microphone selection and SDL AudioIn capture

This commit is contained in:
nmzik committed 2026-09-15 11:24:05 +04:00
1 parent 98b5d88d67
commit d1372abf46
9 files changed
+164 -24

No files matched your search

+100 -21
View File
@@ -3,6 +3,7 @@
#include "SDL.h"
#include "common/assert.h"
#include "common/common.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/magicEnum.h"
#include "common/stringUtils.h"
@@ -38,6 +39,9 @@ constexpr int AUDIO_OUT_PORT_TYPE_AUX = 127;
constexpr uint32_t AUDIO_OUT_PARAM_FORMAT_MASK = 0x000000ffu;
constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x1;
constexpr uint32_t AUDIO_IN_GRAIN_MAX_ASYNC = 384;
static bool audio_out_port_type_is_valid(int type) {
return (type >= AUDIO_OUT_PORT_TYPE_MAIN && type <= AUDIO_OUT_PORT_TYPE_PADSPK) ||
type == AUDIO_OUT_PORT_TYPE_VIBRATION || type == AUDIO_OUT_PORT_TYPE_AUDIO3D ||
@@ -91,7 +95,7 @@ public:
Id AudioInOpen(uint32_t samples_num, uint32_t freq, Format format, bool asynchronous);
int AudioInClose(Id handle);
bool AudioInValid(Id handle);
int AudioInGetSilentState(Id handle);
int AudioInInput(Id handle, void* dest);
static constexpr int OUT_PORTS_MAX = 32;
@@ -113,13 +117,14 @@ private:
};
struct PortIn {
bool used = false;
bool busy = false;
bool asynchronous = false;
uint32_t samples_num = 0;
uint32_t freq = 0;
uint32_t bytes_per_frame = 0;
uint64_t last_input_time = 0;
bool used = false;
bool busy = false;
bool asynchronous = false;
uint32_t samples_num = 0;
uint32_t freq = 0;
uint32_t bytes_per_frame = 0;
uint64_t last_input_time = 0;
SDL_AudioDeviceID audio_device = 0;
};
PortIn* GetAudioInPort(Id handle); // Caller holds m_mutex.
@@ -135,6 +140,8 @@ private:
static SDL_AudioFormat SdlFormat(Format format);
static bool OpenSdlDevice(PortOut* port);
static void CloseSdlDevice(PortOut* port);
static void OpenSdlDevice(PortIn* port, Format format);
static void CloseSdlDevice(PortIn* port);
static const void* PrepareOutputBuffer(const PortOut& port, const void* data,
std::vector<uint8_t>* buffer);
static bool QueueSdlAudio(PortOut* port, const void* data, bool blocking);
@@ -202,6 +209,9 @@ Audio::~Audio() {
for (auto& port: m_out_ports) {
CloseSdlDevice(&port);
}
for (auto& port: m_in_ports) {
CloseSdlDevice(&port);
}
}
bool Audio::FormatIsFloat(Format format) {
@@ -570,6 +580,38 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
return first_port.samples_num;
}
void Audio::OpenSdlDevice(PortIn* port, Format format) {
const auto& name = Config::GetAudioInputDevice();
if (name.empty()) {
return;
}
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
LOGF("AudioIn: SDL init failed: %s\n", SDL_GetError());
return;
}
SDL_AudioSpec desired {};
desired.freq = static_cast<int>(port->freq);
desired.format = SdlFormat(format);
desired.channels = static_cast<Uint8>(port->bytes_per_frame / BytesPerSample(format));
desired.samples = static_cast<Uint16>(port->samples_num);
// SDL converts capture data to the guest format when allowed_changes is zero.
port->audio_device = SDL_OpenAudioDevice(name.c_str(), 1, &desired, nullptr, 0);
if (port->audio_device == 0) {
LOGF("AudioIn: cannot open '%s': %s; using silence\n", name.c_str(), SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return;
}
LOGF("AudioIn: opened '%s' (%u Hz, %u ch)\n", name.c_str(), port->freq, desired.channels);
}
void Audio::CloseSdlDevice(PortIn* port) {
if (port->audio_device != 0) {
SDL_CloseAudioDevice(port->audio_device);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
port->audio_device = 0;
}
}
Audio::Id Audio::AudioInOpen(uint32_t samples_num, uint32_t freq, Format format, bool asynchronous) {
Common::LockGuard lock(m_mutex);
@@ -586,6 +628,7 @@ Audio::Id Audio::AudioInOpen(uint32_t samples_num, uint32_t freq, Format format,
if (format == Format::Signed16bitStereo || format == Format::FloatStereo) {
port.bytes_per_frame *= 2;
}
OpenSdlDevice(&port, format);
return Id::Create(id);
}
@@ -611,20 +654,29 @@ int Audio::AudioInClose(Id handle) {
if (port->busy) {
return AUDIO_IN_ERROR_BUSY;
}
CloseSdlDevice(port);
*port = {};
return OK;
}
bool Audio::AudioInValid(Id handle) {
int Audio::AudioInGetSilentState(Id handle) {
Common::LockGuard lock(m_mutex);
return GetAudioInPort(handle) != nullptr;
auto* port = GetAudioInPort(handle);
if (port == nullptr) {
return AUDIO_IN_ERROR_INVALID_HANDLE;
}
if (port->audio_device == 0 ||
SDL_GetAudioDeviceStatus(port->audio_device) == SDL_AUDIO_STOPPED) {
return AUDIO_IN_SILENT_STATE_DEVICE_NONE;
}
return 0;
}
int Audio::AudioInInput(Id handle, void* dest) {
PortIn snapshot;
{
Common::LockGuard lock(m_mutex);
auto* port = GetAudioInPort(handle);
auto* port = GetAudioInPort(handle);
if (port == nullptr) {
return AUDIO_IN_ERROR_INVALID_HANDLE;
}
@@ -634,6 +686,9 @@ int Audio::AudioInInput(Id handle, void* dest) {
port->busy = true;
snapshot = *port;
}
if (snapshot.audio_device != 0 && dest != nullptr) {
SDL_PauseAudioDevice(snapshot.audio_device, 0);
}
const uint64_t block_time = (1000000ULL * snapshot.samples_num) / snapshot.freq;
uint64_t wait_time = 0;
@@ -641,7 +696,7 @@ int Audio::AudioInInput(Id handle, void* dest) {
if (dest != nullptr) {
wait_time = block_time;
}
} else if (snapshot.last_input_time != 0) {
} else if (snapshot.last_input_time != 0 && (snapshot.audio_device == 0 || dest == nullptr)) {
const auto now = LibKernel::KernelGetProcessTime();
const auto next = snapshot.last_input_time + block_time;
if (next > now) {
@@ -649,17 +704,44 @@ int Audio::AudioInInput(Id handle, void* dest) {
}
}
Common::Thread::SleepMicro(wait_time);
uint32_t frames = snapshot.samples_num;
bool failed = false;
if (dest != nullptr) {
// No input device backend: return silence matching AUDIO_IN_SILENT_STATE_DEVICE_NONE.
std::memset(dest, 0, snapshot.samples_num * snapshot.bytes_per_frame);
if (snapshot.audio_device != 0) {
while (!snapshot.asynchronous &&
SDL_GetQueuedAudioSize(snapshot.audio_device) <
frames * snapshot.bytes_per_frame &&
SDL_GetAudioDeviceStatus(snapshot.audio_device) == SDL_AUDIO_PLAYING) {
Common::Thread::SleepMicro(1000);
}
failed = SDL_GetAudioDeviceStatus(snapshot.audio_device) != SDL_AUDIO_PLAYING;
if (!failed) {
if (snapshot.asynchronous) {
frames = AUDIO_IN_GRAIN_MAX_ASYNC;
}
frames = SDL_DequeueAudio(snapshot.audio_device, dest,
frames * snapshot.bytes_per_frame) /
snapshot.bytes_per_frame;
}
}
if (snapshot.audio_device == 0 || failed) {
std::memset(dest, 0, frames * snapshot.bytes_per_frame);
}
} else if (snapshot.audio_device != 0) {
SDL_PauseAudioDevice(snapshot.audio_device, 1);
SDL_ClearQueuedAudio(snapshot.audio_device);
}
{
Common::LockGuard lock(m_mutex);
auto& port = m_in_ports[handle.GetId()];
auto& port = m_in_ports[handle.GetId()];
if (failed) {
LOGF("AudioIn: capture stopped; using silence\n");
CloseSdlDevice(&port);
}
port.last_input_time = dest != nullptr ? LibKernel::KernelGetProcessTime() : 0;
port.busy = false;
}
return dest != nullptr ? static_cast<int>(snapshot.samples_num) : 0;
return dest != nullptr ? static_cast<int>(frames) : 0;
}
namespace AudioOut {
@@ -851,8 +933,6 @@ namespace AudioIn {
LIB_NAME("AudioIn", "AudioIn");
constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x1;
static int OpenPort(int user_id, int type, int index, uint32_t len, uint32_t freq,
uint32_t param, bool asynchronous) {
LOGF("\t user_id = %d\n"
@@ -935,12 +1015,11 @@ int KYTY_SYSV_ABI AudioInGetSilentState(int handle) {
EXIT_IF(g_audio == nullptr);
if (handle <= 0 || !g_audio->AudioInValid(Audio::Id(handle))) {
if (handle <= 0) {
return AUDIO_IN_ERROR_INVALID_HANDLE;
}
// Audio input has no device backend yet, so every valid port receives silence.
return AUDIO_IN_SILENT_STATE_DEVICE_NONE;
return g_audio->AudioInGetSilentState(Audio::Id(handle));
}
} // namespace AudioIn