Start playback thread on playback engine construction

This commit is contained in:
Zachary Hall 2024-09-30 13:33:25 -07:00
parent 7e43808b4d
commit 856226e641
3 changed files with 125 additions and 58 deletions

22
config.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <soundtouch_config.h>
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
#define NO_THREADS
#endif
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
#define SDL_SAMPLE_FMT AUDIO_S16SYS
#else
#define SDL_SAMPLE_FMT AUDIO_F32SYS
#endif
#ifdef __ANDROID__
#define USE_OBOE
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
#define SAMPLE_FMT oboe::AudioFormat::I16
#else
#define SAMPLE_FMT oboe::AudioFormat::Float
#endif
#else
#define SAMPLE_FMT SDL_SAMPLE_FMT
#define USE_SDL
#endif

View file

@ -135,6 +135,7 @@ void PlaybackInstance::SDLCallback(void *userdata, Uint8 *stream, int len) {
} }
void PlaybackInstance::Load(const char *file, int idx) { void PlaybackInstance::Load(const char *file, int idx) {
SDL_LockAudioDevice(device); SDL_LockAudioDevice(device);
load_finished.store(false);
playback_ready.store(false); playback_ready.store(false);
if (process != nullptr) delete process; if (process != nullptr) delete process;
try { try {
@ -188,7 +189,6 @@ void PlaybackInstance::Load(const char *file, int idx) {
} }
delete backend_spec_proxy; delete backend_spec_proxy;
playback_ready.store(true); playback_ready.store(true);
load_finished.store(true);
this->process->set_position(0.0); this->process->set_position(0.0);
} else { } else {
set_error("Failed to create playback backend."); set_error("Failed to create playback backend.");
@ -196,8 +196,13 @@ void PlaybackInstance::Load(const char *file, int idx) {
delete process; delete process;
process = nullptr; process = nullptr;
} }
initial_render = true;
SDL_UnlockAudioDevice(device); SDL_UnlockAudioDevice(device);
load_finished.store(true);
flag_mutex.lock();
stopped.store(false);
paused = false;
just_started.store(true);
flag_mutex.unlock();
} }
void PlaybackInstance::Unload() { void PlaybackInstance::Unload() {
if (process == nullptr) return; if (process == nullptr) return;
@ -206,7 +211,8 @@ void PlaybackInstance::Unload() {
process = nullptr; process = nullptr;
SDL_FreeAudioStream(sdl_stream); SDL_FreeAudioStream(sdl_stream);
sdl_stream = nullptr; sdl_stream = nullptr;
free(buf); if (buf) free(buf);
buf = nullptr;
SDL_UnlockAudioDevice(device); SDL_UnlockAudioDevice(device);
} }
void PlaybackInstance::UpdateST() { void PlaybackInstance::UpdateST() {
@ -249,19 +255,14 @@ void PlaybackInstance::InitLoopFunction() {
} }
SDL_AudioSpec obtained; SDL_AudioSpec obtained;
SDL_AudioSpec desired; SDL_AudioSpec desired;
desired.format = desired.format = SDL_SAMPLE_FMT;
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
AUDIO_S16SYS;
#else
AUDIO_F32SYS;
#endif
desired.freq = 48000; desired.freq = 48000;
desired.samples = 200; desired.samples = 200;
desired.channels = 2; desired.channels = 2;
desired.callback = PlaybackInstance::SDLCallback; desired.callback = PlaybackInstance::SDLCallback;
desired.userdata = this; desired.userdata = this;
st = new SoundTouch(); st = new SoundTouch();
#ifndef __ANDROID__ #ifdef USE_SDL
if ((device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) { if ((device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) {
ERROR.writefln("Error opening audio device: '%s'", SDL_GetError()); ERROR.writefln("Error opening audio device: '%s'", SDL_GetError());
set_error("Failed to open audio device!"); set_error("Failed to open audio device!");
@ -269,18 +270,13 @@ void PlaybackInstance::InitLoopFunction() {
loop_started = false; loop_started = false;
return; return;
} }
#else SDL_PauseAudioDevice(device, 0);
#elif defined(USE_OBOE)
oboe::AudioStreamBuilder builder; oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Output); builder.setDirection(oboe::Direction::Output);
builder.setSharingMode(oboe::SharingMode::Shared); builder.setSharingMode(oboe::SharingMode::Shared);
builder.setPerformanceMode(oboe::PerformanceMode::None); builder.setPerformanceMode(oboe::PerformanceMode::None);
builder.setFormat( builder.setFormat(SAMPLE_FMT);
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
oboe::AudioFormat::I16
#else
oboe::AudioFormat::Float
#endif
);
builder.setDataCallback(this); builder.setDataCallback(this);
auto res = builder.openStream(ostream); auto res = builder.openStream(ostream);
if (res != oboe::Result::OK) { if (res != oboe::Result::OK) {
@ -318,25 +314,46 @@ Total samples: %u"
, obtained.samples , obtained.samples
); );
ostream->requestStart(); ostream->requestStart();
#else
#error Invalid configuration detected.
#endif #endif
spec = obtained; spec = obtained;
st->setSampleRate(spec.freq); st->setSampleRate(spec.freq);
st->setChannels(spec.channels); st->setChannels(spec.channels);
UpdateST(); UpdateST();
SDL_PauseAudioDevice(device, 0);
Load(filePath.c_str(), 0);
reload = false; reload = false;
if (process && process->process_running()) {
playback_ready.store(true);
} else {
playback_ready.store(false); playback_ready.store(false);
} flag_mutex.lock();
load_finished.store(true); this->position = 0.0;
seeking.store(false);
paused = true;
flag_mutex.unlock();
playback_ready.store(false);
length = 0.0;
memset(&backend_spec, 0, sizeof(backend_spec));
if (sdl_stream) SDL_FreeAudioStream(sdl_stream);
sdl_stream = nullptr;
bufsize = 0;
if (buf) free((void*)buf);
buf = nullptr;
if (process) delete process;
process = nullptr;
stream_changed.store(false);
file_changed.store(false);
load_requested.store(false);
current_file = {};
current_stream = -1;
current_title = {};
seeking.store(false);
pause_changed.store(false);
just_stopped.store(true);
just_started.store(false);
stopped.store(true);
set_signal(PlaybackSignalStarted); set_signal(PlaybackSignalStarted);
load_finished.store(true);
} }
void PlaybackInstance::LoopFunction() { void PlaybackInstance::LoopFunction() {
if (file_changed.exchange(false) || load_requested.exchange(false)) { if (file_changed.exchange(false) || load_requested.exchange(false)) {
Unload(); Unload();
Load(filePath.c_str(), 0); Load(filePath.c_str(), 0);
@ -345,6 +362,8 @@ void PlaybackInstance::LoopFunction() {
} else { } else {
playback_ready.store(false); playback_ready.store(false);
} }
load_finished.store(true);
just_started.store(true);
} }
if (stream_changed.exchange(false)) { if (stream_changed.exchange(false)) {
current_file_mutex.lock(); current_file_mutex.lock();
@ -375,8 +394,8 @@ void PlaybackInstance::LoopFunction() {
if (process && process->process_running()) process->set_position(this->position); if (process && process->process_running()) process->set_position(this->position);
set_signal(PlaybackSignalSeeked); set_signal(PlaybackSignalSeeked);
} }
if (pause_changed.exchange(false)) { if (pause_changed.exchange(false) || just_stopped.exchange(false) || just_started.exchange(false)) {
SDL_PauseAudioDevice(device, paused ? 1 : 0); SDL_PauseAudioDevice(device, (stopped.load() || paused) ? 1 : 0);
if (paused) { if (paused) {
set_signal(PlaybackSignalPaused); set_signal(PlaybackSignalPaused);
} else { } else {
@ -402,9 +421,9 @@ void PlaybackInstance::DeinitLoopFunction() {
playback_ready.store(false); playback_ready.store(false);
// ==== // ====
Unload(); Unload();
#ifndef __ANDROID__ #ifdef USE_SDL
SDL_CloseAudioDevice(device); SDL_CloseAudioDevice(device);
#else #elif defined(USE_OBOE)
if (ostream && ostream->getState() != oboe::StreamState::Closed) { if (ostream && ostream->getState() != oboe::StreamState::Closed) {
ostream->stop(); ostream->stop();
ostream->close(); ostream->close();
@ -413,7 +432,8 @@ void PlaybackInstance::DeinitLoopFunction() {
#endif #endif
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
delete st; delete st;
free(buf); st = nullptr;
if (buf) free(buf);
current_file_mutex.lock(); current_file_mutex.lock();
current_file = {}; current_file = {};
current_file_mutex.unlock(); current_file_mutex.unlock();
@ -435,14 +455,17 @@ PlaybackInstance::PlaybackInstance() {
running = false; running = false;
paused = true; paused = true;
position = 0; position = 0;
sdl_stream = nullptr;
length = 0; length = 0;
volume = 100.0; volume = 100.0;
real_volume = 1.0;
speed = 1.0; speed = 1.0;
pitch = 1.0; pitch = 1.0;
tempo = 1.0; tempo = 1.0;
prev_speed = -1.0; prev_speed = -1.0;
prev_pitch = -1.0; prev_pitch = -1.0;
prev_tempo = -1.0; prev_tempo = -1.0;
buf = nullptr;
tempo_changed.store(true); tempo_changed.store(true);
speed_changed.store(true); speed_changed.store(true);
pitch_changed.store(true); pitch_changed.store(true);
@ -450,6 +473,21 @@ PlaybackInstance::PlaybackInstance() {
playback_ready = false; playback_ready = false;
bufsize = 0; bufsize = 0;
process = nullptr; process = nullptr;
running.store(true);
#if NO_THREADS
start_loop();
#else
thread = std::thread(&PlaybackInstance::ThreadFunc, this);
loop_started = true;
#endif
while (loop_started && !load_finished.exchange(false)) {
#if NO_THREADS
LoopHook();
#endif
std::this_thread::sleep_for(20ms);
}
load_finished.store(false);
} }
std::optional<std::string> PlaybackInstance::get_current_file() { std::optional<std::string> PlaybackInstance::get_current_file() {
current_file_mutex.lock(); current_file_mutex.lock();
@ -465,38 +503,31 @@ std::optional<std::string> PlaybackInstance::get_current_title() {
} }
PlaybackInstance::~PlaybackInstance() { PlaybackInstance::~PlaybackInstance() {
Stop(); Stop();
running.store(false);
#ifdef NO_THREADS
stop_loop();
#else
thread.join();
#endif
} }
void PlaybackInstance::Load(std::string filePath) { void PlaybackInstance::Load(std::string filePath) {
load_finished.store(false);
this->filePath = filePath; this->filePath = filePath;
INFO.writefln("Loading %s...", filePath.c_str()); INFO.writefln("Loading %s...", filePath.c_str());
if (running.exchange(true)) {
load_requested.store(true);
} else {
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
start_loop();
#else
thread = std::thread(&PlaybackInstance::ThreadFunc, this);
loop_started = true;
#endif
}
flag_mutex.lock(); flag_mutex.lock();
this->position = 0.0; this->position = 0.0;
seeking.store(true); seeking.store(true);
paused = true; paused = true;
Update(); Update();
flag_mutex.unlock(); flag_mutex.unlock();
file_changed.store(true);
wait([this]() {
return load_finished.load();
});
} }
void PlaybackInstance::Start(std::string filePath, int streamIdx) { void PlaybackInstance::Start(std::string filePath, int streamIdx) {
load_finished.store(false);
Load(filePath); Load(filePath);
while (loop_started && !load_finished.exchange(false)) {
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
LoopHook();
#endif
std::this_thread::sleep_for(20ms);
}
if (!loop_started) {
return;
}
INFO.writefln("Playing %s...", filePath.c_str()); INFO.writefln("Playing %s...", filePath.c_str());
flag_mutex.lock(); flag_mutex.lock();
this->position = 0.0; this->position = 0.0;
@ -506,6 +537,9 @@ void PlaybackInstance::Start(std::string filePath, int streamIdx) {
stream_changed.store(true); stream_changed.store(true);
Update(); Update();
flag_mutex.unlock(); flag_mutex.unlock();
wait([this]() {
return load_finished.load();
});
} }
void PlaybackInstance::play_stream(int idx) { void PlaybackInstance::play_stream(int idx) {
flag_mutex.lock(); flag_mutex.lock();
@ -515,6 +549,7 @@ void PlaybackInstance::play_stream(int idx) {
current_stream = idx; current_stream = idx;
stream_changed.store(true); stream_changed.store(true);
Update(); Update();
just_started.store(true);
flag_mutex.unlock(); flag_mutex.unlock();
} }
double PlaybackInstance::GetPosition() { double PlaybackInstance::GetPosition() {
@ -543,13 +578,11 @@ bool PlaybackInstance::IsPaused() {
} }
void PlaybackInstance::Stop() { void PlaybackInstance::Stop() {
if (running.exchange(false)) { flag_mutex.lock();
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__) stopped.store(true);
stop_loop(); just_started.store(false);
#else just_stopped.store(true);
thread.join(); flag_mutex.unlock();
#endif
}
} }
void PlaybackInstance::Update() { void PlaybackInstance::Update() {
if (prev_pitch != pitch) { if (prev_pitch != pitch) {

View file

@ -21,6 +21,7 @@ extern "C" {
#include <map> #include <map>
#include "file_backend.hpp" #include "file_backend.hpp"
#include "playback_backend.hpp" #include "playback_backend.hpp"
#include "config.hpp"
#include "playback_process.hpp" #include "playback_process.hpp"
using namespace soundtouch; using namespace soundtouch;
using std::span; using std::span;
@ -243,6 +244,9 @@ private:
std::atomic_bool load_requested; std::atomic_bool load_requested;
std::atomic_bool load_finished; std::atomic_bool load_finished;
std::atomic_bool stream_changed; std::atomic_bool stream_changed;
std::atomic_bool stopped;
std::atomic_bool just_stopped;
std::atomic_bool just_started;
std::mutex flag_mutex; std::mutex flag_mutex;
std::mutex error_mutex; std::mutex error_mutex;
std::thread thread; std::thread thread;
@ -276,6 +280,14 @@ private:
float prev_pitch, prev_speed, prev_tempo; float prev_pitch, prev_speed, prev_tempo;
FILE_TYPE *file; FILE_TYPE *file;
bool initial_render = false; bool initial_render = false;
inline void wait(std::function<bool()> fn) {
while (!fn()) {
#ifdef NO_THREADS
LoopHook();
#endif
std::this_thread::sleep_for(20ms);
}
}
public: public:
PlaybackInstance(); PlaybackInstance();
~PlaybackInstance() override; ~PlaybackInstance() override;