xref: /qemu/audio/dsoundaudio.c (revision 3b472e71d50fe33f2e0dfdd447dde5910ddf0761)
1  /*
2   * QEMU DirectSound audio driver
3   *
4   * Copyright (c) 2005 Vassili Karpov (malc)
5   *
6   * Permission is hereby granted, free of charge, to any person obtaining a copy
7   * of this software and associated documentation files (the "Software"), to deal
8   * in the Software without restriction, including without limitation the rights
9   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in
14   * all copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22   * THE SOFTWARE.
23   */
24  
25  /*
26   * SEAL 1.07 by Carlos 'pel' Hasan was used as documentation
27   */
28  
29  #include "qemu/osdep.h"
30  #include "audio.h"
31  
32  #define AUDIO_CAP "dsound"
33  #include "audio_int.h"
34  #include "qemu/host-utils.h"
35  #include "qemu/module.h"
36  
37  #include <windows.h>
38  #include <mmsystem.h>
39  #include <objbase.h>
40  #include <dsound.h>
41  
42  #include "audio_win_int.h"
43  
44  /* #define DEBUG_DSOUND */
45  
46  typedef struct {
47      LPDIRECTSOUND dsound;
48      LPDIRECTSOUNDCAPTURE dsound_capture;
49      struct audsettings settings;
50      Audiodev *dev;
51  } dsound;
52  
53  typedef struct {
54      HWVoiceOut hw;
55      LPDIRECTSOUNDBUFFER dsound_buffer;
56      bool first_time;
57      dsound *s;
58  } DSoundVoiceOut;
59  
60  typedef struct {
61      HWVoiceIn hw;
62      LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer;
63      bool first_time;
64      dsound *s;
65  } DSoundVoiceIn;
66  
67  static void dsound_log_hresult (HRESULT hr)
68  {
69      const char *str = "BUG";
70  
71      switch (hr) {
72      case DS_OK:
73          str = "The method succeeded";
74          break;
75  #ifdef DS_NO_VIRTUALIZATION
76      case DS_NO_VIRTUALIZATION:
77          str = "The buffer was created, but another 3D algorithm was substituted";
78          break;
79  #endif
80  #ifdef DS_INCOMPLETE
81      case DS_INCOMPLETE:
82          str = "The method succeeded, but not all the optional effects were obtained";
83          break;
84  #endif
85  #ifdef DSERR_ACCESSDENIED
86      case DSERR_ACCESSDENIED:
87          str = "The request failed because access was denied";
88          break;
89  #endif
90  #ifdef DSERR_ALLOCATED
91      case DSERR_ALLOCATED:
92          str = "The request failed because resources, "
93                "such as a priority level, were already in use "
94                "by another caller";
95          break;
96  #endif
97  #ifdef DSERR_ALREADYINITIALIZED
98      case DSERR_ALREADYINITIALIZED:
99          str = "The object is already initialized";
100          break;
101  #endif
102  #ifdef DSERR_BADFORMAT
103      case DSERR_BADFORMAT:
104          str = "The specified wave format is not supported";
105          break;
106  #endif
107  #ifdef DSERR_BADSENDBUFFERGUID
108      case DSERR_BADSENDBUFFERGUID:
109          str = "The GUID specified in an audiopath file "
110                "does not match a valid mix-in buffer";
111          break;
112  #endif
113  #ifdef DSERR_BUFFERLOST
114      case DSERR_BUFFERLOST:
115          str = "The buffer memory has been lost and must be restored";
116          break;
117  #endif
118  #ifdef DSERR_BUFFERTOOSMALL
119      case DSERR_BUFFERTOOSMALL:
120          str = "The buffer size is not great enough to "
121                "enable effects processing";
122          break;
123  #endif
124  #ifdef DSERR_CONTROLUNAVAIL
125      case DSERR_CONTROLUNAVAIL:
126          str = "The buffer control (volume, pan, and so on) "
127                "requested by the caller is not available. "
128                "Controls must be specified when the buffer is created, "
129                "using the dwFlags member of DSBUFFERDESC";
130          break;
131  #endif
132  #ifdef DSERR_DS8_REQUIRED
133      case DSERR_DS8_REQUIRED:
134          str = "A DirectSound object of class CLSID_DirectSound8 or later "
135                "is required for the requested functionality. "
136                "For more information, see IDirectSound8 Interface";
137          break;
138  #endif
139  #ifdef DSERR_FXUNAVAILABLE
140      case DSERR_FXUNAVAILABLE:
141          str = "The effects requested could not be found on the system, "
142                "or they are in the wrong order or in the wrong location; "
143                "for example, an effect expected in hardware "
144                "was found in software";
145          break;
146  #endif
147  #ifdef DSERR_GENERIC
148      case DSERR_GENERIC:
149          str = "An undetermined error occurred inside the DirectSound subsystem";
150          break;
151  #endif
152  #ifdef DSERR_INVALIDCALL
153      case DSERR_INVALIDCALL:
154          str = "This function is not valid for the current state of this object";
155          break;
156  #endif
157  #ifdef DSERR_INVALIDPARAM
158      case DSERR_INVALIDPARAM:
159          str = "An invalid parameter was passed to the returning function";
160          break;
161  #endif
162  #ifdef DSERR_NOAGGREGATION
163      case DSERR_NOAGGREGATION:
164          str = "The object does not support aggregation";
165          break;
166  #endif
167  #ifdef DSERR_NODRIVER
168      case DSERR_NODRIVER:
169          str = "No sound driver is available for use, "
170                "or the given GUID is not a valid DirectSound device ID";
171          break;
172  #endif
173  #ifdef DSERR_NOINTERFACE
174      case DSERR_NOINTERFACE:
175          str = "The requested COM interface is not available";
176          break;
177  #endif
178  #ifdef DSERR_OBJECTNOTFOUND
179      case DSERR_OBJECTNOTFOUND:
180          str = "The requested object was not found";
181          break;
182  #endif
183  #ifdef DSERR_OTHERAPPHASPRIO
184      case DSERR_OTHERAPPHASPRIO:
185          str = "Another application has a higher priority level, "
186                "preventing this call from succeeding";
187          break;
188  #endif
189  #ifdef DSERR_OUTOFMEMORY
190      case DSERR_OUTOFMEMORY:
191          str = "The DirectSound subsystem could not allocate "
192                 "sufficient memory to complete the caller's request";
193          break;
194  #endif
195  #ifdef DSERR_PRIOLEVELNEEDED
196      case DSERR_PRIOLEVELNEEDED:
197          str = "A cooperative level of DSSCL_PRIORITY or higher is required";
198          break;
199  #endif
200  #ifdef DSERR_SENDLOOP
201      case DSERR_SENDLOOP:
202          str = "A circular loop of send effects was detected";
203          break;
204  #endif
205  #ifdef DSERR_UNINITIALIZED
206      case DSERR_UNINITIALIZED:
207          str = "The Initialize method has not been called "
208                "or has not been called successfully "
209                "before other methods were called";
210          break;
211  #endif
212  #ifdef DSERR_UNSUPPORTED
213      case DSERR_UNSUPPORTED:
214          str = "The function called is not supported at this time";
215          break;
216  #endif
217      default:
218          AUD_log (AUDIO_CAP, "Reason: Unknown (HRESULT 0x%lx)\n", hr);
219          return;
220      }
221  
222      AUD_log (AUDIO_CAP, "Reason: %s\n", str);
223  }
224  
225  static void GCC_FMT_ATTR (2, 3) dsound_logerr (
226      HRESULT hr,
227      const char *fmt,
228      ...
229      )
230  {
231      va_list ap;
232  
233      va_start (ap, fmt);
234      AUD_vlog (AUDIO_CAP, fmt, ap);
235      va_end (ap);
236  
237      dsound_log_hresult (hr);
238  }
239  
240  static void GCC_FMT_ATTR (3, 4) dsound_logerr2 (
241      HRESULT hr,
242      const char *typ,
243      const char *fmt,
244      ...
245      )
246  {
247      va_list ap;
248  
249      AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ);
250      va_start (ap, fmt);
251      AUD_vlog (AUDIO_CAP, fmt, ap);
252      va_end (ap);
253  
254      dsound_log_hresult (hr);
255  }
256  
257  #ifdef DEBUG_DSOUND
258  static void print_wave_format (WAVEFORMATEX *wfx)
259  {
260      dolog ("tag             = %d\n", wfx->wFormatTag);
261      dolog ("nChannels       = %d\n", wfx->nChannels);
262      dolog ("nSamplesPerSec  = %ld\n", wfx->nSamplesPerSec);
263      dolog ("nAvgBytesPerSec = %ld\n", wfx->nAvgBytesPerSec);
264      dolog ("nBlockAlign     = %d\n", wfx->nBlockAlign);
265      dolog ("wBitsPerSample  = %d\n", wfx->wBitsPerSample);
266      dolog ("cbSize          = %d\n", wfx->cbSize);
267  }
268  #endif
269  
270  static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb, dsound *s)
271  {
272      HRESULT hr;
273  
274      hr = IDirectSoundBuffer_Restore (dsb);
275  
276      if (hr != DS_OK) {
277          dsound_logerr (hr, "Could not restore playback buffer\n");
278          return -1;
279      }
280      return 0;
281  }
282  
283  #include "dsound_template.h"
284  #define DSBTYPE_IN
285  #include "dsound_template.h"
286  #undef DSBTYPE_IN
287  
288  static int dsound_get_status_out (LPDIRECTSOUNDBUFFER dsb, DWORD *statusp,
289                                    dsound *s)
290  {
291      HRESULT hr;
292  
293      hr = IDirectSoundBuffer_GetStatus (dsb, statusp);
294      if (FAILED (hr)) {
295          dsound_logerr (hr, "Could not get playback buffer status\n");
296          return -1;
297      }
298  
299      if (*statusp & DSBSTATUS_BUFFERLOST) {
300          dsound_restore_out(dsb, s);
301          return -1;
302      }
303  
304      return 0;
305  }
306  
307  static int dsound_get_status_in (LPDIRECTSOUNDCAPTUREBUFFER dscb,
308                                   DWORD *statusp)
309  {
310      HRESULT hr;
311  
312      hr = IDirectSoundCaptureBuffer_GetStatus (dscb, statusp);
313      if (FAILED (hr)) {
314          dsound_logerr (hr, "Could not get capture buffer status\n");
315          return -1;
316      }
317  
318      return 0;
319  }
320  
321  static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb,
322                                   dsound *s)
323  {
324      int err;
325      LPVOID p1, p2;
326      DWORD blen1, blen2, len1, len2;
327  
328      err = dsound_lock_out (
329          dsb,
330          &hw->info,
331          0,
332          hw->size_emul,
333          &p1, &p2,
334          &blen1, &blen2,
335          1,
336          s
337          );
338      if (err) {
339          return;
340      }
341  
342      len1 = blen1 / hw->info.bytes_per_frame;
343      len2 = blen2 / hw->info.bytes_per_frame;
344  
345  #ifdef DEBUG_DSOUND
346      dolog ("clear %p,%ld,%ld %p,%ld,%ld\n",
347             p1, blen1, len1,
348             p2, blen2, len2);
349  #endif
350  
351      if (p1 && len1) {
352          audio_pcm_info_clear_buf (&hw->info, p1, len1);
353      }
354  
355      if (p2 && len2) {
356          audio_pcm_info_clear_buf (&hw->info, p2, len2);
357      }
358  
359      dsound_unlock_out (dsb, p1, p2, blen1, blen2);
360  }
361  
362  static int dsound_set_cooperative_level(dsound *s)
363  {
364      HRESULT hr;
365      HWND hwnd;
366  
367      hwnd = GetDesktopWindow();
368      hr = IDirectSound_SetCooperativeLevel (
369          s->dsound,
370          hwnd,
371          DSSCL_PRIORITY
372          );
373  
374      if (FAILED (hr)) {
375          dsound_logerr (hr, "Could not set cooperative level for window %p\n",
376                         hwnd);
377          return -1;
378      }
379  
380      return 0;
381  }
382  
383  static void dsound_enable_out(HWVoiceOut *hw, bool enable)
384  {
385      HRESULT hr;
386      DWORD status;
387      DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
388      LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer;
389      dsound *s = ds->s;
390  
391      if (!dsb) {
392          dolog ("Attempt to control voice without a buffer\n");
393          return;
394      }
395  
396      if (enable) {
397          if (dsound_get_status_out (dsb, &status, s)) {
398              return;
399          }
400  
401          if (status & DSBSTATUS_PLAYING) {
402              dolog ("warning: Voice is already playing\n");
403              return;
404          }
405  
406          dsound_clear_sample (hw, dsb, s);
407  
408          hr = IDirectSoundBuffer_Play (dsb, 0, 0, DSBPLAY_LOOPING);
409          if (FAILED (hr)) {
410              dsound_logerr (hr, "Could not start playing buffer\n");
411              return;
412          }
413      } else {
414          if (dsound_get_status_out (dsb, &status, s)) {
415              return;
416          }
417  
418          if (status & DSBSTATUS_PLAYING) {
419              hr = IDirectSoundBuffer_Stop (dsb);
420              if (FAILED (hr)) {
421                  dsound_logerr (hr, "Could not stop playing buffer\n");
422                  return;
423              }
424          } else {
425              dolog ("warning: Voice is not playing\n");
426          }
427      }
428  }
429  
430  static void *dsound_get_buffer_out(HWVoiceOut *hw, size_t *size)
431  {
432      DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
433      LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer;
434      HRESULT hr;
435      DWORD ppos, wpos, act_size;
436      size_t req_size;
437      int err;
438      void *ret;
439  
440      hr = IDirectSoundBuffer_GetCurrentPosition(
441          dsb, &ppos, ds->first_time ? &wpos : NULL);
442      if (FAILED(hr)) {
443          dsound_logerr(hr, "Could not get playback buffer position\n");
444          *size = 0;
445          return NULL;
446      }
447  
448      if (ds->first_time) {
449          hw->pos_emul = wpos;
450          ds->first_time = false;
451      }
452  
453      req_size = audio_ring_dist(ppos, hw->pos_emul, hw->size_emul);
454      req_size = MIN(req_size, hw->size_emul - hw->pos_emul);
455  
456      if (req_size == 0) {
457          *size = 0;
458          return NULL;
459      }
460  
461      err = dsound_lock_out(dsb, &hw->info, hw->pos_emul, req_size, &ret, NULL,
462                            &act_size, NULL, false, ds->s);
463      if (err) {
464          dolog("Failed to lock buffer\n");
465          *size = 0;
466          return NULL;
467      }
468  
469      *size = act_size;
470      return ret;
471  }
472  
473  static size_t dsound_put_buffer_out(HWVoiceOut *hw, void *buf, size_t len)
474  {
475      DSoundVoiceOut *ds = (DSoundVoiceOut *) hw;
476      LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer;
477      int err = dsound_unlock_out(dsb, buf, NULL, len, 0);
478  
479      if (err) {
480          dolog("Failed to unlock buffer!!\n");
481          return 0;
482      }
483      hw->pos_emul = (hw->pos_emul + len) % hw->size_emul;
484  
485      return len;
486  }
487  
488  static void dsound_enable_in(HWVoiceIn *hw, bool enable)
489  {
490      HRESULT hr;
491      DWORD status;
492      DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
493      LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer;
494  
495      if (!dscb) {
496          dolog ("Attempt to control capture voice without a buffer\n");
497          return;
498      }
499  
500      if (enable) {
501          if (dsound_get_status_in (dscb, &status)) {
502              return;
503          }
504  
505          if (status & DSCBSTATUS_CAPTURING) {
506              dolog ("warning: Voice is already capturing\n");
507              return;
508          }
509  
510          /* clear ?? */
511  
512          hr = IDirectSoundCaptureBuffer_Start (dscb, DSCBSTART_LOOPING);
513          if (FAILED (hr)) {
514              dsound_logerr (hr, "Could not start capturing\n");
515              return;
516          }
517      } else {
518          if (dsound_get_status_in (dscb, &status)) {
519              return;
520          }
521  
522          if (status & DSCBSTATUS_CAPTURING) {
523              hr = IDirectSoundCaptureBuffer_Stop (dscb);
524              if (FAILED (hr)) {
525                  dsound_logerr (hr, "Could not stop capturing\n");
526                  return;
527              }
528          } else {
529              dolog ("warning: Voice is not capturing\n");
530          }
531      }
532  }
533  
534  static void *dsound_get_buffer_in(HWVoiceIn *hw, size_t *size)
535  {
536      DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
537      LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer;
538      HRESULT hr;
539      DWORD cpos, rpos, act_size;
540      size_t req_size;
541      int err;
542      void *ret;
543  
544      hr = IDirectSoundCaptureBuffer_GetCurrentPosition(
545          dscb, &cpos, ds->first_time ? &rpos : NULL);
546      if (FAILED(hr)) {
547          dsound_logerr(hr, "Could not get capture buffer position\n");
548          *size = 0;
549          return NULL;
550      }
551  
552      if (ds->first_time) {
553          hw->pos_emul = rpos;
554          ds->first_time = false;
555      }
556  
557      req_size = audio_ring_dist(cpos, hw->pos_emul, hw->size_emul);
558      req_size = MIN(*size, MIN(req_size, hw->size_emul - hw->pos_emul));
559  
560      if (req_size == 0) {
561          *size = 0;
562          return NULL;
563      }
564  
565      err = dsound_lock_in(dscb, &hw->info, hw->pos_emul, req_size, &ret, NULL,
566                           &act_size, NULL, false, ds->s);
567      if (err) {
568          dolog("Failed to lock buffer\n");
569          *size = 0;
570          return NULL;
571      }
572  
573      *size = act_size;
574      return ret;
575  }
576  
577  static void dsound_put_buffer_in(HWVoiceIn *hw, void *buf, size_t len)
578  {
579      DSoundVoiceIn *ds = (DSoundVoiceIn *) hw;
580      LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer;
581      int err = dsound_unlock_in(dscb, buf, NULL, len, 0);
582  
583      if (err) {
584          dolog("Failed to unlock buffer!!\n");
585          return;
586      }
587      hw->pos_emul = (hw->pos_emul + len) % hw->size_emul;
588  }
589  
590  static void dsound_audio_fini (void *opaque)
591  {
592      HRESULT hr;
593      dsound *s = opaque;
594  
595      if (!s->dsound) {
596          g_free(s);
597          return;
598      }
599  
600      hr = IDirectSound_Release (s->dsound);
601      if (FAILED (hr)) {
602          dsound_logerr (hr, "Could not release DirectSound\n");
603      }
604      s->dsound = NULL;
605  
606      if (!s->dsound_capture) {
607          g_free(s);
608          return;
609      }
610  
611      hr = IDirectSoundCapture_Release (s->dsound_capture);
612      if (FAILED (hr)) {
613          dsound_logerr (hr, "Could not release DirectSoundCapture\n");
614      }
615      s->dsound_capture = NULL;
616  
617      g_free(s);
618  }
619  
620  static void *dsound_audio_init(Audiodev *dev)
621  {
622      int err;
623      HRESULT hr;
624      dsound *s = g_malloc0(sizeof(dsound));
625      AudiodevDsoundOptions *dso;
626  
627      assert(dev->driver == AUDIODEV_DRIVER_DSOUND);
628      s->dev = dev;
629      dso = &dev->u.dsound;
630  
631      if (!dso->has_latency) {
632          dso->has_latency = true;
633          dso->latency = 10000; /* 10 ms */
634      }
635  
636      hr = CoInitialize (NULL);
637      if (FAILED (hr)) {
638          dsound_logerr (hr, "Could not initialize COM\n");
639          g_free(s);
640          return NULL;
641      }
642  
643      hr = CoCreateInstance (
644          &CLSID_DirectSound,
645          NULL,
646          CLSCTX_ALL,
647          &IID_IDirectSound,
648          (void **) &s->dsound
649          );
650      if (FAILED (hr)) {
651          dsound_logerr (hr, "Could not create DirectSound instance\n");
652          g_free(s);
653          return NULL;
654      }
655  
656      hr = IDirectSound_Initialize (s->dsound, NULL);
657      if (FAILED (hr)) {
658          dsound_logerr (hr, "Could not initialize DirectSound\n");
659  
660          hr = IDirectSound_Release (s->dsound);
661          if (FAILED (hr)) {
662              dsound_logerr (hr, "Could not release DirectSound\n");
663          }
664          g_free(s);
665          return NULL;
666      }
667  
668      hr = CoCreateInstance (
669          &CLSID_DirectSoundCapture,
670          NULL,
671          CLSCTX_ALL,
672          &IID_IDirectSoundCapture,
673          (void **) &s->dsound_capture
674          );
675      if (FAILED (hr)) {
676          dsound_logerr (hr, "Could not create DirectSoundCapture instance\n");
677      } else {
678          hr = IDirectSoundCapture_Initialize (s->dsound_capture, NULL);
679          if (FAILED (hr)) {
680              dsound_logerr (hr, "Could not initialize DirectSoundCapture\n");
681  
682              hr = IDirectSoundCapture_Release (s->dsound_capture);
683              if (FAILED (hr)) {
684                  dsound_logerr (hr, "Could not release DirectSoundCapture\n");
685              }
686              s->dsound_capture = NULL;
687          }
688      }
689  
690      err = dsound_set_cooperative_level(s);
691      if (err) {
692          dsound_audio_fini (s);
693          return NULL;
694      }
695  
696      return s;
697  }
698  
699  static struct audio_pcm_ops dsound_pcm_ops = {
700      .init_out = dsound_init_out,
701      .fini_out = dsound_fini_out,
702      .write    = audio_generic_write,
703      .get_buffer_out = dsound_get_buffer_out,
704      .put_buffer_out = dsound_put_buffer_out,
705      .enable_out = dsound_enable_out,
706  
707      .init_in  = dsound_init_in,
708      .fini_in  = dsound_fini_in,
709      .read     = audio_generic_read,
710      .get_buffer_in = dsound_get_buffer_in,
711      .put_buffer_in = dsound_put_buffer_in,
712      .enable_in = dsound_enable_in,
713  };
714  
715  static struct audio_driver dsound_audio_driver = {
716      .name           = "dsound",
717      .descr          = "DirectSound http://wikipedia.org/wiki/DirectSound",
718      .init           = dsound_audio_init,
719      .fini           = dsound_audio_fini,
720      .pcm_ops        = &dsound_pcm_ops,
721      .can_be_default = 1,
722      .max_voices_out = INT_MAX,
723      .max_voices_in  = 1,
724      .voice_size_out = sizeof (DSoundVoiceOut),
725      .voice_size_in  = sizeof (DSoundVoiceIn)
726  };
727  
728  static void register_audio_dsound(void)
729  {
730      audio_driver_register(&dsound_audio_driver);
731  }
732  type_init(register_audio_dsound);
733