-
Notifications
You must be signed in to change notification settings - Fork 151
/
Copy pathwin32.c
318 lines (267 loc) · 10.3 KB
/
win32.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/*
win32: audio output for Windows 32bit
copyright ?-2013 by the mpg123 project - free software under the terms of the LGPL 2.1
see COPYING and AUTHORS files in distribution or http://mpg123.org
initially written (as it seems) by Tony Million
rewrite of basic functionality for callback-less and properly ringbuffered operation by ravenexp
Closing buffer playback fixed by David Wohlferd <limegreensocks (*) yahoo dod com>
*/
#include "mpg123app.h"
#include <windows.h>
#include "debug.h"
/*
Buffer size and number of buffers in the playback ring
NOTE: This particular num/size combination performs best under heavy
loads for my system, however this may not be true for any hardware/OS out there.
Generally, BUFFER_SIZE < 8k || NUM_BUFFERS > 16 || NUM_BUFFERS < 4 are not recommended.
*/
#define BUFFER_SIZE 0x10000
#define NUM_BUFFERS 8 /* total 512k roughly 2.5 sec of CD quality sound */
static void wait_for_buffer(WAVEHDR* hdr, HANDLE hEvent);
static void drain_win32(struct audio_output_struct *ao);
/* Buffer ring queue state */
struct queue_state
{
WAVEHDR buffer_headers[NUM_BUFFERS];
/* The next buffer to be filled and put in playback */
int next_buffer;
/* Buffer playback completion event */
HANDLE play_done_event;
HWAVEOUT waveout;
};
static int open_win32(struct audio_output_struct *ao)
{
struct queue_state* state;
int i;
MMRESULT res;
WAVEFORMATEX out_fmt;
UINT dev_id;
if(!ao) return -1;
if(ao->rate == -1) return 0;
/* Allocate queue state struct for this device */
state = calloc(1, sizeof(struct queue_state));
if(!state) return -1;
ao->userptr = state;
state->play_done_event = CreateEvent(0,FALSE,FALSE,0);
if(state->play_done_event == INVALID_HANDLE_VALUE) return -1;
/* FIXME: real device enumeration by capabilities? */
dev_id = WAVE_MAPPER; /* probably does the same thing */
if (ao->device) {
/* Find device id of device with the same name as ao->device */
/* Device names from waveOutGetDevCaps are limited to 32 */
/* characters, so truncate ao->device for comparison */
ao->device[31] = '\0';
for (UINT i = 0; i < waveOutGetNumDevs(); ++i) {
WAVEOUTCAPS caps;
waveOutGetDevCaps(i, &caps, sizeof(WAVEOUTCAPS));
if (!strcmp(ao->device, caps.szPname)) {
dev_id = i;
break;
}
}
} else {
ao->device = "WaveMapper";
}
/* FIXME: support for smth besides MPG123_ENC_SIGNED_16? */
out_fmt.wFormatTag = WAVE_FORMAT_PCM;
out_fmt.wBitsPerSample = 16;
out_fmt.nChannels = ao->channels;
out_fmt.nSamplesPerSec = ao->rate;
out_fmt.nBlockAlign = out_fmt.nChannels*out_fmt.wBitsPerSample/8;
out_fmt.nAvgBytesPerSec = out_fmt.nBlockAlign*out_fmt.nSamplesPerSec;
out_fmt.cbSize = 0;
res = waveOutOpen(&state->waveout, dev_id, &out_fmt,
(DWORD_PTR)state->play_done_event, 0, CALLBACK_EVENT);
switch(res)
{
case MMSYSERR_NOERROR:
break;
case MMSYSERR_ALLOCATED:
ereturn(-1, "Audio output device is already allocated.");
case MMSYSERR_NODRIVER:
ereturn(-1, "No device driver is present.");
case MMSYSERR_NOMEM:
ereturn(-1, "Unable to allocate or lock memory.");
case WAVERR_BADFORMAT:
ereturn(-1, "Unsupported waveform-audio format.");
default:
ereturn(-1, "Unable to open wave output device.");
}
/* Reset event from the "device open" message */
ResetEvent(state->play_done_event);
/* Allocate playback buffers */
for(i = 0; i < NUM_BUFFERS; i++)
if(!(state->buffer_headers[i].lpData = (LPSTR)malloc(BUFFER_SIZE)))
{
ereturn(-1, "Out of memory for playback buffers.");
}
else
{
/* Tell waveOutPrepareHeader the maximum value of dwBufferLength
we will ever send */
state->buffer_headers[i].dwBufferLength = BUFFER_SIZE;
state->buffer_headers[i].dwFlags = 0;
res = waveOutPrepareHeader(state->waveout, &state->buffer_headers[i], sizeof(WAVEHDR));
if(res != MMSYSERR_NOERROR) ereturn(-1, "Can't write to audio output device (prepare).");
/* set the current size of the buffer to 0 */
state->buffer_headers[i].dwBufferLength = 0;
/* set flags to unprepared - must reset this to WHDR_PREPARED before calling write */
state->buffer_headers[i].dwFlags = 0;
}
return 0;
}
static void wait_for_buffer(WAVEHDR* hdr, HANDLE hEvent)
{
/* At this point there are several possible states:
1) Empty or partial buffer (unqueued) - dwFlags == 0
2) Buffer queued or being played - dwFlags == WHDR_PREPARED | WHDR_INQUEUE
3) Buffer unqueued and finished being played - dwFlags == WHDR_PREPARED | WHDR_DONE
4) Buffer removed from queue, but not yet marked as done - dwFlags == WHDR_PREPARED
*/
/* Check buffer header and wait if it's being played. */
if (hdr->dwFlags & WHDR_PREPARED)
{
while(!(hdr->dwFlags & WHDR_DONE))
{
/*debug1("waiting for buffer %i...", state->next_buffer);*/
/* Waits for *a* buffer to finish. May not be the one we
want, so check again */
WaitForSingleObject(hEvent, INFINITE);
}
hdr->dwFlags = 0;
hdr->dwBufferLength = 0;
}
}
static int get_formats_win32(struct audio_output_struct *ao)
{
/* FIXME: support for smth besides MPG123_ENC_SIGNED_16? */
return MPG123_ENC_SIGNED_16;
}
/* Stores audio data to the fixed size buffers and pushes them into the playback queue.
I have one grief with that: The last piece of a track may not reach the output,
only full buffers sent... But we don't get smooth audio otherwise. */
static int write_win32(struct audio_output_struct *ao, unsigned char *buf, int len)
{
struct queue_state* state;
MMRESULT res;
WAVEHDR* hdr;
int rest_len; /* Input data bytes left for next recursion. */
int bufill; /* Bytes we stuff into buffer now. */
if(!ao || !ao->userptr) return -1;
if(!buf || len <= 0) return 0;
state = (struct queue_state*)ao->userptr;
hdr = &state->buffer_headers[state->next_buffer];
wait_for_buffer(hdr, state->play_done_event);
/* Now see how much we want to stuff in and then stuff it in. */
bufill = BUFFER_SIZE - hdr->dwBufferLength;
if(len < bufill) bufill = len;
rest_len = len - bufill;
memcpy(hdr->lpData + hdr->dwBufferLength, buf, bufill);
hdr->dwBufferLength += bufill;
if(hdr->dwBufferLength == BUFFER_SIZE)
{ /* Send the buffer out when it's full. */
hdr->dwFlags |= WHDR_PREPARED;
res = waveOutWrite(state->waveout, hdr, sizeof(WAVEHDR));
if(res != MMSYSERR_NOERROR) ereturn(-1, "Can't write to audio output device.");
/* Cycle to the next buffer in the ring queue */
state->next_buffer = (state->next_buffer + 1) % NUM_BUFFERS;
}
/* I'd like to propagate error codes or something... but there are no catchable surprises left.
Anyhow: Here is the recursion that makes ravenexp happy;-) */
if(rest_len && write_win32(ao, buf + bufill, rest_len) < 0) /* Write the rest. */
return -1;
else
return len;
}
/* Flush means abort any pending playback */
static void flush_win32(struct audio_output_struct *ao)
{
struct queue_state* state;
WAVEHDR* hdr;
if(!ao || !ao->userptr) return;
state = (struct queue_state*)ao->userptr;
/* Cancel any buffers in queue. Ignore errors since we are void and
can't return them anyway */
waveOutReset(state->waveout);
/* Discard any partial buffer */
hdr = &state->buffer_headers[state->next_buffer];
/* If WHDR_PREPARED is not set, this is (potentially) a partial buffer */
if (!(hdr->dwFlags & WHDR_PREPARED))
hdr->dwBufferLength = 0;
}
/* output final buffer (if any) */
static void write_final_buffer(struct queue_state *state)
{
WAVEHDR* hdr;
hdr = &state->buffer_headers[state->next_buffer];
if((!(hdr->dwFlags & WHDR_PREPARED)) && (hdr->dwBufferLength != 0))
{
hdr->dwFlags |= WHDR_PREPARED;
/* ignore any errors */
waveOutWrite(state->waveout, hdr, sizeof(WAVEHDR));
/* Cycle to the next buffer in the ring queue */
state->next_buffer = (state->next_buffer + 1) % NUM_BUFFERS;
}
}
/* Note: I tried to fix this stuff without testing.
There were some obvious errors in the code.
Someone run this on a win32 machine! -- ThOr */
static void drain_win32(struct audio_output_struct *ao)
{
int i, z;
struct queue_state* state;
if(!ao || !ao->userptr) return;
state = (struct queue_state*)ao->userptr;
/* output final buffer (if any) */
write_final_buffer(state);
/* I _think_ I understood how this should work. -- ThOr */
z = state->next_buffer;
for(i = 0; i < NUM_BUFFERS; i++)
{
wait_for_buffer(&state->buffer_headers[z], state->play_done_event);
z = (z + 1) % NUM_BUFFERS;
}
}
static int close_win32(struct audio_output_struct *ao)
{
int i;
struct queue_state* state;
if(!ao || !ao->userptr) return -1;
state = (struct queue_state*)ao->userptr;
/* wait for all active buffers to complete */
drain_win32(ao);
CloseHandle(state->play_done_event);
for(i = 0; i < NUM_BUFFERS; i++)
{
state->buffer_headers[i].dwFlags |= WHDR_PREPARED;
waveOutUnprepareHeader(state->waveout, &state->buffer_headers[i], sizeof(WAVEHDR));
free(state->buffer_headers[i].lpData);
}
waveOutClose(state->waveout);
free(ao->userptr);
ao->userptr = 0;
return 0;
}
static int init_win32(audio_output_t* ao)
{
if(!ao) return -1;
/* Set callbacks */
ao->open = open_win32;
ao->flush = flush_win32;
ao->write = write_win32;
ao->get_formats = get_formats_win32;
ao->close = close_win32;
/* Success */
return 0;
}
/*
Module information data structure
*/
mpg123_module_t mpg123_output_module_info = {
/* api_version */ MPG123_MODULE_API_VERSION,
/* name */ "win32",
/* description */ "Audio output for Windows (winmm).",
/* revision */ "$Rev:$",
/* handle */ NULL,
/* init_output */ init_win32,
};