Statistics
| Branch: | Revision:

root / audio / fmodaudio.c @ 197bc219

History | View | Annotate | Download (17.4 kB)

1
/*
2
 * QEMU FMOD audio driver
3
 *
4
 * Copyright (c) 2004-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
#include <fmod.h>
25
#include <fmod_errors.h>
26
#include "qemu-common.h"
27
#include "audio.h"
28

    
29
#define AUDIO_CAP "fmod"
30
#include "audio_int.h"
31

    
32
typedef struct FMODVoiceOut {
33
    HWVoiceOut hw;
34
    unsigned int old_pos;
35
    FSOUND_SAMPLE *fmod_sample;
36
    int channel;
37
} FMODVoiceOut;
38

    
39
typedef struct FMODVoiceIn {
40
    HWVoiceIn hw;
41
    FSOUND_SAMPLE *fmod_sample;
42
} FMODVoiceIn;
43

    
44
static struct {
45
    const char *drvname;
46
    int nb_samples;
47
    int freq;
48
    int nb_channels;
49
    int bufsize;
50
    int threshold;
51
    int broken_adc;
52
} conf = {
53
    .nb_samples  = 2048 * 2,
54
    .freq        = 44100,
55
    .nb_channels = 2,
56
};
57

    
58
static void GCC_FMT_ATTR (1, 2) fmod_logerr (const char *fmt, ...)
59
{
60
    va_list ap;
61

    
62
    va_start (ap, fmt);
63
    AUD_vlog (AUDIO_CAP, fmt, ap);
64
    va_end (ap);
65

    
66
    AUD_log (AUDIO_CAP, "Reason: %s\n",
67
             FMOD_ErrorString (FSOUND_GetError ()));
68
}
69

    
70
static void GCC_FMT_ATTR (2, 3) fmod_logerr2 (
71
    const char *typ,
72
    const char *fmt,
73
    ...
74
    )
75
{
76
    va_list ap;
77

    
78
    AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ);
79

    
80
    va_start (ap, fmt);
81
    AUD_vlog (AUDIO_CAP, fmt, ap);
82
    va_end (ap);
83

    
84
    AUD_log (AUDIO_CAP, "Reason: %s\n",
85
             FMOD_ErrorString (FSOUND_GetError ()));
86
}
87

    
88
static int fmod_write (SWVoiceOut *sw, void *buf, int len)
89
{
90
    return audio_pcm_sw_write (sw, buf, len);
91
}
92

    
93
static void fmod_clear_sample (FMODVoiceOut *fmd)
94
{
95
    HWVoiceOut *hw = &fmd->hw;
96
    int status;
97
    void *p1 = 0, *p2 = 0;
98
    unsigned int len1 = 0, len2 = 0;
99

    
100
    status = FSOUND_Sample_Lock (
101
        fmd->fmod_sample,
102
        0,
103
        hw->samples << hw->info.shift,
104
        &p1,
105
        &p2,
106
        &len1,
107
        &len2
108
        );
109

    
110
    if (!status) {
111
        fmod_logerr ("Failed to lock sample\n");
112
        return;
113
    }
114

    
115
    if ((len1 & hw->info.align) || (len2 & hw->info.align)) {
116
        dolog ("Lock returned misaligned length %d, %d, alignment %d\n",
117
               len1, len2, hw->info.align + 1);
118
        goto fail;
119
    }
120

    
121
    if ((len1 + len2) - (hw->samples << hw->info.shift)) {
122
        dolog ("Lock returned incomplete length %d, %d\n",
123
               len1 + len2, hw->samples << hw->info.shift);
124
        goto fail;
125
    }
126

    
127
    audio_pcm_info_clear_buf (&hw->info, p1, hw->samples);
128

    
129
 fail:
130
    status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, len1, len2);
131
    if (!status) {
132
        fmod_logerr ("Failed to unlock sample\n");
133
    }
134
}
135

    
136
static void fmod_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len)
137
{
138
    int src_len1 = dst_len;
139
    int src_len2 = 0;
140
    int pos = hw->rpos + dst_len;
141
    struct st_sample *src1 = hw->mix_buf + hw->rpos;
142
    struct st_sample *src2 = NULL;
143

    
144
    if (pos > hw->samples) {
145
        src_len1 = hw->samples - hw->rpos;
146
        src2 = hw->mix_buf;
147
        src_len2 = dst_len - src_len1;
148
        pos = src_len2;
149
    }
150

    
151
    if (src_len1) {
152
        hw->clip (dst, src1, src_len1);
153
    }
154

    
155
    if (src_len2) {
156
        dst = advance (dst, src_len1 << hw->info.shift);
157
        hw->clip (dst, src2, src_len2);
158
    }
159

    
160
    hw->rpos = pos % hw->samples;
161
}
162

    
163
static int fmod_unlock_sample (FSOUND_SAMPLE *sample, void *p1, void *p2,
164
                               unsigned int blen1, unsigned int blen2)
165
{
166
    int status = FSOUND_Sample_Unlock (sample, p1, p2, blen1, blen2);
167
    if (!status) {
168
        fmod_logerr ("Failed to unlock sample\n");
169
        return -1;
170
    }
171
    return 0;
172
}
173

    
174
static int fmod_lock_sample (
175
    FSOUND_SAMPLE *sample,
176
    struct audio_pcm_info *info,
177
    int pos,
178
    int len,
179
    void **p1,
180
    void **p2,
181
    unsigned int *blen1,
182
    unsigned int *blen2
183
    )
184
{
185
    int status;
186

    
187
    status = FSOUND_Sample_Lock (
188
        sample,
189
        pos << info->shift,
190
        len << info->shift,
191
        p1,
192
        p2,
193
        blen1,
194
        blen2
195
        );
196

    
197
    if (!status) {
198
        fmod_logerr ("Failed to lock sample\n");
199
        return -1;
200
    }
201

    
202
    if ((*blen1 & info->align) || (*blen2 & info->align)) {
203
        dolog ("Lock returned misaligned length %d, %d, alignment %d\n",
204
               *blen1, *blen2, info->align + 1);
205

    
206
        fmod_unlock_sample (sample, *p1, *p2, *blen1, *blen2);
207

    
208
        *p1 = NULL - 1;
209
        *p2 = NULL - 1;
210
        *blen1 = ~0U;
211
        *blen2 = ~0U;
212
        return -1;
213
    }
214

    
215
    if (!*p1 && *blen1) {
216
        dolog ("warning: !p1 && blen1=%d\n", *blen1);
217
        *blen1 = 0;
218
    }
219

    
220
    if (!p2 && *blen2) {
221
        dolog ("warning: !p2 && blen2=%d\n", *blen2);
222
        *blen2 = 0;
223
    }
224

    
225
    return 0;
226
}
227

    
228
static int fmod_run_out (HWVoiceOut *hw)
229
{
230
    FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
231
    int live, decr;
232
    void *p1 = 0, *p2 = 0;
233
    unsigned int blen1 = 0, blen2 = 0;
234
    unsigned int len1 = 0, len2 = 0;
235
    int nb_live;
236

    
237
    live = audio_pcm_hw_get_live_out2 (hw, &nb_live);
238
    if (!live) {
239
        return 0;
240
    }
241

    
242
    if (!hw->pending_disable
243
        && nb_live
244
        && (conf.threshold && live <= conf.threshold)) {
245
        ldebug ("live=%d nb_live=%d\n", live, nb_live);
246
        return 0;
247
    }
248

    
249
    decr = live;
250

    
251
    if (fmd->channel >= 0) {
252
        int len = decr;
253
        int old_pos = fmd->old_pos;
254
        int ppos = FSOUND_GetCurrentPosition (fmd->channel);
255

    
256
        if (ppos == old_pos || !ppos) {
257
            return 0;
258
        }
259

    
260
        if ((old_pos < ppos) && ((old_pos + len) > ppos)) {
261
            len = ppos - old_pos;
262
        }
263
        else {
264
            if ((old_pos > ppos) && ((old_pos + len) > (ppos + hw->samples))) {
265
                len = hw->samples - old_pos + ppos;
266
            }
267
        }
268
        decr = len;
269

    
270
        if (audio_bug (AUDIO_FUNC, decr < 0)) {
271
            dolog ("decr=%d live=%d ppos=%d old_pos=%d len=%d\n",
272
                   decr, live, ppos, old_pos, len);
273
            return 0;
274
        }
275
    }
276

    
277

    
278
    if (!decr) {
279
        return 0;
280
    }
281

    
282
    if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info,
283
                          fmd->old_pos, decr,
284
                          &p1, &p2,
285
                          &blen1, &blen2)) {
286
        return 0;
287
    }
288

    
289
    len1 = blen1 >> hw->info.shift;
290
    len2 = blen2 >> hw->info.shift;
291
    ldebug ("%p %p %d %d %d %d\n", p1, p2, len1, len2, blen1, blen2);
292
    decr = len1 + len2;
293

    
294
    if (p1 && len1) {
295
        fmod_write_sample (hw, p1, len1);
296
    }
297

    
298
    if (p2 && len2) {
299
        fmod_write_sample (hw, p2, len2);
300
    }
301

    
302
    fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2);
303

    
304
    fmd->old_pos = (fmd->old_pos + decr) % hw->samples;
305
    return decr;
306
}
307

    
308
static int aud_to_fmodfmt (audfmt_e fmt, int stereo)
309
{
310
    int mode = FSOUND_LOOP_NORMAL;
311

    
312
    switch (fmt) {
313
    case AUD_FMT_S8:
314
        mode |= FSOUND_SIGNED | FSOUND_8BITS;
315
        break;
316

    
317
    case AUD_FMT_U8:
318
        mode |= FSOUND_UNSIGNED | FSOUND_8BITS;
319
        break;
320

    
321
    case AUD_FMT_S16:
322
        mode |= FSOUND_SIGNED | FSOUND_16BITS;
323
        break;
324

    
325
    case AUD_FMT_U16:
326
        mode |= FSOUND_UNSIGNED | FSOUND_16BITS;
327
        break;
328

    
329
    default:
330
        dolog ("Internal logic error: Bad audio format %d\n", fmt);
331
#ifdef DEBUG_FMOD
332
        abort ();
333
#endif
334
        mode |= FSOUND_8BITS;
335
    }
336
    mode |= stereo ? FSOUND_STEREO : FSOUND_MONO;
337
    return mode;
338
}
339

    
340
static void fmod_fini_out (HWVoiceOut *hw)
341
{
342
    FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
343

    
344
    if (fmd->fmod_sample) {
345
        FSOUND_Sample_Free (fmd->fmod_sample);
346
        fmd->fmod_sample = 0;
347

    
348
        if (fmd->channel >= 0) {
349
            FSOUND_StopSound (fmd->channel);
350
        }
351
    }
352
}
353

    
354
static int fmod_init_out (HWVoiceOut *hw, struct audsettings *as)
355
{
356
    int bits16, mode, channel;
357
    FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
358
    struct audsettings obt_as = *as;
359

    
360
    mode = aud_to_fmodfmt (as->fmt, as->nchannels == 2 ? 1 : 0);
361
    fmd->fmod_sample = FSOUND_Sample_Alloc (
362
        FSOUND_FREE,            /* index */
363
        conf.nb_samples,        /* length */
364
        mode,                   /* mode */
365
        as->freq,               /* freq */
366
        255,                    /* volume */
367
        128,                    /* pan */
368
        255                     /* priority */
369
        );
370

    
371
    if (!fmd->fmod_sample) {
372
        fmod_logerr2 ("DAC", "Failed to allocate FMOD sample\n");
373
        return -1;
374
    }
375

    
376
    channel = FSOUND_PlaySoundEx (FSOUND_FREE, fmd->fmod_sample, 0, 1);
377
    if (channel < 0) {
378
        fmod_logerr2 ("DAC", "Failed to start playing sound\n");
379
        FSOUND_Sample_Free (fmd->fmod_sample);
380
        return -1;
381
    }
382
    fmd->channel = channel;
383

    
384
    /* FMOD always operates on little endian frames? */
385
    obt_as.endianness = 0;
386
    audio_pcm_init_info (&hw->info, &obt_as);
387
    bits16 = (mode & FSOUND_16BITS) != 0;
388
    hw->samples = conf.nb_samples;
389
    return 0;
390
}
391

    
392
static int fmod_ctl_out (HWVoiceOut *hw, int cmd, ...)
393
{
394
    int status;
395
    FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
396

    
397
    switch (cmd) {
398
    case VOICE_ENABLE:
399
        fmod_clear_sample (fmd);
400
        status = FSOUND_SetPaused (fmd->channel, 0);
401
        if (!status) {
402
            fmod_logerr ("Failed to resume channel %d\n", fmd->channel);
403
        }
404
        break;
405

    
406
    case VOICE_DISABLE:
407
        status = FSOUND_SetPaused (fmd->channel, 1);
408
        if (!status) {
409
            fmod_logerr ("Failed to pause channel %d\n", fmd->channel);
410
        }
411
        break;
412
    }
413
    return 0;
414
}
415

    
416
static int fmod_init_in (HWVoiceIn *hw, struct audsettings *as)
417
{
418
    int bits16, mode;
419
    FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
420
    struct audsettings obt_as = *as;
421

    
422
    if (conf.broken_adc) {
423
        return -1;
424
    }
425

    
426
    mode = aud_to_fmodfmt (as->fmt, as->nchannels == 2 ? 1 : 0);
427
    fmd->fmod_sample = FSOUND_Sample_Alloc (
428
        FSOUND_FREE,            /* index */
429
        conf.nb_samples,        /* length */
430
        mode,                   /* mode */
431
        as->freq,               /* freq */
432
        255,                    /* volume */
433
        128,                    /* pan */
434
        255                     /* priority */
435
        );
436

    
437
    if (!fmd->fmod_sample) {
438
        fmod_logerr2 ("ADC", "Failed to allocate FMOD sample\n");
439
        return -1;
440
    }
441

    
442
    /* FMOD always operates on little endian frames? */
443
    obt_as.endianness = 0;
444
    audio_pcm_init_info (&hw->info, &obt_as);
445
    bits16 = (mode & FSOUND_16BITS) != 0;
446
    hw->samples = conf.nb_samples;
447
    return 0;
448
}
449

    
450
static void fmod_fini_in (HWVoiceIn *hw)
451
{
452
    FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
453

    
454
    if (fmd->fmod_sample) {
455
        FSOUND_Record_Stop ();
456
        FSOUND_Sample_Free (fmd->fmod_sample);
457
        fmd->fmod_sample = 0;
458
    }
459
}
460

    
461
static int fmod_run_in (HWVoiceIn *hw)
462
{
463
    FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
464
    int hwshift = hw->info.shift;
465
    int live, dead, new_pos, len;
466
    unsigned int blen1 = 0, blen2 = 0;
467
    unsigned int len1, len2;
468
    unsigned int decr;
469
    void *p1, *p2;
470

    
471
    live = audio_pcm_hw_get_live_in (hw);
472
    dead = hw->samples - live;
473
    if (!dead) {
474
        return 0;
475
    }
476

    
477
    new_pos = FSOUND_Record_GetPosition ();
478
    if (new_pos < 0) {
479
        fmod_logerr ("Could not get recording position\n");
480
        return 0;
481
    }
482

    
483
    len = audio_ring_dist (new_pos,  hw->wpos, hw->samples);
484
    if (!len) {
485
        return 0;
486
    }
487
    len = audio_MIN (len, dead);
488

    
489
    if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info,
490
                          hw->wpos, len,
491
                          &p1, &p2,
492
                          &blen1, &blen2)) {
493
        return 0;
494
    }
495

    
496
    len1 = blen1 >> hwshift;
497
    len2 = blen2 >> hwshift;
498
    decr = len1 + len2;
499

    
500
    if (p1 && blen1) {
501
        hw->conv (hw->conv_buf + hw->wpos, p1, len1, &nominal_volume);
502
    }
503
    if (p2 && len2) {
504
        hw->conv (hw->conv_buf, p2, len2, &nominal_volume);
505
    }
506

    
507
    fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2);
508
    hw->wpos = (hw->wpos + decr) % hw->samples;
509
    return decr;
510
}
511

    
512
static struct {
513
    const char *name;
514
    int type;
515
} drvtab[] = {
516
    { .name = "none",   .type = FSOUND_OUTPUT_NOSOUND },
517
#ifdef _WIN32
518
    { .name = "winmm",  .type = FSOUND_OUTPUT_WINMM   },
519
    { .name = "dsound", .type = FSOUND_OUTPUT_DSOUND  },
520
    { .name = "a3d",    .type = FSOUND_OUTPUT_A3D     },
521
    { .name = "asio",   .type = FSOUND_OUTPUT_ASIO    },
522
#endif
523
#ifdef __linux__
524
    { .name = "oss",    .type = FSOUND_OUTPUT_OSS     },
525
    { .name = "alsa",   .type = FSOUND_OUTPUT_ALSA    },
526
    { .name = "esd",    .type = FSOUND_OUTPUT_ESD     },
527
#endif
528
#ifdef __APPLE__
529
    { .name = "mac",    .type = FSOUND_OUTPUT_MAC     },
530
#endif
531
#if 0
532
    { .name = "xbox",   .type = FSOUND_OUTPUT_XBOX    },
533
    { .name = "ps2",    .type = FSOUND_OUTPUT_PS2     },
534
    { .name = "gcube",  .type = FSOUND_OUTPUT_GC      },
535
#endif
536
    { .name = "none-realtime", .type = FSOUND_OUTPUT_NOSOUND_NONREALTIME }
537
};
538

    
539
static void *fmod_audio_init (void)
540
{
541
    size_t i;
542
    double ver;
543
    int status;
544
    int output_type = -1;
545
    const char *drv = conf.drvname;
546

    
547
    ver = FSOUND_GetVersion ();
548
    if (ver < FMOD_VERSION) {
549
        dolog ("Wrong FMOD version %f, need at least %f\n", ver, FMOD_VERSION);
550
        return NULL;
551
    }
552

    
553
#ifdef __linux__
554
    if (ver < 3.75) {
555
        dolog ("FMOD before 3.75 has bug preventing ADC from working\n"
556
               "ADC will be disabled.\n");
557
        conf.broken_adc = 1;
558
    }
559
#endif
560

    
561
    if (drv) {
562
        int found = 0;
563
        for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
564
            if (!strcmp (drv, drvtab[i].name)) {
565
                output_type = drvtab[i].type;
566
                found = 1;
567
                break;
568
            }
569
        }
570
        if (!found) {
571
            dolog ("Unknown FMOD driver `%s'\n", drv);
572
            dolog ("Valid drivers:\n");
573
            for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
574
                dolog ("  %s\n", drvtab[i].name);
575
            }
576
        }
577
    }
578

    
579
    if (output_type != -1) {
580
        status = FSOUND_SetOutput (output_type);
581
        if (!status) {
582
            fmod_logerr ("FSOUND_SetOutput(%d) failed\n", output_type);
583
            return NULL;
584
        }
585
    }
586

    
587
    if (conf.bufsize) {
588
        status = FSOUND_SetBufferSize (conf.bufsize);
589
        if (!status) {
590
            fmod_logerr ("FSOUND_SetBufferSize (%d) failed\n", conf.bufsize);
591
        }
592
    }
593

    
594
    status = FSOUND_Init (conf.freq, conf.nb_channels, 0);
595
    if (!status) {
596
        fmod_logerr ("FSOUND_Init failed\n");
597
        return NULL;
598
    }
599

    
600
    return &conf;
601
}
602

    
603
static int fmod_read (SWVoiceIn *sw, void *buf, int size)
604
{
605
    return audio_pcm_sw_read (sw, buf, size);
606
}
607

    
608
static int fmod_ctl_in (HWVoiceIn *hw, int cmd, ...)
609
{
610
    int status;
611
    FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
612

    
613
    switch (cmd) {
614
    case VOICE_ENABLE:
615
        status = FSOUND_Record_StartSample (fmd->fmod_sample, 1);
616
        if (!status) {
617
            fmod_logerr ("Failed to start recording\n");
618
        }
619
        break;
620

    
621
    case VOICE_DISABLE:
622
        status = FSOUND_Record_Stop ();
623
        if (!status) {
624
            fmod_logerr ("Failed to stop recording\n");
625
        }
626
        break;
627
    }
628
    return 0;
629
}
630

    
631
static void fmod_audio_fini (void *opaque)
632
{
633
    (void) opaque;
634
    FSOUND_Close ();
635
}
636

    
637
static struct audio_option fmod_options[] = {
638
    {
639
        .name  = "DRV",
640
        .tag   = AUD_OPT_STR,
641
        .valp  = &conf.drvname,
642
        .descr = "FMOD driver"
643
    },
644
    {
645
        .name  = "FREQ",
646
        .tag   = AUD_OPT_INT,
647
        .valp  = &conf.freq,
648
        .descr = "Default frequency"
649
    },
650
    {
651
        .name  = "SAMPLES",
652
        .tag   = AUD_OPT_INT,
653
        .valp  = &conf.nb_samples,
654
        .descr = "Buffer size in samples"
655
    },
656
    {
657
        .name  = "CHANNELS",
658
        .tag   = AUD_OPT_INT,
659
        .valp  = &conf.nb_channels,
660
        .descr = "Number of default channels (1 - mono, 2 - stereo)"
661
    },
662
    {
663
        .name  = "BUFSIZE",
664
        .tag   = AUD_OPT_INT,
665
        .valp  = &conf.bufsize,
666
        .descr = "(undocumented)"
667
    }
668
#if 0
669
    {
670
        .name  = "THRESHOLD",
671
        .tag   = AUD_OPT_INT,
672
        .valp  = &conf.threshold,
673
        .descr = "(undocumented)"
674
    }
675
#endif
676
    { /* End of list */ }
677
};
678

    
679
static struct audio_pcm_ops fmod_pcm_ops = {
680
    .init_out = fmod_init_out,
681
    .fini_out = fmod_fini_out,
682
    .run_out  = fmod_run_out,
683
    .write    = fmod_write,
684
    .ctl_out  = fmod_ctl_out,
685

    
686
    .init_in  = fmod_init_in,
687
    .fini_in  = fmod_fini_in,
688
    .run_in   = fmod_run_in,
689
    .read     = fmod_read,
690
    .ctl_in   = fmod_ctl_in
691
};
692

    
693
struct audio_driver fmod_audio_driver = {
694
    .name           = "fmod",
695
    .descr          = "FMOD 3.xx http://www.fmod.org",
696
    .options        = fmod_options,
697
    .init           = fmod_audio_init,
698
    .fini           = fmod_audio_fini,
699
    .pcm_ops        = &fmod_pcm_ops,
700
    .can_be_default = 1,
701
    .max_voices_out = INT_MAX,
702
    .max_voices_in  = INT_MAX,
703
    .voice_size_out = sizeof (FMODVoiceOut),
704
    .voice_size_in  = sizeof (FMODVoiceIn)
705
};