1cf2c1839SGerd Hoffmann /*
2cf2c1839SGerd Hoffmann * Copyright (C) 2010 Red Hat, Inc.
3cf2c1839SGerd Hoffmann *
4cf2c1839SGerd Hoffmann * maintained by Gerd Hoffmann <kraxel@redhat.com>
5cf2c1839SGerd Hoffmann *
6cf2c1839SGerd Hoffmann * This program is free software; you can redistribute it and/or
7cf2c1839SGerd Hoffmann * modify it under the terms of the GNU General Public License as
8cf2c1839SGerd Hoffmann * published by the Free Software Foundation; either version 2 or
9cf2c1839SGerd Hoffmann * (at your option) version 3 of the License.
10cf2c1839SGerd Hoffmann *
11cf2c1839SGerd Hoffmann * This program is distributed in the hope that it will be useful,
12cf2c1839SGerd Hoffmann * but WITHOUT ANY WARRANTY; without even the implied warranty of
13cf2c1839SGerd Hoffmann * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14cf2c1839SGerd Hoffmann * GNU General Public License for more details.
15cf2c1839SGerd Hoffmann *
16cf2c1839SGerd Hoffmann * You should have received a copy of the GNU General Public License
17cf2c1839SGerd Hoffmann * along with this program; if not, see <http://www.gnu.org/licenses/>.
18cf2c1839SGerd Hoffmann */
19cf2c1839SGerd Hoffmann
206086a565SPeter Maydell #include "qemu/osdep.h"
2187776ab7SPaolo Bonzini #include "qemu/host-utils.h"
220b8fa32fSMarkus Armbruster #include "qemu/module.h"
23d49b6836SMarkus Armbruster #include "qemu/error-report.h"
241de7afc9SPaolo Bonzini #include "qemu/timer.h"
25*f6061733SPaolo Bonzini #include "qapi/error.h"
263e313753SGerd Hoffmann #include "ui/qemu-spice.h"
273e313753SGerd Hoffmann
283e313753SGerd Hoffmann #define AUDIO_CAP "spice"
293e313753SGerd Hoffmann #include "audio.h"
303e313753SGerd Hoffmann #include "audio_int.h"
313e313753SGerd Hoffmann
32795ca114SJeremy White #if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3
33795ca114SJeremy White #define LINE_OUT_SAMPLES (480 * 4)
34795ca114SJeremy White #else
35795ca114SJeremy White #define LINE_OUT_SAMPLES (256 * 4)
36795ca114SJeremy White #endif
37795ca114SJeremy White
38795ca114SJeremy White #if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3
39795ca114SJeremy White #define LINE_IN_SAMPLES (480 * 4)
40795ca114SJeremy White #else
41795ca114SJeremy White #define LINE_IN_SAMPLES (256 * 4)
42795ca114SJeremy White #endif
433e313753SGerd Hoffmann
443e313753SGerd Hoffmann typedef struct SpiceVoiceOut {
453e313753SGerd Hoffmann HWVoiceOut hw;
463e313753SGerd Hoffmann SpicePlaybackInstance sin;
47857271a2SKővágó, Zoltán RateCtl rate;
483e313753SGerd Hoffmann int active;
493e313753SGerd Hoffmann uint32_t *frame;
508c198ff0SKővágó, Zoltán uint32_t fpos;
513e313753SGerd Hoffmann uint32_t fsize;
523e313753SGerd Hoffmann } SpiceVoiceOut;
533e313753SGerd Hoffmann
543e313753SGerd Hoffmann typedef struct SpiceVoiceIn {
553e313753SGerd Hoffmann HWVoiceIn hw;
563e313753SGerd Hoffmann SpiceRecordInstance sin;
57857271a2SKővágó, Zoltán RateCtl rate;
583e313753SGerd Hoffmann int active;
593e313753SGerd Hoffmann } SpiceVoiceIn;
603e313753SGerd Hoffmann
613e313753SGerd Hoffmann static const SpicePlaybackInterface playback_sif = {
623e313753SGerd Hoffmann .base.type = SPICE_INTERFACE_PLAYBACK,
633e313753SGerd Hoffmann .base.description = "playback",
643e313753SGerd Hoffmann .base.major_version = SPICE_INTERFACE_PLAYBACK_MAJOR,
653e313753SGerd Hoffmann .base.minor_version = SPICE_INTERFACE_PLAYBACK_MINOR,
663e313753SGerd Hoffmann };
673e313753SGerd Hoffmann
683e313753SGerd Hoffmann static const SpiceRecordInterface record_sif = {
693e313753SGerd Hoffmann .base.type = SPICE_INTERFACE_RECORD,
703e313753SGerd Hoffmann .base.description = "record",
713e313753SGerd Hoffmann .base.major_version = SPICE_INTERFACE_RECORD_MAJOR,
723e313753SGerd Hoffmann .base.minor_version = SPICE_INTERFACE_RECORD_MINOR,
733e313753SGerd Hoffmann };
743e313753SGerd Hoffmann
spice_audio_init(Audiodev * dev,Error ** errp)75*f6061733SPaolo Bonzini static void *spice_audio_init(Audiodev *dev, Error **errp)
763e313753SGerd Hoffmann {
773e313753SGerd Hoffmann if (!using_spice) {
78*f6061733SPaolo Bonzini error_setg(errp, "Cannot use spice audio without -spice");
793e313753SGerd Hoffmann return NULL;
803e313753SGerd Hoffmann }
81*f6061733SPaolo Bonzini
823e313753SGerd Hoffmann return &spice_audio_init;
833e313753SGerd Hoffmann }
843e313753SGerd Hoffmann
spice_audio_fini(void * opaque)853e313753SGerd Hoffmann static void spice_audio_fini (void *opaque)
863e313753SGerd Hoffmann {
873e313753SGerd Hoffmann /* nothing */
883e313753SGerd Hoffmann }
893e313753SGerd Hoffmann
903e313753SGerd Hoffmann /* playback */
913e313753SGerd Hoffmann
line_out_init(HWVoiceOut * hw,struct audsettings * as,void * drv_opaque)925706db1dSKővágó, Zoltán static int line_out_init(HWVoiceOut *hw, struct audsettings *as,
935706db1dSKővágó, Zoltán void *drv_opaque)
943e313753SGerd Hoffmann {
953e313753SGerd Hoffmann SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
963e313753SGerd Hoffmann struct audsettings settings;
973e313753SGerd Hoffmann
98795ca114SJeremy White #if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3
99795ca114SJeremy White settings.freq = spice_server_get_best_playback_rate(NULL);
100795ca114SJeremy White #else
1013e313753SGerd Hoffmann settings.freq = SPICE_INTERFACE_PLAYBACK_FREQ;
102795ca114SJeremy White #endif
1033e313753SGerd Hoffmann settings.nchannels = SPICE_INTERFACE_PLAYBACK_CHAN;
10485bc5852SKővágó, Zoltán settings.fmt = AUDIO_FORMAT_S16;
1053e313753SGerd Hoffmann settings.endianness = AUDIO_HOST_ENDIANNESS;
1063e313753SGerd Hoffmann
1073e313753SGerd Hoffmann audio_pcm_init_info (&hw->info, &settings);
1083e313753SGerd Hoffmann hw->samples = LINE_OUT_SAMPLES;
1093e313753SGerd Hoffmann out->active = 0;
1103e313753SGerd Hoffmann
1113e313753SGerd Hoffmann out->sin.base.sif = &playback_sif.base;
11205b53636SGerd Hoffmann qemu_spice.add_interface(&out->sin.base);
113795ca114SJeremy White #if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3
114795ca114SJeremy White spice_server_set_playback_rate(&out->sin, settings.freq);
115795ca114SJeremy White #endif
1163e313753SGerd Hoffmann return 0;
1173e313753SGerd Hoffmann }
1183e313753SGerd Hoffmann
line_out_fini(HWVoiceOut * hw)1193e313753SGerd Hoffmann static void line_out_fini (HWVoiceOut *hw)
1203e313753SGerd Hoffmann {
1213e313753SGerd Hoffmann SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
1223e313753SGerd Hoffmann
1233e313753SGerd Hoffmann spice_server_remove_interface (&out->sin.base);
1243e313753SGerd Hoffmann }
1253e313753SGerd Hoffmann
line_out_get_free(HWVoiceOut * hw)12690320051SVolker Rümelin static size_t line_out_get_free(HWVoiceOut *hw)
12790320051SVolker Rümelin {
12890320051SVolker Rümelin SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
12990320051SVolker Rümelin
13090320051SVolker Rümelin return audio_rate_peek_bytes(&out->rate, &hw->info);
13190320051SVolker Rümelin }
13290320051SVolker Rümelin
line_out_get_buffer(HWVoiceOut * hw,size_t * size)1338c198ff0SKővágó, Zoltán static void *line_out_get_buffer(HWVoiceOut *hw, size_t *size)
1343e313753SGerd Hoffmann {
1353e313753SGerd Hoffmann SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
1363e313753SGerd Hoffmann
1373e313753SGerd Hoffmann if (!out->frame) {
1383e313753SGerd Hoffmann spice_server_playback_get_buffer(&out->sin, &out->frame, &out->fsize);
1398c198ff0SKővágó, Zoltán out->fpos = 0;
1403e313753SGerd Hoffmann }
1418c198ff0SKővágó, Zoltán
1423e313753SGerd Hoffmann if (out->frame) {
143aec6d0dcSVolker Rümelin *size = MIN((out->fsize - out->fpos) << 2, *size);
1448c198ff0SKővágó, Zoltán }
145aec6d0dcSVolker Rümelin
1468c198ff0SKővágó, Zoltán return out->frame + out->fpos;
1478c198ff0SKővágó, Zoltán }
1488c198ff0SKővágó, Zoltán
line_out_put_buffer(HWVoiceOut * hw,void * buf,size_t size)1498c198ff0SKővágó, Zoltán static size_t line_out_put_buffer(HWVoiceOut *hw, void *buf, size_t size)
1508c198ff0SKővágó, Zoltán {
1518c198ff0SKővágó, Zoltán SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
1528c198ff0SKővágó, Zoltán
15390320051SVolker Rümelin audio_rate_add_bytes(&out->rate, size);
15490320051SVolker Rümelin
155d4b70fa4SVolker Rümelin if (buf) {
1568c198ff0SKővágó, Zoltán assert(buf == out->frame + out->fpos && out->fpos <= out->fsize);
1578c198ff0SKővágó, Zoltán out->fpos += size >> 2;
1588c198ff0SKővágó, Zoltán
1598c198ff0SKővágó, Zoltán if (out->fpos == out->fsize) { /* buffer full */
1603e313753SGerd Hoffmann spice_server_playback_put_samples(&out->sin, out->frame);
1618c198ff0SKővágó, Zoltán out->frame = NULL;
1623e313753SGerd Hoffmann }
163d4b70fa4SVolker Rümelin }
1648c198ff0SKővágó, Zoltán
1658c198ff0SKővágó, Zoltán return size;
1663e313753SGerd Hoffmann }
1673e313753SGerd Hoffmann
line_out_enable(HWVoiceOut * hw,bool enable)168571a8c52SKővágó, Zoltán static void line_out_enable(HWVoiceOut *hw, bool enable)
1693e313753SGerd Hoffmann {
1703e313753SGerd Hoffmann SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw);
1713e313753SGerd Hoffmann
172571a8c52SKővágó, Zoltán if (enable) {
1733e313753SGerd Hoffmann if (out->active) {
174571a8c52SKővágó, Zoltán return;
1753e313753SGerd Hoffmann }
1763e313753SGerd Hoffmann out->active = 1;
177857271a2SKővágó, Zoltán audio_rate_start(&out->rate);
1783e313753SGerd Hoffmann spice_server_playback_start (&out->sin);
179571a8c52SKővágó, Zoltán } else {
1803e313753SGerd Hoffmann if (!out->active) {
181571a8c52SKővágó, Zoltán return;
1823e313753SGerd Hoffmann }
1833e313753SGerd Hoffmann out->active = 0;
1843e313753SGerd Hoffmann if (out->frame) {
1858c198ff0SKővágó, Zoltán memset(out->frame + out->fpos, 0, (out->fsize - out->fpos) << 2);
1863e313753SGerd Hoffmann spice_server_playback_put_samples (&out->sin, out->frame);
1878c198ff0SKővágó, Zoltán out->frame = NULL;
1883e313753SGerd Hoffmann }
1893e313753SGerd Hoffmann spice_server_playback_stop (&out->sin);
190571a8c52SKővágó, Zoltán }
191571a8c52SKővágó, Zoltán }
192571a8c52SKővágó, Zoltán
193a70c99c6SMarc-André Lureau #if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2))
line_out_volume(HWVoiceOut * hw,Volume * vol)194cecc1e79SKővágó, Zoltán static void line_out_volume(HWVoiceOut *hw, Volume *vol)
195571a8c52SKővágó, Zoltán {
196571a8c52SKővágó, Zoltán SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw);
197571a8c52SKővágó, Zoltán uint16_t svol[2];
198a70c99c6SMarc-André Lureau
199cecc1e79SKővágó, Zoltán assert(vol->channels == 2);
200cecc1e79SKővágó, Zoltán svol[0] = vol->vol[0] * 257;
201cecc1e79SKővágó, Zoltán svol[1] = vol->vol[1] * 257;
202571a8c52SKővágó, Zoltán spice_server_playback_set_volume(&out->sin, 2, svol);
203571a8c52SKővágó, Zoltán spice_server_playback_set_mute(&out->sin, vol->mute);
204571a8c52SKővágó, Zoltán }
205a70c99c6SMarc-André Lureau #endif
2063e313753SGerd Hoffmann
2073e313753SGerd Hoffmann /* record */
2083e313753SGerd Hoffmann
line_in_init(HWVoiceIn * hw,struct audsettings * as,void * drv_opaque)2095706db1dSKővágó, Zoltán static int line_in_init(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque)
2103e313753SGerd Hoffmann {
2113e313753SGerd Hoffmann SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
2123e313753SGerd Hoffmann struct audsettings settings;
2133e313753SGerd Hoffmann
214795ca114SJeremy White #if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3
215795ca114SJeremy White settings.freq = spice_server_get_best_record_rate(NULL);
216795ca114SJeremy White #else
2173e313753SGerd Hoffmann settings.freq = SPICE_INTERFACE_RECORD_FREQ;
218795ca114SJeremy White #endif
2193e313753SGerd Hoffmann settings.nchannels = SPICE_INTERFACE_RECORD_CHAN;
22085bc5852SKővágó, Zoltán settings.fmt = AUDIO_FORMAT_S16;
2213e313753SGerd Hoffmann settings.endianness = AUDIO_HOST_ENDIANNESS;
2223e313753SGerd Hoffmann
2233e313753SGerd Hoffmann audio_pcm_init_info (&hw->info, &settings);
2243e313753SGerd Hoffmann hw->samples = LINE_IN_SAMPLES;
2253e313753SGerd Hoffmann in->active = 0;
2263e313753SGerd Hoffmann
2273e313753SGerd Hoffmann in->sin.base.sif = &record_sif.base;
22805b53636SGerd Hoffmann qemu_spice.add_interface(&in->sin.base);
229795ca114SJeremy White #if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3
230795ca114SJeremy White spice_server_set_record_rate(&in->sin, settings.freq);
231795ca114SJeremy White #endif
2323e313753SGerd Hoffmann return 0;
2333e313753SGerd Hoffmann }
2343e313753SGerd Hoffmann
line_in_fini(HWVoiceIn * hw)2353e313753SGerd Hoffmann static void line_in_fini (HWVoiceIn *hw)
2363e313753SGerd Hoffmann {
2373e313753SGerd Hoffmann SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
2383e313753SGerd Hoffmann
2393e313753SGerd Hoffmann spice_server_remove_interface (&in->sin.base);
2403e313753SGerd Hoffmann }
2413e313753SGerd Hoffmann
line_in_read(HWVoiceIn * hw,void * buf,size_t len)2428c198ff0SKővágó, Zoltán static size_t line_in_read(HWVoiceIn *hw, void *buf, size_t len)
2433e313753SGerd Hoffmann {
2443e313753SGerd Hoffmann SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
245613fe02bSVolker Rümelin uint64_t to_read = audio_rate_get_bytes(&in->rate, &hw->info, len) >> 2;
2468c198ff0SKővágó, Zoltán size_t ready = spice_server_record_get_samples(&in->sin, buf, to_read);
2473e313753SGerd Hoffmann
24870ded68bSVolker Rümelin /*
24970ded68bSVolker Rümelin * If the client didn't send new frames, it most likely disconnected.
25070ded68bSVolker Rümelin * Generate silence in this case to avoid a stalled audio stream.
25170ded68bSVolker Rümelin */
2523e313753SGerd Hoffmann if (ready == 0) {
2538c198ff0SKővágó, Zoltán memset(buf, 0, to_read << 2);
2548c198ff0SKővágó, Zoltán ready = to_read;
2553e313753SGerd Hoffmann }
2563e313753SGerd Hoffmann
2578c198ff0SKővágó, Zoltán return ready << 2;
2583e313753SGerd Hoffmann }
2593e313753SGerd Hoffmann
line_in_enable(HWVoiceIn * hw,bool enable)260571a8c52SKővágó, Zoltán static void line_in_enable(HWVoiceIn *hw, bool enable)
2613e313753SGerd Hoffmann {
2623e313753SGerd Hoffmann SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw);
2633e313753SGerd Hoffmann
264571a8c52SKővágó, Zoltán if (enable) {
2653e313753SGerd Hoffmann if (in->active) {
266571a8c52SKővágó, Zoltán return;
2673e313753SGerd Hoffmann }
2683e313753SGerd Hoffmann in->active = 1;
269857271a2SKővágó, Zoltán audio_rate_start(&in->rate);
2703e313753SGerd Hoffmann spice_server_record_start (&in->sin);
271571a8c52SKővágó, Zoltán } else {
2723e313753SGerd Hoffmann if (!in->active) {
273571a8c52SKővágó, Zoltán return;
2743e313753SGerd Hoffmann }
2753e313753SGerd Hoffmann in->active = 0;
2763e313753SGerd Hoffmann spice_server_record_stop (&in->sin);
277571a8c52SKővágó, Zoltán }
278571a8c52SKővágó, Zoltán }
279571a8c52SKővágó, Zoltán
280a70c99c6SMarc-André Lureau #if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2))
line_in_volume(HWVoiceIn * hw,Volume * vol)281cecc1e79SKővágó, Zoltán static void line_in_volume(HWVoiceIn *hw, Volume *vol)
282571a8c52SKővágó, Zoltán {
283571a8c52SKővágó, Zoltán SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw);
284571a8c52SKővágó, Zoltán uint16_t svol[2];
285a70c99c6SMarc-André Lureau
286cecc1e79SKővágó, Zoltán assert(vol->channels == 2);
287cecc1e79SKővágó, Zoltán svol[0] = vol->vol[0] * 257;
288cecc1e79SKővágó, Zoltán svol[1] = vol->vol[1] * 257;
289571a8c52SKővágó, Zoltán spice_server_record_set_volume(&in->sin, 2, svol);
290571a8c52SKővágó, Zoltán spice_server_record_set_mute(&in->sin, vol->mute);
291571a8c52SKővágó, Zoltán }
292a70c99c6SMarc-André Lureau #endif
2933e313753SGerd Hoffmann
2943e313753SGerd Hoffmann static struct audio_pcm_ops audio_callbacks = {
2953e313753SGerd Hoffmann .init_out = line_out_init,
2963e313753SGerd Hoffmann .fini_out = line_out_fini,
2978c198ff0SKővágó, Zoltán .write = audio_generic_write,
29890320051SVolker Rümelin .buffer_get_free = line_out_get_free,
2998c198ff0SKővágó, Zoltán .get_buffer_out = line_out_get_buffer,
3008c198ff0SKővágó, Zoltán .put_buffer_out = line_out_put_buffer,
301571a8c52SKővágó, Zoltán .enable_out = line_out_enable,
302571a8c52SKővágó, Zoltán #if (SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && \
303571a8c52SKővágó, Zoltán (SPICE_INTERFACE_PLAYBACK_MINOR >= 2)
304571a8c52SKővágó, Zoltán .volume_out = line_out_volume,
305571a8c52SKővágó, Zoltán #endif
3063e313753SGerd Hoffmann
3073e313753SGerd Hoffmann .init_in = line_in_init,
3083e313753SGerd Hoffmann .fini_in = line_in_fini,
3098c198ff0SKővágó, Zoltán .read = line_in_read,
310a2893c83SVolker Rümelin .run_buffer_in = audio_generic_run_buffer_in,
311571a8c52SKővágó, Zoltán .enable_in = line_in_enable,
312571a8c52SKővágó, Zoltán #if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2))
313571a8c52SKővágó, Zoltán .volume_in = line_in_volume,
314571a8c52SKővágó, Zoltán #endif
3153e313753SGerd Hoffmann };
3163e313753SGerd Hoffmann
317d3893a39SGerd Hoffmann static struct audio_driver spice_audio_driver = {
3183e313753SGerd Hoffmann .name = "spice",
3193e313753SGerd Hoffmann .descr = "spice audio driver",
3203e313753SGerd Hoffmann .init = spice_audio_init,
3213e313753SGerd Hoffmann .fini = spice_audio_fini,
3223e313753SGerd Hoffmann .pcm_ops = &audio_callbacks,
3233e313753SGerd Hoffmann .max_voices_out = 1,
3243e313753SGerd Hoffmann .max_voices_in = 1,
3253e313753SGerd Hoffmann .voice_size_out = sizeof (SpiceVoiceOut),
3263e313753SGerd Hoffmann .voice_size_in = sizeof (SpiceVoiceIn),
3273e313753SGerd Hoffmann };
3283e313753SGerd Hoffmann
register_audio_spice(void)329d3893a39SGerd Hoffmann static void register_audio_spice(void)
330d3893a39SGerd Hoffmann {
331d3893a39SGerd Hoffmann audio_driver_register(&spice_audio_driver);
332d3893a39SGerd Hoffmann }
333d3893a39SGerd Hoffmann type_init(register_audio_spice);
334f6b12dfdSGerd Hoffmann
335f6b12dfdSGerd Hoffmann module_dep("ui-spice-core");
336