1 /*
2  * Win32 waveOut Plugin for Audacious
3  * Copyright 2016 John Lindgren
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions, and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions, and the following disclaimer in the documentation
13  *    provided with the distribution.
14  *
15  * This software is provided "as is" and without any warranty, express or
16  * implied. In no event shall the authors be liable for any damages arising from
17  * the use of this software.
18  */
19 
20 #include <assert.h>
21 #include <pthread.h>
22 
23 #include <windows.h>
24 
25 // missing from MinGW header
26 #ifndef WAVE_FORMAT_IEEE_FLOAT
27 #define WAVE_FORMAT_IEEE_FLOAT 3
28 #endif
29 
30 #include <libaudcore/audstrings.h>
31 #include <libaudcore/i18n.h>
32 #include <libaudcore/plugin.h>
33 #include <libaudcore/runtime.h>
34 
35 #define NUM_BLOCKS 4
36 
37 class WaveOut : public OutputPlugin
38 {
39 public:
40     static const char about[];
41 
42     static constexpr PluginInfo info = {
43         N_("Win32 waveOut"),
44         PACKAGE,
45         about
46     };
47 
WaveOut()48     constexpr WaveOut () : OutputPlugin (info, 5) {}
49 
50     StereoVolume get_volume ();
51     void set_volume (StereoVolume v);
52 
53     bool open_audio (int aud_format, int rate, int chans, String & error);
54     void close_audio ();
55 
56     void period_wait ();
57     int write_audio (const void * data, int size);
58     void drain ();
59 
60     int get_delay ();
61 
62     void pause (bool pause);
63     void flush ();
64 };
65 
66 EXPORT WaveOut aud_plugin_instance;
67 
68 const char WaveOut::about[] =
69  N_("Win32 waveOut Plugin for Audacious\n"
70     "Copyright 2016 John Lindgren");
71 
72 // lock order is state, then buffer
73 static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
74 static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
75 static pthread_cond_t buffer_cond = PTHREAD_COND_INITIALIZER;
76 
77 static int waveout_format;
78 static int waveout_chan, waveout_rate;
79 static int block_size;
80 
81 // guarded by state_mutex
82 static HWAVEOUT device;
83 static bool prebuffer_flag, paused_flag;
84 static int64_t total_frames;
85 
86 // guarded by buffer_mutex
87 static Index<WAVEHDR> headers;
88 static Index<Index<char>> blocks;
89 static int next_block, blocks_free;
90 
get_volume()91 StereoVolume WaveOut::get_volume ()
92 {
93     DWORD vol = 0;
94     waveOutGetVolume ((HWAVEOUT) WAVE_MAPPER, & vol);
95     return {aud::rescale (int (vol & 0xffff), 0xffff, 100),
96             aud::rescale (int (vol >> 16), 0xffff, 100)};
97 }
98 
set_volume(StereoVolume v)99 void WaveOut::set_volume (StereoVolume v)
100 {
101     int left = aud::rescale (v.left, 100, 0xffff);
102     int right = aud::rescale (v.right, 100, 0xffff);
103     waveOutSetVolume ((HWAVEOUT) WAVE_MAPPER, left | (right << 16));
104 }
105 
callback(HWAVEOUT,UINT uMsg,DWORD_PTR,DWORD_PTR,DWORD_PTR)106 static void CALLBACK callback (HWAVEOUT, UINT uMsg, DWORD_PTR, DWORD_PTR, DWORD_PTR)
107 {
108     if (uMsg != WOM_DONE)
109         return;
110 
111     pthread_mutex_lock (& buffer_mutex);
112 
113     assert (blocks_free < NUM_BLOCKS);
114     int old_block = (next_block + blocks_free) % NUM_BLOCKS;
115     blocks[old_block].resize (0);
116     blocks_free ++;
117 
118     pthread_cond_broadcast (& buffer_cond);
119     pthread_mutex_unlock (& buffer_mutex);
120 }
121 
open_audio(int format,int rate,int chan,String & error)122 bool WaveOut::open_audio (int format, int rate, int chan, String & error)
123 {
124     if (format != FMT_S16_NE && format != FMT_S32_NE && format != FMT_FLOAT)
125     {
126         error = String ("waveOut error: Unsupported audio format.  Supported "
127                         "bit depths are 16, 32, and floating point.");
128         return false;
129     }
130 
131     AUDDBG ("Opening audio for format %d, %d channels, %d Hz.\n", format, chan, rate);
132 
133     waveout_format = format;
134     waveout_chan = chan;
135     waveout_rate = rate;
136 
137     WAVEFORMATEX desc;
138     desc.wFormatTag = (format == FMT_FLOAT) ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
139     desc.nChannels = chan;
140     desc.nSamplesPerSec = rate;
141     desc.nAvgBytesPerSec = FMT_SIZEOF (format) * chan * rate;
142     desc.nBlockAlign = FMT_SIZEOF (format) * chan;
143     desc.wBitsPerSample = 8 * FMT_SIZEOF (format);
144     desc.cbSize = 0;
145 
146     MMRESULT res = waveOutOpen (& device, WAVE_MAPPER, & desc,
147      (DWORD_PTR) callback, 0, CALLBACK_FUNCTION);
148 
149     if (res != MMSYSERR_NOERROR)
150     {
151         error = String (str_printf ("Error opening device (%d)!", (int) res));
152         return false;
153     }
154 
155     int block_ms = aud_get_int ("output_buffer_size") / NUM_BLOCKS;
156     block_size = FMT_SIZEOF (format) * chan * aud::rescale (block_ms, 1000, rate);
157 
158     headers.insert (0, NUM_BLOCKS);
159     blocks.insert (0, NUM_BLOCKS);
160 
161     next_block = 0;
162     blocks_free = NUM_BLOCKS;
163 
164     waveOutPause (device);
165     prebuffer_flag = true;
166     paused_flag = false;
167     total_frames = 0;
168 
169     return true;
170 }
171 
close_audio()172 void WaveOut::close_audio ()
173 {
174     AUDDBG ("Closing audio.\n");
175     flush ();
176     waveOutClose (device);
177 
178     headers.clear ();
179     blocks.clear ();
180 }
181 
period_wait()182 void WaveOut::period_wait ()
183 {
184     pthread_mutex_lock (& buffer_mutex);
185 
186     while (! blocks_free)
187         pthread_cond_wait (& buffer_cond, & buffer_mutex);
188 
189     pthread_mutex_unlock (& buffer_mutex);
190 }
191 
192 // assumes state_mutex locked
write_block(int b)193 static void write_block (int b)
194 {
195     WAVEHDR & header = headers[b];
196     Index<char> & block = blocks[b];
197 
198     header.lpData = block.begin ();
199     header.dwBufferLength = block.len ();
200 
201     waveOutPrepareHeader (device, & header, sizeof header);
202     waveOutWrite (device, & header, sizeof header);
203 }
204 
205 // assumes state_mutex locked
check_started()206 static void check_started ()
207 {
208     if (prebuffer_flag)
209     {
210         AUDDBG ("Prebuffering complete.\n");
211         if (! paused_flag)
212             waveOutRestart (device);
213 
214         prebuffer_flag = false;
215     }
216 }
217 
write_audio(const void * data,int len)218 int WaveOut::write_audio (const void * data, int len)
219 {
220     int written = 0;
221     pthread_mutex_lock (& state_mutex);
222     pthread_mutex_lock (& buffer_mutex);
223 
224     while (len > 0 && blocks_free > 0)
225     {
226         int block_to_write = -1;
227 
228         Index<char> & block = blocks[next_block];
229         int copy = aud::min (len, block_size - block.len ());
230         block.insert ((const char *) data, -1, copy);
231 
232         if (block.len () == block_size)
233         {
234             block_to_write = next_block;
235             next_block = (next_block + 1) % NUM_BLOCKS;
236             blocks_free --;
237         }
238 
239         written += copy;
240         data = (const char *) data + copy;
241         len -= copy;
242 
243         if (block_to_write >= 0)
244         {
245             pthread_mutex_unlock (& buffer_mutex);
246             write_block (block_to_write);
247             pthread_mutex_lock (& buffer_mutex);
248         }
249     }
250 
251     bool filled = ! blocks_free;
252     pthread_mutex_unlock (& buffer_mutex);
253 
254     if (filled)
255         check_started ();
256 
257     total_frames += written / (FMT_SIZEOF (waveout_format) * waveout_chan);
258 
259     pthread_mutex_unlock (& state_mutex);
260     return written;
261 }
262 
drain()263 void WaveOut::drain ()
264 {
265     AUDDBG ("Draining.\n");
266     pthread_mutex_lock (& state_mutex);
267     pthread_mutex_lock (& buffer_mutex);
268 
269     int block_to_write = -1;
270 
271     // write last partial block, if any
272     if (blocks_free > 0 && blocks[next_block].len () > 0)
273     {
274         block_to_write = next_block;
275         next_block = (next_block + 1) % NUM_BLOCKS;
276         blocks_free --;
277     }
278 
279     pthread_mutex_unlock (& buffer_mutex);
280 
281     if (block_to_write >= 0)
282         write_block (block_to_write);
283 
284     check_started ();
285 
286     pthread_mutex_unlock (& state_mutex);
287     pthread_mutex_lock (& buffer_mutex);
288 
289     while (blocks_free < NUM_BLOCKS)
290         pthread_cond_wait (& buffer_cond, & buffer_mutex);
291 
292     pthread_mutex_unlock (& buffer_mutex);
293 }
294 
get_delay()295 int WaveOut::get_delay ()
296 {
297     int delay = 0;
298     pthread_mutex_lock (& state_mutex);
299 
300     MMTIME t;
301     t.wType = TIME_SAMPLES;
302 
303     if (waveOutGetPosition (device, & t, sizeof t) == MMSYSERR_NOERROR && t.wType == TIME_SAMPLES)
304     {
305         // frame_diff is intentionally truncated to a signed 32-bit word to
306         // account for overflow in the DWORD frame counter returned by Windows
307         int32_t frame_diff = total_frames - t.u.sample;
308         delay = aud::rescale ((int) frame_diff, waveout_rate, 1000);
309     }
310 
311     pthread_mutex_unlock (& state_mutex);
312     return delay;
313 }
314 
pause(bool pause)315 void WaveOut::pause (bool pause)
316 {
317     AUDDBG (pause ? "Pausing.\n" : "Unpausing.\n");
318     pthread_mutex_lock (& state_mutex);
319 
320     if (! prebuffer_flag)
321     {
322         if (pause)
323             waveOutPause (device);
324         else
325             waveOutRestart (device);
326     }
327 
328     paused_flag = pause;
329 
330     pthread_mutex_unlock (& state_mutex);
331 }
332 
flush()333 void WaveOut::flush ()
334 {
335     AUDDBG ("Flushing buffers.\n");
336     pthread_mutex_lock (& state_mutex);
337 
338     waveOutReset (device);
339 
340     pthread_mutex_lock (& buffer_mutex);
341 
342     for (auto & header : headers)
343         waveOutUnprepareHeader (device, & header, sizeof header);
344     for (auto & block : blocks)
345         block.resize (0);
346 
347     next_block = 0;
348     blocks_free = NUM_BLOCKS;
349 
350     pthread_cond_broadcast (& buffer_cond);
351     pthread_mutex_unlock (& buffer_mutex);
352 
353     waveOutPause (device);
354     prebuffer_flag = true;
355     total_frames = 0;
356 
357     pthread_mutex_unlock (& state_mutex);
358 }
359