root / audio / spiceaudio.c @ 00a152b4
History | View | Annotate | Download (9 kB)
1 |
/*
|
---|---|
2 |
* Copyright (C) 2010 Red Hat, Inc.
|
3 |
*
|
4 |
* maintained by Gerd Hoffmann <kraxel@redhat.com>
|
5 |
*
|
6 |
* This program is free software; you can redistribute it and/or
|
7 |
* modify it under the terms of the GNU General Public License as
|
8 |
* published by the Free Software Foundation; either version 2 or
|
9 |
* (at your option) version 3 of the License.
|
10 |
*
|
11 |
* This program is distributed in the hope that it will be useful,
|
12 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14 |
* GNU General Public License for more details.
|
15 |
*
|
16 |
* You should have received a copy of the GNU General Public License
|
17 |
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
18 |
*/
|
19 |
|
20 |
#include "hw/hw.h" |
21 |
#include "qemu-timer.h" |
22 |
#include "ui/qemu-spice.h" |
23 |
|
24 |
#define AUDIO_CAP "spice" |
25 |
#include "audio.h" |
26 |
#include "audio_int.h" |
27 |
|
28 |
#define LINE_IN_SAMPLES 1024 |
29 |
#define LINE_OUT_SAMPLES 1024 |
30 |
|
31 |
typedef struct SpiceRateCtl { |
32 |
int64_t start_ticks; |
33 |
int64_t bytes_sent; |
34 |
} SpiceRateCtl; |
35 |
|
36 |
typedef struct SpiceVoiceOut { |
37 |
HWVoiceOut hw; |
38 |
SpicePlaybackInstance sin; |
39 |
SpiceRateCtl rate; |
40 |
int active;
|
41 |
uint32_t *frame; |
42 |
uint32_t *fpos; |
43 |
uint32_t fsize; |
44 |
} SpiceVoiceOut; |
45 |
|
46 |
typedef struct SpiceVoiceIn { |
47 |
HWVoiceIn hw; |
48 |
SpiceRecordInstance sin; |
49 |
SpiceRateCtl rate; |
50 |
int active;
|
51 |
uint32_t samples[LINE_IN_SAMPLES]; |
52 |
} SpiceVoiceIn; |
53 |
|
54 |
static const SpicePlaybackInterface playback_sif = { |
55 |
.base.type = SPICE_INTERFACE_PLAYBACK, |
56 |
.base.description = "playback",
|
57 |
.base.major_version = SPICE_INTERFACE_PLAYBACK_MAJOR, |
58 |
.base.minor_version = SPICE_INTERFACE_PLAYBACK_MINOR, |
59 |
}; |
60 |
|
61 |
static const SpiceRecordInterface record_sif = { |
62 |
.base.type = SPICE_INTERFACE_RECORD, |
63 |
.base.description = "record",
|
64 |
.base.major_version = SPICE_INTERFACE_RECORD_MAJOR, |
65 |
.base.minor_version = SPICE_INTERFACE_RECORD_MINOR, |
66 |
}; |
67 |
|
68 |
static void *spice_audio_init (void) |
69 |
{ |
70 |
if (!using_spice) {
|
71 |
return NULL; |
72 |
} |
73 |
return &spice_audio_init;
|
74 |
} |
75 |
|
76 |
static void spice_audio_fini (void *opaque) |
77 |
{ |
78 |
/* nothing */
|
79 |
} |
80 |
|
81 |
static void rate_start (SpiceRateCtl *rate) |
82 |
{ |
83 |
memset (rate, 0, sizeof (*rate)); |
84 |
rate->start_ticks = qemu_get_clock_ns (vm_clock); |
85 |
} |
86 |
|
87 |
static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate) |
88 |
{ |
89 |
int64_t now; |
90 |
int64_t ticks; |
91 |
int64_t bytes; |
92 |
int64_t samples; |
93 |
|
94 |
now = qemu_get_clock_ns (vm_clock); |
95 |
ticks = now - rate->start_ticks; |
96 |
bytes = muldiv64 (ticks, info->bytes_per_second, get_ticks_per_sec ()); |
97 |
samples = (bytes - rate->bytes_sent) >> info->shift; |
98 |
if (samples < 0 || samples > 65536) { |
99 |
fprintf (stderr, "Resetting rate control (%" PRId64 " samples)\n", samples); |
100 |
rate_start (rate); |
101 |
samples = 0;
|
102 |
} |
103 |
rate->bytes_sent += samples << info->shift; |
104 |
return samples;
|
105 |
} |
106 |
|
107 |
/* playback */
|
108 |
|
109 |
static int line_out_init (HWVoiceOut *hw, struct audsettings *as) |
110 |
{ |
111 |
SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); |
112 |
struct audsettings settings;
|
113 |
|
114 |
settings.freq = SPICE_INTERFACE_PLAYBACK_FREQ; |
115 |
settings.nchannels = SPICE_INTERFACE_PLAYBACK_CHAN; |
116 |
settings.fmt = AUD_FMT_S16; |
117 |
settings.endianness = AUDIO_HOST_ENDIANNESS; |
118 |
|
119 |
audio_pcm_init_info (&hw->info, &settings); |
120 |
hw->samples = LINE_OUT_SAMPLES; |
121 |
out->active = 0;
|
122 |
|
123 |
out->sin.base.sif = &playback_sif.base; |
124 |
qemu_spice_add_interface (&out->sin.base); |
125 |
return 0; |
126 |
} |
127 |
|
128 |
static void line_out_fini (HWVoiceOut *hw) |
129 |
{ |
130 |
SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); |
131 |
|
132 |
spice_server_remove_interface (&out->sin.base); |
133 |
} |
134 |
|
135 |
static int line_out_run (HWVoiceOut *hw, int live) |
136 |
{ |
137 |
SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); |
138 |
int rpos, decr;
|
139 |
int samples;
|
140 |
|
141 |
if (!live) {
|
142 |
return 0; |
143 |
} |
144 |
|
145 |
decr = rate_get_samples (&hw->info, &out->rate); |
146 |
decr = audio_MIN (live, decr); |
147 |
|
148 |
samples = decr; |
149 |
rpos = hw->rpos; |
150 |
while (samples) {
|
151 |
int left_till_end_samples = hw->samples - rpos;
|
152 |
int len = audio_MIN (samples, left_till_end_samples);
|
153 |
|
154 |
if (!out->frame) {
|
155 |
spice_server_playback_get_buffer (&out->sin, &out->frame, &out->fsize); |
156 |
out->fpos = out->frame; |
157 |
} |
158 |
if (out->frame) {
|
159 |
len = audio_MIN (len, out->fsize); |
160 |
hw->clip (out->fpos, hw->mix_buf + rpos, len); |
161 |
out->fsize -= len; |
162 |
out->fpos += len; |
163 |
if (out->fsize == 0) { |
164 |
spice_server_playback_put_samples (&out->sin, out->frame); |
165 |
out->frame = out->fpos = NULL;
|
166 |
} |
167 |
} |
168 |
rpos = (rpos + len) % hw->samples; |
169 |
samples -= len; |
170 |
} |
171 |
hw->rpos = rpos; |
172 |
return decr;
|
173 |
} |
174 |
|
175 |
static int line_out_write (SWVoiceOut *sw, void *buf, int len) |
176 |
{ |
177 |
return audio_pcm_sw_write (sw, buf, len);
|
178 |
} |
179 |
|
180 |
static int line_out_ctl (HWVoiceOut *hw, int cmd, ...) |
181 |
{ |
182 |
SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); |
183 |
|
184 |
switch (cmd) {
|
185 |
case VOICE_ENABLE:
|
186 |
if (out->active) {
|
187 |
break;
|
188 |
} |
189 |
out->active = 1;
|
190 |
rate_start (&out->rate); |
191 |
spice_server_playback_start (&out->sin); |
192 |
break;
|
193 |
case VOICE_DISABLE:
|
194 |
if (!out->active) {
|
195 |
break;
|
196 |
} |
197 |
out->active = 0;
|
198 |
if (out->frame) {
|
199 |
memset (out->fpos, 0, out->fsize << 2); |
200 |
spice_server_playback_put_samples (&out->sin, out->frame); |
201 |
out->frame = out->fpos = NULL;
|
202 |
} |
203 |
spice_server_playback_stop (&out->sin); |
204 |
break;
|
205 |
} |
206 |
return 0; |
207 |
} |
208 |
|
209 |
/* record */
|
210 |
|
211 |
static int line_in_init (HWVoiceIn *hw, struct audsettings *as) |
212 |
{ |
213 |
SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); |
214 |
struct audsettings settings;
|
215 |
|
216 |
settings.freq = SPICE_INTERFACE_RECORD_FREQ; |
217 |
settings.nchannels = SPICE_INTERFACE_RECORD_CHAN; |
218 |
settings.fmt = AUD_FMT_S16; |
219 |
settings.endianness = AUDIO_HOST_ENDIANNESS; |
220 |
|
221 |
audio_pcm_init_info (&hw->info, &settings); |
222 |
hw->samples = LINE_IN_SAMPLES; |
223 |
in->active = 0;
|
224 |
|
225 |
in->sin.base.sif = &record_sif.base; |
226 |
qemu_spice_add_interface (&in->sin.base); |
227 |
return 0; |
228 |
} |
229 |
|
230 |
static void line_in_fini (HWVoiceIn *hw) |
231 |
{ |
232 |
SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); |
233 |
|
234 |
spice_server_remove_interface (&in->sin.base); |
235 |
} |
236 |
|
237 |
static int line_in_run (HWVoiceIn *hw) |
238 |
{ |
239 |
SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); |
240 |
int num_samples;
|
241 |
int ready;
|
242 |
int len[2]; |
243 |
uint64_t delta_samp; |
244 |
const uint32_t *samples;
|
245 |
|
246 |
if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in (hw))) {
|
247 |
return 0; |
248 |
} |
249 |
|
250 |
delta_samp = rate_get_samples (&hw->info, &in->rate); |
251 |
num_samples = audio_MIN (num_samples, delta_samp); |
252 |
|
253 |
ready = spice_server_record_get_samples (&in->sin, in->samples, num_samples); |
254 |
samples = in->samples; |
255 |
if (ready == 0) { |
256 |
static const uint32_t silence[LINE_IN_SAMPLES]; |
257 |
samples = silence; |
258 |
ready = LINE_IN_SAMPLES; |
259 |
} |
260 |
|
261 |
num_samples = audio_MIN (ready, num_samples); |
262 |
|
263 |
if (hw->wpos + num_samples > hw->samples) {
|
264 |
len[0] = hw->samples - hw->wpos;
|
265 |
len[1] = num_samples - len[0]; |
266 |
} else {
|
267 |
len[0] = num_samples;
|
268 |
len[1] = 0; |
269 |
} |
270 |
|
271 |
hw->conv (hw->conv_buf + hw->wpos, samples, len[0]);
|
272 |
|
273 |
if (len[1]) { |
274 |
hw->conv (hw->conv_buf, samples + len[0], len[1]); |
275 |
} |
276 |
|
277 |
hw->wpos = (hw->wpos + num_samples) % hw->samples; |
278 |
|
279 |
return num_samples;
|
280 |
} |
281 |
|
282 |
static int line_in_read (SWVoiceIn *sw, void *buf, int size) |
283 |
{ |
284 |
return audio_pcm_sw_read (sw, buf, size);
|
285 |
} |
286 |
|
287 |
static int line_in_ctl (HWVoiceIn *hw, int cmd, ...) |
288 |
{ |
289 |
SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); |
290 |
|
291 |
switch (cmd) {
|
292 |
case VOICE_ENABLE:
|
293 |
if (in->active) {
|
294 |
break;
|
295 |
} |
296 |
in->active = 1;
|
297 |
rate_start (&in->rate); |
298 |
spice_server_record_start (&in->sin); |
299 |
break;
|
300 |
case VOICE_DISABLE:
|
301 |
if (!in->active) {
|
302 |
break;
|
303 |
} |
304 |
in->active = 0;
|
305 |
spice_server_record_stop (&in->sin); |
306 |
break;
|
307 |
} |
308 |
return 0; |
309 |
} |
310 |
|
311 |
static struct audio_option audio_options[] = { |
312 |
{ /* end of list */ },
|
313 |
}; |
314 |
|
315 |
static struct audio_pcm_ops audio_callbacks = { |
316 |
.init_out = line_out_init, |
317 |
.fini_out = line_out_fini, |
318 |
.run_out = line_out_run, |
319 |
.write = line_out_write, |
320 |
.ctl_out = line_out_ctl, |
321 |
|
322 |
.init_in = line_in_init, |
323 |
.fini_in = line_in_fini, |
324 |
.run_in = line_in_run, |
325 |
.read = line_in_read, |
326 |
.ctl_in = line_in_ctl, |
327 |
}; |
328 |
|
329 |
struct audio_driver spice_audio_driver = {
|
330 |
.name = "spice",
|
331 |
.descr = "spice audio driver",
|
332 |
.options = audio_options, |
333 |
.init = spice_audio_init, |
334 |
.fini = spice_audio_fini, |
335 |
.pcm_ops = &audio_callbacks, |
336 |
.max_voices_out = 1,
|
337 |
.max_voices_in = 1,
|
338 |
.voice_size_out = sizeof (SpiceVoiceOut),
|
339 |
.voice_size_in = sizeof (SpiceVoiceIn),
|
340 |
}; |
341 |
|
342 |
void qemu_spice_audio_init (void) |
343 |
{ |
344 |
spice_audio_driver.can_be_default = 1;
|
345 |
} |