#include "libs/audio.h" #include "common/assert.h" #include "common/emulatorConfig.h" #include "common/logging/log.h" #include "common/threads.h" #include "kernel/pthread.h" #include "libs/audio_internal.h" #include "libs/controller.h" #include "libs/dualSenseHaptics.h" #include "libs/errno.h" #include "libs/libs.h" #include #include #include #include #include #include namespace Libs::Audio { namespace { constexpr int AUDIO_OUT_PORT_TYPE_MAIN = 0; constexpr int AUDIO_OUT_PORT_TYPE_BGM = 1; constexpr int AUDIO_OUT_PORT_TYPE_VOICE = 2; constexpr int AUDIO_OUT_PORT_TYPE_PERSONAL = 3; constexpr int AUDIO_OUT_PORT_TYPE_PADSPK = 4; constexpr int AUDIO_OUT_PORT_TYPE_VIBRATION = 10; constexpr int AUDIO_OUT_PORT_TYPE_AUDIO3D = 126; constexpr int AUDIO_OUT_PORT_TYPE_AUX = 127; constexpr uint32_t AUDIO_OUT_PARAM_FORMAT_MASK = 0x000000ffu; constexpr uint64_t AUDIO_OUT_TARGET_LATENCY_US = 40000; constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x1; constexpr uint32_t AUDIO_IN_GRAIN_MAX_ASYNC = 384; struct OutputClock { uint64_t next_time = 0; uint64_t remainder = 0; uint64_t Advance(uint64_t now, uint32_t frames, uint32_t rate) { // Preserve small scheduling errors; discard timing debt after a discontinuity. if (next_time == 0 || (now > next_time && now - next_time > 100000)) { next_time = now; remainder = 0; } const auto deadline = next_time; const auto duration = 1000000ULL * frames + remainder; next_time += duration / rate; remainder = duration % rate; return deadline; } }; 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 || type == AUDIO_OUT_PORT_TYPE_AUX; } } // namespace class Audio { public: using Format = AudioInternal::Format; class Id { public: explicit Id(int id): m_id(id - 1) {} [[nodiscard]] int ToInt() const { return m_id + 1; } [[nodiscard]] bool IsValid() const { return m_id >= 0; } friend class Audio; private: Id() = default; static Id Invalid() { return {}; } static Id Create(int audio_id) { Id r; r.m_id = audio_id; return r; } [[nodiscard]] int GetId() const { return m_id; } int m_id = -1; }; struct OutputParam { Id handle; const void* data = nullptr; const float* gains = nullptr; }; Audio() = default; virtual ~Audio(); KYTY_CLASS_NO_COPY(Audio); Id AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format); bool AudioOutClose(Id handle); bool AudioOutValid(Id handle); bool AudioOutHasDevice(Id handle); bool AudioOutSetVolume(Id handle, uint32_t bitflag, const int* volume); uint32_t AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking = true); bool AudioOutGetStatus(Id handle, int* type, int* channels_num); Id AudioInOpen(uint32_t samples_num, uint32_t freq, Format format, bool asynchronous); int AudioInClose(Id handle); int AudioInGetSilentState(Id handle); int AudioInInput(Id handle, void* dest); static constexpr int OUT_PORTS_MAX = 32; static constexpr int IN_PORTS_MAX = 8; private: struct PortOut { bool used = false; int type = 0; uint32_t samples_num = 0; uint32_t freq = 0; Format format = Format::Unknown; OutputClock clock; bool queue_primed = false; int channels_num = 0; int volume[12] = {}; SDL_AudioStream* stream = nullptr; Controller::DualSenseHaptics::Stream* haptics = nullptr; }; 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; SDL_AudioStream* stream = nullptr; SDL_AudioDeviceID device = 0; }; PortIn* GetAudioInPort(Id handle); // Caller holds m_mutex. Common::Mutex m_mutex; PortOut m_out_ports[OUT_PORTS_MAX]; PortIn m_in_ports[IN_PORTS_MAX]; static bool FormatIsFloat(Format format); static bool FormatIsStd(Format format); static uint32_t BytesPerSample(Format format); static uint32_t OutputChannels(const PortOut& port); 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* buffer, float gain, const float* gains); static bool QueueSdlAudio(PortOut* port, const void* data, bool blocking, float gain, const float* gains); }; static Audio* g_audio = nullptr; namespace AudioInternal { int AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format) { if (g_audio == nullptr) { return 0; } auto id = g_audio->AudioOutOpen(type, samples_num, freq, format); return id.IsValid() ? id.ToInt() : 0; } void AudioOutClose(int handle) { if (g_audio != nullptr && handle > 0) { (void)g_audio->AudioOutClose(Audio::Id(handle)); } } bool AudioOutHasDevice(int handle) { return g_audio != nullptr && handle > 0 && g_audio->AudioOutHasDevice(Audio::Id(handle)); } uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) { if (g_audio == nullptr || params == nullptr || num == 0) { return 0; } std::vector output_params; output_params.reserve(num); for (uint32_t i = 0; i < num; i++) { if (params[i].handle > 0 && params[i].data != nullptr) { output_params.push_back( Audio::OutputParam {Audio::Id(params[i].handle), params[i].data, params[i].gains}); } } if (output_params.empty()) { return 0; } return g_audio->AudioOutOutputs(output_params.data(), static_cast(output_params.size()), blocking); } } // namespace AudioInternal void Initialize() { EXIT_IF(g_audio != nullptr); g_audio = new Audio; } void Shutdown() { delete g_audio; g_audio = nullptr; } Audio::~Audio() { for (auto& port: m_out_ports) { CloseSdlDevice(&port); } for (auto& port: m_in_ports) { CloseSdlDevice(&port); } } bool Audio::FormatIsFloat(Format format) { switch (format) { case Format::FloatMono: case Format::FloatStereo: case Format::Float8Ch: case Format::Float8ChStd: case Format::Float12Ch: return true; default: return false; } } bool Audio::FormatIsStd(Format format) { return (format == Format::Signed16bit8ChStd || format == Format::Float8ChStd); } uint32_t Audio::BytesPerSample(Format format) { return FormatIsFloat(format) ? sizeof(float) : sizeof(int16_t); } uint32_t Audio::OutputChannels(const PortOut& port) { // SDL only takes up to 8 channels. Keep the guest buffer's channel count separate. return std::min(port.channels_num, 8); } SDL_AudioFormat Audio::SdlFormat(Format format) { return FormatIsFloat(format) ? SDL_AUDIO_F32 : SDL_AUDIO_S16; } bool Audio::OpenSdlDevice(PortOut* port) { EXIT_IF(port == nullptr); if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { LOGF("AudioOut: SDL audio init failed: %s\n", SDL_GetError()); return false; } SDL_AudioSpec desired {}; desired.freq = static_cast(port->freq); desired.format = SdlFormat(port->format); desired.channels = static_cast(OutputChannels(*port)); port->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired, nullptr, nullptr); if (port->stream == nullptr) { LOGF("AudioOut: SDL_OpenAudioDeviceStream failed: %s\n", SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return false; } if (!SDL_ResumeAudioStreamDevice(port->stream)) { LOGF("AudioOut: SDL_ResumeAudioStreamDevice failed: %s\n", SDL_GetError()); CloseSdlDevice(port); return false; } LOGF("AudioOut: opened SDL stream (%d Hz, %d ch, format 0x%04x)\n", desired.freq, desired.channels, static_cast(desired.format)); return true; } void Audio::CloseSdlDevice(PortOut* port) { EXIT_IF(port == nullptr); Controller::DualSenseHaptics::Close(port->haptics); port->haptics = nullptr; if (port->stream != nullptr) { SDL_DestroyAudioStream(port->stream); SDL_QuitSubSystem(SDL_INIT_AUDIO); } port->stream = nullptr; } const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data, std::vector* buffer, float gain, const float* gains) { EXIT_IF(data == nullptr); EXIT_IF(buffer == nullptr); const auto frames = port.samples_num; const auto channels = static_cast(port.channels_num); const auto output_channels = OutputChannels(port); const auto bytes_per_sample = BytesPerSample(port.format); const bool reorder = channels >= 8 && !FormatIsStd(port.format); std::array scales {}; bool volume_changed = false; for (uint32_t ch = 0; ch < channels; ch++) { scales[ch] = (static_cast(port.volume[ch]) / 32768.0) * gain * (gains != nullptr ? gains[ch] : 1.0f); volume_changed |= scales[ch] != 1.0; } if (!volume_changed && !reorder) { return data; } buffer->resize(frames * output_channels * bytes_per_sample); // SDL wants back speakers before side speakers; non-STD PCM has them reversed. static constexpr uint32_t SDL_8CH_MAP[8] = {0, 1, 2, 3, 6, 7, 4, 5}; if (FormatIsFloat(port.format)) { auto* dst = reinterpret_cast(buffer->data()); const auto* src = static_cast(data); for (uint32_t frame = 0; frame < frames; frame++) { for (uint32_t ch = 0; ch < output_channels; ch++) { const auto src_ch = reorder ? SDL_8CH_MAP[ch] : ch; dst[frame * output_channels + ch] = static_cast(src[frame * channels + src_ch] * scales[src_ch]); } if (channels == 12) { // Add the four top channels to their front/back channels, turning 12 into 8. // SDL handles the rest if the output device has fewer channels. static constexpr uint32_t HEIGHT_DST[4] = {0, 1, 4, 5}; for (uint32_t ch = 0; ch < 4; ch++) { dst[frame * output_channels + HEIGHT_DST[ch]] += static_cast(src[frame * channels + 8 + ch] * scales[8 + ch]); } } } } else { auto* dst = reinterpret_cast(buffer->data()); const auto* src = static_cast(data); for (uint32_t frame = 0; frame < frames; frame++) { for (uint32_t ch = 0; ch < output_channels; ch++) { const auto src_ch = reorder ? SDL_8CH_MAP[ch] : ch; const double sample = src[frame * channels + src_ch] * scales[src_ch]; dst[frame * output_channels + ch] = static_cast( std::clamp(sample, -32768.0, 32767.0)); } } } return buffer->data(); } bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking, float gain, const float* gains) { EXIT_IF(port == nullptr); if (port->stream == nullptr || data == nullptr) { return false; } std::vector prepared_buffer; const void* prepared_data = PrepareOutputBuffer(*port, data, &prepared_buffer, gain, gains); const auto output_channels = OutputChannels(*port); const auto prepared_size = BytesPerSample(port->format) * output_channels * port->samples_num; uint32_t min_queued_size = 0; if (blocking) { const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0; const auto buffers = buffer_us != 0 ? static_cast((AUDIO_OUT_TARGET_LATENCY_US + buffer_us - 1) / buffer_us) : 2u; min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u); const auto wait_start = LibKernel::KernelGetProcessTime(); auto queued = SDL_GetAudioStreamQueued(port->stream); while (queued > static_cast(min_queued_size)) { if (LibKernel::KernelGetProcessTime() - wait_start > 200000) { SDL_ClearAudioStream(port->stream); port->queue_primed = false; break; } Common::Thread::SleepMicro(1000); queued = SDL_GetAudioStreamQueued(port->stream); } if (queued < static_cast(prepared_size)) { port->queue_primed = false; } } if (!SDL_PutAudioStreamData(port->stream, prepared_data, static_cast(prepared_size))) { LOGF("AudioOut: SDL_PutAudioStreamData failed: %s\n", SDL_GetError()); port->queue_primed = false; return false; } // Unpaced priming must not advance the playback deadline into the future. if (!port->queue_primed) { port->clock = {}; } if (blocking && !port->queue_primed && SDL_GetAudioStreamQueued(port->stream) >= static_cast(min_queued_size)) { port->queue_primed = true; } return true; } Audio::Id Audio::AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format) { if (freq == 0) { return Id::Invalid(); } Common::LockGuard lock(m_mutex); for (int id = 0; id < OUT_PORTS_MAX; id++) { if (!m_out_ports[id].used) { auto& port = m_out_ports[id]; port.used = true; port.type = type; port.samples_num = samples_num; port.freq = freq; port.format = format; switch (format) { case Format::Signed16bitMono: case Format::FloatMono: port.channels_num = 1; break; case Format::Signed16bitStereo: case Format::FloatStereo: port.channels_num = 2; break; case Format::Signed16bit8Ch: case Format::Float8Ch: case Format::Signed16bit8ChStd: case Format::Float8ChStd: port.channels_num = 8; break; case Format::Float12Ch: port.channels_num = 12; break; default: EXIT("unknown format"); } for (int i = 0; i < port.channels_num; i++) { port.volume[i] = 32768; } // Pad speaker ports keep the main output too; it plays them whenever no DualSense does. if (type != AUDIO_OUT_PORT_TYPE_VIBRATION) { OpenSdlDevice(&port); } if (type == AUDIO_OUT_PORT_TYPE_VIBRATION || type == AUDIO_OUT_PORT_TYPE_PADSPK) { port.haptics = Controller::DualSenseHaptics::Open(freq, type == AUDIO_OUT_PORT_TYPE_PADSPK); } return Id::Create(id); } } return Id::Invalid(); } bool Audio::AudioOutClose(Id handle) { Common::LockGuard lock(m_mutex); if (AudioOutValid(handle)) { auto& port = m_out_ports[handle.GetId()]; CloseSdlDevice(&port); port = {}; return true; } return false; } bool Audio::AudioOutValid(Id handle) { Common::LockGuard lock(m_mutex); return (handle.GetId() >= 0 && handle.GetId() < OUT_PORTS_MAX && m_out_ports[handle.GetId()].used); } bool Audio::AudioOutHasDevice(Id handle) { Common::LockGuard lock(m_mutex); return (handle.GetId() >= 0 && handle.GetId() < OUT_PORTS_MAX && m_out_ports[handle.GetId()].used && m_out_ports[handle.GetId()].stream != nullptr); } bool Audio::AudioOutGetStatus(Id handle, int* type, int* channels_num) { Common::LockGuard lock(m_mutex); if (AudioOutValid(handle)) { auto& port = m_out_ports[handle.GetId()]; *type = port.type; *channels_num = port.channels_num; return true; } return false; } bool Audio::AudioOutSetVolume(Id handle, uint32_t bitflag, const int* volume) { Common::LockGuard lock(m_mutex); if (AudioOutValid(handle)) { auto& port = m_out_ports[handle.GetId()]; for (int i = 0; i < port.channels_num; i++, bitflag >>= 1u) { auto bit = bitflag & 0x1u; if (bit == 1) { port.volume[i] = volume[i]; LOGF("\t port.volume[%d] = volume[%d] (%d)\n", i, i, volume[i]); } } return true; } return false; } uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking) { EXIT_NOT_IMPLEMENTED(num == 0 || num > OUT_PORTS_MAX); EXIT_NOT_IMPLEMENTED(!AudioOutValid(params[0].handle)); const auto& first_port = m_out_ports[params[0].handle.GetId()]; std::array paced {}; bool any_device = false; uint64_t deadline = 0; for (uint32_t i = 0; i < num; i++) { auto& port = m_out_ports[params[i].handle.GetId()]; const float gain = port.type == AUDIO_OUT_PORT_TYPE_PADSPK ? Controller::GetSettingScale(Controller::Setting::SpeakerVolume) : port.type == AUDIO_OUT_PORT_TYPE_VIBRATION ? Controller::GetSettingScale(Controller::Setting::VibrationIntensity) : 1.0f; uint64_t controller_queued_us = 0; bool controller_uses_bluetooth = false; if (port.haptics != nullptr) { // Keep the stream alive against close and volume changes. Common::LockGuard lock(m_mutex); controller_queued_us = Controller::DualSenseHaptics::Queue( port.haptics, Controller::GetActiveControllerId(), params[i].data, port.samples_num, static_cast(port.channels_num), FormatIsFloat(port.format), port.volume, gain, params[i].gains); controller_uses_bluetooth = Controller::DualSenseHaptics::UsesBluetooth(port.haptics); } if (controller_queued_us == 0) { // No DualSense took it (e.g. it was unplugged); a pad speaker port plays on the main // output instead, and a vibration port has none. if (QueueSdlAudio(&port, params[i].data, blocking, gain, params[i].gains)) { any_device = true; paced[i] = port.queue_primed; } } else { port.queue_primed = false; // Bluetooth HID has its own sender. Let the sample clock pace a batch // without another audio device, instead of waiting on the HID queue. if (port.type == AUDIO_OUT_PORT_TYPE_PADSPK && !controller_uses_bluetooth) { any_device = true; if (blocking && controller_queued_us > AUDIO_OUT_TARGET_LATENCY_US) { // Limit a stalled USB endpoint to one audio block of waiting. const uint64_t block_us = 1000000ULL * port.samples_num / port.freq; deadline = std::max(deadline, LibKernel::KernelGetProcessTime() + std::min(controller_queued_us - AUDIO_OUT_TARGET_LATENCY_US, block_us)); } } } } // Submit the whole batch before pacing it. Audible devices own its cadence while // priming; silent/vibration-only batches use the same sample clock as SDL output. const auto now = LibKernel::KernelGetProcessTime(); for (uint32_t i = 0; i < num; i++) { auto& port = m_out_ports[params[i].handle.GetId()]; if (blocking && (paced[i] || !any_device)) { deadline = std::max(deadline, port.clock.Advance(now, port.samples_num, port.freq)); } else { port.clock = {}; } if (!blocking) { port.queue_primed = false; } } if (deadline > now) { Common::Thread::SleepMicro(deadline - now); } return first_port.samples_num; } static bool RecordingDevicePresent(SDL_AudioDeviceID device) { int count = 0; SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&count); bool present = false; for (int i = 0; i < count; i++) { if (devices[i] == device) { present = true; break; } } SDL_free(devices); return present; } void Audio::OpenSdlDevice(PortIn* port, Format format) { const auto& name = Config::GetAudioInputDevice(); if (name.empty()) { return; } if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { LOGF("AudioIn: SDL init failed: %s\n", SDL_GetError()); return; } int device_count = 0; SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&device_count); SDL_AudioDeviceID device = 0; for (int i = 0; i < device_count; i++) { const char* device_name = SDL_GetAudioDeviceName(devices[i]); if (device_name != nullptr && name == device_name) { device = devices[i]; break; } } SDL_free(devices); if (device == 0) { LOGF("AudioIn: cannot find '%s'; using silence\n", name.c_str()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return; } SDL_AudioSpec desired {}; desired.freq = static_cast(port->freq); desired.format = SdlFormat(format); desired.channels = static_cast(port->bytes_per_frame / BytesPerSample(format)); port->stream = SDL_OpenAudioDeviceStream(device, &desired, nullptr, nullptr); if (port->stream == nullptr) { LOGF("AudioIn: cannot open '%s': %s; using silence\n", name.c_str(), SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return; } port->device = device; LOGF("AudioIn: opened '%s' (%u Hz, %d ch)\n", name.c_str(), port->freq, desired.channels); } void Audio::CloseSdlDevice(PortIn* port) { if (port->stream != nullptr) { SDL_DestroyAudioStream(port->stream); SDL_QuitSubSystem(SDL_INIT_AUDIO); port->stream = nullptr; port->device = 0; } } Audio::Id Audio::AudioInOpen(uint32_t samples_num, uint32_t freq, Format format, bool asynchronous) { Common::LockGuard lock(m_mutex); for (int id = 0; id < IN_PORTS_MAX; id++) { if (!m_in_ports[id].used) { auto& port = m_in_ports[id]; port = {}; port.used = true; port.asynchronous = asynchronous; port.samples_num = samples_num; port.freq = freq; port.bytes_per_frame = BytesPerSample(format); if (format == Format::Signed16bitStereo || format == Format::FloatStereo) { port.bytes_per_frame *= 2; } OpenSdlDevice(&port, format); return Id::Create(id); } } return Id::Invalid(); } Audio::PortIn* Audio::GetAudioInPort(Id handle) { if (handle.GetId() < 0 || handle.GetId() >= IN_PORTS_MAX || !m_in_ports[handle.GetId()].used) { return nullptr; } return &m_in_ports[handle.GetId()]; } int Audio::AudioInClose(Id handle) { Common::LockGuard lock(m_mutex); auto* port = GetAudioInPort(handle); if (port == nullptr) { return AUDIO_IN_ERROR_INVALID_HANDLE; } if (port->busy) { return AUDIO_IN_ERROR_BUSY; } CloseSdlDevice(port); *port = {}; return OK; } int Audio::AudioInGetSilentState(Id handle) { Common::LockGuard lock(m_mutex); auto* port = GetAudioInPort(handle); if (port == nullptr) { return AUDIO_IN_ERROR_INVALID_HANDLE; } if (port->stream == nullptr || !RecordingDevicePresent(port->device)) { 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); if (port == nullptr) { return AUDIO_IN_ERROR_INVALID_HANDLE; } if (port->busy) { return AUDIO_IN_ERROR_BUSY; } port->busy = true; snapshot = *port; } bool failed = snapshot.stream != nullptr && dest != nullptr && (!RecordingDevicePresent(snapshot.device) || !SDL_ResumeAudioStreamDevice(snapshot.stream)); const uint64_t block_time = (1000000ULL * snapshot.samples_num) / snapshot.freq; uint64_t wait_time = 0; if (snapshot.asynchronous) { if (dest != nullptr) { wait_time = block_time; } } else if (snapshot.last_input_time != 0 && (snapshot.stream == nullptr || dest == nullptr)) { const auto now = LibKernel::KernelGetProcessTime(); const auto next = snapshot.last_input_time + block_time; if (next > now) { wait_time = next - now; } } Common::Thread::SleepMicro(wait_time); uint32_t frames = snapshot.samples_num; bool timed_out = false; if (dest != nullptr) { if (snapshot.stream != nullptr && !failed) { const int requested = static_cast(frames * snapshot.bytes_per_frame); const auto deadline = LibKernel::KernelGetProcessTime() + std::max(20000, block_time * 4); const auto device = SDL_GetAudioStreamDevice(snapshot.stream); int available = SDL_GetAudioStreamAvailable(snapshot.stream); while (!snapshot.asynchronous && available >= 0 && available < requested && device != 0 && !SDL_AudioDevicePaused(device) && LibKernel::KernelGetProcessTime() < deadline) { Common::Thread::SleepMicro(1000); available = SDL_GetAudioStreamAvailable(snapshot.stream); } failed = available < 0 || device == 0 || SDL_AudioDevicePaused(device); timed_out = !snapshot.asynchronous && available < requested; if (!failed && !timed_out) { if (snapshot.asynchronous) { frames = AUDIO_IN_GRAIN_MAX_ASYNC; } const int bytes = SDL_GetAudioStreamData( snapshot.stream, dest, static_cast(frames * snapshot.bytes_per_frame)); failed = bytes < 0; if (!failed) { frames = static_cast(bytes) / snapshot.bytes_per_frame; } } } if (snapshot.stream == nullptr || failed || timed_out) { std::memset(dest, 0, frames * snapshot.bytes_per_frame); } } else if (snapshot.stream != nullptr) { SDL_PauseAudioStreamDevice(snapshot.stream); SDL_ClearAudioStream(snapshot.stream); } { Common::LockGuard lock(m_mutex); 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(frames) : 0; } namespace AudioOut { LIB_NAME("AudioOut", "AudioOut"); struct AudioOutOutputParam { int handle; const void* ptr; }; struct AudioOutPortState { uint16_t output; uint8_t channel; uint8_t reserved1[1]; int16_t volume; uint16_t reroute_counter; uint64_t flag; uint64_t reserved2[2]; }; int KYTY_SYSV_ABI AudioOutInit() { PRINT_NAME(); return OK; } int KYTY_SYSV_ABI AudioOutOpen(int user_id, int type, int index, uint32_t len, uint32_t freq, uint32_t param) { PRINT_NAME(); LOGF("\t user_id = %d\n" "\t type = %d\n" "\t index = %d\n" "\t len = %u\n" "\t freq = %u\n", user_id, type, index, len, freq); if (!audio_out_port_type_is_valid(type)) { return AUDIO_OUT_ERROR_INVALID_PORT_TYPE; } if (freq == 0) { return AUDIO_OUT_ERROR_INVALID_SAMPLE_FREQ; } EXIT_NOT_IMPLEMENTED(index != 0); Audio::Format format = Audio::Format::Unknown; const auto format_param = param & AUDIO_OUT_PARAM_FORMAT_MASK; switch (format_param) { case 0: format = Audio::Format::Signed16bitMono; break; case 1: format = Audio::Format::Signed16bitStereo; break; case 2: format = Audio::Format::Signed16bit8Ch; break; case 3: format = Audio::Format::FloatMono; break; case 4: format = Audio::Format::FloatStereo; break; case 5: format = Audio::Format::Float8Ch; break; case 6: format = Audio::Format::Signed16bit8ChStd; break; case 7: format = Audio::Format::Float8ChStd; break; default:; } LOGF("\t param = %u (format=%u, %s)\n", param, format_param, magic_enum::enum_name(format)); EXIT_NOT_IMPLEMENTED(format == Audio::Format::Unknown); EXIT_IF(g_audio == nullptr); auto id = g_audio->AudioOutOpen(type, len, freq, format); if (!id.IsValid()) { return AUDIO_OUT_ERROR_PORT_FULL; } return id.ToInt(); } int KYTY_SYSV_ABI AudioOutClose(int handle) { PRINT_NAME(); if (!g_audio->AudioOutClose(Audio::Id(handle))) { return AUDIO_OUT_ERROR_INVALID_PORT; } return OK; } int KYTY_SYSV_ABI AudioOutGetPortState(int handle, AudioOutPortState* state) { PRINT_NAME(); int type = 0; int channels_num = 0; if (!g_audio->AudioOutGetStatus(Audio::Id(handle), &type, &channels_num)) { return AUDIO_OUT_ERROR_INVALID_PORT; } EXIT_NOT_IMPLEMENTED(state == nullptr); state->reroute_counter = 0; state->volume = 127; switch (type) { case AUDIO_OUT_PORT_TYPE_MAIN: case AUDIO_OUT_PORT_TYPE_BGM: case AUDIO_OUT_PORT_TYPE_AUDIO3D: state->output = 1; state->channel = (channels_num > 2 ? 2 : channels_num); break; case AUDIO_OUT_PORT_TYPE_VOICE: case AUDIO_OUT_PORT_TYPE_PERSONAL: state->output = 0x40; state->channel = 1; break; case AUDIO_OUT_PORT_TYPE_PADSPK: case AUDIO_OUT_PORT_TYPE_VIBRATION: state->output = 4; state->channel = 1; break; case AUDIO_OUT_PORT_TYPE_AUX: state->output = 0x80; state->channel = 0; break; default: EXIT("unknown port type: %d\n", type); } LOGF("\t output = %" PRIu16 "\n" "\t channel = %" PRIu8 "\n", state->output, state->channel); return OK; } int KYTY_SYSV_ABI AudioOutSetVolume(int handle, uint32_t flag, int* vol) { PRINT_NAME(); LOGF("\t handle = %d\n" "\t flag = %u\n", handle, flag); EXIT_IF(g_audio == nullptr); EXIT_NOT_IMPLEMENTED(vol == nullptr); if (!g_audio->AudioOutSetVolume(Audio::Id(handle), flag, vol)) { return AUDIO_OUT_ERROR_INVALID_PORT; } return OK; } int KYTY_SYSV_ABI AudioOutOutputs(AudioOutOutputParam* param, uint32_t num) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(param == nullptr); if (num == 0 || num > Audio::OUT_PORTS_MAX) { return AUDIO_OUT_ERROR_INVALID_SIZE; } Audio::OutputParam params[Audio::OUT_PORTS_MAX]; EXIT_IF(g_audio == nullptr); for (uint32_t i = 0; i < num; i++) { params[i].handle = Audio::Id(param[i].handle); params[i].data = param[i].ptr; if (!g_audio->AudioOutValid(params[i].handle)) { return AUDIO_OUT_ERROR_INVALID_PORT; } } return static_cast(g_audio->AudioOutOutputs(params, num)); } int KYTY_SYSV_ABI AudioOutOutput(int handle, const void* ptr) { // EXIT_NOT_IMPLEMENTED(ptr == nullptr); Audio::OutputParam params[1]; EXIT_IF(g_audio == nullptr); params[0].handle = Audio::Id(handle); params[0].data = ptr; if (!g_audio->AudioOutValid(params[0].handle)) { return AUDIO_OUT_ERROR_INVALID_PORT; } return static_cast(g_audio->AudioOutOutputs(params, 1)); } } // namespace AudioOut namespace AudioIn { LIB_NAME("AudioIn", "AudioIn"); 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" "\t type = %d\n" "\t index = %d\n" "\t len = %u\n" "\t freq = %u\n", user_id, type, index, len, freq); if (type != 0 && type != 1) { return AUDIO_IN_ERROR_INVALID_TYPE; } if (index != 0) { return AUDIO_IN_ERROR_INVALID_PARAM; } if (len != 128 && (asynchronous || len != 256)) { return AUDIO_IN_ERROR_INVALID_SIZE; } if (freq != 48000 && (asynchronous || freq != 16000)) { return AUDIO_IN_ERROR_INVALID_FREQ; } Audio::Format format = Audio::Format::Unknown; switch (param) { case 1: format = Audio::Format::Signed16bitMono; break; case 2: format = Audio::Format::Signed16bitStereo; break; case 0x10: case 0x11: format = Audio::Format::FloatMono; break; case 0x12: format = Audio::Format::FloatStereo; break; default: return AUDIO_IN_ERROR_INVALID_PARAM; } LOGF("\t param = %u (%s)\n", param, magic_enum::enum_name(format)); EXIT_IF(g_audio == nullptr); auto id = g_audio->AudioInOpen(len, freq, format, asynchronous); if (!id.IsValid()) { return AUDIO_IN_ERROR_PORT_FULL; } return id.ToInt(); } int KYTY_SYSV_ABI AudioInOpen(int user_id, int type, int index, uint32_t len, uint32_t freq, uint32_t param) { PRINT_NAME(); return OpenPort(user_id, type, index, len, freq, param, false); } int KYTY_SYSV_ABI AudioInHqOpen(int user_id, int type, int index, uint32_t len, uint32_t freq, uint32_t param) { PRINT_NAME(); return OpenPort(user_id, type, index, len, freq, param, true); } int KYTY_SYSV_ABI AudioInClose(int handle) { PRINT_NAME(); EXIT_IF(g_audio == nullptr); if (handle <= 0) { return AUDIO_IN_ERROR_INVALID_HANDLE; } return g_audio->AudioInClose(Audio::Id(handle)); } int KYTY_SYSV_ABI AudioInInput(int handle, void* dest) { PRINT_NAME(); EXIT_IF(g_audio == nullptr); if (handle <= 0) { return AUDIO_IN_ERROR_INVALID_HANDLE; } return g_audio->AudioInInput(Audio::Id(handle), dest); } int KYTY_SYSV_ABI AudioInGetSilentState(int handle) { PRINT_NAME(); EXIT_IF(g_audio == nullptr); if (handle <= 0) { return AUDIO_IN_ERROR_INVALID_HANDLE; } return g_audio->AudioInGetSilentState(Audio::Id(handle)); } } // namespace AudioIn namespace VoiceQoS { LIB_NAME("VoiceQoS", "VoiceQoS"); int KYTY_SYSV_ABI VoiceQoSInit(void* mem_block, uint32_t mem_size, int32_t app_type) { PRINT_NAME(); LOGF("\t mem_block = %016" PRIx64 "\n" "\t mem_size = %" PRIu32 "\n" "\t app_type = %" PRId32 "\n", reinterpret_cast(mem_block), mem_size, app_type); return OK; } } // namespace VoiceQoS } // namespace Libs::Audio