1 //////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2004-2021 musikcube team
4 //
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions are met:
9 //
10 // * Redistributions of source code must retain the above copyright notice,
11 // this list of conditions and the following disclaimer.
12 //
13 // * Redistributions in binary form must reproduce the above copyright
14 // notice, this list of conditions and the following disclaimer in the
15 // documentation and/or other materials provided with the distribution.
16 //
17 // * Neither the name of the author nor the names of other contributors may
18 // be used to endorse or promote products derived from this software
19 // without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
32 //
33 //////////////////////////////////////////////////////////////////////////////
34
35 #include "SndioOut.h"
36
37 #include <musikcore/sdk/constants.h>
38 #include <musikcore/sdk/IPreferences.h>
39 #include <math.h>
40 #include <limits.h>
41 #include <iostream>
42 #include <functional>
43
44 #define BUFFER_COUNT 16
45 #define ERROR(str) std::cerr << "SndioOut Error: " << str << "\n";
46 #define INFO(str) std::cerr << "SndioOut Info: " << str << "\n";
47 #define LOCK() std::unique_lock<std::mutex> lock(this->mutex);
48 #define WAIT() this->threadEvent.wait(lock);
49 #define NOTIFY() this->threadEvent.notify_all();
50
51 #define STOP() \
52 if(started) { \
53 if (!sio_stop(handle)) { \
54 INFO("failed to stop sndio") \
55 quit = true; \
56 continue; \
57 } \
58 else { \
59 INFO("stopped handle") \
60 started = false; \
61 } \
62 } \
63 else { \
64 INFO("already stopped") \
65 }
66
67 #define START() \
68 if(!started) { \
69 if (!sio_start(handle)) { \
70 INFO("failed to start sndio") \
71 quit = true; \
72 continue; \
73 } \
74 else { \
75 INFO("started handle") \
76 started = true; \
77 } \
78 } \
79 else { \
80 INFO("handle already started") \
81 }
82
83 #define PREF_DEVICE_ID "device_id"
84
85 using namespace musik::core::sdk;
86
87 static char* deviceId = nullptr;
88
SetPreferences(musik::core::sdk::IPreferences * prefs)89 extern "C" void SetPreferences(musik::core::sdk::IPreferences* prefs) {
90 size_t len = prefs->GetString(PREF_DEVICE_ID, nullptr, 0, "");
91 if (len > 1) {
92 delete[] deviceId;
93 deviceId = new char[len];
94 prefs->GetString(PREF_DEVICE_ID, deviceId, len, "");
95 prefs->Save();
96 INFO("setting deviceId to " + std::string(deviceId));
97 }
98 }
99
SndioOut()100 SndioOut::SndioOut() {
101 INFO("---------- sndout.ctor ----------")
102 this->volume = 1.0f;
103 this->state = StateStopped;
104 this->latency = 0.0;
105 this->writeThread.reset(new std::thread(
106 std::bind(&SndioOut::WriteLoop, this)));
107 }
108
~SndioOut()109 SndioOut::~SndioOut() {
110 this->PushCommand(Command::Quit);
111 INFO("joining thread")
112 this->writeThread->join();
113 INFO("thread finished")
114 }
115
Release()116 void SndioOut::Release() {
117 delete this;
118 }
119
PushCommand(Command command)120 void SndioOut::PushCommand(Command command) {
121 //INFO("PushCommand.start")
122 {
123 LOCK()
124 commands.push_back(command);
125 }
126 NOTIFY()
127 //INFO("PushCommand.end")
128 }
129
Pause()130 void SndioOut::Pause() {
131 this->PushCommand(Command::Pause);
132 }
133
Resume()134 void SndioOut::Resume() {
135 this->PushCommand(Command::Resume);
136 }
137
SetVolume(double volume)138 void SndioOut::SetVolume(double volume) {
139 this->volume = volume;
140 this->PushCommand(Command::SetVolume);
141 }
142
GetVolume()143 double SndioOut::GetVolume() {
144 return this->volume;
145 }
146
Stop()147 void SndioOut::Stop() {
148 this->PushCommand(Command::Stop);
149 this->DiscardBuffers();
150 }
151
Drain()152 void SndioOut::Drain() {
153 this->PushCommand(Command::Drain);
154 }
155
GetDeviceList()156 IDeviceList* SndioOut::GetDeviceList() {
157 return nullptr;
158 }
159
SetDefaultDevice(const char * deviceId)160 bool SndioOut::SetDefaultDevice(const char* deviceId) {
161 return false;
162 }
163
GetDefaultDevice()164 IDevice* SndioOut::GetDefaultDevice() {
165 return nullptr;
166 }
167
Play(IBuffer * buffer,IBufferProvider * provider)168 OutputState SndioOut::Play(IBuffer *buffer, IBufferProvider *provider) {
169 std::this_thread::yield();
170
171 if (this->state != StatePlaying) {
172 return OutputState::InvalidState;
173 }
174
175 {
176 LOCK()
177 if (this->CountBuffersWithProvider(provider) >= BUFFER_COUNT) {
178 return OutputState::BufferFull;
179 }
180 this->buffers.push_back(BufferContext{provider, buffer});
181 }
182
183 NOTIFY()
184 return OutputState::BufferWritten;
185 }
186
WriteLoop()187 void SndioOut::WriteLoop() {
188 bool started = false;
189 sio_hdl* handle = nullptr;
190 sio_par pars = { 0 };
191 short* pcm = nullptr;
192 long pcmSamples = 0;
193 float ditherState = 0.0;
194 bool quit = false;
195 BufferContext next {nullptr, nullptr};
196
197 /* open the output device. we only do this once */
198 const char* device = (::deviceId && strlen(::deviceId)) ? deviceId : nullptr;
199 handle = sio_open(device, SIO_PLAY, 0);
200 if (handle == nullptr) {
201 INFO("failed to init device")
202 quit = true;
203 }
204 else {
205 if (!sio_start(handle)) {
206 INFO("device opened, but couldn't start")
207 quit = true;
208 }
209 else {
210 started = true;
211 INFO("device handle initialized")
212 }
213 }
214
215 while (!quit) {
216 {
217 /* we wait until we have commands to process or samples to play */
218 LOCK()
219 while (!quit && !this->commands.size() &&
220 (this->state != StatePlaying || !this->buffers.size()))
221 {
222 //INFO("waiting")
223 WAIT()
224 //INFO("done waiting")
225 }
226
227 if (quit) {
228 continue;
229 }
230
231 /* process commands */
232 for (auto command : this->commands) {
233 switch (command) {
234 case Command::Pause: {
235 INFO("command.pause")
236 STOP()
237 this->state = StatePaused;
238 } break;
239 case Command::Resume: {
240 INFO("command.resume")
241 START()
242 this->state = StatePlaying;
243 } break;
244 case Command::Stop: {
245 INFO("command.stop")
246 STOP()
247 this->state = StateStopped;
248 } break;
249 case Command::SetVolume: {
250 INFO("command.setvolume")
251 if (handle) {
252 sio_setvol(handle, lround(this->volume * SIO_MAXVOL));
253 }
254 } break;
255 case Command::Drain: {
256 INFO("command.drain")
257 STOP()
258 START()
259 } break;
260 case Command::Quit: {
261 quit = true;
262 continue;
263 } break;
264 }
265 }
266
267 this->commands.clear();
268
269 /* play the next buffer, if it exists */
270 if (started &&
271 this->state == StatePlaying &&
272 this->buffers.size())
273 {
274 next = this->buffers.front();
275 this->buffers.pop_front();
276 }
277 else {
278 //INFO(std::string("state=") + std::to_string((int) this->state));
279 //INFO(std::string("count=") + std::to_string((int) this->buffers.size()));
280 continue;
281 }
282 }
283
284 auto buffer = next.buffer;
285
286 if (buffer) {
287 /* ensure our output params are setup properly based on this
288 buffer's params */
289 if (pars.pchan != buffer->Channels() ||
290 pars.rate != buffer->SampleRate())
291 {
292 INFO("updating output params")
293
294 STOP()
295
296 sio_initpar(&pars);
297 pars.pchan = buffer->Channels();
298 pars.rate = buffer->SampleRate();
299 pars.sig = 1;
300 pars.le = SIO_LE_NATIVE;
301 pars.bits = 16;
302 pars.appbufsz = (pars.rate * 250) / 1000;
303
304 if (!sio_setpar(handle, &pars)) {
305 INFO("sio_setpar() failed");
306 quit = true;
307 continue;
308 }
309
310 if (!sio_getpar(handle, &pars)) {
311 INFO("sio_getpar() failed");
312 quit = true;
313 continue;
314 }
315
316 START()
317
318 this->latency = (double) pars.bufsz / pars.pchan / pars.rate;
319 }
320
321 /* allocate PCM buffer, if needed. most of the time we'll be
322 able to re-use the previously allocated one. */
323 long samples = buffer->Samples();
324 if (!pcm || samples > pcmSamples) {
325 delete[] pcm;
326 pcm = new short[samples];
327 pcmSamples = samples;
328 }
329
330 /* convert to 16-bit PCM */
331 float* src = buffer->BufferPointer();
332 short* dst = pcm;
333 for (long i = 0; i < samples; i++) {
334 float sample = *src;
335 if (sample > 1.0f) sample = 1.0f;
336 if (sample < -1.0f) sample = -1.0f;
337 sample *= SHRT_MAX;
338
339 /* triangle (high pass) dither, based on Audacity's
340 implementation */
341 float r = (rand() / (float) RAND_MAX - 0.5f);
342 sample = sample + r - ditherState;
343 ditherState = r;
344
345 *dst = sample;
346 ++dst; ++src;
347 }
348
349 /* write the entire output buffer. this may require multiple passes;
350 that's ok, just loop until we're done */
351 char* data = (char*) pcm;
352 size_t dataLength = samples * sizeof(short);
353 size_t totalWritten = 0;
354
355 {
356 // LOCK()
357 // INFO("start write")
358 while (totalWritten < dataLength) {
359 size_t remaining = dataLength - totalWritten;
360 size_t written = sio_write(handle, data, remaining);
361 if (written == 0) { INFO("failed to write!") break; }
362 // else { std::cerr << "wrote " << written << " of " << remaining << " bytes\n"; }
363 totalWritten += written;
364 data += written;
365 }
366 // INFO("end write")
367 }
368
369 next.provider->OnBufferProcessed(buffer);
370 }
371 }
372
373 /* done, free remaining buffers and close the handle */
374 if (handle) {
375 INFO("closing")
376 sio_close(handle);
377 handle = nullptr;
378 }
379
380 this->DiscardBuffers();
381
382 delete[] pcm;
383 }
384
Latency()385 double SndioOut::Latency() {
386 return this->latency;
387 }
388
DiscardBuffers()389 void SndioOut::DiscardBuffers() {
390 std::list<BufferContext> toNotify;
391 {
392 LOCK()
393 std::swap(toNotify, this->buffers);
394 }
395
396 for (auto& it : toNotify) {
397 it.provider->OnBufferProcessed(it.buffer);
398 }
399 }
400
CountBuffersWithProvider(IBufferProvider * provider)401 size_t SndioOut::CountBuffersWithProvider(IBufferProvider* provider) {
402 size_t count = 0;
403 for (auto& it : this->buffers) {
404 if (it.provider == provider) {
405 ++count;
406 }
407 }
408 return count;
409 }
410
411