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 "qemu/osdep.h" 21 #include "qemu/host-utils.h" 22 #include "qemu/module.h" 23 #include "qemu/error-report.h" 24 #include "qemu/timer.h" 25 #include "qapi/error.h" 26 #include "ui/qemu-spice.h" 27 28 #define AUDIO_CAP "spice" 29 #include "audio.h" 30 #include "audio_int.h" 31 32 #if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 33 #define LINE_OUT_SAMPLES (480 * 4) 34 #else 35 #define LINE_OUT_SAMPLES (256 * 4) 36 #endif 37 38 #if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 39 #define LINE_IN_SAMPLES (480 * 4) 40 #else 41 #define LINE_IN_SAMPLES (256 * 4) 42 #endif 43 44 typedef struct SpiceVoiceOut { 45 HWVoiceOut hw; 46 SpicePlaybackInstance sin; 47 RateCtl rate; 48 int active; 49 uint32_t *frame; 50 uint32_t fpos; 51 uint32_t fsize; 52 } SpiceVoiceOut; 53 54 typedef struct SpiceVoiceIn { 55 HWVoiceIn hw; 56 SpiceRecordInstance sin; 57 RateCtl rate; 58 int active; 59 } SpiceVoiceIn; 60 61 static const SpicePlaybackInterface playback_sif = { 62 .base.type = SPICE_INTERFACE_PLAYBACK, 63 .base.description = "playback", 64 .base.major_version = SPICE_INTERFACE_PLAYBACK_MAJOR, 65 .base.minor_version = SPICE_INTERFACE_PLAYBACK_MINOR, 66 }; 67 68 static const SpiceRecordInterface record_sif = { 69 .base.type = SPICE_INTERFACE_RECORD, 70 .base.description = "record", 71 .base.major_version = SPICE_INTERFACE_RECORD_MAJOR, 72 .base.minor_version = SPICE_INTERFACE_RECORD_MINOR, 73 }; 74 75 static void *spice_audio_init(Audiodev *dev, Error **errp) 76 { 77 if (!using_spice) { 78 error_setg(errp, "Cannot use spice audio without -spice"); 79 return NULL; 80 } 81 82 return &spice_audio_init; 83 } 84 85 static void spice_audio_fini (void *opaque) 86 { 87 /* nothing */ 88 } 89 90 /* playback */ 91 92 static int line_out_init(HWVoiceOut *hw, struct audsettings *as, 93 void *drv_opaque) 94 { 95 SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); 96 struct audsettings settings; 97 98 #if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 99 settings.freq = spice_server_get_best_playback_rate(NULL); 100 #else 101 settings.freq = SPICE_INTERFACE_PLAYBACK_FREQ; 102 #endif 103 settings.nchannels = SPICE_INTERFACE_PLAYBACK_CHAN; 104 settings.fmt = AUDIO_FORMAT_S16; 105 settings.endianness = AUDIO_HOST_ENDIANNESS; 106 107 audio_pcm_init_info (&hw->info, &settings); 108 hw->samples = LINE_OUT_SAMPLES; 109 out->active = 0; 110 111 out->sin.base.sif = &playback_sif.base; 112 qemu_spice.add_interface(&out->sin.base); 113 #if SPICE_INTERFACE_PLAYBACK_MAJOR > 1 || SPICE_INTERFACE_PLAYBACK_MINOR >= 3 114 spice_server_set_playback_rate(&out->sin, settings.freq); 115 #endif 116 return 0; 117 } 118 119 static void line_out_fini (HWVoiceOut *hw) 120 { 121 SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); 122 123 spice_server_remove_interface (&out->sin.base); 124 } 125 126 static size_t line_out_get_free(HWVoiceOut *hw) 127 { 128 SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw); 129 130 return audio_rate_peek_bytes(&out->rate, &hw->info); 131 } 132 133 static void *line_out_get_buffer(HWVoiceOut *hw, size_t *size) 134 { 135 SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw); 136 137 if (!out->frame) { 138 spice_server_playback_get_buffer(&out->sin, &out->frame, &out->fsize); 139 out->fpos = 0; 140 } 141 142 if (out->frame) { 143 *size = MIN((out->fsize - out->fpos) << 2, *size); 144 } 145 146 return out->frame + out->fpos; 147 } 148 149 static size_t line_out_put_buffer(HWVoiceOut *hw, void *buf, size_t size) 150 { 151 SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw); 152 153 audio_rate_add_bytes(&out->rate, size); 154 155 if (buf) { 156 assert(buf == out->frame + out->fpos && out->fpos <= out->fsize); 157 out->fpos += size >> 2; 158 159 if (out->fpos == out->fsize) { /* buffer full */ 160 spice_server_playback_put_samples(&out->sin, out->frame); 161 out->frame = NULL; 162 } 163 } 164 165 return size; 166 } 167 168 static void line_out_enable(HWVoiceOut *hw, bool enable) 169 { 170 SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); 171 172 if (enable) { 173 if (out->active) { 174 return; 175 } 176 out->active = 1; 177 audio_rate_start(&out->rate); 178 spice_server_playback_start (&out->sin); 179 } else { 180 if (!out->active) { 181 return; 182 } 183 out->active = 0; 184 if (out->frame) { 185 memset(out->frame + out->fpos, 0, (out->fsize - out->fpos) << 2); 186 spice_server_playback_put_samples (&out->sin, out->frame); 187 out->frame = NULL; 188 } 189 spice_server_playback_stop (&out->sin); 190 } 191 } 192 193 #if ((SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && (SPICE_INTERFACE_PLAYBACK_MINOR >= 2)) 194 static void line_out_volume(HWVoiceOut *hw, Volume *vol) 195 { 196 SpiceVoiceOut *out = container_of(hw, SpiceVoiceOut, hw); 197 uint16_t svol[2]; 198 199 assert(vol->channels == 2); 200 svol[0] = vol->vol[0] * 257; 201 svol[1] = vol->vol[1] * 257; 202 spice_server_playback_set_volume(&out->sin, 2, svol); 203 spice_server_playback_set_mute(&out->sin, vol->mute); 204 } 205 #endif 206 207 /* record */ 208 209 static int line_in_init(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) 210 { 211 SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); 212 struct audsettings settings; 213 214 #if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 215 settings.freq = spice_server_get_best_record_rate(NULL); 216 #else 217 settings.freq = SPICE_INTERFACE_RECORD_FREQ; 218 #endif 219 settings.nchannels = SPICE_INTERFACE_RECORD_CHAN; 220 settings.fmt = AUDIO_FORMAT_S16; 221 settings.endianness = AUDIO_HOST_ENDIANNESS; 222 223 audio_pcm_init_info (&hw->info, &settings); 224 hw->samples = LINE_IN_SAMPLES; 225 in->active = 0; 226 227 in->sin.base.sif = &record_sif.base; 228 qemu_spice.add_interface(&in->sin.base); 229 #if SPICE_INTERFACE_RECORD_MAJOR > 2 || SPICE_INTERFACE_RECORD_MINOR >= 3 230 spice_server_set_record_rate(&in->sin, settings.freq); 231 #endif 232 return 0; 233 } 234 235 static void line_in_fini (HWVoiceIn *hw) 236 { 237 SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); 238 239 spice_server_remove_interface (&in->sin.base); 240 } 241 242 static size_t line_in_read(HWVoiceIn *hw, void *buf, size_t len) 243 { 244 SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); 245 uint64_t to_read = audio_rate_get_bytes(&in->rate, &hw->info, len) >> 2; 246 size_t ready = spice_server_record_get_samples(&in->sin, buf, to_read); 247 248 /* 249 * If the client didn't send new frames, it most likely disconnected. 250 * Generate silence in this case to avoid a stalled audio stream. 251 */ 252 if (ready == 0) { 253 memset(buf, 0, to_read << 2); 254 ready = to_read; 255 } 256 257 return ready << 2; 258 } 259 260 static void line_in_enable(HWVoiceIn *hw, bool enable) 261 { 262 SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); 263 264 if (enable) { 265 if (in->active) { 266 return; 267 } 268 in->active = 1; 269 audio_rate_start(&in->rate); 270 spice_server_record_start (&in->sin); 271 } else { 272 if (!in->active) { 273 return; 274 } 275 in->active = 0; 276 spice_server_record_stop (&in->sin); 277 } 278 } 279 280 #if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2)) 281 static void line_in_volume(HWVoiceIn *hw, Volume *vol) 282 { 283 SpiceVoiceIn *in = container_of(hw, SpiceVoiceIn, hw); 284 uint16_t svol[2]; 285 286 assert(vol->channels == 2); 287 svol[0] = vol->vol[0] * 257; 288 svol[1] = vol->vol[1] * 257; 289 spice_server_record_set_volume(&in->sin, 2, svol); 290 spice_server_record_set_mute(&in->sin, vol->mute); 291 } 292 #endif 293 294 static struct audio_pcm_ops audio_callbacks = { 295 .init_out = line_out_init, 296 .fini_out = line_out_fini, 297 .write = audio_generic_write, 298 .buffer_get_free = line_out_get_free, 299 .get_buffer_out = line_out_get_buffer, 300 .put_buffer_out = line_out_put_buffer, 301 .enable_out = line_out_enable, 302 #if (SPICE_INTERFACE_PLAYBACK_MAJOR >= 1) && \ 303 (SPICE_INTERFACE_PLAYBACK_MINOR >= 2) 304 .volume_out = line_out_volume, 305 #endif 306 307 .init_in = line_in_init, 308 .fini_in = line_in_fini, 309 .read = line_in_read, 310 .run_buffer_in = audio_generic_run_buffer_in, 311 .enable_in = line_in_enable, 312 #if ((SPICE_INTERFACE_RECORD_MAJOR >= 2) && (SPICE_INTERFACE_RECORD_MINOR >= 2)) 313 .volume_in = line_in_volume, 314 #endif 315 }; 316 317 static struct audio_driver spice_audio_driver = { 318 .name = "spice", 319 .descr = "spice audio driver", 320 .init = spice_audio_init, 321 .fini = spice_audio_fini, 322 .pcm_ops = &audio_callbacks, 323 .max_voices_out = 1, 324 .max_voices_in = 1, 325 .voice_size_out = sizeof (SpiceVoiceOut), 326 .voice_size_in = sizeof (SpiceVoiceIn), 327 }; 328 329 static void register_audio_spice(void) 330 { 331 audio_driver_register(&spice_audio_driver); 332 } 333 type_init(register_audio_spice); 334 335 module_dep("ui-spice-core"); 336