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