Prepare for playlists in playback

This commit is contained in:
Zachary Hall 2024-12-08 11:01:43 -08:00
parent 9b9ba29dfc
commit 373f9de27c
2 changed files with 152 additions and 74 deletions

View file

@ -31,47 +31,93 @@ void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) {
SDL_memset((void*)stream, 0, len); SDL_memset((void*)stream, 0, len);
if (!playback_ready.load()) return; if (!playback_ready.load()) return;
if (process == nullptr) return; if (process == nullptr) return;
if (st == nullptr) return;
size_t unit = sizeof(SAMPLETYPE) * spec.channels; size_t unit = sizeof(SAMPLETYPE) * spec.channels;
size_t bytes_per_iter = (bufsize / unit) * unit; size_t bytes_per_iter = (bufsize / unit) * unit;
while (st->numSamples() * unit <= (size_t)len) { SAMPLETYPE *tmpbuf = (SAMPLETYPE*)malloc(bytes_per_iter);
if (process == nullptr) { memset(tmpbuf, 0, bytes_per_iter);
return; while (m_st->numSamples() * unit <= (size_t)len) {
} for (auto &data : m_playback_data) {
Uint8 *new_buf; Uint8 *new_buf;
int new_bufsize; int new_bufsize;
new_buf = buf; new_buf = buf;
bytes_per_iter = backend_spec.size; bytes_per_iter = data.m_backend_spec.size;
if (bytes_per_iter > bufsize) { if (bytes_per_iter > bufsize) {
bytes_per_iter = bufsize; bytes_per_iter = bufsize;
} }
new_bufsize = bytes_per_iter; new_bufsize = bytes_per_iter;
if (this->process != nullptr) { size_t new_bytes = data.m_pProcess->render(buf, bytes_per_iter);
size_t new_bytes = this->process->render(buf, bytes_per_iter); if (SDL_AudioStreamPut(data.m_pStream, buf, new_bytes) < 0) {
if (SDL_AudioStreamPut(sdl_stream, buf, new_bytes) < 0) {
ERROR.writefln("SDL_AudioStreamPut: %s", SDL_GetError()); ERROR.writefln("SDL_AudioStreamPut: %s", SDL_GetError());
DEBUG.writefln("Current audio backend: %s", process->get_backend_id().c_str()); DEBUG.writefln("Current audio backend: %s", data.m_pProcess->get_backend_id().c_str());
DEBUG.writefln("SDL_AudioStream *sdl_stream = %0lx", (size_t)sdl_stream); DEBUG.writefln("SDL_AudioStream *sdl_stream = %0lx", (size_t)data.m_pStream);
return; return;
} }
do { do {
new_bufsize = SDL_AudioStreamGet(sdl_stream, buf, std::min((size_t)backend_spec.samples, bufsize / unit) * unit); new_bufsize = SDL_AudioStreamGet(data.m_pStream, buf, bytes_per_iter);
if (new_bufsize < 0) { if (new_bufsize < 0) {
ERROR.writefln("SDL_AudioStreamGet: %s", SDL_GetError()); ERROR.writefln("SDL_AudioStreamGet: %s", SDL_GetError());
DEBUG.writefln("Current audio backend: %s", process->get_backend_id().c_str()); DEBUG.writefln("Current audio backend: %s", data.m_pProcess->get_backend_id().c_str());
return; return;
} }
SAMPLETYPE *sbuf = (SAMPLETYPE*)buf; SAMPLETYPE *sbuf = (SAMPLETYPE*)buf;
for (size_t i = 0; i < (new_bufsize / unit) * spec.channels; i++) { for (size_t i = 0; i < (new_bufsize / unit) * spec.channels; i++) {
sbuf[i] *= real_volume; if (data.m_fade_time >= 0.0) {
if ((i % spec.channels) == 0) {
data.m_fade_time -= 1.0 / (spec.freq);
if (data.m_fade_time <= 0.0) {
data.delete_needed = true;
break;
}
}
sbuf[i] *= data.m_fade_time;
}
tmpbuf[i] += sbuf[i];
}
data.m_st->putSamples(sbuf, new_bufsize / unit);
if (data.delete_needed) {
break;
} }
st->putSamples(sbuf, new_bufsize / unit);
} while (new_bufsize > 0); } while (new_bufsize > 0);
}
for (auto &data : m_playback_data) {
size_t tmplen = 0;
size_t bufused = (bufsize / unit) * unit;
bool done = false;
{
uint samples;
do {
samples = data.m_st->receiveSamples((SAMPLETYPE*)(void*)buf, tmplen / unit);
m_st->putSamples((SAMPLETYPE*)(void*)buf, samples);
} while (samples > 0);
}
for (size_t i = 0; i < len; i += bufused) {
if (i + bufused >= len) {
tmplen = len - i;
} else { } else {
return; tmplen = bufused;
}
uint samples = m_st->receiveSamples((SAMPLETYPE*)(void*)buf, tmplen / unit);
for (size_t j = 0; j < samples * spec.channels; j++) {
((SAMPLETYPE*)(void*)stream)[j] += ((SAMPLETYPE*)(void*)buf)[(i * bufused / unit) + j];
}
if (samples == 0) {
done = true;
break;
}
}
if (data.delete_needed) {
continue;
}
}
for (size_t i = 0; i > m_playback_data.size();) {
auto &data = m_playback_data[i];
if (data.delete_needed) {
m_playback_data.erase(m_playback_data.begin() + i);
} else {
i++;
}
} }
} }
st->receiveSamples((SAMPLETYPE*)stream, len / unit);
} }
#ifdef __ANDROID__ #ifdef __ANDROID__
oboe::DataCallbackResult PlaybackInstance::onAudioReady( oboe::DataCallbackResult PlaybackInstance::onAudioReady(
@ -109,18 +155,23 @@ void PlaybackInstance::Load(const char *file, int idx) {
LockAudioDevice(); LockAudioDevice();
load_finished.store(false); load_finished.store(false);
playback_ready.store(false); playback_ready.store(false);
if (process != nullptr) delete process; for (auto &data : m_playback_data) {
data.m_fade_time = 1.0;
}
PlaybackProcess *process = nullptr;
playback_data data;
try { try {
process = new PlaybackProcess(file, idx); process = new PlaybackProcess(file, idx);
} catch (std::exception e) { } catch (std::exception e) {
ERROR.writefln("Exception caught when creating process: %s", e.what()); ERROR.writefln("Exception caught when creating process: %s", e.what());
process = nullptr; data.delete_needed = true;
} }
length = 0.0; length = 0.0;
if (process != nullptr && process->process_running()) { if (!data.delete_needed && process->process_running()) {
length = process->get_length(); length = process->get_length();
auto backend_spec_proxy = process->get_audio_spec(); auto backend_spec_proxy = process->get_audio_spec();
memset(&backend_spec, 0, sizeof(backend_spec)); SDL_AudioSpec backend_spec;
memset(&backend_spec, 0, sizeof(data.m_backend_spec));
backend_spec.channels = backend_spec_proxy->channel_count(); backend_spec.channels = backend_spec_proxy->channel_count();
audio_data_t sample_fmt; audio_data_t sample_fmt;
sample_fmt.size = backend_spec_proxy->bits() / 8; sample_fmt.size = backend_spec_proxy->bits() / 8;
@ -136,23 +187,22 @@ void PlaybackInstance::Load(const char *file, int idx) {
DEBUG.writefln("\tChannels: %d", backend_spec.channels); DEBUG.writefln("\tChannels: %d", backend_spec.channels);
DEBUG.writefln("\tSample rate: %d", backend_spec.freq); DEBUG.writefln("\tSample rate: %d", backend_spec.freq);
DEBUG.writefln("\tSamples: %d", backend_spec.samples); DEBUG.writefln("\tSamples: %d", backend_spec.samples);
if (sdl_stream != nullptr) { try {
SDL_FreeAudioStream(sdl_stream); data = playback_data(backend_spec, spec, process);
} } catch (CustomException &e) {
sdl_stream = SDL_NewAudioStream(backend_spec.format, backend_spec.channels, backend_spec.freq, spec.format, spec.channels, spec.freq); set_error(e.what());
if (sdl_stream == nullptr) {
ERROR.writefln("SDL_NewAudioStream: %s", SDL_GetError());
DEBUG.writefln("format: AUDIO_%s%d%s", sample_fmt.is_float ? "F" : sample_fmt.is_signed ? "S" : "U", sample_fmt.size * 8, sample_fmt.endian ? "MSB" : "LSB");
set_error("Failed to create SDL audio stream");
set_signal(PlaybackSignalErrorOccurred); set_signal(PlaybackSignalErrorOccurred);
ERROR.writeln(e.what());
}
size_t required_size = sample_fmt.size * data.m_backend_spec.samples;
m_st->setChannels(spec.channels);
m_st->setRate(spec.freq);
m_st->flush();
data.m_backend_spec.size = required_size;
if (bufsize < required_size || buf == nullptr) {
bufsize = required_size;
buf = (Uint8*)((buf == nullptr) ? malloc(bufsize) : realloc(buf, bufsize));
} }
size_t required_size = sample_fmt.size * backend_spec.samples;
st->setChannels(spec.channels);
st->setRate(spec.freq);
st->flush();
backend_spec.size = required_size;
bufsize = backend_spec.size;
buf = (Uint8*)malloc(bufsize);
if (buf == nullptr) { if (buf == nullptr) {
ERROR.writeln("Failed to allocate memory for playback!"); ERROR.writeln("Failed to allocate memory for playback!");
set_error("Failed to allocate memory for playback!"); set_error("Failed to allocate memory for playback!");
@ -176,12 +226,8 @@ void PlaybackInstance::Load(const char *file, int idx) {
flag_mutex.unlock(); flag_mutex.unlock();
} }
void PlaybackInstance::Unload() { void PlaybackInstance::Unload() {
if (process == nullptr) return;
LockAudioDevice(); LockAudioDevice();
delete process; m_playback_data.clear();
process = nullptr;
SDL_FreeAudioStream(sdl_stream);
sdl_stream = nullptr;
if (buf) free(buf); if (buf) free(buf);
buf = nullptr; buf = nullptr;
UnlockAudioDevice(); UnlockAudioDevice();
@ -190,25 +236,25 @@ void PlaybackInstance::UpdateST() {
bool any_changed = false; bool any_changed = false;
if (speed > 0.0f && speed_changed.exchange(false)) { if (speed > 0.0f && speed_changed.exchange(false)) {
any_changed = true; any_changed = true;
st->setRate(speed); m_st->setRate(speed);
set_signal(PlaybackSignalSpeedChanged); set_signal(PlaybackSignalSpeedChanged);
} }
if (tempo > 0.0f && tempo_changed.exchange(false)) { if (tempo > 0.0f && tempo_changed.exchange(false)) {
any_changed = true; any_changed = true;
st->setTempo(tempo); m_st->setTempo(tempo);
set_signal(PlaybackSignalTempoChanged); set_signal(PlaybackSignalTempoChanged);
} }
if (pitch > 0.0f && pitch_changed.exchange(false)) { if (pitch > 0.0f && pitch_changed.exchange(false)) {
any_changed = true; any_changed = true;
st->setPitch(pitch); m_st->setPitch(pitch);
set_signal(PlaybackSignalPitchChanged); set_signal(PlaybackSignalPitchChanged);
} }
if (any_changed && process != nullptr) { if (any_changed && process != nullptr) {
process->set_rate(st->getInputOutputSampleRatio()); process->set_rate(m_st->getInputOutputSampleRatio());
} }
} }
double PlaybackInstance::GetMaxSeconds() { double PlaybackInstance::GetMaxSeconds() {
return std::max((double)(MaxSpeed * MaxTempo), st->getInputOutputSampleRatio()); return std::max((double)(MaxSpeed * MaxTempo), m_st->getInputOutputSampleRatio());
} }
void PlaybackInstance::InitLoopFunction() { void PlaybackInstance::InitLoopFunction() {
bool reload = false; bool reload = false;
@ -232,7 +278,7 @@ void PlaybackInstance::InitLoopFunction() {
desired.channels = 2; desired.channels = 2;
desired.callback = PlaybackInstance::SDLCallback; desired.callback = PlaybackInstance::SDLCallback;
desired.userdata = this; desired.userdata = this;
st = new SoundTouch(); m_st = new SoundTouch();
#ifdef USE_SDL #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());
@ -289,8 +335,8 @@ Total samples: %u"
#error Invalid configuration detected. #error Invalid configuration detected.
#endif #endif
spec = obtained; spec = obtained;
st->setSampleRate(spec.freq); m_st->setSampleRate(spec.freq);
st->setChannels(spec.channels); m_st->setChannels(spec.channels);
UpdateST(); UpdateST();
reload = false; reload = false;
@ -302,9 +348,7 @@ Total samples: %u"
flag_mutex.unlock(); flag_mutex.unlock();
playback_ready.store(false); playback_ready.store(false);
length = 0.0; length = 0.0;
memset(&backend_spec, 0, sizeof(backend_spec)); m_playback_data.clear();
if (sdl_stream) SDL_FreeAudioStream(sdl_stream);
sdl_stream = nullptr;
bufsize = 0; bufsize = 0;
if (buf) free((void*)buf); if (buf) free((void*)buf);
buf = nullptr; buf = nullptr;
@ -406,8 +450,8 @@ void PlaybackInstance::DeinitLoopFunction() {
SDL_CloseAudioDevice(device); SDL_CloseAudioDevice(device);
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
#endif #endif
delete st; delete m_st;
st = nullptr; m_st = nullptr;
if (buf) free(buf); if (buf) free(buf);
current_file_mutex.lock(); current_file_mutex.lock();
current_file = {}; current_file = {};
@ -430,7 +474,6 @@ 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; real_volume = 1.0;

View file

@ -21,6 +21,8 @@
#include "playback_process.hpp" #include "playback_process.hpp"
#include <google/protobuf/any.h> #include <google/protobuf/any.h>
#include <ipc/internal.pb.h> #include <ipc/internal.pb.h>
#include "playlist.hpp"
#include "util.hpp"
using namespace soundtouch; using namespace soundtouch;
using std::span; using std::span;
using std::optional; using std::optional;
@ -272,12 +274,45 @@ private:
double length; double length;
bool paused; bool paused;
Uint8* buf; Uint8* buf;
size_t tmpbuf_len;
size_t bufsize; size_t bufsize;
SDL_AudioDeviceID device; SDL_AudioDeviceID device;
SoundTouch *st;
SDL_AudioSpec spec; SDL_AudioSpec spec;
SDL_AudioSpec backend_spec; struct playback_data {
SDL_AudioStream *sdl_stream = nullptr; SDL_AudioStream *m_pStream = nullptr;
SDL_AudioSpec m_backend_spec = {0};
PlaybackProcess *m_pProcess = nullptr;
SoundTouch *m_st = nullptr;
bool delete_needed = false;
inline bool fading() {
return m_fade_time >= 0.0;
}
double m_fade_time = -1.0;
inline bool valid() {
return m_st != nullptr && m_pStream != nullptr && m_pProcess != nullptr;
}
inline playback_data() {}
inline playback_data(SDL_AudioSpec backend_spec, SDL_AudioSpec spec, PlaybackProcess *process, double speed = 1.0, double tempo = 1.0, double pitch = 1.0) {
m_backend_spec = backend_spec;
m_pStream = SDL_NewAudioStream(backend_spec.format, backend_spec.channels, backend_spec.freq, spec.format, spec.channels, spec.freq);
if (m_pStream == nullptr) throw CustomException(fmt::format("Could not create stream: {}", SDL_GetError()));
m_st = new SoundTouch();
m_st->setChannels(spec.channels);
m_st->setSampleRate(spec.freq);
m_st->setRate(speed);
m_st->setPitch(pitch);
m_st->setTempo(tempo);
}
inline ~playback_data() {
if (m_pStream != nullptr) SDL_FreeAudioStream(m_pStream);
delete m_pProcess;
delete m_st;
}
};
Playlist playlist;
SoundTouch *m_st;
std::vector<playback_data> m_playback_data;
void CleanPlaybackData();
void SDLCallbackInner(Uint8 *stream, int len); void SDLCallbackInner(Uint8 *stream, int len);
static void SDLCallback(void *userdata, Uint8 *stream, int len); static void SDLCallback(void *userdata, Uint8 *stream, int len);
void Load(const char *file, int idx); void Load(const char *file, int idx);