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 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 853e313753SGerd Hoffmann static void spice_audio_fini (void *opaque) 863e313753SGerd Hoffmann { 873e313753SGerd Hoffmann /* nothing */ 883e313753SGerd Hoffmann } 893e313753SGerd Hoffmann 903e313753SGerd Hoffmann /* playback */ 913e313753SGerd Hoffmann 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 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 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 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 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 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)) 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 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 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 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 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)) 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 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