|
|
1.1 ! root 1: /* ! 2: * QEMU FMOD audio output driver ! 3: * ! 4: * Copyright (c) 2004 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 "vl.h" ! 27: ! 28: #include "audio/audio_int.h" ! 29: ! 30: typedef struct FMODVoice { ! 31: HWVoice hw; ! 32: unsigned int old_pos; ! 33: FSOUND_SAMPLE *fmod_sample; ! 34: int channel; ! 35: } FMODVoice; ! 36: ! 37: #define dolog(...) AUD_log ("fmod", __VA_ARGS__) ! 38: #ifdef DEBUG ! 39: #define ldebug(...) dolog (__VA_ARGS__) ! 40: #else ! 41: #define ldebug(...) ! 42: #endif ! 43: ! 44: #define QC_FMOD_DRV "QEMU_FMOD_DRV" ! 45: #define QC_FMOD_FREQ "QEMU_FMOD_FREQ" ! 46: #define QC_FMOD_SAMPLES "QEMU_FMOD_SAMPLES" ! 47: #define QC_FMOD_CHANNELS "QEMU_FMOD_CHANNELS" ! 48: #define QC_FMOD_BUFSIZE "QEMU_FMOD_BUFSIZE" ! 49: #define QC_FMOD_THRESHOLD "QEMU_FMOD_THRESHOLD" ! 50: ! 51: static struct { ! 52: int nb_samples; ! 53: int freq; ! 54: int nb_channels; ! 55: int bufsize; ! 56: int threshold; ! 57: } conf = { ! 58: 2048, ! 59: 44100, ! 60: 1, ! 61: 0, ! 62: 128 ! 63: }; ! 64: ! 65: #define errstr() FMOD_ErrorString (FSOUND_GetError ()) ! 66: ! 67: static int fmod_hw_write (SWVoice *sw, void *buf, int len) ! 68: { ! 69: return pcm_hw_write (sw, buf, len); ! 70: } ! 71: ! 72: static void fmod_clear_sample (FMODVoice *fmd) ! 73: { ! 74: HWVoice *hw = &fmd->hw; ! 75: int status; ! 76: void *p1 = 0, *p2 = 0; ! 77: unsigned int len1 = 0, len2 = 0; ! 78: ! 79: status = FSOUND_Sample_Lock ( ! 80: fmd->fmod_sample, ! 81: 0, ! 82: hw->samples << hw->shift, ! 83: &p1, ! 84: &p2, ! 85: &len1, ! 86: &len2 ! 87: ); ! 88: ! 89: if (!status) { ! 90: dolog ("Failed to lock sample\nReason: %s\n", errstr ()); ! 91: return; ! 92: } ! 93: ! 94: if ((len1 & hw->align) || (len2 & hw->align)) { ! 95: dolog ("Locking sample returned unaligned length %d, %d\n", ! 96: len1, len2); ! 97: goto fail; ! 98: } ! 99: ! 100: if (len1 + len2 != hw->samples << hw->shift) { ! 101: dolog ("Locking sample returned incomplete length %d, %d\n", ! 102: len1 + len2, hw->samples << hw->shift); ! 103: goto fail; ! 104: } ! 105: pcm_hw_clear (hw, p1, hw->samples); ! 106: ! 107: fail: ! 108: status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, len1, len2); ! 109: if (!status) { ! 110: dolog ("Failed to unlock sample\nReason: %s\n", errstr ()); ! 111: } ! 112: } ! 113: ! 114: static int fmod_write_sample (HWVoice *hw, uint8_t *dst, st_sample_t *src, ! 115: int src_size, int src_pos, int dst_len) ! 116: { ! 117: int src_len1 = dst_len, src_len2 = 0, pos = src_pos + dst_len; ! 118: st_sample_t *src1 = src + src_pos, *src2 = 0; ! 119: ! 120: if (src_pos + dst_len > src_size) { ! 121: src_len1 = src_size - src_pos; ! 122: src2 = src; ! 123: src_len2 = dst_len - src_len1; ! 124: pos = src_len2; ! 125: } ! 126: ! 127: if (src_len1) { ! 128: hw->clip (dst, src1, src_len1); ! 129: memset (src1, 0, src_len1 * sizeof (st_sample_t)); ! 130: advance (dst, src_len1); ! 131: } ! 132: ! 133: if (src_len2) { ! 134: hw->clip (dst, src2, src_len2); ! 135: memset (src2, 0, src_len2 * sizeof (st_sample_t)); ! 136: } ! 137: return pos; ! 138: } ! 139: ! 140: static int fmod_unlock_sample (FMODVoice *fmd, void *p1, void *p2, ! 141: unsigned int blen1, unsigned int blen2) ! 142: { ! 143: int status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, blen1, blen2); ! 144: if (!status) { ! 145: dolog ("Failed to unlock sample\nReason: %s\n", errstr ()); ! 146: return -1; ! 147: } ! 148: return 0; ! 149: } ! 150: ! 151: static int fmod_lock_sample (FMODVoice *fmd, int pos, int len, ! 152: void **p1, void **p2, ! 153: unsigned int *blen1, unsigned int *blen2) ! 154: { ! 155: HWVoice *hw = &fmd->hw; ! 156: int status; ! 157: ! 158: status = FSOUND_Sample_Lock ( ! 159: fmd->fmod_sample, ! 160: pos << hw->shift, ! 161: len << hw->shift, ! 162: p1, ! 163: p2, ! 164: blen1, ! 165: blen2 ! 166: ); ! 167: ! 168: if (!status) { ! 169: dolog ("Failed to lock sample\nReason: %s\n", errstr ()); ! 170: return -1; ! 171: } ! 172: ! 173: if ((*blen1 & hw->align) || (*blen2 & hw->align)) { ! 174: dolog ("Locking sample returned unaligned length %d, %d\n", ! 175: *blen1, *blen2); ! 176: fmod_unlock_sample (fmd, *p1, *p2, *blen1, *blen2); ! 177: return -1; ! 178: } ! 179: return 0; ! 180: } ! 181: ! 182: static void fmod_hw_run (HWVoice *hw) ! 183: { ! 184: FMODVoice *fmd = (FMODVoice *) hw; ! 185: int rpos, live, decr; ! 186: void *p1 = 0, *p2 = 0; ! 187: unsigned int blen1 = 0, blen2 = 0; ! 188: unsigned int len1 = 0, len2 = 0; ! 189: int nb_active; ! 190: ! 191: live = pcm_hw_get_live2 (hw, &nb_active); ! 192: if (live <= 0) { ! 193: return; ! 194: } ! 195: ! 196: if (!hw->pending_disable ! 197: && nb_active ! 198: && conf.threshold ! 199: && live <= conf.threshold) { ! 200: ldebug ("live=%d nb_active=%d\n", live, nb_active); ! 201: return; ! 202: } ! 203: ! 204: decr = live; ! 205: ! 206: #if 1 ! 207: if (fmd->channel >= 0) { ! 208: int pos2 = (fmd->old_pos + decr) % hw->samples; ! 209: int pos = FSOUND_GetCurrentPosition (fmd->channel); ! 210: ! 211: if (fmd->old_pos < pos && pos2 >= pos) { ! 212: decr = pos - fmd->old_pos - (pos2 == pos) - 1; ! 213: } ! 214: else if (fmd->old_pos > pos && pos2 >= pos && pos2 < fmd->old_pos) { ! 215: decr = (hw->samples - fmd->old_pos) + pos - (pos2 == pos) - 1; ! 216: } ! 217: /* ldebug ("pos=%d pos2=%d old=%d live=%d decr=%d\n", */ ! 218: /* pos, pos2, fmd->old_pos, live, decr); */ ! 219: } ! 220: #endif ! 221: ! 222: if (decr <= 0) { ! 223: return; ! 224: } ! 225: ! 226: if (fmod_lock_sample (fmd, fmd->old_pos, decr, &p1, &p2, &blen1, &blen2)) { ! 227: return; ! 228: } ! 229: ! 230: len1 = blen1 >> hw->shift; ! 231: len2 = blen2 >> hw->shift; ! 232: ldebug ("%p %p %d %d %d %d\n", p1, p2, len1, len2, blen1, blen2); ! 233: decr = len1 + len2; ! 234: rpos = hw->rpos; ! 235: ! 236: if (len1) { ! 237: rpos = fmod_write_sample (hw, p1, hw->mix_buf, hw->samples, rpos, len1); ! 238: } ! 239: ! 240: if (len2) { ! 241: rpos = fmod_write_sample (hw, p2, hw->mix_buf, hw->samples, rpos, len2); ! 242: } ! 243: ! 244: fmod_unlock_sample (fmd, p1, p2, blen1, blen2); ! 245: ! 246: pcm_hw_dec_live (hw, decr); ! 247: hw->rpos = rpos % hw->samples; ! 248: fmd->old_pos = (fmd->old_pos + decr) % hw->samples; ! 249: } ! 250: ! 251: static int AUD_to_fmodfmt (audfmt_e fmt, int stereo) ! 252: { ! 253: int mode = FSOUND_LOOP_NORMAL; ! 254: ! 255: switch (fmt) { ! 256: case AUD_FMT_S8: ! 257: mode |= FSOUND_SIGNED | FSOUND_8BITS; ! 258: break; ! 259: ! 260: case AUD_FMT_U8: ! 261: mode |= FSOUND_UNSIGNED | FSOUND_8BITS; ! 262: break; ! 263: ! 264: case AUD_FMT_S16: ! 265: mode |= FSOUND_SIGNED | FSOUND_16BITS; ! 266: break; ! 267: ! 268: case AUD_FMT_U16: ! 269: mode |= FSOUND_UNSIGNED | FSOUND_16BITS; ! 270: break; ! 271: ! 272: default: ! 273: dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt); ! 274: exit (EXIT_FAILURE); ! 275: } ! 276: mode |= stereo ? FSOUND_STEREO : FSOUND_MONO; ! 277: return mode; ! 278: } ! 279: ! 280: static void fmod_hw_fini (HWVoice *hw) ! 281: { ! 282: FMODVoice *fmd = (FMODVoice *) hw; ! 283: ! 284: if (fmd->fmod_sample) { ! 285: FSOUND_Sample_Free (fmd->fmod_sample); ! 286: fmd->fmod_sample = 0; ! 287: ! 288: if (fmd->channel >= 0) { ! 289: FSOUND_StopSound (fmd->channel); ! 290: } ! 291: } ! 292: } ! 293: ! 294: static int fmod_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) ! 295: { ! 296: int bits16, mode, channel; ! 297: FMODVoice *fmd = (FMODVoice *) hw; ! 298: ! 299: mode = AUD_to_fmodfmt (fmt, nchannels == 2 ? 1 : 0); ! 300: fmd->fmod_sample = FSOUND_Sample_Alloc ( ! 301: FSOUND_FREE, /* index */ ! 302: conf.nb_samples, /* length */ ! 303: mode, /* mode */ ! 304: freq, /* freq */ ! 305: 255, /* volume */ ! 306: 128, /* pan */ ! 307: 255 /* priority */ ! 308: ); ! 309: ! 310: if (!fmd->fmod_sample) { ! 311: dolog ("Failed to allocate FMOD sample\nReason: %s\n", errstr ()); ! 312: return -1; ! 313: } ! 314: ! 315: channel = FSOUND_PlaySoundEx (FSOUND_FREE, fmd->fmod_sample, 0, 1); ! 316: if (channel < 0) { ! 317: dolog ("Failed to start playing sound\nReason: %s\n", errstr ()); ! 318: FSOUND_Sample_Free (fmd->fmod_sample); ! 319: return -1; ! 320: } ! 321: fmd->channel = channel; ! 322: ! 323: hw->freq = freq; ! 324: hw->fmt = fmt; ! 325: hw->nchannels = nchannels; ! 326: bits16 = fmt == AUD_FMT_U16 || fmt == AUD_FMT_S16; ! 327: hw->bufsize = conf.nb_samples << (nchannels == 2) << bits16; ! 328: return 0; ! 329: } ! 330: ! 331: static int fmod_hw_ctl (HWVoice *hw, int cmd, ...) ! 332: { ! 333: int status; ! 334: FMODVoice *fmd = (FMODVoice *) hw; ! 335: ! 336: switch (cmd) { ! 337: case VOICE_ENABLE: ! 338: fmod_clear_sample (fmd); ! 339: status = FSOUND_SetPaused (fmd->channel, 0); ! 340: if (!status) { ! 341: dolog ("Failed to resume channel %d\nReason: %s\n", ! 342: fmd->channel, errstr ()); ! 343: } ! 344: break; ! 345: ! 346: case VOICE_DISABLE: ! 347: status = FSOUND_SetPaused (fmd->channel, 1); ! 348: if (!status) { ! 349: dolog ("Failed to pause channel %d\nReason: %s\n", ! 350: fmd->channel, errstr ()); ! 351: } ! 352: break; ! 353: } ! 354: return 0; ! 355: } ! 356: ! 357: static struct { ! 358: const char *name; ! 359: int type; ! 360: } drvtab[] = { ! 361: {"none", FSOUND_OUTPUT_NOSOUND}, ! 362: #ifdef _WIN32 ! 363: {"winmm", FSOUND_OUTPUT_WINMM}, ! 364: {"dsound", FSOUND_OUTPUT_DSOUND}, ! 365: {"a3d", FSOUND_OUTPUT_A3D}, ! 366: {"asio", FSOUND_OUTPUT_ASIO}, ! 367: #endif ! 368: #ifdef __linux__ ! 369: {"oss", FSOUND_OUTPUT_OSS}, ! 370: {"alsa", FSOUND_OUTPUT_ALSA}, ! 371: {"esd", FSOUND_OUTPUT_ESD}, ! 372: #endif ! 373: #ifdef __APPLE__ ! 374: {"mac", FSOUND_OUTPUT_MAC}, ! 375: #endif ! 376: #if 0 ! 377: {"xbox", FSOUND_OUTPUT_XBOX}, ! 378: {"ps2", FSOUND_OUTPUT_PS2}, ! 379: {"gcube", FSOUND_OUTPUT_GC}, ! 380: #endif ! 381: {"nort", FSOUND_OUTPUT_NOSOUND_NONREALTIME} ! 382: }; ! 383: ! 384: static void *fmod_audio_init (void) ! 385: { ! 386: int i; ! 387: double ver; ! 388: int status; ! 389: int output_type = -1; ! 390: const char *drv = audio_get_conf_str (QC_FMOD_DRV, NULL); ! 391: ! 392: ver = FSOUND_GetVersion (); ! 393: if (ver < FMOD_VERSION) { ! 394: dolog ("Wrong FMOD version %f, need at least %f\n", ver, FMOD_VERSION); ! 395: return NULL; ! 396: } ! 397: ! 398: if (drv) { ! 399: int found = 0; ! 400: for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { ! 401: if (!strcmp (drv, drvtab[i].name)) { ! 402: output_type = drvtab[i].type; ! 403: found = 1; ! 404: break; ! 405: } ! 406: } ! 407: if (!found) { ! 408: dolog ("Unknown FMOD output driver `%s'\n", drv); ! 409: } ! 410: } ! 411: ! 412: if (output_type != -1) { ! 413: status = FSOUND_SetOutput (output_type); ! 414: if (!status) { ! 415: dolog ("FSOUND_SetOutput(%d) failed\nReason: %s\n", ! 416: output_type, errstr ()); ! 417: return NULL; ! 418: } ! 419: } ! 420: ! 421: conf.freq = audio_get_conf_int (QC_FMOD_FREQ, conf.freq); ! 422: conf.nb_samples = audio_get_conf_int (QC_FMOD_SAMPLES, conf.nb_samples); ! 423: conf.nb_channels = ! 424: audio_get_conf_int (QC_FMOD_CHANNELS, ! 425: (audio_state.nb_hw_voices > 1 ! 426: ? audio_state.nb_hw_voices ! 427: : conf.nb_channels)); ! 428: conf.bufsize = audio_get_conf_int (QC_FMOD_BUFSIZE, conf.bufsize); ! 429: conf.threshold = audio_get_conf_int (QC_FMOD_THRESHOLD, conf.threshold); ! 430: ! 431: if (conf.bufsize) { ! 432: status = FSOUND_SetBufferSize (conf.bufsize); ! 433: if (!status) { ! 434: dolog ("FSOUND_SetBufferSize (%d) failed\nReason: %s\n", ! 435: conf.bufsize, errstr ()); ! 436: } ! 437: } ! 438: ! 439: status = FSOUND_Init (conf.freq, conf.nb_channels, 0); ! 440: if (!status) { ! 441: dolog ("FSOUND_Init failed\nReason: %s\n", errstr ()); ! 442: return NULL; ! 443: } ! 444: ! 445: return &conf; ! 446: } ! 447: ! 448: static void fmod_audio_fini (void *opaque) ! 449: { ! 450: FSOUND_Close (); ! 451: } ! 452: ! 453: struct pcm_ops fmod_pcm_ops = { ! 454: fmod_hw_init, ! 455: fmod_hw_fini, ! 456: fmod_hw_run, ! 457: fmod_hw_write, ! 458: fmod_hw_ctl ! 459: }; ! 460: ! 461: struct audio_output_driver fmod_output_driver = { ! 462: "fmod", ! 463: fmod_audio_init, ! 464: fmod_audio_fini, ! 465: &fmod_pcm_ops, ! 466: 1, ! 467: INT_MAX, ! 468: sizeof (FMODVoice) ! 469: };
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.