xref: /illumos-kvm-cmd/audio/fmodaudio.c (revision 68396ea9)
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 broken_adc;
51 } conf = {
52     .nb_samples  = 2048 * 2,
53     .freq        = 44100,
54     .nb_channels = 2,
55 };
56 
fmod_logerr(const char * fmt,...)57 static void GCC_FMT_ATTR (1, 2) fmod_logerr (const char *fmt, ...)
58 {
59     va_list ap;
60 
61     va_start (ap, fmt);
62     AUD_vlog (AUDIO_CAP, fmt, ap);
63     va_end (ap);
64 
65     AUD_log (AUDIO_CAP, "Reason: %s\n",
66              FMOD_ErrorString (FSOUND_GetError ()));
67 }
68 
fmod_logerr2(const char * typ,const char * fmt,...)69 static void GCC_FMT_ATTR (2, 3) fmod_logerr2 (
70     const char *typ,
71     const char *fmt,
72     ...
73     )
74 {
75     va_list ap;
76 
77     AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ);
78 
79     va_start (ap, fmt);
80     AUD_vlog (AUDIO_CAP, fmt, ap);
81     va_end (ap);
82 
83     AUD_log (AUDIO_CAP, "Reason: %s\n",
84              FMOD_ErrorString (FSOUND_GetError ()));
85 }
86 
fmod_write(SWVoiceOut * sw,void * buf,int len)87 static int fmod_write (SWVoiceOut *sw, void *buf, int len)
88 {
89     return audio_pcm_sw_write (sw, buf, len);
90 }
91 
fmod_clear_sample(FMODVoiceOut * fmd)92 static void fmod_clear_sample (FMODVoiceOut *fmd)
93 {
94     HWVoiceOut *hw = &fmd->hw;
95     int status;
96     void *p1 = 0, *p2 = 0;
97     unsigned int len1 = 0, len2 = 0;
98 
99     status = FSOUND_Sample_Lock (
100         fmd->fmod_sample,
101         0,
102         hw->samples << hw->info.shift,
103         &p1,
104         &p2,
105         &len1,
106         &len2
107         );
108 
109     if (!status) {
110         fmod_logerr ("Failed to lock sample\n");
111         return;
112     }
113 
114     if ((len1 & hw->info.align) || (len2 & hw->info.align)) {
115         dolog ("Lock returned misaligned length %d, %d, alignment %d\n",
116                len1, len2, hw->info.align + 1);
117         goto fail;
118     }
119 
120     if ((len1 + len2) - (hw->samples << hw->info.shift)) {
121         dolog ("Lock returned incomplete length %d, %d\n",
122                len1 + len2, hw->samples << hw->info.shift);
123         goto fail;
124     }
125 
126     audio_pcm_info_clear_buf (&hw->info, p1, hw->samples);
127 
128  fail:
129     status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, len1, len2);
130     if (!status) {
131         fmod_logerr ("Failed to unlock sample\n");
132     }
133 }
134 
fmod_write_sample(HWVoiceOut * hw,uint8_t * dst,int dst_len)135 static void fmod_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len)
136 {
137     int src_len1 = dst_len;
138     int src_len2 = 0;
139     int pos = hw->rpos + dst_len;
140     struct st_sample *src1 = hw->mix_buf + hw->rpos;
141     struct st_sample *src2 = NULL;
142 
143     if (pos > hw->samples) {
144         src_len1 = hw->samples - hw->rpos;
145         src2 = hw->mix_buf;
146         src_len2 = dst_len - src_len1;
147         pos = src_len2;
148     }
149 
150     if (src_len1) {
151         hw->clip (dst, src1, src_len1);
152     }
153 
154     if (src_len2) {
155         dst = advance (dst, src_len1 << hw->info.shift);
156         hw->clip (dst, src2, src_len2);
157     }
158 
159     hw->rpos = pos % hw->samples;
160 }
161 
fmod_unlock_sample(FSOUND_SAMPLE * sample,void * p1,void * p2,unsigned int blen1,unsigned int blen2)162 static int fmod_unlock_sample (FSOUND_SAMPLE *sample, void *p1, void *p2,
163                                unsigned int blen1, unsigned int blen2)
164 {
165     int status = FSOUND_Sample_Unlock (sample, p1, p2, blen1, blen2);
166     if (!status) {
167         fmod_logerr ("Failed to unlock sample\n");
168         return -1;
169     }
170     return 0;
171 }
172 
fmod_lock_sample(FSOUND_SAMPLE * sample,struct audio_pcm_info * info,int pos,int len,void ** p1,void ** p2,unsigned int * blen1,unsigned int * blen2)173 static int fmod_lock_sample (
174     FSOUND_SAMPLE *sample,
175     struct audio_pcm_info *info,
176     int pos,
177     int len,
178     void **p1,
179     void **p2,
180     unsigned int *blen1,
181     unsigned int *blen2
182     )
183 {
184     int status;
185 
186     status = FSOUND_Sample_Lock (
187         sample,
188         pos << info->shift,
189         len << info->shift,
190         p1,
191         p2,
192         blen1,
193         blen2
194         );
195 
196     if (!status) {
197         fmod_logerr ("Failed to lock sample\n");
198         return -1;
199     }
200 
201     if ((*blen1 & info->align) || (*blen2 & info->align)) {
202         dolog ("Lock returned misaligned length %d, %d, alignment %d\n",
203                *blen1, *blen2, info->align + 1);
204 
205         fmod_unlock_sample (sample, *p1, *p2, *blen1, *blen2);
206 
207         *p1 = NULL - 1;
208         *p2 = NULL - 1;
209         *blen1 = ~0U;
210         *blen2 = ~0U;
211         return -1;
212     }
213 
214     if (!*p1 && *blen1) {
215         dolog ("warning: !p1 && blen1=%d\n", *blen1);
216         *blen1 = 0;
217     }
218 
219     if (!p2 && *blen2) {
220         dolog ("warning: !p2 && blen2=%d\n", *blen2);
221         *blen2 = 0;
222     }
223 
224     return 0;
225 }
226 
fmod_run_out(HWVoiceOut * hw,int live)227 static int fmod_run_out (HWVoiceOut *hw, int live)
228 {
229     FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
230     int decr;
231     void *p1 = 0, *p2 = 0;
232     unsigned int blen1 = 0, blen2 = 0;
233     unsigned int len1 = 0, len2 = 0;
234 
235     if (!hw->pending_disable) {
236         return 0;
237     }
238 
239     decr = live;
240 
241     if (fmd->channel >= 0) {
242         int len = decr;
243         int old_pos = fmd->old_pos;
244         int ppos = FSOUND_GetCurrentPosition (fmd->channel);
245 
246         if (ppos == old_pos || !ppos) {
247             return 0;
248         }
249 
250         if ((old_pos < ppos) && ((old_pos + len) > ppos)) {
251             len = ppos - old_pos;
252         }
253         else {
254             if ((old_pos > ppos) && ((old_pos + len) > (ppos + hw->samples))) {
255                 len = hw->samples - old_pos + ppos;
256             }
257         }
258         decr = len;
259 
260         if (audio_bug (AUDIO_FUNC, decr < 0)) {
261             dolog ("decr=%d live=%d ppos=%d old_pos=%d len=%d\n",
262                    decr, live, ppos, old_pos, len);
263             return 0;
264         }
265     }
266 
267 
268     if (!decr) {
269         return 0;
270     }
271 
272     if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info,
273                           fmd->old_pos, decr,
274                           &p1, &p2,
275                           &blen1, &blen2)) {
276         return 0;
277     }
278 
279     len1 = blen1 >> hw->info.shift;
280     len2 = blen2 >> hw->info.shift;
281     ldebug ("%p %p %d %d %d %d\n", p1, p2, len1, len2, blen1, blen2);
282     decr = len1 + len2;
283 
284     if (p1 && len1) {
285         fmod_write_sample (hw, p1, len1);
286     }
287 
288     if (p2 && len2) {
289         fmod_write_sample (hw, p2, len2);
290     }
291 
292     fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2);
293 
294     fmd->old_pos = (fmd->old_pos + decr) % hw->samples;
295     return decr;
296 }
297 
aud_to_fmodfmt(audfmt_e fmt,int stereo)298 static int aud_to_fmodfmt (audfmt_e fmt, int stereo)
299 {
300     int mode = FSOUND_LOOP_NORMAL;
301 
302     switch (fmt) {
303     case AUD_FMT_S8:
304         mode |= FSOUND_SIGNED | FSOUND_8BITS;
305         break;
306 
307     case AUD_FMT_U8:
308         mode |= FSOUND_UNSIGNED | FSOUND_8BITS;
309         break;
310 
311     case AUD_FMT_S16:
312         mode |= FSOUND_SIGNED | FSOUND_16BITS;
313         break;
314 
315     case AUD_FMT_U16:
316         mode |= FSOUND_UNSIGNED | FSOUND_16BITS;
317         break;
318 
319     default:
320         dolog ("Internal logic error: Bad audio format %d\n", fmt);
321 #ifdef DEBUG_FMOD
322         abort ();
323 #endif
324         mode |= FSOUND_8BITS;
325     }
326     mode |= stereo ? FSOUND_STEREO : FSOUND_MONO;
327     return mode;
328 }
329 
fmod_fini_out(HWVoiceOut * hw)330 static void fmod_fini_out (HWVoiceOut *hw)
331 {
332     FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
333 
334     if (fmd->fmod_sample) {
335         FSOUND_Sample_Free (fmd->fmod_sample);
336         fmd->fmod_sample = 0;
337 
338         if (fmd->channel >= 0) {
339             FSOUND_StopSound (fmd->channel);
340         }
341     }
342 }
343 
fmod_init_out(HWVoiceOut * hw,struct audsettings * as)344 static int fmod_init_out (HWVoiceOut *hw, struct audsettings *as)
345 {
346     int bits16, mode, channel;
347     FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
348     struct audsettings obt_as = *as;
349 
350     mode = aud_to_fmodfmt (as->fmt, as->nchannels == 2 ? 1 : 0);
351     fmd->fmod_sample = FSOUND_Sample_Alloc (
352         FSOUND_FREE,            /* index */
353         conf.nb_samples,        /* length */
354         mode,                   /* mode */
355         as->freq,               /* freq */
356         255,                    /* volume */
357         128,                    /* pan */
358         255                     /* priority */
359         );
360 
361     if (!fmd->fmod_sample) {
362         fmod_logerr2 ("DAC", "Failed to allocate FMOD sample\n");
363         return -1;
364     }
365 
366     channel = FSOUND_PlaySoundEx (FSOUND_FREE, fmd->fmod_sample, 0, 1);
367     if (channel < 0) {
368         fmod_logerr2 ("DAC", "Failed to start playing sound\n");
369         FSOUND_Sample_Free (fmd->fmod_sample);
370         return -1;
371     }
372     fmd->channel = channel;
373 
374     /* FMOD always operates on little endian frames? */
375     obt_as.endianness = 0;
376     audio_pcm_init_info (&hw->info, &obt_as);
377     bits16 = (mode & FSOUND_16BITS) != 0;
378     hw->samples = conf.nb_samples;
379     return 0;
380 }
381 
fmod_ctl_out(HWVoiceOut * hw,int cmd,...)382 static int fmod_ctl_out (HWVoiceOut *hw, int cmd, ...)
383 {
384     int status;
385     FMODVoiceOut *fmd = (FMODVoiceOut *) hw;
386 
387     switch (cmd) {
388     case VOICE_ENABLE:
389         fmod_clear_sample (fmd);
390         status = FSOUND_SetPaused (fmd->channel, 0);
391         if (!status) {
392             fmod_logerr ("Failed to resume channel %d\n", fmd->channel);
393         }
394         break;
395 
396     case VOICE_DISABLE:
397         status = FSOUND_SetPaused (fmd->channel, 1);
398         if (!status) {
399             fmod_logerr ("Failed to pause channel %d\n", fmd->channel);
400         }
401         break;
402     }
403     return 0;
404 }
405 
fmod_init_in(HWVoiceIn * hw,struct audsettings * as)406 static int fmod_init_in (HWVoiceIn *hw, struct audsettings *as)
407 {
408     int bits16, mode;
409     FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
410     struct audsettings obt_as = *as;
411 
412     if (conf.broken_adc) {
413         return -1;
414     }
415 
416     mode = aud_to_fmodfmt (as->fmt, as->nchannels == 2 ? 1 : 0);
417     fmd->fmod_sample = FSOUND_Sample_Alloc (
418         FSOUND_FREE,            /* index */
419         conf.nb_samples,        /* length */
420         mode,                   /* mode */
421         as->freq,               /* freq */
422         255,                    /* volume */
423         128,                    /* pan */
424         255                     /* priority */
425         );
426 
427     if (!fmd->fmod_sample) {
428         fmod_logerr2 ("ADC", "Failed to allocate FMOD sample\n");
429         return -1;
430     }
431 
432     /* FMOD always operates on little endian frames? */
433     obt_as.endianness = 0;
434     audio_pcm_init_info (&hw->info, &obt_as);
435     bits16 = (mode & FSOUND_16BITS) != 0;
436     hw->samples = conf.nb_samples;
437     return 0;
438 }
439 
fmod_fini_in(HWVoiceIn * hw)440 static void fmod_fini_in (HWVoiceIn *hw)
441 {
442     FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
443 
444     if (fmd->fmod_sample) {
445         FSOUND_Record_Stop ();
446         FSOUND_Sample_Free (fmd->fmod_sample);
447         fmd->fmod_sample = 0;
448     }
449 }
450 
fmod_run_in(HWVoiceIn * hw)451 static int fmod_run_in (HWVoiceIn *hw)
452 {
453     FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
454     int hwshift = hw->info.shift;
455     int live, dead, new_pos, len;
456     unsigned int blen1 = 0, blen2 = 0;
457     unsigned int len1, len2;
458     unsigned int decr;
459     void *p1, *p2;
460 
461     live = audio_pcm_hw_get_live_in (hw);
462     dead = hw->samples - live;
463     if (!dead) {
464         return 0;
465     }
466 
467     new_pos = FSOUND_Record_GetPosition ();
468     if (new_pos < 0) {
469         fmod_logerr ("Could not get recording position\n");
470         return 0;
471     }
472 
473     len = audio_ring_dist (new_pos,  hw->wpos, hw->samples);
474     if (!len) {
475         return 0;
476     }
477     len = audio_MIN (len, dead);
478 
479     if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info,
480                           hw->wpos, len,
481                           &p1, &p2,
482                           &blen1, &blen2)) {
483         return 0;
484     }
485 
486     len1 = blen1 >> hwshift;
487     len2 = blen2 >> hwshift;
488     decr = len1 + len2;
489 
490     if (p1 && blen1) {
491         hw->conv (hw->conv_buf + hw->wpos, p1, len1);
492     }
493     if (p2 && len2) {
494         hw->conv (hw->conv_buf, p2, len2);
495     }
496 
497     fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2);
498     hw->wpos = (hw->wpos + decr) % hw->samples;
499     return decr;
500 }
501 
502 static struct {
503     const char *name;
504     int type;
505 } drvtab[] = {
506     { .name = "none",   .type = FSOUND_OUTPUT_NOSOUND },
507 #ifdef _WIN32
508     { .name = "winmm",  .type = FSOUND_OUTPUT_WINMM   },
509     { .name = "dsound", .type = FSOUND_OUTPUT_DSOUND  },
510     { .name = "a3d",    .type = FSOUND_OUTPUT_A3D     },
511     { .name = "asio",   .type = FSOUND_OUTPUT_ASIO    },
512 #endif
513 #ifdef __linux__
514     { .name = "oss",    .type = FSOUND_OUTPUT_OSS     },
515     { .name = "alsa",   .type = FSOUND_OUTPUT_ALSA    },
516     { .name = "esd",    .type = FSOUND_OUTPUT_ESD     },
517 #endif
518 #ifdef __APPLE__
519     { .name = "mac",    .type = FSOUND_OUTPUT_MAC     },
520 #endif
521 #if 0
522     { .name = "xbox",   .type = FSOUND_OUTPUT_XBOX    },
523     { .name = "ps2",    .type = FSOUND_OUTPUT_PS2     },
524     { .name = "gcube",  .type = FSOUND_OUTPUT_GC      },
525 #endif
526     { .name = "none-realtime", .type = FSOUND_OUTPUT_NOSOUND_NONREALTIME }
527 };
528 
fmod_audio_init(void)529 static void *fmod_audio_init (void)
530 {
531     size_t i;
532     double ver;
533     int status;
534     int output_type = -1;
535     const char *drv = conf.drvname;
536 
537     ver = FSOUND_GetVersion ();
538     if (ver < FMOD_VERSION) {
539         dolog ("Wrong FMOD version %f, need at least %f\n", ver, FMOD_VERSION);
540         return NULL;
541     }
542 
543 #ifdef __linux__
544     if (ver < 3.75) {
545         dolog ("FMOD before 3.75 has bug preventing ADC from working\n"
546                "ADC will be disabled.\n");
547         conf.broken_adc = 1;
548     }
549 #endif
550 
551     if (drv) {
552         int found = 0;
553         for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
554             if (!strcmp (drv, drvtab[i].name)) {
555                 output_type = drvtab[i].type;
556                 found = 1;
557                 break;
558             }
559         }
560         if (!found) {
561             dolog ("Unknown FMOD driver `%s'\n", drv);
562             dolog ("Valid drivers:\n");
563             for (i = 0; i < ARRAY_SIZE (drvtab); i++) {
564                 dolog ("  %s\n", drvtab[i].name);
565             }
566         }
567     }
568 
569     if (output_type != -1) {
570         status = FSOUND_SetOutput (output_type);
571         if (!status) {
572             fmod_logerr ("FSOUND_SetOutput(%d) failed\n", output_type);
573             return NULL;
574         }
575     }
576 
577     if (conf.bufsize) {
578         status = FSOUND_SetBufferSize (conf.bufsize);
579         if (!status) {
580             fmod_logerr ("FSOUND_SetBufferSize (%d) failed\n", conf.bufsize);
581         }
582     }
583 
584     status = FSOUND_Init (conf.freq, conf.nb_channels, 0);
585     if (!status) {
586         fmod_logerr ("FSOUND_Init failed\n");
587         return NULL;
588     }
589 
590     return &conf;
591 }
592 
fmod_read(SWVoiceIn * sw,void * buf,int size)593 static int fmod_read (SWVoiceIn *sw, void *buf, int size)
594 {
595     return audio_pcm_sw_read (sw, buf, size);
596 }
597 
fmod_ctl_in(HWVoiceIn * hw,int cmd,...)598 static int fmod_ctl_in (HWVoiceIn *hw, int cmd, ...)
599 {
600     int status;
601     FMODVoiceIn *fmd = (FMODVoiceIn *) hw;
602 
603     switch (cmd) {
604     case VOICE_ENABLE:
605         status = FSOUND_Record_StartSample (fmd->fmod_sample, 1);
606         if (!status) {
607             fmod_logerr ("Failed to start recording\n");
608         }
609         break;
610 
611     case VOICE_DISABLE:
612         status = FSOUND_Record_Stop ();
613         if (!status) {
614             fmod_logerr ("Failed to stop recording\n");
615         }
616         break;
617     }
618     return 0;
619 }
620 
fmod_audio_fini(void * opaque)621 static void fmod_audio_fini (void *opaque)
622 {
623     (void) opaque;
624     FSOUND_Close ();
625 }
626 
627 static struct audio_option fmod_options[] = {
628     {
629         .name  = "DRV",
630         .tag   = AUD_OPT_STR,
631         .valp  = &conf.drvname,
632         .descr = "FMOD driver"
633     },
634     {
635         .name  = "FREQ",
636         .tag   = AUD_OPT_INT,
637         .valp  = &conf.freq,
638         .descr = "Default frequency"
639     },
640     {
641         .name  = "SAMPLES",
642         .tag   = AUD_OPT_INT,
643         .valp  = &conf.nb_samples,
644         .descr = "Buffer size in samples"
645     },
646     {
647         .name  = "CHANNELS",
648         .tag   = AUD_OPT_INT,
649         .valp  = &conf.nb_channels,
650         .descr = "Number of default channels (1 - mono, 2 - stereo)"
651     },
652     {
653         .name  = "BUFSIZE",
654         .tag   = AUD_OPT_INT,
655         .valp  = &conf.bufsize,
656         .descr = "(undocumented)"
657     },
658     { /* End of list */ }
659 };
660 
661 static struct audio_pcm_ops fmod_pcm_ops = {
662     .init_out = fmod_init_out,
663     .fini_out = fmod_fini_out,
664     .run_out  = fmod_run_out,
665     .write    = fmod_write,
666     .ctl_out  = fmod_ctl_out,
667 
668     .init_in  = fmod_init_in,
669     .fini_in  = fmod_fini_in,
670     .run_in   = fmod_run_in,
671     .read     = fmod_read,
672     .ctl_in   = fmod_ctl_in
673 };
674 
675 struct audio_driver fmod_audio_driver = {
676     .name           = "fmod",
677     .descr          = "FMOD 3.xx http://www.fmod.org",
678     .options        = fmod_options,
679     .init           = fmod_audio_init,
680     .fini           = fmod_audio_fini,
681     .pcm_ops        = &fmod_pcm_ops,
682     .can_be_default = 1,
683     .max_voices_out = INT_MAX,
684     .max_voices_in  = INT_MAX,
685     .voice_size_out = sizeof (FMODVoiceOut),
686     .voice_size_in  = sizeof (FMODVoiceIn)
687 };
688