mirror of
				https://git.proxmox.com/git/qemu
				synced 2025-10-31 14:49:14 +00:00 
			
		
		
		
	 7372f88dc1
			
		
	
	
		7372f88dc1
		
	
	
	
	
		
			
			git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1133 c046a42c-6fe2-441c-8c8c-71466251a162
		
			
				
	
	
		
			470 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			470 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * QEMU FMOD audio output driver
 | |
|  * 
 | |
|  * Copyright (c) 2004 Vassili Karpov (malc)
 | |
|  * 
 | |
|  * Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
|  * of this software and associated documentation files (the "Software"), to deal
 | |
|  * in the Software without restriction, including without limitation the rights
 | |
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | |
|  * copies of the Software, and to permit persons to whom the Software is
 | |
|  * furnished to do so, subject to the following conditions:
 | |
|  *
 | |
|  * The above copyright notice and this permission notice shall be included in
 | |
|  * all copies or substantial portions of the Software.
 | |
|  *
 | |
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 | |
|  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | |
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | |
|  * THE SOFTWARE.
 | |
|  */
 | |
| #include <fmod.h>
 | |
| #include <fmod_errors.h>
 | |
| #include "vl.h"
 | |
| 
 | |
| #include "audio/audio_int.h"
 | |
| 
 | |
| typedef struct FMODVoice {
 | |
|     HWVoice hw;
 | |
|     unsigned int old_pos;
 | |
|     FSOUND_SAMPLE *fmod_sample;
 | |
|     int channel;
 | |
| } FMODVoice;
 | |
| 
 | |
| #define dolog(...) AUD_log ("fmod", __VA_ARGS__)
 | |
| #ifdef DEBUG
 | |
| #define ldebug(...) dolog (__VA_ARGS__)
 | |
| #else
 | |
| #define ldebug(...)
 | |
| #endif
 | |
| 
 | |
| #define QC_FMOD_DRV "QEMU_FMOD_DRV"
 | |
| #define QC_FMOD_FREQ "QEMU_FMOD_FREQ"
 | |
| #define QC_FMOD_SAMPLES "QEMU_FMOD_SAMPLES"
 | |
| #define QC_FMOD_CHANNELS "QEMU_FMOD_CHANNELS"
 | |
| #define QC_FMOD_BUFSIZE "QEMU_FMOD_BUFSIZE"
 | |
| #define QC_FMOD_THRESHOLD "QEMU_FMOD_THRESHOLD"
 | |
| 
 | |
| static struct {
 | |
|     int nb_samples;
 | |
|     int freq;
 | |
|     int nb_channels;
 | |
|     int bufsize;
 | |
|     int threshold;
 | |
| } conf = {
 | |
|     2048,
 | |
|     44100,
 | |
|     1,
 | |
|     0,
 | |
|     128
 | |
| };
 | |
| 
 | |
| #define errstr() FMOD_ErrorString (FSOUND_GetError ())
 | |
| 
 | |
| static int fmod_hw_write (SWVoice *sw, void *buf, int len)
 | |
| {
 | |
|     return pcm_hw_write (sw, buf, len);
 | |
| }
 | |
| 
 | |
| static void fmod_clear_sample (FMODVoice *fmd)
 | |
| {
 | |
|     HWVoice *hw = &fmd->hw;
 | |
|     int status;
 | |
|     void *p1 = 0, *p2 = 0;
 | |
|     unsigned int len1 = 0, len2 = 0;
 | |
| 
 | |
|     status = FSOUND_Sample_Lock (
 | |
|         fmd->fmod_sample,
 | |
|         0,
 | |
|         hw->samples << hw->shift,
 | |
|         &p1,
 | |
|         &p2,
 | |
|         &len1,
 | |
|         &len2
 | |
|         );
 | |
| 
 | |
|     if (!status) {
 | |
|         dolog ("Failed to lock sample\nReason: %s\n", errstr ());
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if ((len1 & hw->align) || (len2 & hw->align)) {
 | |
|         dolog ("Locking sample returned unaligned length %d, %d\n",
 | |
|                len1, len2);
 | |
|         goto fail;
 | |
|     }
 | |
| 
 | |
|     if (len1 + len2 != hw->samples << hw->shift) {
 | |
|         dolog ("Locking sample returned incomplete length %d, %d\n",
 | |
|                len1 + len2, hw->samples << hw->shift);
 | |
|         goto fail;
 | |
|     }
 | |
|     pcm_hw_clear (hw, p1, hw->samples);
 | |
| 
 | |
|  fail:
 | |
|     status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, len1, len2);
 | |
|     if (!status) {
 | |
|         dolog ("Failed to unlock sample\nReason: %s\n", errstr ());
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int fmod_write_sample (HWVoice *hw, uint8_t *dst, st_sample_t *src,
 | |
|                               int src_size, int src_pos, int dst_len)
 | |
| {
 | |
|     int src_len1 = dst_len, src_len2 = 0, pos = src_pos + dst_len;
 | |
|     st_sample_t *src1 = src + src_pos, *src2 = 0;
 | |
| 
 | |
|     if (src_pos + dst_len > src_size) {
 | |
|         src_len1 = src_size - src_pos;
 | |
|         src2 = src;
 | |
|         src_len2 = dst_len - src_len1;
 | |
|         pos = src_len2;
 | |
|     }
 | |
| 
 | |
|     if (src_len1) {
 | |
|         hw->clip (dst, src1, src_len1);
 | |
|         memset (src1, 0, src_len1 * sizeof (st_sample_t));
 | |
|         advance (dst, src_len1);
 | |
|     }
 | |
| 
 | |
|     if (src_len2) {
 | |
|         hw->clip (dst, src2, src_len2);
 | |
|         memset (src2, 0, src_len2 * sizeof (st_sample_t));
 | |
|     }
 | |
|     return pos;
 | |
| }
 | |
| 
 | |
| static int fmod_unlock_sample (FMODVoice *fmd, void *p1, void *p2,
 | |
|                                unsigned int blen1, unsigned int blen2)
 | |
| {
 | |
|     int status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, blen1, blen2);
 | |
|     if (!status) {
 | |
|         dolog ("Failed to unlock sample\nReason: %s\n", errstr ());
 | |
|         return -1;
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static int fmod_lock_sample (FMODVoice *fmd, int pos, int len,
 | |
|                              void **p1, void **p2,
 | |
|                              unsigned int *blen1, unsigned int *blen2)
 | |
| {
 | |
|     HWVoice *hw = &fmd->hw;
 | |
|     int status;
 | |
| 
 | |
|     status = FSOUND_Sample_Lock (
 | |
|         fmd->fmod_sample,
 | |
|         pos << hw->shift,
 | |
|         len << hw->shift,
 | |
|         p1,
 | |
|         p2,
 | |
|         blen1,
 | |
|         blen2
 | |
|         );
 | |
| 
 | |
|     if (!status) {
 | |
|         dolog ("Failed to lock sample\nReason: %s\n", errstr ());
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     if ((*blen1 & hw->align) || (*blen2 & hw->align)) {
 | |
|         dolog ("Locking sample returned unaligned length %d, %d\n",
 | |
|                *blen1, *blen2);
 | |
|         fmod_unlock_sample (fmd, *p1, *p2, *blen1, *blen2);
 | |
|         return -1;
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static void fmod_hw_run (HWVoice *hw)
 | |
| {
 | |
|     FMODVoice *fmd = (FMODVoice *) hw;
 | |
|     int rpos, live, decr;
 | |
|     void *p1 = 0, *p2 = 0;
 | |
|     unsigned int blen1 = 0, blen2 = 0;
 | |
|     unsigned int len1 = 0, len2 = 0;
 | |
|     int nb_active;
 | |
| 
 | |
|     live = pcm_hw_get_live2 (hw, &nb_active);
 | |
|     if (live <= 0) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (!hw->pending_disable
 | |
|         && nb_active
 | |
|         && conf.threshold
 | |
|         && live <= conf.threshold) {
 | |
|         ldebug ("live=%d nb_active=%d\n", live, nb_active);
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     decr = live;
 | |
| 
 | |
| #if 1
 | |
|     if (fmd->channel >= 0) {
 | |
|         int pos2 = (fmd->old_pos + decr) % hw->samples;
 | |
|         int pos = FSOUND_GetCurrentPosition (fmd->channel);
 | |
| 
 | |
|         if (fmd->old_pos < pos && pos2 >= pos) {
 | |
|             decr = pos - fmd->old_pos - (pos2 == pos) - 1;
 | |
|         }
 | |
|         else if (fmd->old_pos > pos && pos2 >= pos && pos2 < fmd->old_pos) {
 | |
|             decr = (hw->samples - fmd->old_pos) + pos - (pos2 == pos) - 1;
 | |
|         }
 | |
| /*         ldebug ("pos=%d pos2=%d old=%d live=%d decr=%d\n", */
 | |
| /*                 pos, pos2, fmd->old_pos, live, decr); */
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     if (decr <= 0) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     if (fmod_lock_sample (fmd, fmd->old_pos, decr, &p1, &p2, &blen1, &blen2)) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     len1 = blen1 >> hw->shift;
 | |
|     len2 = blen2 >> hw->shift;
 | |
|     ldebug ("%p %p %d %d %d %d\n", p1, p2, len1, len2, blen1, blen2);
 | |
|     decr = len1 + len2;
 | |
|     rpos = hw->rpos;
 | |
| 
 | |
|     if (len1) {
 | |
|         rpos = fmod_write_sample (hw, p1, hw->mix_buf, hw->samples, rpos, len1);
 | |
|     }
 | |
| 
 | |
|     if (len2) {
 | |
|         rpos = fmod_write_sample (hw, p2, hw->mix_buf, hw->samples, rpos, len2);
 | |
|     }
 | |
| 
 | |
|     fmod_unlock_sample (fmd, p1, p2, blen1, blen2);
 | |
| 
 | |
|     pcm_hw_dec_live (hw, decr);
 | |
|     hw->rpos = rpos % hw->samples;
 | |
|     fmd->old_pos = (fmd->old_pos + decr) % hw->samples;
 | |
| }
 | |
| 
 | |
| static int AUD_to_fmodfmt (audfmt_e fmt, int stereo)
 | |
| {
 | |
|     int mode = FSOUND_LOOP_NORMAL;
 | |
| 
 | |
|     switch (fmt) {
 | |
|     case AUD_FMT_S8:
 | |
|         mode |= FSOUND_SIGNED | FSOUND_8BITS;
 | |
|         break;
 | |
| 
 | |
|     case AUD_FMT_U8:
 | |
|         mode |= FSOUND_UNSIGNED | FSOUND_8BITS;
 | |
|         break;
 | |
| 
 | |
|     case AUD_FMT_S16:
 | |
|         mode |= FSOUND_SIGNED | FSOUND_16BITS;
 | |
|         break;
 | |
| 
 | |
|     case AUD_FMT_U16:
 | |
|         mode |= FSOUND_UNSIGNED | FSOUND_16BITS;
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt);
 | |
|         exit (EXIT_FAILURE);
 | |
|     }
 | |
|     mode |= stereo ? FSOUND_STEREO : FSOUND_MONO;
 | |
|     return mode;
 | |
| }
 | |
| 
 | |
| static void fmod_hw_fini (HWVoice *hw)
 | |
| {
 | |
|     FMODVoice *fmd = (FMODVoice *) hw;
 | |
| 
 | |
|     if (fmd->fmod_sample) {
 | |
|         FSOUND_Sample_Free (fmd->fmod_sample);
 | |
|         fmd->fmod_sample = 0;
 | |
| 
 | |
|         if (fmd->channel >= 0) {
 | |
|             FSOUND_StopSound (fmd->channel);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int fmod_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt)
 | |
| {
 | |
|     int bits16, mode, channel;
 | |
|     FMODVoice *fmd = (FMODVoice *) hw;
 | |
| 
 | |
|     mode = AUD_to_fmodfmt (fmt, nchannels == 2 ? 1 : 0);
 | |
|     fmd->fmod_sample = FSOUND_Sample_Alloc (
 | |
|         FSOUND_FREE,            /* index */
 | |
|         conf.nb_samples,        /* length */
 | |
|         mode,                   /* mode */
 | |
|         freq,                   /* freq */
 | |
|         255,                    /* volume */
 | |
|         128,                    /* pan */
 | |
|         255                     /* priority */
 | |
|         );
 | |
| 
 | |
|     if (!fmd->fmod_sample) {
 | |
|         dolog ("Failed to allocate FMOD sample\nReason: %s\n", errstr ());
 | |
|         return -1;
 | |
|     }
 | |
| 
 | |
|     channel = FSOUND_PlaySoundEx (FSOUND_FREE, fmd->fmod_sample, 0, 1);
 | |
|     if (channel < 0) {
 | |
|         dolog ("Failed to start playing sound\nReason: %s\n", errstr ());
 | |
|         FSOUND_Sample_Free (fmd->fmod_sample);
 | |
|         return -1;
 | |
|     }
 | |
|     fmd->channel = channel;
 | |
| 
 | |
|     hw->freq = freq;
 | |
|     hw->fmt = fmt;
 | |
|     hw->nchannels = nchannels;
 | |
|     bits16 = fmt == AUD_FMT_U16 || fmt == AUD_FMT_S16;
 | |
|     hw->bufsize = conf.nb_samples << (nchannels == 2) << bits16;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static int fmod_hw_ctl (HWVoice *hw, int cmd, ...)
 | |
| {
 | |
|     int status;
 | |
|     FMODVoice *fmd = (FMODVoice *) hw;
 | |
| 
 | |
|     switch (cmd) {
 | |
|     case VOICE_ENABLE:
 | |
|         fmod_clear_sample (fmd);
 | |
|         status = FSOUND_SetPaused (fmd->channel, 0);
 | |
|         if (!status) {
 | |
|             dolog ("Failed to resume channel %d\nReason: %s\n",
 | |
|                    fmd->channel, errstr ());
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|     case VOICE_DISABLE:
 | |
|         status = FSOUND_SetPaused (fmd->channel, 1);
 | |
|         if (!status) {
 | |
|             dolog ("Failed to pause channel %d\nReason: %s\n",
 | |
|                    fmd->channel, errstr ());
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static struct {
 | |
|     const char *name;
 | |
|     int type;
 | |
| } drvtab[] = {
 | |
|     {"none", FSOUND_OUTPUT_NOSOUND},
 | |
| #ifdef _WIN32
 | |
|     {"winmm", FSOUND_OUTPUT_WINMM},
 | |
|     {"dsound", FSOUND_OUTPUT_DSOUND},
 | |
|     {"a3d", FSOUND_OUTPUT_A3D},
 | |
|     {"asio", FSOUND_OUTPUT_ASIO},
 | |
| #endif
 | |
| #ifdef __linux__
 | |
|     {"oss", FSOUND_OUTPUT_OSS},
 | |
|     {"alsa", FSOUND_OUTPUT_ALSA},
 | |
|     {"esd", FSOUND_OUTPUT_ESD},
 | |
| #endif
 | |
| #ifdef __APPLE__
 | |
|     {"mac", FSOUND_OUTPUT_MAC},
 | |
| #endif
 | |
| #if 0
 | |
|     {"xbox", FSOUND_OUTPUT_XBOX},
 | |
|     {"ps2", FSOUND_OUTPUT_PS2},
 | |
|     {"gcube", FSOUND_OUTPUT_GC},
 | |
| #endif
 | |
|     {"nort", FSOUND_OUTPUT_NOSOUND_NONREALTIME}
 | |
| };
 | |
| 
 | |
| static void *fmod_audio_init (void)
 | |
| {
 | |
|     int i;
 | |
|     double ver;
 | |
|     int status;
 | |
|     int output_type = -1;
 | |
|     const char *drv = audio_get_conf_str (QC_FMOD_DRV, NULL);
 | |
| 
 | |
|     ver = FSOUND_GetVersion ();
 | |
|     if (ver < FMOD_VERSION) {
 | |
|         dolog ("Wrong FMOD version %f, need at least %f\n", ver, FMOD_VERSION);
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     if (drv) {
 | |
|         int found = 0;
 | |
|         for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) {
 | |
|             if (!strcmp (drv, drvtab[i].name)) {
 | |
|                 output_type = drvtab[i].type;
 | |
|                 found = 1;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         if (!found) {
 | |
|             dolog ("Unknown FMOD output driver `%s'\n", drv);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (output_type != -1) {
 | |
|         status = FSOUND_SetOutput (output_type);
 | |
|         if (!status) {
 | |
|             dolog ("FSOUND_SetOutput(%d) failed\nReason: %s\n",
 | |
|                    output_type, errstr ());
 | |
|             return NULL;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     conf.freq = audio_get_conf_int (QC_FMOD_FREQ, conf.freq);
 | |
|     conf.nb_samples = audio_get_conf_int (QC_FMOD_SAMPLES, conf.nb_samples);
 | |
|     conf.nb_channels =
 | |
|         audio_get_conf_int (QC_FMOD_CHANNELS,
 | |
|                             (audio_state.nb_hw_voices > 1
 | |
|                              ? audio_state.nb_hw_voices
 | |
|                              : conf.nb_channels));
 | |
|     conf.bufsize = audio_get_conf_int (QC_FMOD_BUFSIZE, conf.bufsize);
 | |
|     conf.threshold = audio_get_conf_int (QC_FMOD_THRESHOLD, conf.threshold);
 | |
| 
 | |
|     if (conf.bufsize) {
 | |
|         status = FSOUND_SetBufferSize (conf.bufsize);
 | |
|         if (!status) {
 | |
|             dolog ("FSOUND_SetBufferSize (%d) failed\nReason: %s\n",
 | |
|                    conf.bufsize, errstr ());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     status = FSOUND_Init (conf.freq, conf.nb_channels, 0);
 | |
|     if (!status) {
 | |
|         dolog ("FSOUND_Init failed\nReason: %s\n", errstr ());
 | |
|         return NULL;
 | |
|     }
 | |
| 
 | |
|     return &conf;
 | |
| }
 | |
| 
 | |
| static void fmod_audio_fini (void *opaque)
 | |
| {
 | |
|     FSOUND_Close ();
 | |
| }
 | |
| 
 | |
| struct pcm_ops fmod_pcm_ops = {
 | |
|     fmod_hw_init,
 | |
|     fmod_hw_fini,
 | |
|     fmod_hw_run,
 | |
|     fmod_hw_write,
 | |
|     fmod_hw_ctl
 | |
| };
 | |
| 
 | |
| struct audio_output_driver fmod_output_driver = {
 | |
|     "fmod",
 | |
|     fmod_audio_init,
 | |
|     fmod_audio_fini,
 | |
|     &fmod_pcm_ops,
 | |
|     1,
 | |
|     INT_MAX,
 | |
|     sizeof (FMODVoice)
 | |
| };
 |