From 650f3497105ec10fcb99afbc782977dadbbaf290 Mon Sep 17 00:00:00 2001 From: casey langen Date: Thu, 21 Dec 2017 12:46:36 +0000 Subject: [PATCH] Initial implementation of sndio output driver for BSD. --- src/plugins/sndioout/CMakeLists.txt | 2 +- src/plugins/sndioout/SndioOut.cpp | 160 ++++++++++++++++++++++++++-- src/plugins/sndioout/SndioOut.h | 10 ++ 3 files changed, 163 insertions(+), 9 deletions(-) diff --git a/src/plugins/sndioout/CMakeLists.txt b/src/plugins/sndioout/CMakeLists.txt index 0befbdae2..5e5fb13dc 100644 --- a/src/plugins/sndioout/CMakeLists.txt +++ b/src/plugins/sndioout/CMakeLists.txt @@ -4,4 +4,4 @@ set (sndioout_SOURCES ) add_library(sndioout SHARED ${sndioout_SOURCES}) -target_link_libraries(sndioout ${musikcube_LINK_LIBS}) +target_link_libraries(sndioout ${musikcube_LINK_LIBS} sndio) diff --git a/src/plugins/sndioout/SndioOut.cpp b/src/plugins/sndioout/SndioOut.cpp index 659ef019e..c5bc638e1 100644 --- a/src/plugins/sndioout/SndioOut.cpp +++ b/src/plugins/sndioout/SndioOut.cpp @@ -35,16 +35,32 @@ #include "SndioOut.h" #include -#include +#include +#include +#include + +#define DUMPSTATE() std::cerr << "handle=" << this->handle << " state=" << this->state << "\n"; +#define ERROR(str) std::cerr << "SndioOut Error: " << str << "\n"; +#define INFO(str) std::cerr << "SndioOut Info: " << str << "\n"; +#define LOCK() std::unique_lock lock(this->mutex); + using namespace musik::core::sdk; -SndioOut::SndioOut() { +SndioOut::SndioOut() { this->volume = 1.0f; this->state = StateStopped; + this->handle = nullptr; + this->buffer = nullptr; + this->bufferSamples = 0; + this->latency = 0.0; + this->pars = { 0 }; } SndioOut::~SndioOut() { + this->Stop(); + delete[] this->buffer; + this->bufferSamples = 0; } void SndioOut::Release() { @@ -52,15 +68,41 @@ void SndioOut::Release() { } void SndioOut::Pause() { - this->state = StatePaused; + INFO("Pause()") + LOCK() + if (this->handle && this->state == StatePlaying) { + if (!sio_stop(this->handle)) { + ERROR("pause failed") + this->Stop(); + } + else { + INFO("paused") + this->state = StatePaused; + } + } } void SndioOut::Resume() { + INFO("Resume()") + LOCK() + if (this->handle && this->state == StatePaused) { + if (!sio_start(this->handle)) { + ERROR("resume failed") + this->Stop(); + } + else { + INFO("playing") + } + } this->state = StatePlaying; } void SndioOut::SetVolume(double volume) { this->volume = volume; + + if (this->handle) { + sio_setvol(this->handle, lround(volume * SIO_MAXVOL)); + } } double SndioOut::GetVolume() { @@ -68,11 +110,24 @@ double SndioOut::GetVolume() { } void SndioOut::Stop() { + INFO("Stop()") + LOCK() + if (this->handle) { + sio_close(this->handle); + } + this->handle = nullptr; + this->pars = { 0 }; + this->latency = 0; this->state = StateStopped; } void SndioOut::Drain() { - + INFO("Drain()") + LOCK() + if (this->handle) { + sio_stop(this->handle); + sio_start(this->handle); + } } IDeviceList* SndioOut::GetDeviceList() { @@ -87,14 +142,103 @@ IDevice* SndioOut::GetDefaultDevice() { return nullptr; } -int SndioOut::Play(IBuffer *buffer, IBufferProvider *provider) { - if (this->state == StatePaused) { - return OutputInvalidState; +bool SndioOut::InitDevice(IBuffer *buffer) { + this->handle = sio_open(nullptr, SIO_PLAY, 0); + + if (this->handle == nullptr) { + return false; } + int n = 1; bool littleEndian = *(char *) &n == 1; + + sio_initpar(&this->pars); + this->pars.pchan = buffer->Channels(); + this->pars.rate = buffer->SampleRate(); + this->pars.sig = 1; + this->pars.le = !!littleEndian; + this->pars.bits = 16; + + /* stolen from cmus; presumeably they've already iterated + this value and it should be a reasonable default */ + this->pars.appbufsz = pars.rate * 300 / 1000; + + if (!sio_setpar(this->handle, &this->pars)) { + return false; + } + + if (!sio_getpar(this->handle, &this->pars)) { + return false; + } + + if (!sio_start(this->handle)) { + return false; + } + + this->latency = (double) + this->pars.bufsz / + this->pars.pchan / + this->pars.rate; + + this->SetVolume(this->volume); + + return true; +} + +int SndioOut::Play(IBuffer *buffer, IBufferProvider *provider) { + //DUMPSTATE() + + if (this->handle == nullptr) { + INFO("initializing device..."); + if (!this->InitDevice(buffer)) { + ERROR("failed to initialize device") + return OutputInvalidState; + } + INFO("initialized"); + this->state = StateStopped; + } + + if (!this->handle || this->state == StatePaused) { + return OutputInvalidState; + } + + this->state = StatePlaying; + + /* convert to 16-bit PCM */ + long samples = buffer->Samples(); + if (!this->buffer || samples > this->bufferSamples) { + delete[] this->buffer; + this->buffer = new short[samples]; + this->bufferSamples = samples; + } + + float* src = buffer->BufferPointer(); + short* dst = this->buffer; + for (long i = 0; i < samples; i++) { + *dst = (short)((*src) * SHRT_MAX); + ++dst; ++src; + } + + /* write the entire output buffer. this may require multiple passes; + that's ok, just loop until we're done */ + char* data = (char*) this->buffer; + size_t dataLength = samples * sizeof(short); + size_t totalWritten = 0; + + while (totalWritten < dataLength && this->state == StatePlaying) { + size_t remaining = dataLength - totalWritten; + size_t written = 0; + { + LOCK() + written = sio_write(this->handle, data, remaining); + } + totalWritten += written; + data += written; + } + + provider->OnBufferProcessed(buffer); return OutputBufferWritten; } double SndioOut::Latency() { - return 0.0; + return this->latency; } diff --git a/src/plugins/sndioout/SndioOut.h b/src/plugins/sndioout/SndioOut.h index 9a5b42b67..d10a00df4 100644 --- a/src/plugins/sndioout/SndioOut.h +++ b/src/plugins/sndioout/SndioOut.h @@ -34,6 +34,8 @@ #pragma once #include +#include +#include using namespace musik::core::sdk; @@ -60,6 +62,8 @@ class SndioOut : public IOutput { virtual IDevice* GetDefaultDevice() override; private: + bool InitDevice(IBuffer *buffer); + enum State { StateStopped, StatePaused, @@ -68,4 +72,10 @@ class SndioOut : public IOutput { State state; double volume; + sio_hdl* handle; + sio_par pars; + short* buffer; + long bufferSamples; + double latency; + std::recursive_mutex mutex; };