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