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