1 /*
2 *
3 * ao_wmm.c
4 *
5 * Copyright (C) Benjamin Gerard - March 2007
6 *
7 * This file is part of libao, a cross-platform library. See
8 * README for a history of this source code.
9 *
10 * libao is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * libao is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with GNU Make; see the file COPYING. If not, write to
22 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 ********************************************************************
25
26 last mod: $Id: ao_wmm.c 17629 2010-11-18 12:04:46Z xiphmont $
27
28 ********************************************************************/
29
30 //#define PREPARE_EACH
31 #define _CRT_SECURE_NO_DEPRECATE
32
33 #include <windows.h>
34 #include <mmreg.h>
35 #include <mmsystem.h>
36 #include <ksmedia.h>
37
38 #include <stdlib.h>
39 #include <string.h>
40
41 #include <stdarg.h>
42 #include <stdio.h>
43
44 #ifndef KSDATAFORMAT_SUBTYPE_PCM
45 #define KSDATAFORMAT_SUBTYPE_PCM (GUID) {0x00000001,0x0000,0x0010,{0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71}}
46 #endif
47
48
49 #include "ao/ao.h"
50 /* #include "ao/plugin.h" */
51
52 #define GALLOC_WVHD_TYPE (GHND)
53 #define GALLOC_DATA_TYPE (GHND)
54
mmerror(MMRESULT mmrError)55 static const char * mmerror(MMRESULT mmrError)
56 {
57 static char mmbuffer[1024];
58 int len;
59 sprintf(mmbuffer,"mm:%d ",(int)mmrError);
60 len = (int)strlen(mmbuffer);
61 waveOutGetErrorText(mmrError, mmbuffer+len, sizeof(mmbuffer)-len);
62 mmbuffer[sizeof(mmbuffer)-1] = 0;
63 return mmbuffer;
64 }
65
66 static char * ao_wmm_options[] = {"dev", "id", "matrix","verbose","quiet","debug"};
67 static ao_info ao_wmm_info =
68 {
69 /* type */ AO_TYPE_LIVE,
70 /* name */ "WMM audio driver output ",
71 /* short-name */ "wmm",
72 /* author */ "Benjamin Gerard <benjihan@users.sourceforge.net>",
73 /* comment */ "Outputs audio to the Windows MultiMedia driver.",
74 /* prefered format */ AO_FMT_LITTLE,
75 /* priority */ 20,
76 /* options */ ao_wmm_options,
77 /* # of options */ sizeof(ao_wmm_options)/sizeof(*ao_wmm_options)
78 };
79
80 typedef struct {
81 WAVEHDR wh; /* waveheader */
82 char * data; /* sample data ptr */
83 int idx; /* index of this header */
84 int count; /* current byte count */
85 int length; /* size of data */
86 int sent; /* set when header is sent to device */
87 } myWH_t;
88
89 typedef struct ao_wmm_internal {
90 UINT id; /* device id */
91 HWAVEOUT hwo; /* waveout handler */
92 WAVEOUTCAPS caps; /* device caps */
93 WAVEFORMATEXTENSIBLE wavefmt; /* sample format */
94
95 int opened; /* device has been opened */
96 int prepared; /* waveheaders have been prepared */
97 int blocks; /* number of blocks (wave headers) */
98 int splPerBlock; /* sample per blocks. */
99 int msPerBlock; /* millisecond per block (approx.) */
100
101 void * bigbuffer; /* Allocated buffer for waveheaders and sound data */
102 myWH_t * wh; /* Pointer to waveheaders in bigbuffer */
103 BYTE * spl; /* Pointer to sound data in bigbuffer */
104
105 int sent_blocks; /* Number of waveheader sent (not ack). */
106 int full_blocks; /* Number of waveheader full (ready to send). */
107 int widx; /* Index to the block being currently filled. */
108 int ridx; /* Index to the block being sent. */
109
110 } ao_wmm_internal;
111
ao_wmm_test(void)112 int ao_wmm_test(void)
113 {
114 return 1; /* This plugin works in default mode */
115 }
116
ao_wmm_driver_info(void)117 ao_info *ao_wmm_driver_info(void)
118 {
119 return &ao_wmm_info;
120 }
121
ao_wmm_set_option(ao_device * device,const char * key,const char * value)122 int ao_wmm_set_option(ao_device *device,
123 const char *key, const char *value)
124 {
125 ao_wmm_internal *internal = (ao_wmm_internal *) device->internal;
126 int res = 0;
127
128 if (!strcmp(key, "dev")) {
129 if (!strcmp(value,"default")) {
130 key = "id";
131 value = "0";
132 } else {
133 WAVEOUTCAPS caps;
134 int i, max = waveOutGetNumDevs();
135
136 adebug("searching for device %s among %d\n", value, max);
137 for (i=0; i<max; ++i) {
138 MMRESULT mmres = waveOutGetDevCaps(i, &caps, sizeof(caps));
139 if (mmres == MMSYSERR_NOERROR) {
140 res = !strcmp(value, caps.szPname);
141 adebug("checking id=%d, name='%s', ver=%d.%d => [%s]\n",
142 i,caps.szPname,caps.vDriverVersion>>8,caps.vDriverVersion&255,res?"YES":"no");
143 if (res) {
144 internal->id = i;
145 internal->caps = caps;
146 break;
147 }
148 } else {
149 aerror("waveOutGetDevCaps(%d) => %s",i,mmerror(mmres));
150 }
151 }
152 goto finish;
153 }
154 }
155
156 if (!strcmp(key,"id")) {
157 MMRESULT mmres;
158 WAVEOUTCAPS caps;
159
160 int id = strtol(value,0,0);
161 int max = waveOutGetNumDevs();
162
163 if (id >= 0 && id <= max) {
164 if (id-- == 0) {
165 adebug("set default wavemapper\n");
166 id = WAVE_MAPPER;
167 }
168 mmres = waveOutGetDevCaps(id, &caps, sizeof(caps));
169
170 if (mmres == MMSYSERR_NOERROR) {
171 res = 1;
172 adebug("checking id=%d, name='%s', ver=%d.%d => [YES]\n",
173 id,caps.szPname,caps.vDriverVersion>>8,caps.vDriverVersion&255);
174 internal->id = id;
175 internal->caps = caps;
176 } else {
177 aerror("waveOutGetDevCaps(%d) => %s",id,mmerror(mmres));
178 }
179 }
180 }
181
182 finish:
183 return res;
184 }
185
186
ao_wmm_device_init(ao_device * device)187 int ao_wmm_device_init(ao_device *device)
188 {
189 ao_wmm_internal *internal;
190 int res;
191
192 internal = (ao_wmm_internal *) malloc(sizeof(ao_wmm_internal));
193 device->internal = internal;
194 if (internal != NULL) {
195 memset(internal,0,sizeof(ao_wmm_internal));
196 internal->id = WAVE_MAPPER;
197 internal->blocks = 32;
198 internal->splPerBlock = 512;
199 /* set default device */
200 ao_wmm_set_option(device,"id","0");
201 }
202
203 res = internal != NULL;
204
205 device->output_matrix = strdup("L,R,C,LFE,BL,BR,CL,CR,BC,SL,SR");
206 device->output_matrix_order = AO_OUTPUT_MATRIX_COLLAPSIBLE;
207
208 return res;
209 }
210
_ao_open_device(ao_device * device)211 static int _ao_open_device(ao_device *device)
212 {
213 ao_wmm_internal *internal = (ao_wmm_internal *) device->internal;
214 int res;
215 MMRESULT mmres;
216
217 mmres =
218 waveOutOpen(&internal->hwo,
219 internal->id,
220 &internal->wavefmt.Format,
221 (DWORD_PTR)0/* waveOutProc */,
222 (DWORD_PTR)device,
223 CALLBACK_NULL/* |WAVE_FORMAT_DIRECT */|WAVE_ALLOWSYNC);
224
225 if(mmres == MMSYSERR_NOERROR){
226 adebug("waveOutOpen id=%d, channels=%d, bits=%d, rate %d => SUCCESS\n",
227 internal->id,
228 internal->wavefmt.Format.nChannels,
229 internal->wavefmt.Format.wBitsPerSample,
230 internal->wavefmt.Format.nSamplesPerSec);
231 }else{
232 aerror("waveOutOpen id=%d, channels=%d, bits=%d, rate %d => FAILED\n",
233 internal->id,
234 internal->wavefmt.Format.nChannels,
235 internal->wavefmt.Format.wBitsPerSample,
236 internal->wavefmt.Format.nSamplesPerSec);
237 }
238
239 if (mmres == MMSYSERR_NOERROR) {
240 UINT id;
241 if (MMSYSERR_NOERROR == waveOutGetID(internal->hwo,&id)) {
242 internal->id = id;
243 }
244 }
245
246 res = (mmres == MMSYSERR_NOERROR);
247 return res;
248 }
249
_ao_close_device(ao_device * device)250 static int _ao_close_device(ao_device *device)
251 {
252 ao_wmm_internal * internal = (ao_wmm_internal *) device->internal;
253 int res;
254 MMRESULT mmres;
255
256 mmres = waveOutClose(internal->hwo);
257 if(mmres == MMSYSERR_NOERROR) {
258 adebug("waveOutClose(%d)\n => %s\n", internal->id, mmerror(mmres));
259 }else{
260 aerror("waveOutClose(%d)\n => %s\n", internal->id, mmerror(mmres));
261 }
262 res = (mmres == MMSYSERR_NOERROR);
263
264 return res;
265 }
266
_ao_alloc_wave_headers(ao_device * device)267 static int _ao_alloc_wave_headers(ao_device *device)
268 {
269 ao_wmm_internal *internal = (ao_wmm_internal *) device->internal;
270 int bytesPerBlock = internal->wavefmt.Format.nBlockAlign * internal->splPerBlock;
271 /* int bytes = internal->blocks * (sizeof(WAVEHDR) + bytesPerBlock); */
272 int bytes = internal->blocks * (sizeof(*internal->wh) + bytesPerBlock);
273 int res;
274 MMRESULT mmres;
275
276 adebug("_ao_alloc_wave_headers blocks=%d, bytes/blocks=%d, total=%d\n",
277 internal->blocks,bytesPerBlock,bytes);
278
279 internal->bigbuffer = malloc(bytes);
280 if (internal->bigbuffer != NULL) {
281 int i;
282 BYTE * b;
283
284 memset(internal->bigbuffer,0,bytes);
285 internal->wh = internal->bigbuffer;
286 internal->spl = (LPBYTE) (internal->wh+internal->blocks);
287 for (i=0, b=internal->spl; i<internal->blocks; ++i, b+=bytesPerBlock) {
288 internal->wh[i].data = b;
289 internal->wh[i].wh.lpData = internal->wh[i].data;
290 internal->wh[i].length = bytesPerBlock;
291 internal->wh[i].wh.dwBufferLength = internal->wh[i].length;
292 internal->wh[i].wh.dwUser = (DWORD_PTR)device;
293 mmres = waveOutPrepareHeader(internal->hwo,
294 &internal->wh[i].wh,sizeof(WAVEHDR));
295 if (MMSYSERR_NOERROR != mmres) {
296 aerror("waveOutPrepareHeader(%d) => %s\n",i, mmerror(mmres));
297 break;
298 }
299 }
300 if (i<internal->blocks) {
301 while (--i >= 0) {
302 waveOutUnprepareHeader(internal->hwo,
303 &internal->wh[i].wh,sizeof(WAVEHDR));
304 }
305 free(internal->bigbuffer);
306 internal->wh = 0;
307 internal->spl = 0;
308 internal->bigbuffer = 0;
309 } else {
310 /* all ok ! */
311 }
312 } else {
313 adebug("malloc() => FAILED\n");
314 }
315
316 res = (internal->bigbuffer != NULL);
317 if(!res){
318 aerror("_ao_alloc_wave_headers() => FAILED\n");
319 }else{
320 adebug("_ao_alloc_wave_headers() => success\n");
321 }
322 return res;
323 }
324
325 static int _ao_get_free_block(ao_device * device);
_ao_wait_wave_headers(ao_device * device,int wait_all)326 static int _ao_wait_wave_headers(ao_device *device, int wait_all)
327 {
328 ao_wmm_internal *internal = (ao_wmm_internal *) device->internal;
329 int res = 1;
330
331 adebug("wait for %d blocks (%swait all)\n",
332 internal->sent_blocks,wait_all?"":"not ");
333
334 while (internal->sent_blocks > 0) {
335 int n;
336 _ao_get_free_block(device);
337 n = internal->sent_blocks;
338 if (n > 0) {
339 unsigned int ms = (internal->msPerBlock>>1)+1;
340 if (wait_all) ms *= n;
341 adebug("sleep for %ums wait on %d blocks\n",ms, internal->sent_blocks);
342 Sleep(ms);
343 }
344 }
345
346 res &= !internal->sent_blocks;
347 if(!res){
348 aerror("_ao_wait_wave_headers => FAILED\n");
349 }else{
350 adebug("_ao_wait_wave_headers => success\n");
351 }
352 return res;
353 }
354
_ao_free_wave_headers(ao_device * device)355 static int _ao_free_wave_headers(ao_device *device)
356 {
357 ao_wmm_internal *internal = (ao_wmm_internal *) device->internal;
358 MMRESULT mmres;
359 int res = 1;
360
361 if (internal->wh) {
362 int i;
363
364 /* Reset so we dont need to wait ... Just a satefy net
365 * since _ao_wait_wave_headers() has been called once before.
366 */
367 mmres = waveOutReset(internal->hwo);
368 adebug("waveOutReset(%d) => %s\n", internal->id, mmerror(mmres));
369 /* Wait again to be sure reseted waveheaders has been released. */
370 _ao_wait_wave_headers(device,0);
371
372 for (i=internal->blocks; --i>=0; ) {
373 mmres = waveOutUnprepareHeader(internal->hwo,
374 &internal->wh[i].wh,sizeof(WAVEHDR));
375 if (mmres != MMSYSERR_NOERROR)
376 aerror("waveOutUnprepareHeader(%d) => %s\n", i, mmerror(mmres));
377
378 res &= mmres == MMSYSERR_NOERROR;
379 }
380 internal->wh = 0;
381 internal->spl = 0;
382 }
383
384 if(!res){
385 aerror("_ao_alloc_wave_headers() => FAILED\n");
386 }else{
387 adebug("_ao_alloc_wave_headers() => success\n");
388 }
389 return res;
390 }
391
392
393 /*
394 * open the audio device for writing to
395 */
ao_wmm_open(ao_device * device,ao_sample_format * format)396 int ao_wmm_open(ao_device * device, ao_sample_format * format)
397 {
398 ao_wmm_internal *internal = (ao_wmm_internal *) device->internal;
399 int res = 0;
400 WAVEFORMATEXTENSIBLE wavefmt;
401
402 adebug("open() channels=%d, bits=%d, rate=%d, format %d(%s)\n",
403 device->output_channels,format->bits,format->rate,format->byte_format,
404 format->byte_format==AO_FMT_LITTLE
405 ?"little"
406 :(format->byte_format==AO_FMT_NATIVE
407 ?"native"
408 :(format->byte_format==AO_FMT_BIG?"big":"unknown")));
409
410 if(internal->opened) {
411 aerror("open() => already opened\n");
412 goto error_no_close;
413 }
414
415 /* Force LITTLE as specified by WIN32 API */
416 format->byte_format = AO_FMT_LITTLE;
417 device->driver_byte_format = AO_FMT_LITTLE;
418
419 /* $$$ WMM 8 bit samples are unsigned... Not sure for ao ... */
420 /* Yes, ao 8 bit PCM is unsigned -- Monty */
421
422 /* Make sample format */
423 memset(&wavefmt,0,sizeof(wavefmt));
424 wavefmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
425 wavefmt.Format.nChannels = device->output_channels;
426 wavefmt.Format.wBitsPerSample = (((format->bits+7)>>3)<<3);
427 wavefmt.Format.nSamplesPerSec = format->rate;
428 wavefmt.Format.nBlockAlign = (wavefmt.Format.wBitsPerSample>>3)*wavefmt.Format.nChannels;
429 wavefmt.Format.nAvgBytesPerSec = wavefmt.Format.nSamplesPerSec*wavefmt.Format.nBlockAlign;
430 wavefmt.Format.cbSize = 22;
431 wavefmt.Samples.wValidBitsPerSample = format->bits;
432 wavefmt.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
433 wavefmt.dwChannelMask = device->output_mask;
434
435 internal->wavefmt = wavefmt;
436
437 /* $$$ later this should be optionnal parms */
438 internal->blocks = 64;
439 internal->splPerBlock = 512;
440 internal->msPerBlock =
441 (internal->splPerBlock * 1000 + format->rate - 1) / format->rate;
442
443 /* Open device */
444 if(!_ao_open_device(device))
445 goto error;
446 internal->opened = 1;
447
448 /* Allocate buffers */
449 if (!_ao_alloc_wave_headers(device))
450 goto error;
451 internal->prepared = 1;
452
453 res = 1;
454 error:
455 if (!res) {
456 if (internal->prepared) {
457 _ao_free_wave_headers(device);
458 internal->prepared = 0;
459 }
460 if (internal->opened) {
461 _ao_close_device(device);
462 internal->opened = 0;
463 }
464 }
465
466 error_no_close:
467 if(res){
468 adebug("open() => success\n");
469 }else{
470 aerror("open() => FAILED\n");
471 }
472 return res;
473 }
474
475
476
477 /* Send a block to audio hardware */
_ao_send_block(ao_device * device,const int idx)478 static int _ao_send_block(ao_device *device, const int idx)
479 {
480 ao_wmm_internal * internal = (ao_wmm_internal *) device->internal;
481 MMRESULT mmres;
482
483 /* Satanity checks */
484 if (internal->wh[idx].sent) {
485 adebug("block %d marked SENT\n",idx);
486 return 0;
487 }
488 if (!!(internal->wh[idx].wh.dwFlags & WHDR_DONE)) {
489 adebug("block %d marked DONE\n",idx);
490 return 0;
491 }
492
493 /* count <= 0, just pretend it's been sent */
494 if (internal->wh[idx].count <= 0) {
495 internal->wh[idx].sent = 2; /* set with 2 so we can track these special cases */
496 internal->wh[idx].wh.dwFlags |= WHDR_DONE;
497 ++internal->sent_blocks;
498 return 1;
499 }
500
501 internal->wh[idx].wh.dwBufferLength = internal->wh[idx].count;
502 internal->wh[idx].count = 0;
503 mmres = waveOutWrite(internal->hwo,
504 &internal->wh[idx].wh, sizeof(WAVEHDR));
505 internal->wh[idx].sent = (mmres == MMSYSERR_NOERROR);
506 /*&& !(internal->wh[idx].wh.dwFlags & WHDR_DONE);*/
507 internal->sent_blocks += internal->wh[idx].sent;
508 if (mmres != MMSYSERR_NOERROR) {
509 adebug("waveOutWrite(%d) => %s\n",idx,mmerror(mmres));
510 }
511 return mmres == MMSYSERR_NOERROR;
512 }
513
514 /* Get idx of next free block. */
_ao_get_free_block(ao_device * device)515 static int _ao_get_free_block(ao_device * device)
516 {
517 ao_wmm_internal * internal = (ao_wmm_internal *) device->internal;
518 const int idx = internal->widx;
519 int ridx = internal->ridx;
520
521 while (internal->wh[ridx].sent && !!(internal->wh[ridx].wh.dwFlags & WHDR_DONE)) {
522 /* block successfully sent to hardware, release it */
523 /*debug("_ao_get_free_block: release block %d\n",ridx);*/
524 internal->wh[ridx].sent = 0;
525 internal->wh[ridx].wh.dwFlags &= ~WHDR_DONE;
526
527 --internal->full_blocks;
528 if (internal->full_blocks<0) {
529 adebug("internal error with full block counter\n");
530 internal->full_blocks = 0;
531 }
532
533 --internal->sent_blocks;
534 if (internal->sent_blocks<0) {
535 adebug("internal error with sent block counter\n");
536 internal->sent_blocks = 0;
537 }
538 if (++ridx >= internal->blocks) ridx = 0;
539 }
540 internal->ridx = ridx;
541
542 return internal->wh[idx].sent
543 ? -1
544 : idx;
545 }
546
547 /*
548 * play the sample to the already opened file descriptor
549 */
ao_wmm_play(ao_device * device,const char * output_samples,uint_32 num_bytes)550 int ao_wmm_play(ao_device *device,
551 const char *output_samples, uint_32 num_bytes)
552 {
553 int ret = 1;
554 ao_wmm_internal *internal = (ao_wmm_internal *) device->internal;
555
556 while(ret && num_bytes > 0) {
557 int n;
558 const int idx = _ao_get_free_block(device);
559
560 if (idx == -1) {
561 Sleep(internal->msPerBlock);
562 continue;
563 }
564
565 /* Get free bytes in the block */
566 n = internal->wh[idx].wh.dwBufferLength
567 - internal->wh[idx].count;
568
569 /* Get amount to copy */
570 if (n > (int)num_bytes) {
571 n = num_bytes;
572 }
573
574 /* Do copy */
575 CopyMemory((char*)internal->wh[idx].wh.lpData
576 + internal->wh[idx].count,
577 output_samples, n);
578
579 /* Updates pointers and counters */
580 output_samples += n;
581 num_bytes -= n;
582 internal->wh[idx].count += n;
583
584 /* Is this block full ? */
585 if (internal->wh[idx].count
586 == internal->wh[idx].wh.dwBufferLength) {
587 ++internal->full_blocks;
588 if (++internal->widx == internal->blocks) {
589 internal->widx = 0;
590 }
591 ret = _ao_send_block(device,idx);
592 }
593 }
594
595 adebug("ao_wmm_play => %d rem => [%s]\n",num_bytes,ret?"success":"error");
596 return ret;
597
598 }
599
ao_wmm_close(ao_device * device)600 int ao_wmm_close(ao_device *device)
601 {
602 ao_wmm_internal *internal = (ao_wmm_internal *) device->internal;
603 int ret = 0;
604
605 if (internal->opened && internal->prepared) {
606 _ao_wait_wave_headers(device, 1);
607 }
608
609 if (internal->prepared) {
610 ret = _ao_free_wave_headers(device);
611 internal->prepared = 0;
612 }
613
614 if (internal->opened) {
615 ret = _ao_close_device(device);
616 internal->opened = 0;
617 }
618
619 return ret;
620 }
621
ao_wmm_device_clear(ao_device * device)622 void ao_wmm_device_clear(ao_device *device)
623 {
624 ao_wmm_internal *internal = (ao_wmm_internal *) device->internal;
625
626 if (internal->bigbuffer) {
627 free(internal->bigbuffer); internal->bigbuffer = NULL;
628 }
629 free(internal);
630 device->internal=NULL;
631 }
632
633 ao_functions ao_wmm = {
634 ao_wmm_test,
635 ao_wmm_driver_info,
636 ao_wmm_device_init,
637 ao_wmm_set_option,
638 ao_wmm_open,
639 ao_wmm_play,
640 ao_wmm_close,
641 ao_wmm_device_clear
642 };
643