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