#include "playback.h" #include "SDL_mixer.h" #include "playback_backend.hpp" #include #include extern "C" { #include #include #include } #include #include #include #include #include #ifdef __linux__ #include #endif #include "log.hpp" #include #include "dbus.hpp" #include #include "util.hpp" #include "file_backend.hpp" using namespace std::chrono; size_t CalculateBufSize(SDL_AudioSpec *obtained, double seconds, double max_seconds, size_t samples_override = 0) { return ((((samples_override == 0) ? obtained->samples : samples_override) * std::min(seconds, max_seconds)) + 1) * sizeof(SAMPLETYPE) * obtained->channels; } void PlaybackInstance::SDLCallbackInner(Uint8 *stream, int len) { SDL_memset((void*)stream, 0, len); if (!playback_ready.load()) return; if (process == nullptr) return; size_t unit = sizeof(SAMPLETYPE) * spec.channels; size_t bytes_per_iter = (bufsize / unit) * unit; SAMPLETYPE *tmpbuf = (SAMPLETYPE*)malloc(bytes_per_iter); memset(tmpbuf, 0, bytes_per_iter); while (m_st->numSamples() * unit <= (size_t)len) { for (auto &data : m_playback_data) { Uint8 *new_buf; int new_bufsize; new_buf = buf; bytes_per_iter = data.m_backend_spec.size; if (bytes_per_iter > bufsize) { bytes_per_iter = bufsize; } new_bufsize = bytes_per_iter; size_t new_bytes = data.m_pProcess->render(buf, bytes_per_iter); if (SDL_AudioStreamPut(data.m_pStream, buf, new_bytes) < 0) { ERROR.writefln("SDL_AudioStreamPut: %s", SDL_GetError()); DEBUG.writefln("Current audio backend: %s", data.m_pProcess->get_backend_id().c_str()); DEBUG.writefln("SDL_AudioStream *sdl_stream = %0lx", (size_t)data.m_pStream); return; } do { new_bufsize = SDL_AudioStreamGet(data.m_pStream, buf, bytes_per_iter); if (new_bufsize < 0) { ERROR.writefln("SDL_AudioStreamGet: %s", SDL_GetError()); DEBUG.writefln("Current audio backend: %s", data.m_pProcess->get_backend_id().c_str()); return; } SAMPLETYPE *sbuf = (SAMPLETYPE*)buf; for (size_t i = 0; i < (new_bufsize / unit) * spec.channels; i++) { 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; } } 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 { 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++; } } } } #ifdef __ANDROID__ oboe::DataCallbackResult PlaybackInstance::onAudioReady( oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) { audio_playback_mutex.lock(); if (!audio_playback_paused.load()) { SDLCallbackInner((Uint8*)audioData, numFrames * audioStream->getBytesPerFrame()); } else { memset(audioData, 0, numFrames * audioStream->getBytesPerFrame()); } audio_playback_mutex.unlock(); return oboe::DataCallbackResult::Continue; } #endif void PlaybackInstance::LockAudioDevice() { #ifdef USE_SDL SDL_LockAudioDevice(device); #else audio_playback_mutex.lock(); #endif } void PlaybackInstance::UnlockAudioDevice() { #ifdef USE_SDL SDL_UnlockAudioDevice(device); #else audio_playback_mutex.unlock(); #endif } void PlaybackInstance::SDLCallback(void *userdata, Uint8 *stream, int len) { ((PlaybackInstance*)userdata)->SDLCallbackInner(stream, len); } void PlaybackInstance::Load(const char *file, int idx) { LockAudioDevice(); load_finished.store(false); playback_ready.store(false); for (auto &data : m_playback_data) { data.m_fade_time = 1.0; } PlaybackProcess *process = nullptr; playback_data data; try { process = new PlaybackProcess(file, idx); } catch (std::exception e) { ERROR.writefln("Exception caught when creating process: %s", e.what()); data.delete_needed = true; } length = 0.0; if (!data.delete_needed && process->process_running()) { length = process->get_length(); auto backend_spec_proxy = process->get_audio_spec(); SDL_AudioSpec backend_spec; memset(&backend_spec, 0, sizeof(data.m_backend_spec)); backend_spec.channels = backend_spec_proxy->channel_count(); audio_data_t sample_fmt; sample_fmt.size = backend_spec_proxy->bits() / 8; sample_fmt.endian = backend_spec_proxy->endian() == EndianID::BIG; sample_fmt.is_float = backend_spec_proxy->format_type() == FormatType::FLOAT; sample_fmt.is_signed = backend_spec_proxy->format_type() != FormatType::UNSIGNED; backend_spec.format = sample_spec_to_sdl(sample_fmt); backend_spec.freq = backend_spec_proxy->sample_rate(); backend_spec.samples = backend_spec_proxy->has_max_samples() ? backend_spec_proxy->max_samples() : backend_spec_proxy->has_min_samples() ? backend_spec_proxy->min_samples() : 0; if (backend_spec.samples == 0) backend_spec.samples = 100; DEBUG.writeln("Backend audio specification:"); DEBUG.writefln("\tFormat: %s", sdl_to_str(backend_spec.format).c_str()); DEBUG.writefln("\tChannels: %d", backend_spec.channels); DEBUG.writefln("\tSample rate: %d", backend_spec.freq); DEBUG.writefln("\tSamples: %d", backend_spec.samples); try { data = playback_data(backend_spec, spec, process); } catch (CustomException &e) { set_error(e.what()); 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)); } if (buf == nullptr) { ERROR.writeln("Failed to allocate memory for playback!"); set_error("Failed to allocate memory for playback!"); set_signal(PlaybackSignalErrorOccurred); bufsize = 0; } delete backend_spec_proxy; playback_ready.store(true); } else { set_error("Failed to create playback backend."); set_signal(PlaybackSignalErrorOccurred); delete process; process = nullptr; } UnlockAudioDevice(); load_finished.store(true); flag_mutex.lock(); stopped.store(false); paused = false; just_started.store(true); flag_mutex.unlock(); } void PlaybackInstance::Unload() { LockAudioDevice(); m_playback_data.clear(); if (buf) free(buf); buf = nullptr; UnlockAudioDevice(); } void PlaybackInstance::UpdateST() { bool any_changed = false; if (speed > 0.0f && speed_changed.exchange(false)) { any_changed = true; m_st->setRate(speed); set_signal(PlaybackSignalSpeedChanged); } if (tempo > 0.0f && tempo_changed.exchange(false)) { any_changed = true; m_st->setTempo(tempo); set_signal(PlaybackSignalTempoChanged); } if (pitch > 0.0f && pitch_changed.exchange(false)) { any_changed = true; m_st->setPitch(pitch); set_signal(PlaybackSignalPitchChanged); } if (any_changed && process != nullptr) { process->set_rate(m_st->getInputOutputSampleRatio()); } } double PlaybackInstance::GetMaxSeconds() { return std::max((double)(MaxSpeed * MaxTempo), m_st->getInputOutputSampleRatio()); } void PlaybackInstance::InitLoopFunction() { bool reload = false; init_audio_data(); speed_changed.store(true); tempo_changed.store(true); pitch_changed.store(true); playback_ready.store(false); if (!SDL_WasInit(SDL_INIT_AUDIO)) { if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { ERROR.writefln("Error initializing SDL: '%s'", SDL_GetError()); set_error("Failed to initialize SDL!"); return; } } SDL_AudioSpec obtained; SDL_AudioSpec desired; desired.format = SDL_SAMPLE_FMT; desired.freq = 48000; desired.samples = 400; desired.channels = 2; desired.callback = PlaybackInstance::SDLCallback; desired.userdata = this; m_st = new SoundTouch(); #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) { ERROR.writefln("Error opening audio device: '%s'", SDL_GetError()); set_error("Failed to open audio device!"); running = false; loop_started = false; return; } SDL_PauseAudioDevice(device, 0); #elif defined(USE_OBOE) oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Output); builder.setSharingMode(oboe::SharingMode::Shared); builder.setPerformanceMode(oboe::PerformanceMode::PowerSaving); builder.setFormat(SAMPLE_FMT); builder.setDataCallback(this); auto res = builder.openStream(ostream); if (res != oboe::Result::OK) { ERROR.writefln("Error opening audio device."); set_error("Failed to open audio device!"); running = false; loop_started = false; return; } obtained = desired; obtained.channels = ostream->getChannelCount(); obtained.freq = ostream->getSampleRate(); auto bufferFrames = ostream->getBufferSizeInFrames(); auto bytesPerFrame = ostream->getBytesPerFrame(); auto bytesPerSample = ostream->getBytesPerSample(); obtained.size = bufferFrames * bytesPerFrame; obtained.samples = obtained.size / bytesPerSample; oboe::AudioFormat format = ostream->getFormat(); DEBUG.writefln("About to start audio stream.\n\ Format: %s\n\ Channel count: %u\n\ Sample rate: %u\n\ Buffer size (frames): %u\n\ Bytes per frame: %u\n\ Total bytes: %u\n\ Bytes per sample: %u\n\ Total samples: %u" , oboe::convertToText(format) , obtained.channels , obtained.freq , bufferFrames , bytesPerFrame , obtained.size , bytesPerSample , obtained.samples ); ostream->requestStart(); #else #error Invalid configuration detected. #endif spec = obtained; m_st->setSampleRate(spec.freq); m_st->setChannels(spec.channels); UpdateST(); reload = false; playback_ready.store(false); flag_mutex.lock(); this->position = 0.0; seeking.store(false); paused = true; flag_mutex.unlock(); playback_ready.store(false); length = 0.0; m_playback_data.clear(); 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); load_finished.store(true); } void PlaybackInstance::LoopFunction() { if (file_changed.exchange(false) || load_requested.exchange(false)) { Unload(); Load(filePath.c_str(), 0); if (process && process->process_running()) { playback_ready.store(true); } else { playback_ready.store(false); } load_finished.store(true); just_started.store(true); } if (stream_changed.exchange(false)) { current_file_mutex.lock(); if (current_file.has_value()) { std::string file = current_file.value(); if (current_stream >= streams.size() || current_stream < 0 || streams[current_stream].name == "" || streams[current_stream].length <= 0) { current_stream = 0; } else { LockAudioDevice(); if (process && process->process_running()) process->set_stream_idx(current_stream); UnlockAudioDevice(); } if (process && process->process_running()) { playback_ready.store(true); } else { playback_ready.store(false); } } current_file_mutex.unlock(); } if (flag_mutex.try_lock()) { if (seeking.exchange(false)) { if (process && process->process_running()) process->set_position(this->position); set_signal(PlaybackSignalSeeked); } if (pause_changed.exchange(false) || just_stopped.exchange(false) || just_started.exchange(false)) { audio_playback_paused.store(stopped.load() || paused); #ifdef USE_SDL SDL_PauseAudioDevice(device, (audio_playback_paused.load()) ? 1 : 0); #endif if (paused) { set_signal(PlaybackSignalPaused); } else { set_signal(PlaybackSignalResumed); } } if (update.exchange(false)) { LockAudioDevice(); real_volume = volume / 100.0; UpdateST(); UnlockAudioDevice(); } flag_mutex.unlock(); } if (process) { position = process->get_position(); } else { position = 0.0; } } void PlaybackInstance::Render(std::string opath) { output_file_mutex.lock(); output_file = opath; output_file_mutex.unlock(); } void PlaybackInstance::DeinitLoopFunction() { playback_ready.store(false); // ==== Unload(); #ifdef USE_OBOE if (ostream && ostream->getState() != oboe::StreamState::Closed) { ostream->stop(); ostream->close(); } ostream.reset(); #elif defined(USE_SDL) SDL_CloseAudioDevice(device); SDL_QuitSubSystem(SDL_INIT_AUDIO); #endif delete m_st; m_st = nullptr; if (buf) free(buf); current_file_mutex.lock(); current_file = {}; current_file_mutex.unlock(); set_signal(PlaybackSignalStopped); } void PlaybackInstance::ThreadFunc() { #ifdef __linux__ pthread_setname_np(pthread_self(), "Playback control thread"); #endif start_loop(); while (running && loop_started) { LoopHook(); std::this_thread::sleep_for(20ms); } stop_loop(); } PlaybackInstance::PlaybackInstance() { running = false; paused = true; position = 0; length = 0; volume = 100.0; real_volume = 1.0; speed = 1.0; pitch = 1.0; tempo = 1.0; prev_speed = -1.0; prev_pitch = -1.0; prev_tempo = -1.0; buf = nullptr; tempo_changed.store(true); speed_changed.store(true); pitch_changed.store(true); current_file = {}; playback_ready = false; bufsize = 0; process = nullptr; running.store(true); #ifdef NO_THREADS start_loop(); #else thread = std::thread(&PlaybackInstance::ThreadFunc, this); loop_started = true; #endif while (loop_started && !load_finished.exchange(false)) { #ifdef NO_THREADS LoopHook(); #endif std::this_thread::sleep_for(20ms); } load_finished.store(false); } std::optional PlaybackInstance::get_current_file() { current_file_mutex.lock(); std::optional output = current_file; current_file_mutex.unlock(); return output; } std::optional PlaybackInstance::get_current_title() { current_file_mutex.lock(); std::optional output = current_title; current_file_mutex.unlock(); return output; } PlaybackInstance::~PlaybackInstance() { Stop(); running.store(false); #ifdef NO_THREADS stop_loop(); #else thread.join(); #endif } void PlaybackInstance::Load(std::string filePath) { load_finished.store(false); this->filePath = filePath; INFO.writefln("Loading %s...", filePath.c_str()); flag_mutex.lock(); this->position = 0.0; seeking.store(true); paused = true; Update(); flag_mutex.unlock(); file_changed.store(true); wait([this]() { return load_finished.load(); }); } void PlaybackInstance::Start(std::string filePath, int streamIdx) { load_finished.store(false); Load(filePath); INFO.writefln("Playing %s...", filePath.c_str()); flag_mutex.lock(); this->position = 0.0; seeking.store(true); paused = false; current_stream = streamIdx; stream_changed.store(true); Update(); flag_mutex.unlock(); wait([this]() { return load_finished.load(); }); } void PlaybackInstance::play_stream(int idx) { flag_mutex.lock(); this->position = 0.0; seeking.store(true); paused = false; current_stream = idx; stream_changed.store(true); Update(); just_started.store(true); flag_mutex.unlock(); } double PlaybackInstance::GetPosition() { return position; } double PlaybackInstance::GetLength() { return length; } void PlaybackInstance::Seek(double position) { flag_mutex.lock(); this->position = position; seeking.store(true); flag_mutex.unlock(); } void PlaybackInstance::Pause() { flag_mutex.lock(); paused = !paused; pause_changed.store(true); flag_mutex.unlock(); } bool PlaybackInstance::IsPaused() { return paused; } void PlaybackInstance::Stop() { flag_mutex.lock(); stopped.store(true); just_started.store(false); just_stopped.store(true); flag_mutex.unlock(); } void PlaybackInstance::Update() { if (prev_pitch != pitch) { pitch_changed.store(true); } if (prev_speed != speed) { speed_changed.store(true); } if (prev_tempo != tempo) { tempo_changed.store(true); } update.store(true); } bool PlaybackInstance::IsStopped() { return !running; } void Playback::set_signal(uint16_t signal) { signal_mutex.lock(); for (auto &kv : signals_occurred) { kv.second |= signal; } signal_mutex.unlock(); } uint16_t Playback::handle_signals(uint16_t signals, void *handle) { if (signal_mutex.try_lock()) { if (!signals_occurred.contains(handle)) { signals_occurred[handle] = PlaybackSignalNone; } uint16_t output = signals_occurred[handle]; signals_occurred[handle] &= ~output; signal_mutex.unlock(); return output; } else { return PlaybackSignalNone; } } void Playback::register_handle(void *handle) { signal_mutex.lock(); if (!signals_occurred.contains(handle)) { signals_occurred[handle] = PlaybackSignalNone; } signal_mutex.unlock(); error_mutex.lock(); if (!errors_occurred.contains(handle)) { std::deque new_value; for (size_t i = 0; i < errors.size(); i++) { new_value.push_back(errors[i]); } errors_occurred[handle] = new_value; } error_mutex.unlock(); } void Playback::unregister_handle(void *handle) { signal_mutex.lock(); if (signals_occurred.contains(handle)) { signals_occurred.erase(handle); } signal_mutex.unlock(); error_mutex.lock(); if (errors_occurred.contains(handle)) { errors_occurred.erase(handle); } error_mutex.unlock(); } void PlaybackInstance::SetTempo(float tempo) { this->tempo = tempo; Update(); } void PlaybackInstance::SetPitch(float pitch) { this->pitch = pitch; Update(); } void PlaybackInstance::SetSpeed(float speed) { this->speed = speed; Update(); } void PlaybackInstance::SetVolume(float volume) { this->volume = volume; Update(); } float PlaybackInstance::GetTempo() { return tempo; } float PlaybackInstance::GetPitch() { return pitch; } float PlaybackInstance::GetSpeed() { return speed; } float PlaybackInstance::GetVolume() { return volume; } void Playback::set_error(std::string desc) { error_mutex.lock(); errors.push_back(desc); for (auto &kv : errors_occurred) { kv.second.push_front(desc); } error_mutex.unlock(); set_signal(PlaybackSignalErrorOccurred); } Playback *Playback::Create(bool *daemon_found, bool daemon) { #ifdef DBUS_ENABLED auto *dbus_proxy = DBusAPISender::Create(); if (dbus_proxy != nullptr) { if (daemon_found != nullptr) { *daemon_found = dbus_proxy->IsDaemon(); } DEBUG.writefln("DBus proxy daemon found: %s", *daemon_found ? "true" : "false"); if (daemon) { delete dbus_proxy; return nullptr; } else { return dbus_proxy; } } if (daemon_found != nullptr) { *daemon_found = false; } #endif DEBUG.writeln("Creating new playback instance."); return new PlaybackInstance(); } std::vector PlaybackInstance::get_streams() { std::vector output; for (auto stream : streams) { output.push_back(stream); } return output; } int PlaybackInstance::get_current_stream() { return current_stream; } void PlaybackInstance::set_property(std::string path, google::protobuf::Any value) { if (this->process != nullptr) { this->process->set_property(path, value); } } std::optional PlaybackInstance::get_property(std::string path) { if (this->process != nullptr) { return this->process->get_property(path); } return {}; } std::optional PlaybackInstance::reset_property(std::string path) { return {}; } std::vector PlaybackInstance::get_property_list() { if (process == nullptr) return {}; return process->get_property_list(); }