1 |
/* |
/* |
2 |
* QEMU FMOD audio output driver |
* QEMU FMOD audio driver |
3 |
* |
* |
4 |
* Copyright (c) 2004 Vassili Karpov (malc) |
* Copyright (c) 2004-2005 Vassili Karpov (malc) |
5 |
* |
* |
6 |
* Permission is hereby granted, free of charge, to any person obtaining a copy |
* 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 |
* of this software and associated documentation files (the "Software"), to deal |
8 |
* in the Software without restriction, including without limitation the rights |
* in the Software without restriction, including without limitation the rights |
25 |
#include <fmod_errors.h> |
#include <fmod_errors.h> |
26 |
#include "vl.h" |
#include "vl.h" |
27 |
|
|
28 |
#include "audio/audio_int.h" |
#define AUDIO_CAP "fmod" |
29 |
|
#include "audio_int.h" |
30 |
|
|
31 |
typedef struct FMODVoice { |
typedef struct FMODVoiceOut { |
32 |
HWVoice hw; |
HWVoiceOut hw; |
33 |
unsigned int old_pos; |
unsigned int old_pos; |
34 |
FSOUND_SAMPLE *fmod_sample; |
FSOUND_SAMPLE *fmod_sample; |
35 |
int channel; |
int channel; |
36 |
} FMODVoice; |
} FMODVoiceOut; |
|
|
|
|
#define dolog(...) AUD_log ("fmod", __VA_ARGS__) |
|
|
#ifdef DEBUG |
|
|
#define ldebug(...) dolog (__VA_ARGS__) |
|
|
#else |
|
|
#define ldebug(...) |
|
|
#endif |
|
37 |
|
|
38 |
#define QC_FMOD_DRV "QEMU_FMOD_DRV" |
typedef struct FMODVoiceIn { |
39 |
#define QC_FMOD_FREQ "QEMU_FMOD_FREQ" |
HWVoiceIn hw; |
40 |
#define QC_FMOD_SAMPLES "QEMU_FMOD_SAMPLES" |
FSOUND_SAMPLE *fmod_sample; |
41 |
#define QC_FMOD_CHANNELS "QEMU_FMOD_CHANNELS" |
} FMODVoiceIn; |
|
#define QC_FMOD_BUFSIZE "QEMU_FMOD_BUFSIZE" |
|
|
#define QC_FMOD_THRESHOLD "QEMU_FMOD_THRESHOLD" |
|
42 |
|
|
43 |
static struct { |
static struct { |
44 |
|
const char *drvname; |
45 |
int nb_samples; |
int nb_samples; |
46 |
int freq; |
int freq; |
47 |
int nb_channels; |
int nb_channels; |
48 |
int bufsize; |
int bufsize; |
49 |
int threshold; |
int threshold; |
50 |
|
int broken_adc; |
51 |
} conf = { |
} conf = { |
52 |
2048, |
NULL, |
53 |
|
2048 * 2, |
54 |
44100, |
44100, |
55 |
1, |
2, |
56 |
|
0, |
57 |
0, |
0, |
58 |
128 |
0 |
59 |
}; |
}; |
60 |
|
|
61 |
#define errstr() FMOD_ErrorString (FSOUND_GetError ()) |
static void GCC_FMT_ATTR (1, 2) fmod_logerr (const char *fmt, ...) |
62 |
|
{ |
63 |
|
va_list ap; |
64 |
|
|
65 |
|
va_start (ap, fmt); |
66 |
|
AUD_vlog (AUDIO_CAP, fmt, ap); |
67 |
|
va_end (ap); |
68 |
|
|
69 |
static int fmod_hw_write (SWVoice *sw, void *buf, int len) |
AUD_log (AUDIO_CAP, "Reason: %s\n", |
70 |
|
FMOD_ErrorString (FSOUND_GetError ())); |
71 |
|
} |
72 |
|
|
73 |
|
static void GCC_FMT_ATTR (2, 3) fmod_logerr2 ( |
74 |
|
const char *typ, |
75 |
|
const char *fmt, |
76 |
|
... |
77 |
|
) |
78 |
{ |
{ |
79 |
return pcm_hw_write (sw, buf, len); |
va_list ap; |
80 |
|
|
81 |
|
AUD_log (AUDIO_CAP, "Can not initialize %s\n", typ); |
82 |
|
|
83 |
|
va_start (ap, fmt); |
84 |
|
AUD_vlog (AUDIO_CAP, fmt, ap); |
85 |
|
va_end (ap); |
86 |
|
|
87 |
|
AUD_log (AUDIO_CAP, "Reason: %s\n", |
88 |
|
FMOD_ErrorString (FSOUND_GetError ())); |
89 |
} |
} |
90 |
|
|
91 |
static void fmod_clear_sample (FMODVoice *fmd) |
static int fmod_write (SWVoiceOut *sw, void *buf, int len) |
92 |
{ |
{ |
93 |
HWVoice *hw = &fmd->hw; |
return audio_pcm_sw_write (sw, buf, len); |
94 |
|
} |
95 |
|
|
96 |
|
static void fmod_clear_sample (FMODVoiceOut *fmd) |
97 |
|
{ |
98 |
|
HWVoiceOut *hw = &fmd->hw; |
99 |
int status; |
int status; |
100 |
void *p1 = 0, *p2 = 0; |
void *p1 = 0, *p2 = 0; |
101 |
unsigned int len1 = 0, len2 = 0; |
unsigned int len1 = 0, len2 = 0; |
103 |
status = FSOUND_Sample_Lock ( |
status = FSOUND_Sample_Lock ( |
104 |
fmd->fmod_sample, |
fmd->fmod_sample, |
105 |
0, |
0, |
106 |
hw->samples << hw->shift, |
hw->samples << hw->info.shift, |
107 |
&p1, |
&p1, |
108 |
&p2, |
&p2, |
109 |
&len1, |
&len1, |
111 |
); |
); |
112 |
|
|
113 |
if (!status) { |
if (!status) { |
114 |
dolog ("Failed to lock sample\nReason: %s\n", errstr ()); |
fmod_logerr ("Failed to lock sample\n"); |
115 |
return; |
return; |
116 |
} |
} |
117 |
|
|
118 |
if ((len1 & hw->align) || (len2 & hw->align)) { |
if ((len1 & hw->info.align) || (len2 & hw->info.align)) { |
119 |
dolog ("Locking sample returned unaligned length %d, %d\n", |
dolog ("Lock returned misaligned length %d, %d, alignment %d\n", |
120 |
len1, len2); |
len1, len2, hw->info.align + 1); |
121 |
goto fail; |
goto fail; |
122 |
} |
} |
123 |
|
|
124 |
if (len1 + len2 != hw->samples << hw->shift) { |
if ((len1 + len2) - (hw->samples << hw->info.shift)) { |
125 |
dolog ("Locking sample returned incomplete length %d, %d\n", |
dolog ("Lock returned incomplete length %d, %d\n", |
126 |
len1 + len2, hw->samples << hw->shift); |
len1 + len2, hw->samples << hw->info.shift); |
127 |
goto fail; |
goto fail; |
128 |
} |
} |
129 |
pcm_hw_clear (hw, p1, hw->samples); |
|
130 |
|
audio_pcm_info_clear_buf (&hw->info, p1, hw->samples); |
131 |
|
|
132 |
fail: |
fail: |
133 |
status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, len1, len2); |
status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, len1, len2); |
134 |
if (!status) { |
if (!status) { |
135 |
dolog ("Failed to unlock sample\nReason: %s\n", errstr ()); |
fmod_logerr ("Failed to unlock sample\n"); |
136 |
} |
} |
137 |
} |
} |
138 |
|
|
139 |
static int fmod_write_sample (HWVoice *hw, uint8_t *dst, st_sample_t *src, |
static void fmod_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len) |
|
int src_size, int src_pos, int dst_len) |
|
140 |
{ |
{ |
141 |
int src_len1 = dst_len, src_len2 = 0, pos = src_pos + dst_len; |
int src_len1 = dst_len; |
142 |
st_sample_t *src1 = src + src_pos, *src2 = 0; |
int src_len2 = 0; |
143 |
|
int pos = hw->rpos + dst_len; |
144 |
|
st_sample_t *src1 = hw->mix_buf + hw->rpos; |
145 |
|
st_sample_t *src2 = NULL; |
146 |
|
|
147 |
if (src_pos + dst_len > src_size) { |
if (pos > hw->samples) { |
148 |
src_len1 = src_size - src_pos; |
src_len1 = hw->samples - hw->rpos; |
149 |
src2 = src; |
src2 = hw->mix_buf; |
150 |
src_len2 = dst_len - src_len1; |
src_len2 = dst_len - src_len1; |
151 |
pos = src_len2; |
pos = src_len2; |
152 |
} |
} |
153 |
|
|
154 |
if (src_len1) { |
if (src_len1) { |
155 |
hw->clip (dst, src1, src_len1); |
hw->clip (dst, src1, src_len1); |
156 |
memset (src1, 0, src_len1 * sizeof (st_sample_t)); |
mixeng_clear (src1, src_len1); |
|
advance (dst, src_len1); |
|
157 |
} |
} |
158 |
|
|
159 |
if (src_len2) { |
if (src_len2) { |
160 |
|
dst = advance (dst, src_len1 << hw->info.shift); |
161 |
hw->clip (dst, src2, src_len2); |
hw->clip (dst, src2, src_len2); |
162 |
memset (src2, 0, src_len2 * sizeof (st_sample_t)); |
mixeng_clear (src2, src_len2); |
163 |
} |
} |
164 |
return pos; |
|
165 |
|
hw->rpos = pos % hw->samples; |
166 |
} |
} |
167 |
|
|
168 |
static int fmod_unlock_sample (FMODVoice *fmd, void *p1, void *p2, |
static int fmod_unlock_sample (FSOUND_SAMPLE *sample, void *p1, void *p2, |
169 |
unsigned int blen1, unsigned int blen2) |
unsigned int blen1, unsigned int blen2) |
170 |
{ |
{ |
171 |
int status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, blen1, blen2); |
int status = FSOUND_Sample_Unlock (sample, p1, p2, blen1, blen2); |
172 |
if (!status) { |
if (!status) { |
173 |
dolog ("Failed to unlock sample\nReason: %s\n", errstr ()); |
fmod_logerr ("Failed to unlock sample\n"); |
174 |
return -1; |
return -1; |
175 |
} |
} |
176 |
return 0; |
return 0; |
177 |
} |
} |
178 |
|
|
179 |
static int fmod_lock_sample (FMODVoice *fmd, int pos, int len, |
static int fmod_lock_sample ( |
180 |
void **p1, void **p2, |
FSOUND_SAMPLE *sample, |
181 |
unsigned int *blen1, unsigned int *blen2) |
struct audio_pcm_info *info, |
182 |
|
int pos, |
183 |
|
int len, |
184 |
|
void **p1, |
185 |
|
void **p2, |
186 |
|
unsigned int *blen1, |
187 |
|
unsigned int *blen2 |
188 |
|
) |
189 |
{ |
{ |
|
HWVoice *hw = &fmd->hw; |
|
190 |
int status; |
int status; |
191 |
|
|
192 |
status = FSOUND_Sample_Lock ( |
status = FSOUND_Sample_Lock ( |
193 |
fmd->fmod_sample, |
sample, |
194 |
pos << hw->shift, |
pos << info->shift, |
195 |
len << hw->shift, |
len << info->shift, |
196 |
p1, |
p1, |
197 |
p2, |
p2, |
198 |
blen1, |
blen1, |
200 |
); |
); |
201 |
|
|
202 |
if (!status) { |
if (!status) { |
203 |
dolog ("Failed to lock sample\nReason: %s\n", errstr ()); |
fmod_logerr ("Failed to lock sample\n"); |
204 |
return -1; |
return -1; |
205 |
} |
} |
206 |
|
|
207 |
if ((*blen1 & hw->align) || (*blen2 & hw->align)) { |
if ((*blen1 & info->align) || (*blen2 & info->align)) { |
208 |
dolog ("Locking sample returned unaligned length %d, %d\n", |
dolog ("Lock returned misaligned length %d, %d, alignment %d\n", |
209 |
*blen1, *blen2); |
*blen1, *blen2, info->align + 1); |
210 |
fmod_unlock_sample (fmd, *p1, *p2, *blen1, *blen2); |
|
211 |
|
fmod_unlock_sample (sample, *p1, *p2, *blen1, *blen2); |
212 |
|
|
213 |
|
*p1 = NULL - 1; |
214 |
|
*p2 = NULL - 1; |
215 |
|
*blen1 = ~0U; |
216 |
|
*blen2 = ~0U; |
217 |
return -1; |
return -1; |
218 |
} |
} |
219 |
|
|
220 |
|
if (!*p1 && *blen1) { |
221 |
|
dolog ("warning: !p1 && blen1=%d\n", *blen1); |
222 |
|
*blen1 = 0; |
223 |
|
} |
224 |
|
|
225 |
|
if (!p2 && *blen2) { |
226 |
|
dolog ("warning: !p2 && blen2=%d\n", *blen2); |
227 |
|
*blen2 = 0; |
228 |
|
} |
229 |
|
|
230 |
return 0; |
return 0; |
231 |
} |
} |
232 |
|
|
233 |
static void fmod_hw_run (HWVoice *hw) |
static int fmod_run_out (HWVoiceOut *hw) |
234 |
{ |
{ |
235 |
FMODVoice *fmd = (FMODVoice *) hw; |
FMODVoiceOut *fmd = (FMODVoiceOut *) hw; |
236 |
int rpos, live, decr; |
int live, decr; |
237 |
void *p1 = 0, *p2 = 0; |
void *p1 = 0, *p2 = 0; |
238 |
unsigned int blen1 = 0, blen2 = 0; |
unsigned int blen1 = 0, blen2 = 0; |
239 |
unsigned int len1 = 0, len2 = 0; |
unsigned int len1 = 0, len2 = 0; |
240 |
int nb_active; |
int nb_live; |
241 |
|
|
242 |
live = pcm_hw_get_live2 (hw, &nb_active); |
live = audio_pcm_hw_get_live_out2 (hw, &nb_live); |
243 |
if (live <= 0) { |
if (!live) { |
244 |
return; |
return 0; |
245 |
} |
} |
246 |
|
|
247 |
if (!hw->pending_disable |
if (!hw->pending_disable |
248 |
&& nb_active |
&& nb_live |
249 |
&& conf.threshold |
&& (conf.threshold && live <= conf.threshold)) { |
250 |
&& live <= conf.threshold) { |
ldebug ("live=%d nb_live=%d\n", live, nb_live); |
251 |
ldebug ("live=%d nb_active=%d\n", live, nb_active); |
return 0; |
|
return; |
|
252 |
} |
} |
253 |
|
|
254 |
decr = live; |
decr = live; |
255 |
|
|
|
#if 1 |
|
256 |
if (fmd->channel >= 0) { |
if (fmd->channel >= 0) { |
257 |
int pos2 = (fmd->old_pos + decr) % hw->samples; |
int len = decr; |
258 |
int pos = FSOUND_GetCurrentPosition (fmd->channel); |
int old_pos = fmd->old_pos; |
259 |
|
int ppos = FSOUND_GetCurrentPosition (fmd->channel); |
260 |
|
|
261 |
if (fmd->old_pos < pos && pos2 >= pos) { |
if (ppos == old_pos || !ppos) { |
262 |
decr = pos - fmd->old_pos - (pos2 == pos) - 1; |
return 0; |
263 |
|
} |
264 |
|
|
265 |
|
if ((old_pos < ppos) && ((old_pos + len) > ppos)) { |
266 |
|
len = ppos - old_pos; |
267 |
|
} |
268 |
|
else { |
269 |
|
if ((old_pos > ppos) && ((old_pos + len) > (ppos + hw->samples))) { |
270 |
|
len = hw->samples - old_pos + ppos; |
271 |
|
} |
272 |
} |
} |
273 |
else if (fmd->old_pos > pos && pos2 >= pos && pos2 < fmd->old_pos) { |
decr = len; |
274 |
decr = (hw->samples - fmd->old_pos) + pos - (pos2 == pos) - 1; |
|
275 |
|
if (audio_bug (AUDIO_FUNC, decr < 0)) { |
276 |
|
dolog ("decr=%d live=%d ppos=%d old_pos=%d len=%d\n", |
277 |
|
decr, live, ppos, old_pos, len); |
278 |
|
return 0; |
279 |
} |
} |
|
/* ldebug ("pos=%d pos2=%d old=%d live=%d decr=%d\n", */ |
|
|
/* pos, pos2, fmd->old_pos, live, decr); */ |
|
280 |
} |
} |
|
#endif |
|
281 |
|
|
282 |
if (decr <= 0) { |
|
283 |
return; |
if (!decr) { |
284 |
|
return 0; |
285 |
} |
} |
286 |
|
|
287 |
if (fmod_lock_sample (fmd, fmd->old_pos, decr, &p1, &p2, &blen1, &blen2)) { |
if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info, |
288 |
return; |
fmd->old_pos, decr, |
289 |
|
&p1, &p2, |
290 |
|
&blen1, &blen2)) { |
291 |
|
return 0; |
292 |
} |
} |
293 |
|
|
294 |
len1 = blen1 >> hw->shift; |
len1 = blen1 >> hw->info.shift; |
295 |
len2 = blen2 >> hw->shift; |
len2 = blen2 >> hw->info.shift; |
296 |
ldebug ("%p %p %d %d %d %d\n", p1, p2, len1, len2, blen1, blen2); |
ldebug ("%p %p %d %d %d %d\n", p1, p2, len1, len2, blen1, blen2); |
297 |
decr = len1 + len2; |
decr = len1 + len2; |
|
rpos = hw->rpos; |
|
298 |
|
|
299 |
if (len1) { |
if (p1 && len1) { |
300 |
rpos = fmod_write_sample (hw, p1, hw->mix_buf, hw->samples, rpos, len1); |
fmod_write_sample (hw, p1, len1); |
301 |
} |
} |
302 |
|
|
303 |
if (len2) { |
if (p2 && len2) { |
304 |
rpos = fmod_write_sample (hw, p2, hw->mix_buf, hw->samples, rpos, len2); |
fmod_write_sample (hw, p2, len2); |
305 |
} |
} |
306 |
|
|
307 |
fmod_unlock_sample (fmd, p1, p2, blen1, blen2); |
fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2); |
308 |
|
|
|
pcm_hw_dec_live (hw, decr); |
|
|
hw->rpos = rpos % hw->samples; |
|
309 |
fmd->old_pos = (fmd->old_pos + decr) % hw->samples; |
fmd->old_pos = (fmd->old_pos + decr) % hw->samples; |
310 |
|
return decr; |
311 |
} |
} |
312 |
|
|
313 |
static int AUD_to_fmodfmt (audfmt_e fmt, int stereo) |
static int aud_to_fmodfmt (audfmt_e fmt, int stereo) |
314 |
{ |
{ |
315 |
int mode = FSOUND_LOOP_NORMAL; |
int mode = FSOUND_LOOP_NORMAL; |
316 |
|
|
332 |
break; |
break; |
333 |
|
|
334 |
default: |
default: |
335 |
dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt); |
dolog ("Internal logic error: Bad audio format %d\n", fmt); |
336 |
exit (EXIT_FAILURE); |
#ifdef DEBUG_FMOD |
337 |
|
abort (); |
338 |
|
#endif |
339 |
|
mode |= FSOUND_8BITS; |
340 |
} |
} |
341 |
mode |= stereo ? FSOUND_STEREO : FSOUND_MONO; |
mode |= stereo ? FSOUND_STEREO : FSOUND_MONO; |
342 |
return mode; |
return mode; |
343 |
} |
} |
344 |
|
|
345 |
static void fmod_hw_fini (HWVoice *hw) |
static void fmod_fini_out (HWVoiceOut *hw) |
346 |
{ |
{ |
347 |
FMODVoice *fmd = (FMODVoice *) hw; |
FMODVoiceOut *fmd = (FMODVoiceOut *) hw; |
348 |
|
|
349 |
if (fmd->fmod_sample) { |
if (fmd->fmod_sample) { |
350 |
FSOUND_Sample_Free (fmd->fmod_sample); |
FSOUND_Sample_Free (fmd->fmod_sample); |
356 |
} |
} |
357 |
} |
} |
358 |
|
|
359 |
static int fmod_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) |
static int fmod_init_out (HWVoiceOut *hw, int freq, int nchannels, audfmt_e fmt) |
360 |
{ |
{ |
361 |
int bits16, mode, channel; |
int bits16, mode, channel; |
362 |
FMODVoice *fmd = (FMODVoice *) hw; |
FMODVoiceOut *fmd = (FMODVoiceOut *) hw; |
363 |
|
|
364 |
mode = AUD_to_fmodfmt (fmt, nchannels == 2 ? 1 : 0); |
mode = aud_to_fmodfmt (fmt, nchannels == 2 ? 1 : 0); |
365 |
fmd->fmod_sample = FSOUND_Sample_Alloc ( |
fmd->fmod_sample = FSOUND_Sample_Alloc ( |
366 |
FSOUND_FREE, /* index */ |
FSOUND_FREE, /* index */ |
367 |
conf.nb_samples, /* length */ |
conf.nb_samples, /* length */ |
373 |
); |
); |
374 |
|
|
375 |
if (!fmd->fmod_sample) { |
if (!fmd->fmod_sample) { |
376 |
dolog ("Failed to allocate FMOD sample\nReason: %s\n", errstr ()); |
fmod_logerr2 ("DAC", "Failed to allocate FMOD sample\n"); |
377 |
return -1; |
return -1; |
378 |
} |
} |
379 |
|
|
380 |
channel = FSOUND_PlaySoundEx (FSOUND_FREE, fmd->fmod_sample, 0, 1); |
channel = FSOUND_PlaySoundEx (FSOUND_FREE, fmd->fmod_sample, 0, 1); |
381 |
if (channel < 0) { |
if (channel < 0) { |
382 |
dolog ("Failed to start playing sound\nReason: %s\n", errstr ()); |
fmod_logerr2 ("DAC", "Failed to start playing sound\n"); |
383 |
FSOUND_Sample_Free (fmd->fmod_sample); |
FSOUND_Sample_Free (fmd->fmod_sample); |
384 |
return -1; |
return -1; |
385 |
} |
} |
386 |
fmd->channel = channel; |
fmd->channel = channel; |
387 |
|
|
388 |
hw->freq = freq; |
/* FMOD always operates on little endian frames? */ |
389 |
hw->fmt = fmt; |
audio_pcm_init_info (&hw->info, freq, nchannels, fmt, |
390 |
hw->nchannels = nchannels; |
audio_need_to_swap_endian (0)); |
391 |
bits16 = fmt == AUD_FMT_U16 || fmt == AUD_FMT_S16; |
bits16 = (mode & FSOUND_16BITS) != 0; |
392 |
hw->bufsize = conf.nb_samples << (nchannels == 2) << bits16; |
hw->bufsize = conf.nb_samples << (nchannels == 2) << bits16; |
393 |
return 0; |
return 0; |
394 |
} |
} |
395 |
|
|
396 |
static int fmod_hw_ctl (HWVoice *hw, int cmd, ...) |
static int fmod_ctl_out (HWVoiceOut *hw, int cmd, ...) |
397 |
{ |
{ |
398 |
int status; |
int status; |
399 |
FMODVoice *fmd = (FMODVoice *) hw; |
FMODVoiceOut *fmd = (FMODVoiceOut *) hw; |
400 |
|
|
401 |
switch (cmd) { |
switch (cmd) { |
402 |
case VOICE_ENABLE: |
case VOICE_ENABLE: |
403 |
fmod_clear_sample (fmd); |
fmod_clear_sample (fmd); |
404 |
status = FSOUND_SetPaused (fmd->channel, 0); |
status = FSOUND_SetPaused (fmd->channel, 0); |
405 |
if (!status) { |
if (!status) { |
406 |
dolog ("Failed to resume channel %d\nReason: %s\n", |
fmod_logerr ("Failed to resume channel %d\n", fmd->channel); |
|
fmd->channel, errstr ()); |
|
407 |
} |
} |
408 |
break; |
break; |
409 |
|
|
410 |
case VOICE_DISABLE: |
case VOICE_DISABLE: |
411 |
status = FSOUND_SetPaused (fmd->channel, 1); |
status = FSOUND_SetPaused (fmd->channel, 1); |
412 |
if (!status) { |
if (!status) { |
413 |
dolog ("Failed to pause channel %d\nReason: %s\n", |
fmod_logerr ("Failed to pause channel %d\n", fmd->channel); |
|
fmd->channel, errstr ()); |
|
414 |
} |
} |
415 |
break; |
break; |
416 |
} |
} |
417 |
return 0; |
return 0; |
418 |
} |
} |
419 |
|
|
420 |
|
static int fmod_init_in (HWVoiceIn *hw, int freq, int nchannels, audfmt_e fmt) |
421 |
|
{ |
422 |
|
int bits16, mode; |
423 |
|
FMODVoiceIn *fmd = (FMODVoiceIn *) hw; |
424 |
|
|
425 |
|
if (conf.broken_adc) { |
426 |
|
return -1; |
427 |
|
} |
428 |
|
|
429 |
|
mode = aud_to_fmodfmt (fmt, nchannels == 2 ? 1 : 0); |
430 |
|
fmd->fmod_sample = FSOUND_Sample_Alloc ( |
431 |
|
FSOUND_FREE, /* index */ |
432 |
|
conf.nb_samples, /* length */ |
433 |
|
mode, /* mode */ |
434 |
|
freq, /* freq */ |
435 |
|
255, /* volume */ |
436 |
|
128, /* pan */ |
437 |
|
255 /* priority */ |
438 |
|
); |
439 |
|
|
440 |
|
if (!fmd->fmod_sample) { |
441 |
|
fmod_logerr2 ("ADC", "Failed to allocate FMOD sample\n"); |
442 |
|
return -1; |
443 |
|
} |
444 |
|
|
445 |
|
/* FMOD always operates on little endian frames? */ |
446 |
|
audio_pcm_init_info (&hw->info, freq, nchannels, fmt, |
447 |
|
audio_need_to_swap_endian (0)); |
448 |
|
bits16 = (mode & FSOUND_16BITS) != 0; |
449 |
|
hw->bufsize = conf.nb_samples << (nchannels == 2) << bits16; |
450 |
|
return 0; |
451 |
|
} |
452 |
|
|
453 |
|
static void fmod_fini_in (HWVoiceIn *hw) |
454 |
|
{ |
455 |
|
FMODVoiceIn *fmd = (FMODVoiceIn *) hw; |
456 |
|
|
457 |
|
if (fmd->fmod_sample) { |
458 |
|
FSOUND_Record_Stop (); |
459 |
|
FSOUND_Sample_Free (fmd->fmod_sample); |
460 |
|
fmd->fmod_sample = 0; |
461 |
|
} |
462 |
|
} |
463 |
|
|
464 |
|
static int fmod_run_in (HWVoiceIn *hw) |
465 |
|
{ |
466 |
|
FMODVoiceIn *fmd = (FMODVoiceIn *) hw; |
467 |
|
int hwshift = hw->info.shift; |
468 |
|
int live, dead, new_pos, len; |
469 |
|
unsigned int blen1 = 0, blen2 = 0; |
470 |
|
unsigned int len1, len2; |
471 |
|
unsigned int decr; |
472 |
|
void *p1, *p2; |
473 |
|
|
474 |
|
live = audio_pcm_hw_get_live_in (hw); |
475 |
|
dead = hw->samples - live; |
476 |
|
if (!dead) { |
477 |
|
return 0; |
478 |
|
} |
479 |
|
|
480 |
|
new_pos = FSOUND_Record_GetPosition (); |
481 |
|
if (new_pos < 0) { |
482 |
|
fmod_logerr ("Can not get recording position\n"); |
483 |
|
return 0; |
484 |
|
} |
485 |
|
|
486 |
|
len = audio_ring_dist (new_pos, hw->wpos, hw->samples); |
487 |
|
if (!len) { |
488 |
|
return 0; |
489 |
|
} |
490 |
|
len = audio_MIN (len, dead); |
491 |
|
|
492 |
|
if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info, |
493 |
|
hw->wpos, len, |
494 |
|
&p1, &p2, |
495 |
|
&blen1, &blen2)) { |
496 |
|
return 0; |
497 |
|
} |
498 |
|
|
499 |
|
len1 = blen1 >> hwshift; |
500 |
|
len2 = blen2 >> hwshift; |
501 |
|
decr = len1 + len2; |
502 |
|
|
503 |
|
if (p1 && blen1) { |
504 |
|
hw->conv (hw->conv_buf + hw->wpos, p1, len1, &nominal_volume); |
505 |
|
} |
506 |
|
if (p2 && len2) { |
507 |
|
hw->conv (hw->conv_buf, p2, len2, &nominal_volume); |
508 |
|
} |
509 |
|
|
510 |
|
fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2); |
511 |
|
hw->wpos = (hw->wpos + decr) % hw->samples; |
512 |
|
return decr; |
513 |
|
} |
514 |
|
|
515 |
static struct { |
static struct { |
516 |
const char *name; |
const char *name; |
517 |
int type; |
int type; |
536 |
{"ps2", FSOUND_OUTPUT_PS2}, |
{"ps2", FSOUND_OUTPUT_PS2}, |
537 |
{"gcube", FSOUND_OUTPUT_GC}, |
{"gcube", FSOUND_OUTPUT_GC}, |
538 |
#endif |
#endif |
539 |
{"nort", FSOUND_OUTPUT_NOSOUND_NONREALTIME} |
{"none-realtime", FSOUND_OUTPUT_NOSOUND_NONREALTIME} |
540 |
}; |
}; |
541 |
|
|
542 |
static void *fmod_audio_init (void) |
static void *fmod_audio_init (void) |
543 |
{ |
{ |
544 |
int i; |
size_t i; |
545 |
double ver; |
double ver; |
546 |
int status; |
int status; |
547 |
int output_type = -1; |
int output_type = -1; |
548 |
const char *drv = audio_get_conf_str (QC_FMOD_DRV, NULL); |
const char *drv = conf.drvname; |
549 |
|
|
550 |
ver = FSOUND_GetVersion (); |
ver = FSOUND_GetVersion (); |
551 |
if (ver < FMOD_VERSION) { |
if (ver < FMOD_VERSION) { |
553 |
return NULL; |
return NULL; |
554 |
} |
} |
555 |
|
|
556 |
|
#ifdef __linux__ |
557 |
|
if (ver < 3.75) { |
558 |
|
dolog ("FMOD before 3.75 has bug preventing ADC from working\n" |
559 |
|
"ADC will be disabled.\n"); |
560 |
|
conf.broken_adc = 1; |
561 |
|
} |
562 |
|
#endif |
563 |
|
|
564 |
if (drv) { |
if (drv) { |
565 |
int found = 0; |
int found = 0; |
566 |
for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { |
for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { |
571 |
} |
} |
572 |
} |
} |
573 |
if (!found) { |
if (!found) { |
574 |
dolog ("Unknown FMOD output driver `%s'\n", drv); |
dolog ("Unknown FMOD driver `%s'\n", drv); |
575 |
|
dolog ("Valid drivers:\n"); |
576 |
|
for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { |
577 |
|
dolog (" %s\n", drvtab[i].name); |
578 |
|
} |
579 |
} |
} |
580 |
} |
} |
581 |
|
|
582 |
if (output_type != -1) { |
if (output_type != -1) { |
583 |
status = FSOUND_SetOutput (output_type); |
status = FSOUND_SetOutput (output_type); |
584 |
if (!status) { |
if (!status) { |
585 |
dolog ("FSOUND_SetOutput(%d) failed\nReason: %s\n", |
fmod_logerr ("FSOUND_SetOutput(%d) failed\n", output_type); |
|
output_type, errstr ()); |
|
586 |
return NULL; |
return NULL; |
587 |
} |
} |
588 |
} |
} |
589 |
|
|
|
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); |
|
|
|
|
590 |
if (conf.bufsize) { |
if (conf.bufsize) { |
591 |
status = FSOUND_SetBufferSize (conf.bufsize); |
status = FSOUND_SetBufferSize (conf.bufsize); |
592 |
if (!status) { |
if (!status) { |
593 |
dolog ("FSOUND_SetBufferSize (%d) failed\nReason: %s\n", |
fmod_logerr ("FSOUND_SetBufferSize (%d) failed\n", conf.bufsize); |
|
conf.bufsize, errstr ()); |
|
594 |
} |
} |
595 |
} |
} |
596 |
|
|
597 |
status = FSOUND_Init (conf.freq, conf.nb_channels, 0); |
status = FSOUND_Init (conf.freq, conf.nb_channels, 0); |
598 |
if (!status) { |
if (!status) { |
599 |
dolog ("FSOUND_Init failed\nReason: %s\n", errstr ()); |
fmod_logerr ("FSOUND_Init failed\n"); |
600 |
return NULL; |
return NULL; |
601 |
} |
} |
602 |
|
|
603 |
return &conf; |
return &conf; |
604 |
} |
} |
605 |
|
|
606 |
|
static int fmod_read (SWVoiceIn *sw, void *buf, int size) |
607 |
|
{ |
608 |
|
return audio_pcm_sw_read (sw, buf, size); |
609 |
|
} |
610 |
|
|
611 |
|
static int fmod_ctl_in (HWVoiceIn *hw, int cmd, ...) |
612 |
|
{ |
613 |
|
int status; |
614 |
|
FMODVoiceIn *fmd = (FMODVoiceIn *) hw; |
615 |
|
|
616 |
|
switch (cmd) { |
617 |
|
case VOICE_ENABLE: |
618 |
|
status = FSOUND_Record_StartSample (fmd->fmod_sample, 1); |
619 |
|
if (!status) { |
620 |
|
fmod_logerr ("Failed to start recording\n"); |
621 |
|
} |
622 |
|
break; |
623 |
|
|
624 |
|
case VOICE_DISABLE: |
625 |
|
status = FSOUND_Record_Stop (); |
626 |
|
if (!status) { |
627 |
|
fmod_logerr ("Failed to stop recording\n"); |
628 |
|
} |
629 |
|
break; |
630 |
|
} |
631 |
|
return 0; |
632 |
|
} |
633 |
|
|
634 |
static void fmod_audio_fini (void *opaque) |
static void fmod_audio_fini (void *opaque) |
635 |
{ |
{ |
636 |
|
(void) opaque; |
637 |
FSOUND_Close (); |
FSOUND_Close (); |
638 |
} |
} |
639 |
|
|
640 |
struct pcm_ops fmod_pcm_ops = { |
static struct audio_option fmod_options[] = { |
641 |
fmod_hw_init, |
{"DRV", AUD_OPT_STR, &conf.drvname, |
642 |
fmod_hw_fini, |
"FMOD driver", NULL, 0}, |
643 |
fmod_hw_run, |
{"FREQ", AUD_OPT_INT, &conf.freq, |
644 |
fmod_hw_write, |
"Default frequency", NULL, 0}, |
645 |
fmod_hw_ctl |
{"SAMPLES", AUD_OPT_INT, &conf.nb_samples, |
646 |
|
"Buffer size in samples", NULL, 0}, |
647 |
|
{"CHANNELS", AUD_OPT_INT, &conf.nb_channels, |
648 |
|
"Number of default channels (1 - mono, 2 - stereo)", NULL, 0}, |
649 |
|
{"BUFSIZE", AUD_OPT_INT, &conf.bufsize, |
650 |
|
"(undocumented)", NULL, 0}, |
651 |
|
#if 0 |
652 |
|
{"THRESHOLD", AUD_OPT_INT, &conf.threshold, |
653 |
|
"(undocumented)"}, |
654 |
|
#endif |
655 |
|
|
656 |
|
{NULL, 0, NULL, NULL, NULL, 0} |
657 |
|
}; |
658 |
|
|
659 |
|
static struct audio_pcm_ops fmod_pcm_ops = { |
660 |
|
fmod_init_out, |
661 |
|
fmod_fini_out, |
662 |
|
fmod_run_out, |
663 |
|
fmod_write, |
664 |
|
fmod_ctl_out, |
665 |
|
|
666 |
|
fmod_init_in, |
667 |
|
fmod_fini_in, |
668 |
|
fmod_run_in, |
669 |
|
fmod_read, |
670 |
|
fmod_ctl_in |
671 |
}; |
}; |
672 |
|
|
673 |
struct audio_output_driver fmod_output_driver = { |
struct audio_driver fmod_audio_driver = { |
674 |
"fmod", |
INIT_FIELD (name = ) "fmod", |
675 |
fmod_audio_init, |
INIT_FIELD (descr = ) "FMOD 3.xx http://www.fmod.org", |
676 |
fmod_audio_fini, |
INIT_FIELD (options = ) fmod_options, |
677 |
&fmod_pcm_ops, |
INIT_FIELD (init = ) fmod_audio_init, |
678 |
1, |
INIT_FIELD (fini = ) fmod_audio_fini, |
679 |
INT_MAX, |
INIT_FIELD (pcm_ops = ) &fmod_pcm_ops, |
680 |
sizeof (FMODVoice) |
INIT_FIELD (can_be_default = ) 1, |
681 |
|
INIT_FIELD (max_voices_out = ) INT_MAX, |
682 |
|
INIT_FIELD (max_voices_in = ) INT_MAX, |
683 |
|
INIT_FIELD (voice_size_out = ) sizeof (FMODVoiceOut), |
684 |
|
INIT_FIELD (voice_size_in = ) sizeof (FMODVoiceIn) |
685 |
}; |
}; |