mirror of
https://github.com/Vita3K/Vita3K.git
synced 2026-07-11 01:34:23 +02:00
audio: Rewrite SDL backend to use condvar wait
modules/SceAudio: fix crackling audio, if empty buffer is used too often Co-authored-by: eagleflo <aku.kotkavuo@gmail.com>
This commit is contained in:
@@ -425,14 +425,7 @@ bool late_init(EmuEnvState &state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ResumeAudioThread resume_thread = [&state](SceUID thread_id) {
|
||||
const auto thread = state.kernel.get_thread(thread_id);
|
||||
const std::lock_guard<std::mutex> lock(thread->mutex);
|
||||
if (thread->status == ThreadStatus::wait) {
|
||||
thread->update_status(ThreadStatus::run);
|
||||
}
|
||||
};
|
||||
if (!state.audio.init(resume_thread, state.cfg.audio_backend)) {
|
||||
if (!state.audio.init(state.cfg.audio_backend)) {
|
||||
LOG_WARN("Failed to initialize audio! Audio will not work.");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,5 +6,4 @@ add_library(
|
||||
src/impl/cubeb_audio.cpp)
|
||||
|
||||
target_include_directories(audio PUBLIC include)
|
||||
target_link_libraries(audio PUBLIC SDL3::SDL3)
|
||||
target_link_libraries(audio PRIVATE util cubeb kernel)
|
||||
target_link_libraries(audio PRIVATE util cubeb SDL3::SDL3)
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
|
||||
bool init() override;
|
||||
AudioOutPortPtr open_port(int nb_channels, int freq, int nb_sample) override;
|
||||
void audio_output(ThreadState &thread, AudioOutPort &out_port, const void *buffer) override;
|
||||
void audio_output(AudioOutPort &out_port, const void *buffer) override;
|
||||
void set_volume(AudioOutPort &out_port, float volume) override;
|
||||
void switch_state(const bool pause) override;
|
||||
};
|
||||
|
||||
@@ -18,36 +18,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "../state.h"
|
||||
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <condition_variable>
|
||||
|
||||
class SDLAudioAdapter : public AudioAdapter {
|
||||
private:
|
||||
SDL_AudioDeviceID device_id = 0;
|
||||
int device_buffer_samples = 0;
|
||||
SDL_AudioSpec dst_spec;
|
||||
|
||||
static void SDLCALL thread_wakeup_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount);
|
||||
|
||||
public:
|
||||
SDLAudioAdapter(AudioState &audio_state);
|
||||
explicit SDLAudioAdapter(AudioState &audio_state);
|
||||
~SDLAudioAdapter() override;
|
||||
|
||||
bool init() override;
|
||||
void switch_state(const bool pause) override;
|
||||
AudioOutPortPtr open_port(int nb_channels, int freq, int nb_sample) override;
|
||||
void audio_output(ThreadState &thread, AudioOutPort &out_port, const void *buffer) override;
|
||||
void audio_output(AudioOutPort &out_port, const void *buffer) override;
|
||||
void set_volume(AudioOutPort &out_port, float volume) override;
|
||||
int get_rest_sample(AudioOutPort &out_port) override;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<SDL_AudioStream> AudioStreamPtr;
|
||||
using AudioStreamPtr = std::shared_ptr<SDL_AudioStream>;
|
||||
|
||||
struct SDLAudioOutPort : public AudioOutPort {
|
||||
// thread currently waiting for the audio to be processed
|
||||
SceUID thread = -1;
|
||||
int channels = 2;
|
||||
AudioStreamPtr stream;
|
||||
SDLAudioAdapter &adapter;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cond_var;
|
||||
SDLAudioOutPort(AudioStreamPtr stream, AudioAdapter &adapter)
|
||||
: stream(std::move(stream))
|
||||
, adapter(dynamic_cast<SDLAudioAdapter &>(adapter)) {}
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
#define SCE_AUDIO_OUT_MAX_VOL 32768 //!< Maximum output port volume
|
||||
#define SCE_AUDIO_VOLUME_0DB SCE_AUDIO_OUT_MAX_VOL //!< Maximum output port volume
|
||||
|
||||
typedef std::function<void(SceUID)> ResumeAudioThread;
|
||||
|
||||
struct AudioOutPort {
|
||||
// Channel range from 0 - 32768
|
||||
int left_channel_volume = SCE_AUDIO_VOLUME_0DB;
|
||||
@@ -73,7 +71,7 @@ public:
|
||||
|
||||
virtual bool init() = 0;
|
||||
virtual AudioOutPortPtr open_port(int nb_channels, int freq, int nb_sample) { return nullptr; }
|
||||
virtual void audio_output(ThreadState &thread, AudioOutPort &out_port, const void *buffer) {}
|
||||
virtual void audio_output(AudioOutPort &out_port, const void *buffer) {}
|
||||
virtual void set_volume(AudioOutPort &out_port, float volume) {}
|
||||
virtual void switch_state(const bool pause) {}
|
||||
virtual int get_rest_sample(AudioOutPort &out_port) { return 0; };
|
||||
@@ -87,14 +85,13 @@ struct AudioState {
|
||||
int next_port_id = 1;
|
||||
AudioOutPortPtrs out_ports;
|
||||
AudioInPort in_port;
|
||||
ResumeAudioThread resume_thread;
|
||||
std::string audio_backend;
|
||||
float global_volume = 1;
|
||||
|
||||
bool init(const ResumeAudioThread &resume_thread, const std::string &adapter_name);
|
||||
bool init(const std::string &adapter_name);
|
||||
void set_backend(const std::string &adapter_name);
|
||||
AudioOutPortPtr open_port(int nb_channels, int freq, int nb_sample);
|
||||
void audio_output(ThreadState &thread, AudioOutPort &out_port, const void *buffer);
|
||||
void audio_output(AudioOutPort &out_port, const void *buffer);
|
||||
void set_volume(AudioOutPort &out_port, float volume);
|
||||
void set_global_volume(float volume);
|
||||
void switch_state(const bool pause);
|
||||
|
||||
@@ -20,17 +20,12 @@
|
||||
#include <audio/impl/cubeb_audio.h>
|
||||
#include <audio/impl/sdl_audio.h>
|
||||
|
||||
#include <kernel/thread/thread_state.h>
|
||||
|
||||
#include <util/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
bool AudioState::init(const ResumeAudioThread &resume_thread, const std::string &adapter_name) {
|
||||
this->resume_thread = resume_thread;
|
||||
|
||||
bool AudioState::init(const std::string &adapter_name) {
|
||||
set_backend(adapter_name);
|
||||
if (!adapter)
|
||||
return false;
|
||||
@@ -68,10 +63,8 @@ AudioOutPortPtr AudioState::open_port(int nb_channels, int freq, int nb_sample)
|
||||
return port;
|
||||
}
|
||||
|
||||
void AudioState::audio_output(ThreadState &thread, AudioOutPort &out_port, const void *buffer) {
|
||||
if (!buffer)
|
||||
return;
|
||||
adapter->audio_output(thread, out_port, buffer);
|
||||
void AudioState::audio_output(AudioOutPort &out_port, const void *buffer) {
|
||||
adapter->audio_output(out_port, buffer);
|
||||
|
||||
uint64_t now = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
uint64_t diff = now - out_port.last_output;
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include "audio/impl/cubeb_audio.h"
|
||||
|
||||
#include "kernel/thread/thread_state.h"
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
static long impl_cubeb_audio_callback(cubeb_stream *stream, void *user_data, const void *input, void *output, long nframes) {
|
||||
@@ -122,15 +119,12 @@ AudioOutPortPtr CubebAudioAdapter::open_port(int nb_channels, int freq, int nb_s
|
||||
return port;
|
||||
}
|
||||
|
||||
void CubebAudioAdapter::audio_output(ThreadState &thread, AudioOutPort &out_port, const void *buffer) {
|
||||
void CubebAudioAdapter::audio_output(AudioOutPort &out_port, const void *buffer) {
|
||||
CubebAudioOutPort &port = static_cast<CubebAudioOutPort &>(out_port);
|
||||
|
||||
std::unique_lock<std::mutex> lock(port.mutex);
|
||||
if (port.nb_buffers_ready == port.audio_buffers.size()) {
|
||||
// is it really useful to update the thread status?
|
||||
thread.update_status(ThreadStatus::wait);
|
||||
port.cond_var.wait(lock);
|
||||
thread.update_status(ThreadStatus::run);
|
||||
}
|
||||
|
||||
assert(port.nb_buffers_ready < port.audio_buffers.size());
|
||||
|
||||
@@ -16,12 +16,9 @@
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include "audio/impl/sdl_audio.h"
|
||||
|
||||
#include "kernel/thread/thread_state.h"
|
||||
|
||||
#include <SDL3/SDL_audio.h>
|
||||
|
||||
#include "util/log.h"
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_hints.h>
|
||||
|
||||
#define SDL_CHECK_EXT(condition, ret) \
|
||||
do { \
|
||||
@@ -38,17 +35,10 @@
|
||||
void SDLCALL SDLAudioAdapter::thread_wakeup_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount) {
|
||||
assert(userdata != nullptr);
|
||||
assert(stream != nullptr);
|
||||
SDLAudioOutPort *port = static_cast<SDLAudioOutPort *>(userdata);
|
||||
// Is there a thread waiting for playback to finish?
|
||||
if (port->thread >= 0) {
|
||||
const int samples_available = port->adapter.get_rest_sample(*port);
|
||||
assert(samples_available >= 0);
|
||||
// Running out of data?
|
||||
if (samples_available < (4 * port->adapter.device_buffer_samples) + port->len) {
|
||||
// Wake the thread up.
|
||||
port->adapter.state.resume_thread(port->thread);
|
||||
port->thread = -1;
|
||||
}
|
||||
const int bytes_available = SDL_GetAudioStreamAvailable(stream);
|
||||
if (bytes_available < 2 * total_amount || additional_amount > 0) {
|
||||
SDLAudioOutPort *port = static_cast<SDLAudioOutPort *>(userdata);
|
||||
port->cond_var.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +52,10 @@ SDLAudioAdapter::~SDLAudioAdapter() {
|
||||
}
|
||||
|
||||
bool SDLAudioAdapter::init() {
|
||||
// SDL3 default is 1024 sample frames for 48kHz audio, which is higher than cubeb.
|
||||
// Request smaller device buffer for lower latency callbacks.
|
||||
// 512 sample frames = 2048 bytes for stereo 16-bit, matching cubeb's callback size.
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, "512");
|
||||
device_id = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
|
||||
SDL_CHECK_EXT(device_id > 0, false);
|
||||
return true;
|
||||
@@ -92,19 +86,17 @@ AudioOutPortPtr SDLAudioAdapter::open_port(int nb_channels, int freq, int nb_sam
|
||||
switch_state(false);
|
||||
return port;
|
||||
}
|
||||
void SDLAudioAdapter::audio_output(ThreadState &thread, AudioOutPort &out_port, const void *buffer) {
|
||||
void SDLAudioAdapter::audio_output(AudioOutPort &out_port, const void *buffer) {
|
||||
// Put audio to the port's stream and see how much is left to play.
|
||||
SDLAudioOutPort &port = static_cast<SDLAudioOutPort &>(out_port);
|
||||
SDL_CHECK_VOID(SDL_PutAudioStreamData(port.stream.get(), buffer, out_port.len_bytes));
|
||||
const int samples_available = get_rest_sample(port);
|
||||
// If there's lots of audio left to play, stop this thread.
|
||||
// The audio callback will wake it up later when it's running out of data.
|
||||
if (samples_available >= (4 * device_buffer_samples) + port.len) {
|
||||
port.thread = thread.id;
|
||||
std::unique_lock<std::mutex> mlock(thread.mutex);
|
||||
thread.update_status(ThreadStatus::wait);
|
||||
thread.status_cond.wait(mlock, [&]() { return thread.status == ThreadStatus::run; });
|
||||
const int samples_available = get_rest_sample(port);
|
||||
if (samples_available > device_buffer_samples) {
|
||||
std::unique_lock<std::mutex> lock(port.mutex);
|
||||
port.cond_var.wait_for(lock, std::chrono::microseconds(port.len_microseconds * 2));
|
||||
}
|
||||
SDL_CHECK_VOID(SDL_PutAudioStreamData(port.stream.get(), buffer, out_port.len_bytes));
|
||||
}
|
||||
|
||||
void SDLAudioAdapter::set_volume(AudioOutPort &out_port, float volume) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <audio/state.h>
|
||||
#include <kernel/state.h>
|
||||
#include <kernel/thread/thread_state.h>
|
||||
#include <util/lock_and_find.h>
|
||||
#include <util/tracy.h>
|
||||
|
||||
@@ -194,12 +195,20 @@ EXPORT(int, sceAudioOutOutput, int port, const void *buf) {
|
||||
return RET_ERROR(SCE_AUDIO_OUT_ERROR_INVALID_PORT);
|
||||
}
|
||||
|
||||
// Empty "buf" variable is valid. It mean wait until sound output is completed.
|
||||
// Because this function always returns when all sound is out, then on empty buf it returns immediately.
|
||||
// Return value is the number of samples (value of 0 or greater) registered to the audio driver for normal termination.
|
||||
if (!buf)
|
||||
return 0;
|
||||
|
||||
const ThreadStatePtr thread = emuenv.kernel.get_thread(thread_id);
|
||||
if (!thread) {
|
||||
return RET_ERROR(SCE_AUDIO_OUT_ERROR_INVALID_PORT);
|
||||
}
|
||||
|
||||
emuenv.audio.audio_output(*thread, *prt, buf);
|
||||
// is it really useful to update the thread status?
|
||||
thread->update_status(ThreadStatus::wait);
|
||||
emuenv.audio.audio_output(*prt, buf);
|
||||
thread->update_status(ThreadStatus::run);
|
||||
|
||||
return prt->len;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user