xref: /openbmc/qemu/audio/spiceaudio.c (revision 1b111dc1)
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_clock_get_ns(QEMU_CLOCK_VIRTUAL);
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_clock_get_ns(QEMU_CLOCK_VIRTUAL);
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     case VOICE_VOLUME:
206         {
207 #if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2))
208             SWVoiceOut *sw;
209             va_list ap;
210             uint16_t vol[2];
211 
212             va_start (ap, cmd);
213             sw = va_arg (ap, SWVoiceOut *);
214             va_end (ap);
215 
216             vol[0] = sw->vol.l / ((1ULL << 16) + 1);
217             vol[1] = sw->vol.r / ((1ULL << 16) + 1);
218             spice_server_playback_set_volume (&out->sin, 2, vol);
219             spice_server_playback_set_mute (&out->sin, sw->vol.mute);
220 #endif
221             break;
222         }
223     }
224 
225     return 0;
226 }
227 
228 /* record */
229 
230 static int line_in_init (HWVoiceIn *hw, struct audsettings *as)
231 {
232     SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
233     struct audsettings settings;
234 
235     settings.freq       = SPICE_INTERFACE_RECORD_FREQ;
236     settings.nchannels  = SPICE_INTERFACE_RECORD_CHAN;
237     settings.fmt        = AUD_FMT_S16;
238     settings.endianness = AUDIO_HOST_ENDIANNESS;
239 
240     audio_pcm_init_info (&hw->info, &settings);
241     hw->samples = LINE_IN_SAMPLES;
242     in->active = 0;
243 
244     in->sin.base.sif = &record_sif.base;
245     qemu_spice_add_interface (&in->sin.base);
246     return 0;
247 }
248 
249 static void line_in_fini (HWVoiceIn *hw)
250 {
251     SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
252 
253     spice_server_remove_interface (&in->sin.base);
254 }
255 
256 static int line_in_run (HWVoiceIn *hw)
257 {
258     SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
259     int num_samples;
260     int ready;
261     int len[2];
262     uint64_t delta_samp;
263     const uint32_t *samples;
264 
265     if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in (hw))) {
266         return 0;
267     }
268 
269     delta_samp = rate_get_samples (&hw->info, &in->rate);
270     num_samples = audio_MIN (num_samples, delta_samp);
271 
272     ready = spice_server_record_get_samples (&in->sin, in->samples, num_samples);
273     samples = in->samples;
274     if (ready == 0) {
275         static const uint32_t silence[LINE_IN_SAMPLES];
276         samples = silence;
277         ready = LINE_IN_SAMPLES;
278     }
279 
280     num_samples = audio_MIN (ready, num_samples);
281 
282     if (hw->wpos + num_samples > hw->samples) {
283         len[0] = hw->samples - hw->wpos;
284         len[1] = num_samples - len[0];
285     } else {
286         len[0] = num_samples;
287         len[1] = 0;
288     }
289 
290     hw->conv (hw->conv_buf + hw->wpos, samples, len[0]);
291 
292     if (len[1]) {
293         hw->conv (hw->conv_buf, samples + len[0], len[1]);
294     }
295 
296     hw->wpos = (hw->wpos + num_samples) % hw->samples;
297 
298     return num_samples;
299 }
300 
301 static int line_in_read (SWVoiceIn *sw, void *buf, int size)
302 {
303     return audio_pcm_sw_read (sw, buf, size);
304 }
305 
306 static int line_in_ctl (HWVoiceIn *hw, int cmd, ...)
307 {
308     SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
309 
310     switch (cmd) {
311     case VOICE_ENABLE:
312         if (in->active) {
313             break;
314         }
315         in->active = 1;
316         rate_start (&in->rate);
317         spice_server_record_start (&in->sin);
318         break;
319     case VOICE_DISABLE:
320         if (!in->active) {
321             break;
322         }
323         in->active = 0;
324         spice_server_record_stop (&in->sin);
325         break;
326     case VOICE_VOLUME:
327         {
328 #if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2))
329             SWVoiceIn *sw;
330             va_list ap;
331             uint16_t vol[2];
332 
333             va_start (ap, cmd);
334             sw = va_arg (ap, SWVoiceIn *);
335             va_end (ap);
336 
337             vol[0] = sw->vol.l / ((1ULL << 16) + 1);
338             vol[1] = sw->vol.r / ((1ULL << 16) + 1);
339             spice_server_record_set_volume (&in->sin, 2, vol);
340             spice_server_record_set_mute (&in->sin, sw->vol.mute);
341 #endif
342             break;
343         }
344     }
345 
346     return 0;
347 }
348 
349 static struct audio_option audio_options[] = {
350     { /* end of list */ },
351 };
352 
353 static struct audio_pcm_ops audio_callbacks = {
354     .init_out = line_out_init,
355     .fini_out = line_out_fini,
356     .run_out  = line_out_run,
357     .write    = line_out_write,
358     .ctl_out  = line_out_ctl,
359 
360     .init_in  = line_in_init,
361     .fini_in  = line_in_fini,
362     .run_in   = line_in_run,
363     .read     = line_in_read,
364     .ctl_in   = line_in_ctl,
365 };
366 
367 struct audio_driver spice_audio_driver = {
368     .name           = "spice",
369     .descr          = "spice audio driver",
370     .options        = audio_options,
371     .init           = spice_audio_init,
372     .fini           = spice_audio_fini,
373     .pcm_ops        = &audio_callbacks,
374     .max_voices_out = 1,
375     .max_voices_in  = 1,
376     .voice_size_out = sizeof (SpiceVoiceOut),
377     .voice_size_in  = sizeof (SpiceVoiceIn),
378 #if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2))
379     .ctl_caps       = VOICE_VOLUME_CAP
380 #endif
381 };
382 
383 void qemu_spice_audio_init (void)
384 {
385     spice_audio_driver.can_be_default = 1;
386 }
387