1c2d3d1c2SDorinda Bassey /* 220c51248SMarc-André Lureau * QEMU PipeWire audio driver 3c2d3d1c2SDorinda Bassey * 4c2d3d1c2SDorinda Bassey * Copyright (c) 2023 Red Hat Inc. 5c2d3d1c2SDorinda Bassey * 6c2d3d1c2SDorinda Bassey * Author: Dorinda Bassey <dbassey@redhat.com> 7c2d3d1c2SDorinda Bassey * 8c2d3d1c2SDorinda Bassey * SPDX-License-Identifier: GPL-2.0-or-later 9c2d3d1c2SDorinda Bassey */ 10c2d3d1c2SDorinda Bassey 11c2d3d1c2SDorinda Bassey #include "qemu/osdep.h" 12c2d3d1c2SDorinda Bassey #include "qemu/module.h" 13c2d3d1c2SDorinda Bassey #include "audio.h" 14c2d3d1c2SDorinda Bassey #include "qemu/error-report.h" 15f6061733SPaolo Bonzini #include "qapi/error.h" 16c2d3d1c2SDorinda Bassey #include <spa/param/audio/format-utils.h> 17c2d3d1c2SDorinda Bassey #include <spa/utils/ringbuffer.h> 18c2d3d1c2SDorinda Bassey #include <spa/utils/result.h> 19c2d3d1c2SDorinda Bassey #include <spa/param/props.h> 20c2d3d1c2SDorinda Bassey 21c2d3d1c2SDorinda Bassey #include <pipewire/pipewire.h> 22c2d3d1c2SDorinda Bassey #include "trace.h" 23c2d3d1c2SDorinda Bassey 24c2d3d1c2SDorinda Bassey #define AUDIO_CAP "pipewire" 25c2d3d1c2SDorinda Bassey #define RINGBUFFER_SIZE (1u << 22) 26c2d3d1c2SDorinda Bassey #define RINGBUFFER_MASK (RINGBUFFER_SIZE - 1) 27c2d3d1c2SDorinda Bassey 28c2d3d1c2SDorinda Bassey #include "audio_int.h" 29c2d3d1c2SDorinda Bassey 30c2d3d1c2SDorinda Bassey typedef struct pwvolume { 31c2d3d1c2SDorinda Bassey uint32_t channels; 32c2d3d1c2SDorinda Bassey float values[SPA_AUDIO_MAX_CHANNELS]; 33c2d3d1c2SDorinda Bassey } pwvolume; 34c2d3d1c2SDorinda Bassey 35c2d3d1c2SDorinda Bassey typedef struct pwaudio { 36c2d3d1c2SDorinda Bassey Audiodev *dev; 37c2d3d1c2SDorinda Bassey struct pw_thread_loop *thread_loop; 38c2d3d1c2SDorinda Bassey struct pw_context *context; 39c2d3d1c2SDorinda Bassey 40c2d3d1c2SDorinda Bassey struct pw_core *core; 41c2d3d1c2SDorinda Bassey struct spa_hook core_listener; 42c2d3d1c2SDorinda Bassey int last_seq, pending_seq, error; 43c2d3d1c2SDorinda Bassey } pwaudio; 44c2d3d1c2SDorinda Bassey 45c2d3d1c2SDorinda Bassey typedef struct PWVoice { 46c2d3d1c2SDorinda Bassey pwaudio *g; 47c2d3d1c2SDorinda Bassey struct pw_stream *stream; 48c2d3d1c2SDorinda Bassey struct spa_hook stream_listener; 49c2d3d1c2SDorinda Bassey struct spa_audio_info_raw info; 50c2d3d1c2SDorinda Bassey uint32_t highwater_mark; 51c2d3d1c2SDorinda Bassey uint32_t frame_size, req; 52c2d3d1c2SDorinda Bassey struct spa_ringbuffer ring; 53c2d3d1c2SDorinda Bassey uint8_t buffer[RINGBUFFER_SIZE]; 54c2d3d1c2SDorinda Bassey 55c2d3d1c2SDorinda Bassey pwvolume volume; 56c2d3d1c2SDorinda Bassey bool muted; 57c2d3d1c2SDorinda Bassey } PWVoice; 58c2d3d1c2SDorinda Bassey 59c2d3d1c2SDorinda Bassey typedef struct PWVoiceOut { 60c2d3d1c2SDorinda Bassey HWVoiceOut hw; 61c2d3d1c2SDorinda Bassey PWVoice v; 62c2d3d1c2SDorinda Bassey } PWVoiceOut; 63c2d3d1c2SDorinda Bassey 64c2d3d1c2SDorinda Bassey typedef struct PWVoiceIn { 65c2d3d1c2SDorinda Bassey HWVoiceIn hw; 66c2d3d1c2SDorinda Bassey PWVoice v; 67c2d3d1c2SDorinda Bassey } PWVoiceIn; 68c2d3d1c2SDorinda Bassey 6992fd7868SMarc-André Lureau #define PW_VOICE_IN(v) ((PWVoiceIn *)v) 7092fd7868SMarc-André Lureau #define PW_VOICE_OUT(v) ((PWVoiceOut *)v) 7192fd7868SMarc-André Lureau 72c2d3d1c2SDorinda Bassey static void 73c2d3d1c2SDorinda Bassey stream_destroy(void *data) 74c2d3d1c2SDorinda Bassey { 75c2d3d1c2SDorinda Bassey PWVoice *v = (PWVoice *) data; 76c2d3d1c2SDorinda Bassey spa_hook_remove(&v->stream_listener); 77c2d3d1c2SDorinda Bassey v->stream = NULL; 78c2d3d1c2SDorinda Bassey } 79c2d3d1c2SDorinda Bassey 80c2d3d1c2SDorinda Bassey /* output data processing function to read stuffs from the buffer */ 81c2d3d1c2SDorinda Bassey static void 82c2d3d1c2SDorinda Bassey playback_on_process(void *data) 83c2d3d1c2SDorinda Bassey { 84c2d3d1c2SDorinda Bassey PWVoice *v = data; 85c2d3d1c2SDorinda Bassey void *p; 86c2d3d1c2SDorinda Bassey struct pw_buffer *b; 87c2d3d1c2SDorinda Bassey struct spa_buffer *buf; 88c2d3d1c2SDorinda Bassey uint32_t req, index, n_bytes; 89c2d3d1c2SDorinda Bassey int32_t avail; 90c2d3d1c2SDorinda Bassey 91c2d3d1c2SDorinda Bassey assert(v->stream); 92c2d3d1c2SDorinda Bassey 93c2d3d1c2SDorinda Bassey /* obtain a buffer to read from */ 94c2d3d1c2SDorinda Bassey b = pw_stream_dequeue_buffer(v->stream); 95c2d3d1c2SDorinda Bassey if (b == NULL) { 96c2d3d1c2SDorinda Bassey error_report("out of buffers: %s", strerror(errno)); 97c2d3d1c2SDorinda Bassey return; 98c2d3d1c2SDorinda Bassey } 99c2d3d1c2SDorinda Bassey 100c2d3d1c2SDorinda Bassey buf = b->buffer; 101c2d3d1c2SDorinda Bassey p = buf->datas[0].data; 102c2d3d1c2SDorinda Bassey if (p == NULL) { 103c2d3d1c2SDorinda Bassey return; 104c2d3d1c2SDorinda Bassey } 105c2d3d1c2SDorinda Bassey /* calculate the total no of bytes to read data from buffer */ 106c2d3d1c2SDorinda Bassey req = b->requested * v->frame_size; 107c2d3d1c2SDorinda Bassey if (req == 0) { 108c2d3d1c2SDorinda Bassey req = v->req; 109c2d3d1c2SDorinda Bassey } 110c2d3d1c2SDorinda Bassey n_bytes = SPA_MIN(req, buf->datas[0].maxsize); 111c2d3d1c2SDorinda Bassey 112c2d3d1c2SDorinda Bassey /* get no of available bytes to read data from buffer */ 113c2d3d1c2SDorinda Bassey avail = spa_ringbuffer_get_read_index(&v->ring, &index); 114c2d3d1c2SDorinda Bassey 115c2d3d1c2SDorinda Bassey if (avail <= 0) { 116c2d3d1c2SDorinda Bassey PWVoiceOut *vo = container_of(data, PWVoiceOut, v); 117c2d3d1c2SDorinda Bassey audio_pcm_info_clear_buf(&vo->hw.info, p, n_bytes / v->frame_size); 118c2d3d1c2SDorinda Bassey } else { 119c2d3d1c2SDorinda Bassey if ((uint32_t) avail < n_bytes) { 120c2d3d1c2SDorinda Bassey /* 121c2d3d1c2SDorinda Bassey * PipeWire immediately calls this callback again if we provide 122c2d3d1c2SDorinda Bassey * less than n_bytes. Then audio_pcm_info_clear_buf() fills the 123c2d3d1c2SDorinda Bassey * rest of the buffer with silence. 124c2d3d1c2SDorinda Bassey */ 125c2d3d1c2SDorinda Bassey n_bytes = avail; 126c2d3d1c2SDorinda Bassey } 127c2d3d1c2SDorinda Bassey 128c2d3d1c2SDorinda Bassey spa_ringbuffer_read_data(&v->ring, 129c2d3d1c2SDorinda Bassey v->buffer, RINGBUFFER_SIZE, 130c2d3d1c2SDorinda Bassey index & RINGBUFFER_MASK, p, n_bytes); 131c2d3d1c2SDorinda Bassey 132c2d3d1c2SDorinda Bassey index += n_bytes; 133c2d3d1c2SDorinda Bassey spa_ringbuffer_read_update(&v->ring, index); 134c2d3d1c2SDorinda Bassey 135c2d3d1c2SDorinda Bassey } 136c2d3d1c2SDorinda Bassey buf->datas[0].chunk->offset = 0; 137c2d3d1c2SDorinda Bassey buf->datas[0].chunk->stride = v->frame_size; 138c2d3d1c2SDorinda Bassey buf->datas[0].chunk->size = n_bytes; 139c2d3d1c2SDorinda Bassey 140c2d3d1c2SDorinda Bassey /* queue the buffer for playback */ 141c2d3d1c2SDorinda Bassey pw_stream_queue_buffer(v->stream, b); 142c2d3d1c2SDorinda Bassey } 143c2d3d1c2SDorinda Bassey 144c2d3d1c2SDorinda Bassey /* output data processing function to generate stuffs in the buffer */ 145c2d3d1c2SDorinda Bassey static void 146c2d3d1c2SDorinda Bassey capture_on_process(void *data) 147c2d3d1c2SDorinda Bassey { 148c2d3d1c2SDorinda Bassey PWVoice *v = (PWVoice *) data; 149c2d3d1c2SDorinda Bassey void *p; 150c2d3d1c2SDorinda Bassey struct pw_buffer *b; 151c2d3d1c2SDorinda Bassey struct spa_buffer *buf; 152c2d3d1c2SDorinda Bassey int32_t filled; 153c2d3d1c2SDorinda Bassey uint32_t index, offs, n_bytes; 154c2d3d1c2SDorinda Bassey 155c2d3d1c2SDorinda Bassey assert(v->stream); 156c2d3d1c2SDorinda Bassey 157c2d3d1c2SDorinda Bassey /* obtain a buffer */ 158c2d3d1c2SDorinda Bassey b = pw_stream_dequeue_buffer(v->stream); 159c2d3d1c2SDorinda Bassey if (b == NULL) { 160c2d3d1c2SDorinda Bassey error_report("out of buffers: %s", strerror(errno)); 161c2d3d1c2SDorinda Bassey return; 162c2d3d1c2SDorinda Bassey } 163c2d3d1c2SDorinda Bassey 164c2d3d1c2SDorinda Bassey /* Write data into buffer */ 165c2d3d1c2SDorinda Bassey buf = b->buffer; 166c2d3d1c2SDorinda Bassey p = buf->datas[0].data; 167c2d3d1c2SDorinda Bassey if (p == NULL) { 168c2d3d1c2SDorinda Bassey return; 169c2d3d1c2SDorinda Bassey } 170c2d3d1c2SDorinda Bassey offs = SPA_MIN(buf->datas[0].chunk->offset, buf->datas[0].maxsize); 171c2d3d1c2SDorinda Bassey n_bytes = SPA_MIN(buf->datas[0].chunk->size, buf->datas[0].maxsize - offs); 172c2d3d1c2SDorinda Bassey 173c2d3d1c2SDorinda Bassey filled = spa_ringbuffer_get_write_index(&v->ring, &index); 174c2d3d1c2SDorinda Bassey 175c2d3d1c2SDorinda Bassey 176c2d3d1c2SDorinda Bassey if (filled < 0) { 177c2d3d1c2SDorinda Bassey error_report("%p: underrun write:%u filled:%d", p, index, filled); 178c2d3d1c2SDorinda Bassey } else { 179c2d3d1c2SDorinda Bassey if ((uint32_t) filled + n_bytes > RINGBUFFER_SIZE) { 180c2d3d1c2SDorinda Bassey error_report("%p: overrun write:%u filled:%d + size:%u > max:%u", 181c2d3d1c2SDorinda Bassey p, index, filled, n_bytes, RINGBUFFER_SIZE); 182c2d3d1c2SDorinda Bassey } 183c2d3d1c2SDorinda Bassey } 184c2d3d1c2SDorinda Bassey spa_ringbuffer_write_data(&v->ring, 185c2d3d1c2SDorinda Bassey v->buffer, RINGBUFFER_SIZE, 186c2d3d1c2SDorinda Bassey index & RINGBUFFER_MASK, 187c2d3d1c2SDorinda Bassey SPA_PTROFF(p, offs, void), n_bytes); 188c2d3d1c2SDorinda Bassey index += n_bytes; 189c2d3d1c2SDorinda Bassey spa_ringbuffer_write_update(&v->ring, index); 190c2d3d1c2SDorinda Bassey 191c2d3d1c2SDorinda Bassey /* queue the buffer for playback */ 192c2d3d1c2SDorinda Bassey pw_stream_queue_buffer(v->stream, b); 193c2d3d1c2SDorinda Bassey } 194c2d3d1c2SDorinda Bassey 195c2d3d1c2SDorinda Bassey static void 196c2d3d1c2SDorinda Bassey on_stream_state_changed(void *data, enum pw_stream_state old, 197c2d3d1c2SDorinda Bassey enum pw_stream_state state, const char *error) 198c2d3d1c2SDorinda Bassey { 199c2d3d1c2SDorinda Bassey PWVoice *v = (PWVoice *) data; 200c2d3d1c2SDorinda Bassey 201c2d3d1c2SDorinda Bassey trace_pw_state_changed(pw_stream_get_node_id(v->stream), 202c2d3d1c2SDorinda Bassey pw_stream_state_as_string(state)); 203c2d3d1c2SDorinda Bassey } 204c2d3d1c2SDorinda Bassey 205c2d3d1c2SDorinda Bassey static const struct pw_stream_events capture_stream_events = { 206c2d3d1c2SDorinda Bassey PW_VERSION_STREAM_EVENTS, 207c2d3d1c2SDorinda Bassey .destroy = stream_destroy, 208c2d3d1c2SDorinda Bassey .state_changed = on_stream_state_changed, 209c2d3d1c2SDorinda Bassey .process = capture_on_process 210c2d3d1c2SDorinda Bassey }; 211c2d3d1c2SDorinda Bassey 212c2d3d1c2SDorinda Bassey static const struct pw_stream_events playback_stream_events = { 213c2d3d1c2SDorinda Bassey PW_VERSION_STREAM_EVENTS, 214c2d3d1c2SDorinda Bassey .destroy = stream_destroy, 215c2d3d1c2SDorinda Bassey .state_changed = on_stream_state_changed, 216c2d3d1c2SDorinda Bassey .process = playback_on_process 217c2d3d1c2SDorinda Bassey }; 218c2d3d1c2SDorinda Bassey 219c2d3d1c2SDorinda Bassey static size_t 220c2d3d1c2SDorinda Bassey qpw_read(HWVoiceIn *hw, void *data, size_t len) 221c2d3d1c2SDorinda Bassey { 222c2d3d1c2SDorinda Bassey PWVoiceIn *pw = (PWVoiceIn *) hw; 223c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 224c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 225c2d3d1c2SDorinda Bassey const char *error = NULL; 226c2d3d1c2SDorinda Bassey size_t l; 227c2d3d1c2SDorinda Bassey int32_t avail; 228c2d3d1c2SDorinda Bassey uint32_t index; 229c2d3d1c2SDorinda Bassey 230c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 231c2d3d1c2SDorinda Bassey if (pw_stream_get_state(v->stream, &error) != PW_STREAM_STATE_STREAMING) { 232c2d3d1c2SDorinda Bassey /* wait for stream to become ready */ 233c2d3d1c2SDorinda Bassey l = 0; 234c2d3d1c2SDorinda Bassey goto done_unlock; 235c2d3d1c2SDorinda Bassey } 236c2d3d1c2SDorinda Bassey /* get no of available bytes to read data from buffer */ 237c2d3d1c2SDorinda Bassey avail = spa_ringbuffer_get_read_index(&v->ring, &index); 238c2d3d1c2SDorinda Bassey 239c2d3d1c2SDorinda Bassey trace_pw_read(avail, index, len); 240c2d3d1c2SDorinda Bassey 241c2d3d1c2SDorinda Bassey if (avail < (int32_t) len) { 242c2d3d1c2SDorinda Bassey len = avail; 243c2d3d1c2SDorinda Bassey } 244c2d3d1c2SDorinda Bassey 245c2d3d1c2SDorinda Bassey spa_ringbuffer_read_data(&v->ring, 246c2d3d1c2SDorinda Bassey v->buffer, RINGBUFFER_SIZE, 247c2d3d1c2SDorinda Bassey index & RINGBUFFER_MASK, data, len); 248c2d3d1c2SDorinda Bassey index += len; 249c2d3d1c2SDorinda Bassey spa_ringbuffer_read_update(&v->ring, index); 250c2d3d1c2SDorinda Bassey l = len; 251c2d3d1c2SDorinda Bassey 252c2d3d1c2SDorinda Bassey done_unlock: 253c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 254c2d3d1c2SDorinda Bassey return l; 255c2d3d1c2SDorinda Bassey } 256c2d3d1c2SDorinda Bassey 257c2d3d1c2SDorinda Bassey static size_t qpw_buffer_get_free(HWVoiceOut *hw) 258c2d3d1c2SDorinda Bassey { 259c2d3d1c2SDorinda Bassey PWVoiceOut *pw = (PWVoiceOut *)hw; 260c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 261c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 262c2d3d1c2SDorinda Bassey const char *error = NULL; 263c2d3d1c2SDorinda Bassey int32_t filled, avail; 264c2d3d1c2SDorinda Bassey uint32_t index; 265c2d3d1c2SDorinda Bassey 266c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 267c2d3d1c2SDorinda Bassey if (pw_stream_get_state(v->stream, &error) != PW_STREAM_STATE_STREAMING) { 268c2d3d1c2SDorinda Bassey /* wait for stream to become ready */ 269c2d3d1c2SDorinda Bassey avail = 0; 270c2d3d1c2SDorinda Bassey goto done_unlock; 271c2d3d1c2SDorinda Bassey } 272c2d3d1c2SDorinda Bassey 273c2d3d1c2SDorinda Bassey filled = spa_ringbuffer_get_write_index(&v->ring, &index); 274c2d3d1c2SDorinda Bassey avail = v->highwater_mark - filled; 275c2d3d1c2SDorinda Bassey 276c2d3d1c2SDorinda Bassey done_unlock: 277c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 278c2d3d1c2SDorinda Bassey return avail; 279c2d3d1c2SDorinda Bassey } 280c2d3d1c2SDorinda Bassey 281c2d3d1c2SDorinda Bassey static size_t 282c2d3d1c2SDorinda Bassey qpw_write(HWVoiceOut *hw, void *data, size_t len) 283c2d3d1c2SDorinda Bassey { 284c2d3d1c2SDorinda Bassey PWVoiceOut *pw = (PWVoiceOut *) hw; 285c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 286c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 287c2d3d1c2SDorinda Bassey const char *error = NULL; 288c2d3d1c2SDorinda Bassey int32_t filled, avail; 289c2d3d1c2SDorinda Bassey uint32_t index; 290c2d3d1c2SDorinda Bassey 291c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 292c2d3d1c2SDorinda Bassey if (pw_stream_get_state(v->stream, &error) != PW_STREAM_STATE_STREAMING) { 293c2d3d1c2SDorinda Bassey /* wait for stream to become ready */ 294c2d3d1c2SDorinda Bassey len = 0; 295c2d3d1c2SDorinda Bassey goto done_unlock; 296c2d3d1c2SDorinda Bassey } 297c2d3d1c2SDorinda Bassey filled = spa_ringbuffer_get_write_index(&v->ring, &index); 298c2d3d1c2SDorinda Bassey avail = v->highwater_mark - filled; 299c2d3d1c2SDorinda Bassey 300c2d3d1c2SDorinda Bassey trace_pw_write(filled, avail, index, len); 301c2d3d1c2SDorinda Bassey 302c2d3d1c2SDorinda Bassey if (len > avail) { 303c2d3d1c2SDorinda Bassey len = avail; 304c2d3d1c2SDorinda Bassey } 305c2d3d1c2SDorinda Bassey 306c2d3d1c2SDorinda Bassey if (filled < 0) { 307c2d3d1c2SDorinda Bassey error_report("%p: underrun write:%u filled:%d", pw, index, filled); 308c2d3d1c2SDorinda Bassey } else { 309c2d3d1c2SDorinda Bassey if ((uint32_t) filled + len > RINGBUFFER_SIZE) { 310c2d3d1c2SDorinda Bassey error_report("%p: overrun write:%u filled:%d + size:%zu > max:%u", 311c2d3d1c2SDorinda Bassey pw, index, filled, len, RINGBUFFER_SIZE); 312c2d3d1c2SDorinda Bassey } 313c2d3d1c2SDorinda Bassey } 314c2d3d1c2SDorinda Bassey 315c2d3d1c2SDorinda Bassey spa_ringbuffer_write_data(&v->ring, 316c2d3d1c2SDorinda Bassey v->buffer, RINGBUFFER_SIZE, 317c2d3d1c2SDorinda Bassey index & RINGBUFFER_MASK, data, len); 318c2d3d1c2SDorinda Bassey index += len; 319c2d3d1c2SDorinda Bassey spa_ringbuffer_write_update(&v->ring, index); 320c2d3d1c2SDorinda Bassey 321c2d3d1c2SDorinda Bassey done_unlock: 322c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 323c2d3d1c2SDorinda Bassey return len; 324c2d3d1c2SDorinda Bassey } 325c2d3d1c2SDorinda Bassey 326c2d3d1c2SDorinda Bassey static int 327c2d3d1c2SDorinda Bassey audfmt_to_pw(AudioFormat fmt, int endianness) 328c2d3d1c2SDorinda Bassey { 329c2d3d1c2SDorinda Bassey int format; 330c2d3d1c2SDorinda Bassey 331c2d3d1c2SDorinda Bassey switch (fmt) { 332c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_S8: 333c2d3d1c2SDorinda Bassey format = SPA_AUDIO_FORMAT_S8; 334c2d3d1c2SDorinda Bassey break; 335c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_U8: 336c2d3d1c2SDorinda Bassey format = SPA_AUDIO_FORMAT_U8; 337c2d3d1c2SDorinda Bassey break; 338c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_S16: 339c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_S16_BE : SPA_AUDIO_FORMAT_S16_LE; 340c2d3d1c2SDorinda Bassey break; 341c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_U16: 342c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_U16_BE : SPA_AUDIO_FORMAT_U16_LE; 343c2d3d1c2SDorinda Bassey break; 344c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_S32: 345c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_S32_BE : SPA_AUDIO_FORMAT_S32_LE; 346c2d3d1c2SDorinda Bassey break; 347c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_U32: 348c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_U32_BE : SPA_AUDIO_FORMAT_U32_LE; 349c2d3d1c2SDorinda Bassey break; 350c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_F32: 351c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_F32_BE : SPA_AUDIO_FORMAT_F32_LE; 352c2d3d1c2SDorinda Bassey break; 353c2d3d1c2SDorinda Bassey default: 354c2d3d1c2SDorinda Bassey dolog("Internal logic error: Bad audio format %d\n", fmt); 355c2d3d1c2SDorinda Bassey format = SPA_AUDIO_FORMAT_U8; 356c2d3d1c2SDorinda Bassey break; 357c2d3d1c2SDorinda Bassey } 358c2d3d1c2SDorinda Bassey return format; 359c2d3d1c2SDorinda Bassey } 360c2d3d1c2SDorinda Bassey 361c2d3d1c2SDorinda Bassey static AudioFormat 362c2d3d1c2SDorinda Bassey pw_to_audfmt(enum spa_audio_format fmt, int *endianness, 363c2d3d1c2SDorinda Bassey uint32_t *sample_size) 364c2d3d1c2SDorinda Bassey { 365c2d3d1c2SDorinda Bassey switch (fmt) { 366c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S8: 367c2d3d1c2SDorinda Bassey *sample_size = 1; 368c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S8; 369c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U8: 370c2d3d1c2SDorinda Bassey *sample_size = 1; 371c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U8; 372c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S16_BE: 373c2d3d1c2SDorinda Bassey *sample_size = 2; 374c2d3d1c2SDorinda Bassey *endianness = 1; 375c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S16; 376c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S16_LE: 377c2d3d1c2SDorinda Bassey *sample_size = 2; 378c2d3d1c2SDorinda Bassey *endianness = 0; 379c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S16; 380c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U16_BE: 381c2d3d1c2SDorinda Bassey *sample_size = 2; 382c2d3d1c2SDorinda Bassey *endianness = 1; 383c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U16; 384c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U16_LE: 385c2d3d1c2SDorinda Bassey *sample_size = 2; 386c2d3d1c2SDorinda Bassey *endianness = 0; 387c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U16; 388c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S32_BE: 389c2d3d1c2SDorinda Bassey *sample_size = 4; 390c2d3d1c2SDorinda Bassey *endianness = 1; 391c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S32; 392c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S32_LE: 393c2d3d1c2SDorinda Bassey *sample_size = 4; 394c2d3d1c2SDorinda Bassey *endianness = 0; 395c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S32; 396c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U32_BE: 397c2d3d1c2SDorinda Bassey *sample_size = 4; 398c2d3d1c2SDorinda Bassey *endianness = 1; 399c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U32; 400c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U32_LE: 401c2d3d1c2SDorinda Bassey *sample_size = 4; 402c2d3d1c2SDorinda Bassey *endianness = 0; 403c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U32; 404c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_F32_BE: 405c2d3d1c2SDorinda Bassey *sample_size = 4; 406c2d3d1c2SDorinda Bassey *endianness = 1; 407c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_F32; 408c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_F32_LE: 409c2d3d1c2SDorinda Bassey *sample_size = 4; 410c2d3d1c2SDorinda Bassey *endianness = 0; 411c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_F32; 412c2d3d1c2SDorinda Bassey default: 413c2d3d1c2SDorinda Bassey *sample_size = 1; 414c2d3d1c2SDorinda Bassey dolog("Internal logic error: Bad spa_audio_format %d\n", fmt); 415c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U8; 416c2d3d1c2SDorinda Bassey } 417c2d3d1c2SDorinda Bassey } 418c2d3d1c2SDorinda Bassey 419c2d3d1c2SDorinda Bassey static int 42092f69a2cSMarc-André Lureau qpw_stream_new(pwaudio *c, PWVoice *v, const char *stream_name, 421c2d3d1c2SDorinda Bassey const char *name, enum spa_direction dir) 422c2d3d1c2SDorinda Bassey { 423c2d3d1c2SDorinda Bassey int res; 424c2d3d1c2SDorinda Bassey uint32_t n_params; 425c2d3d1c2SDorinda Bassey const struct spa_pod *params[2]; 426c2d3d1c2SDorinda Bassey uint8_t buffer[1024]; 427c2d3d1c2SDorinda Bassey struct spa_pod_builder b; 428c2d3d1c2SDorinda Bassey uint64_t buf_samples; 429c2d3d1c2SDorinda Bassey struct pw_properties *props; 430c2d3d1c2SDorinda Bassey 431c2d3d1c2SDorinda Bassey props = pw_properties_new(NULL, NULL); 4320c57a055SMarc-André Lureau if (!props) { 4330c57a055SMarc-André Lureau error_report("Failed to create PW properties: %s", g_strerror(errno)); 4340c57a055SMarc-André Lureau return -1; 4350c57a055SMarc-André Lureau } 436c2d3d1c2SDorinda Bassey 437c2d3d1c2SDorinda Bassey /* 75% of the timer period for faster updates */ 438c2d3d1c2SDorinda Bassey buf_samples = (uint64_t)v->g->dev->timer_period * v->info.rate 439c2d3d1c2SDorinda Bassey * 3 / 4 / 1000000; 440c2d3d1c2SDorinda Bassey pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u", 441c2d3d1c2SDorinda Bassey buf_samples, v->info.rate); 442c2d3d1c2SDorinda Bassey 443c2d3d1c2SDorinda Bassey trace_pw_period(buf_samples, v->info.rate); 444c2d3d1c2SDorinda Bassey if (name) { 445c2d3d1c2SDorinda Bassey pw_properties_set(props, PW_KEY_TARGET_OBJECT, name); 446c2d3d1c2SDorinda Bassey } 447c2d3d1c2SDorinda Bassey v->stream = pw_stream_new(c->core, stream_name, props); 448c2d3d1c2SDorinda Bassey if (v->stream == NULL) { 4490c57a055SMarc-André Lureau error_report("Failed to create PW stream: %s", g_strerror(errno)); 450c2d3d1c2SDorinda Bassey return -1; 451c2d3d1c2SDorinda Bassey } 452c2d3d1c2SDorinda Bassey 453c2d3d1c2SDorinda Bassey if (dir == SPA_DIRECTION_INPUT) { 454c2d3d1c2SDorinda Bassey pw_stream_add_listener(v->stream, 455c2d3d1c2SDorinda Bassey &v->stream_listener, &capture_stream_events, v); 456c2d3d1c2SDorinda Bassey } else { 457c2d3d1c2SDorinda Bassey pw_stream_add_listener(v->stream, 458c2d3d1c2SDorinda Bassey &v->stream_listener, &playback_stream_events, v); 459c2d3d1c2SDorinda Bassey } 460c2d3d1c2SDorinda Bassey 461c2d3d1c2SDorinda Bassey n_params = 0; 462c2d3d1c2SDorinda Bassey spa_pod_builder_init(&b, buffer, sizeof(buffer)); 463c2d3d1c2SDorinda Bassey params[n_params++] = spa_format_audio_raw_build(&b, 464c2d3d1c2SDorinda Bassey SPA_PARAM_EnumFormat, 465c2d3d1c2SDorinda Bassey &v->info); 466c2d3d1c2SDorinda Bassey 467c2d3d1c2SDorinda Bassey /* connect the stream to a sink or source */ 468c2d3d1c2SDorinda Bassey res = pw_stream_connect(v->stream, 469c2d3d1c2SDorinda Bassey dir == 470c2d3d1c2SDorinda Bassey SPA_DIRECTION_INPUT ? PW_DIRECTION_INPUT : 471c2d3d1c2SDorinda Bassey PW_DIRECTION_OUTPUT, PW_ID_ANY, 472c2d3d1c2SDorinda Bassey PW_STREAM_FLAG_AUTOCONNECT | 473c2d3d1c2SDorinda Bassey PW_STREAM_FLAG_INACTIVE | 474c2d3d1c2SDorinda Bassey PW_STREAM_FLAG_MAP_BUFFERS | 475c2d3d1c2SDorinda Bassey PW_STREAM_FLAG_RT_PROCESS, params, n_params); 476c2d3d1c2SDorinda Bassey if (res < 0) { 4770c57a055SMarc-André Lureau error_report("Failed to connect PW stream: %s", g_strerror(errno)); 478c2d3d1c2SDorinda Bassey pw_stream_destroy(v->stream); 479c2d3d1c2SDorinda Bassey return -1; 480c2d3d1c2SDorinda Bassey } 481c2d3d1c2SDorinda Bassey 482c2d3d1c2SDorinda Bassey return 0; 483c2d3d1c2SDorinda Bassey } 484c2d3d1c2SDorinda Bassey 48592f69a2cSMarc-André Lureau static void 48692f69a2cSMarc-André Lureau qpw_set_position(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]) 487c2d3d1c2SDorinda Bassey { 48892f69a2cSMarc-André Lureau memcpy(position, (uint32_t[SPA_AUDIO_MAX_CHANNELS]) { SPA_AUDIO_CHANNEL_UNKNOWN, }, 48992f69a2cSMarc-André Lureau sizeof(uint32_t) * SPA_AUDIO_MAX_CHANNELS); 49092f69a2cSMarc-André Lureau /* 49192f69a2cSMarc-André Lureau * TODO: This currently expects the only frontend supporting more than 2 49292f69a2cSMarc-André Lureau * channels is the usb-audio. We will need some means to set channel 49392f69a2cSMarc-André Lureau * order when a new frontend gains multi-channel support. 49492f69a2cSMarc-André Lureau */ 49592f69a2cSMarc-André Lureau switch (channels) { 496c2d3d1c2SDorinda Bassey case 8: 49792f69a2cSMarc-André Lureau position[6] = SPA_AUDIO_CHANNEL_SL; 49892f69a2cSMarc-André Lureau position[7] = SPA_AUDIO_CHANNEL_SR; 49992f69a2cSMarc-André Lureau /* fallthrough */ 500c2d3d1c2SDorinda Bassey case 6: 50192f69a2cSMarc-André Lureau position[2] = SPA_AUDIO_CHANNEL_FC; 50292f69a2cSMarc-André Lureau position[3] = SPA_AUDIO_CHANNEL_LFE; 50392f69a2cSMarc-André Lureau position[4] = SPA_AUDIO_CHANNEL_RL; 50492f69a2cSMarc-André Lureau position[5] = SPA_AUDIO_CHANNEL_RR; 50592f69a2cSMarc-André Lureau /* fallthrough */ 506c2d3d1c2SDorinda Bassey case 2: 50792f69a2cSMarc-André Lureau position[0] = SPA_AUDIO_CHANNEL_FL; 50892f69a2cSMarc-André Lureau position[1] = SPA_AUDIO_CHANNEL_FR; 509c2d3d1c2SDorinda Bassey break; 510c2d3d1c2SDorinda Bassey case 1: 51192f69a2cSMarc-André Lureau position[0] = SPA_AUDIO_CHANNEL_MONO; 512c2d3d1c2SDorinda Bassey break; 513c2d3d1c2SDorinda Bassey default: 51492f69a2cSMarc-André Lureau dolog("Internal error: unsupported channel count %d\n", channels); 515c2d3d1c2SDorinda Bassey } 516c2d3d1c2SDorinda Bassey } 517c2d3d1c2SDorinda Bassey 518c2d3d1c2SDorinda Bassey static int 519c2d3d1c2SDorinda Bassey qpw_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) 520c2d3d1c2SDorinda Bassey { 521c2d3d1c2SDorinda Bassey PWVoiceOut *pw = (PWVoiceOut *) hw; 522c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 523c2d3d1c2SDorinda Bassey struct audsettings obt_as = *as; 524c2d3d1c2SDorinda Bassey pwaudio *c = v->g = drv_opaque; 525c2d3d1c2SDorinda Bassey AudiodevPipewireOptions *popts = &c->dev->u.pipewire; 526c2d3d1c2SDorinda Bassey AudiodevPipewirePerDirectionOptions *ppdo = popts->out; 527c2d3d1c2SDorinda Bassey int r; 528c2d3d1c2SDorinda Bassey 529c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 530c2d3d1c2SDorinda Bassey 531c2d3d1c2SDorinda Bassey v->info.format = audfmt_to_pw(as->fmt, as->endianness); 532c2d3d1c2SDorinda Bassey v->info.channels = as->nchannels; 53392f69a2cSMarc-André Lureau qpw_set_position(as->nchannels, v->info.position); 534c2d3d1c2SDorinda Bassey v->info.rate = as->freq; 535c2d3d1c2SDorinda Bassey 536c2d3d1c2SDorinda Bassey obt_as.fmt = 537c2d3d1c2SDorinda Bassey pw_to_audfmt(v->info.format, &obt_as.endianness, &v->frame_size); 538c2d3d1c2SDorinda Bassey v->frame_size *= as->nchannels; 539c2d3d1c2SDorinda Bassey 540c2d3d1c2SDorinda Bassey v->req = (uint64_t)c->dev->timer_period * v->info.rate 541c2d3d1c2SDorinda Bassey * 1 / 2 / 1000000 * v->frame_size; 542c2d3d1c2SDorinda Bassey 543c2d3d1c2SDorinda Bassey /* call the function that creates a new stream for playback */ 544c2d3d1c2SDorinda Bassey r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id, 545c2d3d1c2SDorinda Bassey ppdo->name, SPA_DIRECTION_OUTPUT); 546c2d3d1c2SDorinda Bassey if (r < 0) { 547c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 548c2d3d1c2SDorinda Bassey return -1; 549c2d3d1c2SDorinda Bassey } 550c2d3d1c2SDorinda Bassey 551c2d3d1c2SDorinda Bassey /* report the audio format we support */ 552c2d3d1c2SDorinda Bassey audio_pcm_init_info(&hw->info, &obt_as); 553c2d3d1c2SDorinda Bassey 554c2d3d1c2SDorinda Bassey /* report the buffer size to qemu */ 555c2d3d1c2SDorinda Bassey hw->samples = audio_buffer_frames( 556c2d3d1c2SDorinda Bassey qapi_AudiodevPipewirePerDirectionOptions_base(ppdo), &obt_as, 46440); 557c2d3d1c2SDorinda Bassey v->highwater_mark = MIN(RINGBUFFER_SIZE, 558c2d3d1c2SDorinda Bassey (ppdo->has_latency ? ppdo->latency : 46440) 559c2d3d1c2SDorinda Bassey * (uint64_t)v->info.rate / 1000000 * v->frame_size); 560c2d3d1c2SDorinda Bassey 561c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 562c2d3d1c2SDorinda Bassey return 0; 563c2d3d1c2SDorinda Bassey } 564c2d3d1c2SDorinda Bassey 565c2d3d1c2SDorinda Bassey static int 566c2d3d1c2SDorinda Bassey qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) 567c2d3d1c2SDorinda Bassey { 568c2d3d1c2SDorinda Bassey PWVoiceIn *pw = (PWVoiceIn *) hw; 569c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 570c2d3d1c2SDorinda Bassey struct audsettings obt_as = *as; 571c2d3d1c2SDorinda Bassey pwaudio *c = v->g = drv_opaque; 572c2d3d1c2SDorinda Bassey AudiodevPipewireOptions *popts = &c->dev->u.pipewire; 573c2d3d1c2SDorinda Bassey AudiodevPipewirePerDirectionOptions *ppdo = popts->in; 574c2d3d1c2SDorinda Bassey int r; 575c2d3d1c2SDorinda Bassey 576c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 577c2d3d1c2SDorinda Bassey 578c2d3d1c2SDorinda Bassey v->info.format = audfmt_to_pw(as->fmt, as->endianness); 579c2d3d1c2SDorinda Bassey v->info.channels = as->nchannels; 58092f69a2cSMarc-André Lureau qpw_set_position(as->nchannels, v->info.position); 581c2d3d1c2SDorinda Bassey v->info.rate = as->freq; 582c2d3d1c2SDorinda Bassey 583c2d3d1c2SDorinda Bassey obt_as.fmt = 584c2d3d1c2SDorinda Bassey pw_to_audfmt(v->info.format, &obt_as.endianness, &v->frame_size); 585c2d3d1c2SDorinda Bassey v->frame_size *= as->nchannels; 586c2d3d1c2SDorinda Bassey 587c2d3d1c2SDorinda Bassey /* call the function that creates a new stream for recording */ 588c2d3d1c2SDorinda Bassey r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id, 589c2d3d1c2SDorinda Bassey ppdo->name, SPA_DIRECTION_INPUT); 590c2d3d1c2SDorinda Bassey if (r < 0) { 591c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 592c2d3d1c2SDorinda Bassey return -1; 593c2d3d1c2SDorinda Bassey } 594c2d3d1c2SDorinda Bassey 595c2d3d1c2SDorinda Bassey /* report the audio format we support */ 596c2d3d1c2SDorinda Bassey audio_pcm_init_info(&hw->info, &obt_as); 597c2d3d1c2SDorinda Bassey 598c2d3d1c2SDorinda Bassey /* report the buffer size to qemu */ 599c2d3d1c2SDorinda Bassey hw->samples = audio_buffer_frames( 600c2d3d1c2SDorinda Bassey qapi_AudiodevPipewirePerDirectionOptions_base(ppdo), &obt_as, 46440); 601c2d3d1c2SDorinda Bassey 602c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 603c2d3d1c2SDorinda Bassey return 0; 604c2d3d1c2SDorinda Bassey } 605c2d3d1c2SDorinda Bassey 606c2d3d1c2SDorinda Bassey static void 60792fd7868SMarc-André Lureau qpw_voice_fini(PWVoice *v) 608c2d3d1c2SDorinda Bassey { 609c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 61092fd7868SMarc-André Lureau 61192fd7868SMarc-André Lureau if (!v->stream) { 61292fd7868SMarc-André Lureau return; 61392fd7868SMarc-André Lureau } 614c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 615c2d3d1c2SDorinda Bassey pw_stream_destroy(v->stream); 616c2d3d1c2SDorinda Bassey v->stream = NULL; 617c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 618c2d3d1c2SDorinda Bassey } 61992fd7868SMarc-André Lureau 62092fd7868SMarc-André Lureau static void 62192fd7868SMarc-André Lureau qpw_fini_out(HWVoiceOut *hw) 62292fd7868SMarc-André Lureau { 62392fd7868SMarc-André Lureau qpw_voice_fini(&PW_VOICE_OUT(hw)->v); 624c2d3d1c2SDorinda Bassey } 625c2d3d1c2SDorinda Bassey 626c2d3d1c2SDorinda Bassey static void 627c2d3d1c2SDorinda Bassey qpw_fini_in(HWVoiceIn *hw) 628c2d3d1c2SDorinda Bassey { 62992fd7868SMarc-André Lureau qpw_voice_fini(&PW_VOICE_IN(hw)->v); 63092fd7868SMarc-André Lureau } 631c2d3d1c2SDorinda Bassey 63292fd7868SMarc-André Lureau static void 63392fd7868SMarc-André Lureau qpw_voice_set_enabled(PWVoice *v, bool enable) 63492fd7868SMarc-André Lureau { 635c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 636c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 63792fd7868SMarc-André Lureau pw_stream_set_active(v->stream, enable); 638c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 639c2d3d1c2SDorinda Bassey } 640c2d3d1c2SDorinda Bassey 641c2d3d1c2SDorinda Bassey static void 642c2d3d1c2SDorinda Bassey qpw_enable_out(HWVoiceOut *hw, bool enable) 643c2d3d1c2SDorinda Bassey { 64492fd7868SMarc-André Lureau qpw_voice_set_enabled(&PW_VOICE_OUT(hw)->v, enable); 645c2d3d1c2SDorinda Bassey } 646c2d3d1c2SDorinda Bassey 647c2d3d1c2SDorinda Bassey static void 648c2d3d1c2SDorinda Bassey qpw_enable_in(HWVoiceIn *hw, bool enable) 649c2d3d1c2SDorinda Bassey { 65092fd7868SMarc-André Lureau qpw_voice_set_enabled(&PW_VOICE_IN(hw)->v, enable); 65192fd7868SMarc-André Lureau } 65292fd7868SMarc-André Lureau 65392fd7868SMarc-André Lureau static void 65492fd7868SMarc-André Lureau qpw_voice_set_volume(PWVoice *v, Volume *vol) 65592fd7868SMarc-André Lureau { 656c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 65792fd7868SMarc-André Lureau int i, ret; 65892fd7868SMarc-André Lureau 659c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 66092fd7868SMarc-André Lureau v->volume.channels = vol->channels; 66192fd7868SMarc-André Lureau 66292fd7868SMarc-André Lureau for (i = 0; i < vol->channels; ++i) { 66392fd7868SMarc-André Lureau v->volume.values[i] = (float)vol->vol[i] / 255; 66492fd7868SMarc-André Lureau } 66592fd7868SMarc-André Lureau 66692fd7868SMarc-André Lureau ret = pw_stream_set_control(v->stream, 66792fd7868SMarc-André Lureau SPA_PROP_channelVolumes, v->volume.channels, v->volume.values, 0); 66892fd7868SMarc-André Lureau trace_pw_vol(ret == 0 ? "success" : "failed"); 66992fd7868SMarc-André Lureau 67092fd7868SMarc-André Lureau v->muted = vol->mute; 67192fd7868SMarc-André Lureau float val = v->muted ? 1.f : 0.f; 67292fd7868SMarc-André Lureau ret = pw_stream_set_control(v->stream, SPA_PROP_mute, 1, &val, 0); 673c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 674c2d3d1c2SDorinda Bassey } 675c2d3d1c2SDorinda Bassey 676c2d3d1c2SDorinda Bassey static void 677c2d3d1c2SDorinda Bassey qpw_volume_out(HWVoiceOut *hw, Volume *vol) 678c2d3d1c2SDorinda Bassey { 67992fd7868SMarc-André Lureau qpw_voice_set_volume(&PW_VOICE_OUT(hw)->v, vol); 680c2d3d1c2SDorinda Bassey } 681c2d3d1c2SDorinda Bassey 682c2d3d1c2SDorinda Bassey static void 683c2d3d1c2SDorinda Bassey qpw_volume_in(HWVoiceIn *hw, Volume *vol) 684c2d3d1c2SDorinda Bassey { 68592fd7868SMarc-André Lureau qpw_voice_set_volume(&PW_VOICE_IN(hw)->v, vol); 686c2d3d1c2SDorinda Bassey } 687c2d3d1c2SDorinda Bassey 688c2d3d1c2SDorinda Bassey static int wait_resync(pwaudio *pw) 689c2d3d1c2SDorinda Bassey { 690c2d3d1c2SDorinda Bassey int res; 691c2d3d1c2SDorinda Bassey pw->pending_seq = pw_core_sync(pw->core, PW_ID_CORE, pw->pending_seq); 692c2d3d1c2SDorinda Bassey 693c2d3d1c2SDorinda Bassey while (true) { 694c2d3d1c2SDorinda Bassey pw_thread_loop_wait(pw->thread_loop); 695c2d3d1c2SDorinda Bassey 696c2d3d1c2SDorinda Bassey res = pw->error; 697c2d3d1c2SDorinda Bassey if (res < 0) { 698c2d3d1c2SDorinda Bassey pw->error = 0; 699c2d3d1c2SDorinda Bassey return res; 700c2d3d1c2SDorinda Bassey } 701c2d3d1c2SDorinda Bassey if (pw->pending_seq == pw->last_seq) { 702c2d3d1c2SDorinda Bassey break; 703c2d3d1c2SDorinda Bassey } 704c2d3d1c2SDorinda Bassey } 705c2d3d1c2SDorinda Bassey return 0; 706c2d3d1c2SDorinda Bassey } 70724a9095cSMarc-André Lureau 708c2d3d1c2SDorinda Bassey static void 709c2d3d1c2SDorinda Bassey on_core_error(void *data, uint32_t id, int seq, int res, const char *message) 710c2d3d1c2SDorinda Bassey { 711c2d3d1c2SDorinda Bassey pwaudio *pw = data; 712c2d3d1c2SDorinda Bassey 713c2d3d1c2SDorinda Bassey error_report("error id:%u seq:%d res:%d (%s): %s", 714c2d3d1c2SDorinda Bassey id, seq, res, spa_strerror(res), message); 715c2d3d1c2SDorinda Bassey 716c2d3d1c2SDorinda Bassey /* stop and exit the thread loop */ 717c2d3d1c2SDorinda Bassey pw_thread_loop_signal(pw->thread_loop, FALSE); 718c2d3d1c2SDorinda Bassey } 719c2d3d1c2SDorinda Bassey 720c2d3d1c2SDorinda Bassey static void 721c2d3d1c2SDorinda Bassey on_core_done(void *data, uint32_t id, int seq) 722c2d3d1c2SDorinda Bassey { 723c2d3d1c2SDorinda Bassey pwaudio *pw = data; 724c2d3d1c2SDorinda Bassey assert(id == PW_ID_CORE); 725c2d3d1c2SDorinda Bassey pw->last_seq = seq; 726c2d3d1c2SDorinda Bassey if (pw->pending_seq == seq) { 727c2d3d1c2SDorinda Bassey /* stop and exit the thread loop */ 728c2d3d1c2SDorinda Bassey pw_thread_loop_signal(pw->thread_loop, FALSE); 729c2d3d1c2SDorinda Bassey } 730c2d3d1c2SDorinda Bassey } 731c2d3d1c2SDorinda Bassey 732c2d3d1c2SDorinda Bassey static const struct pw_core_events core_events = { 733c2d3d1c2SDorinda Bassey PW_VERSION_CORE_EVENTS, 734c2d3d1c2SDorinda Bassey .done = on_core_done, 735c2d3d1c2SDorinda Bassey .error = on_core_error, 736c2d3d1c2SDorinda Bassey }; 737c2d3d1c2SDorinda Bassey 738c2d3d1c2SDorinda Bassey static void * 739f6061733SPaolo Bonzini qpw_audio_init(Audiodev *dev, Error **errp) 740c2d3d1c2SDorinda Bassey { 741c2d3d1c2SDorinda Bassey g_autofree pwaudio *pw = g_new0(pwaudio, 1); 742c2d3d1c2SDorinda Bassey 743c2d3d1c2SDorinda Bassey assert(dev->driver == AUDIODEV_DRIVER_PIPEWIRE); 74487048d20SMarc-André Lureau trace_pw_audio_init(); 74587048d20SMarc-André Lureau 74687048d20SMarc-André Lureau pw_init(NULL, NULL); 747c2d3d1c2SDorinda Bassey 748c2d3d1c2SDorinda Bassey pw->dev = dev; 74920c51248SMarc-André Lureau pw->thread_loop = pw_thread_loop_new("PipeWire thread loop", NULL); 750c2d3d1c2SDorinda Bassey if (pw->thread_loop == NULL) { 751f6061733SPaolo Bonzini error_setg_errno(errp, errno, "Could not create PipeWire loop"); 752c2d3d1c2SDorinda Bassey goto fail; 753c2d3d1c2SDorinda Bassey } 754c2d3d1c2SDorinda Bassey 755c2d3d1c2SDorinda Bassey pw->context = 756c2d3d1c2SDorinda Bassey pw_context_new(pw_thread_loop_get_loop(pw->thread_loop), NULL, 0); 757c2d3d1c2SDorinda Bassey if (pw->context == NULL) { 758f6061733SPaolo Bonzini error_setg_errno(errp, errno, "Could not create PipeWire context"); 759c2d3d1c2SDorinda Bassey goto fail; 760c2d3d1c2SDorinda Bassey } 761c2d3d1c2SDorinda Bassey 762c2d3d1c2SDorinda Bassey if (pw_thread_loop_start(pw->thread_loop) < 0) { 763f6061733SPaolo Bonzini error_setg_errno(errp, errno, "Could not start PipeWire loop"); 764c2d3d1c2SDorinda Bassey goto fail; 765c2d3d1c2SDorinda Bassey } 766c2d3d1c2SDorinda Bassey 767c2d3d1c2SDorinda Bassey pw_thread_loop_lock(pw->thread_loop); 768c2d3d1c2SDorinda Bassey 769c2d3d1c2SDorinda Bassey pw->core = pw_context_connect(pw->context, NULL, 0); 770c2d3d1c2SDorinda Bassey if (pw->core == NULL) { 771c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(pw->thread_loop); 772*4cd78a3dSMichal Privoznik error_setg_errno(errp, errno, "Failed to connect to PipeWire instance"); 773*4cd78a3dSMichal Privoznik goto fail; 774c2d3d1c2SDorinda Bassey } 775c2d3d1c2SDorinda Bassey 776c2d3d1c2SDorinda Bassey if (pw_core_add_listener(pw->core, &pw->core_listener, 777c2d3d1c2SDorinda Bassey &core_events, pw) < 0) { 778c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(pw->thread_loop); 779*4cd78a3dSMichal Privoznik error_setg(errp, "Failed to add PipeWire listener"); 780*4cd78a3dSMichal Privoznik goto fail; 781c2d3d1c2SDorinda Bassey } 782c2d3d1c2SDorinda Bassey if (wait_resync(pw) < 0) { 783c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(pw->thread_loop); 784c2d3d1c2SDorinda Bassey } 785c2d3d1c2SDorinda Bassey 786c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(pw->thread_loop); 787c2d3d1c2SDorinda Bassey 788c2d3d1c2SDorinda Bassey return g_steal_pointer(&pw); 789c2d3d1c2SDorinda Bassey 790c2d3d1c2SDorinda Bassey fail: 791c2d3d1c2SDorinda Bassey if (pw->thread_loop) { 792c2d3d1c2SDorinda Bassey pw_thread_loop_stop(pw->thread_loop); 793c2d3d1c2SDorinda Bassey } 794c2d3d1c2SDorinda Bassey g_clear_pointer(&pw->context, pw_context_destroy); 795c2d3d1c2SDorinda Bassey g_clear_pointer(&pw->thread_loop, pw_thread_loop_destroy); 796c2d3d1c2SDorinda Bassey return NULL; 797c2d3d1c2SDorinda Bassey } 798c2d3d1c2SDorinda Bassey 799c2d3d1c2SDorinda Bassey static void 800c2d3d1c2SDorinda Bassey qpw_audio_fini(void *opaque) 801c2d3d1c2SDorinda Bassey { 802c2d3d1c2SDorinda Bassey pwaudio *pw = opaque; 803c2d3d1c2SDorinda Bassey 804c2d3d1c2SDorinda Bassey if (pw->thread_loop) { 805c2d3d1c2SDorinda Bassey pw_thread_loop_stop(pw->thread_loop); 806c2d3d1c2SDorinda Bassey } 807c2d3d1c2SDorinda Bassey 808c2d3d1c2SDorinda Bassey if (pw->core) { 809c2d3d1c2SDorinda Bassey spa_hook_remove(&pw->core_listener); 810c2d3d1c2SDorinda Bassey spa_zero(pw->core_listener); 811c2d3d1c2SDorinda Bassey pw_core_disconnect(pw->core); 812c2d3d1c2SDorinda Bassey } 813c2d3d1c2SDorinda Bassey 814c2d3d1c2SDorinda Bassey if (pw->context) { 815c2d3d1c2SDorinda Bassey pw_context_destroy(pw->context); 816c2d3d1c2SDorinda Bassey } 817c2d3d1c2SDorinda Bassey pw_thread_loop_destroy(pw->thread_loop); 818c2d3d1c2SDorinda Bassey 819c2d3d1c2SDorinda Bassey g_free(pw); 820c2d3d1c2SDorinda Bassey } 821c2d3d1c2SDorinda Bassey 822c2d3d1c2SDorinda Bassey static struct audio_pcm_ops qpw_pcm_ops = { 823c2d3d1c2SDorinda Bassey .init_out = qpw_init_out, 824c2d3d1c2SDorinda Bassey .fini_out = qpw_fini_out, 825c2d3d1c2SDorinda Bassey .write = qpw_write, 826c2d3d1c2SDorinda Bassey .buffer_get_free = qpw_buffer_get_free, 827c2d3d1c2SDorinda Bassey .run_buffer_out = audio_generic_run_buffer_out, 828c2d3d1c2SDorinda Bassey .enable_out = qpw_enable_out, 829c2d3d1c2SDorinda Bassey .volume_out = qpw_volume_out, 830c2d3d1c2SDorinda Bassey .volume_in = qpw_volume_in, 831c2d3d1c2SDorinda Bassey 832c2d3d1c2SDorinda Bassey .init_in = qpw_init_in, 833c2d3d1c2SDorinda Bassey .fini_in = qpw_fini_in, 834c2d3d1c2SDorinda Bassey .read = qpw_read, 835c2d3d1c2SDorinda Bassey .run_buffer_in = audio_generic_run_buffer_in, 836c2d3d1c2SDorinda Bassey .enable_in = qpw_enable_in 837c2d3d1c2SDorinda Bassey }; 838c2d3d1c2SDorinda Bassey 839c2d3d1c2SDorinda Bassey static struct audio_driver pw_audio_driver = { 840c2d3d1c2SDorinda Bassey .name = "pipewire", 841c2d3d1c2SDorinda Bassey .descr = "http://www.pipewire.org/", 842c2d3d1c2SDorinda Bassey .init = qpw_audio_init, 843c2d3d1c2SDorinda Bassey .fini = qpw_audio_fini, 844c2d3d1c2SDorinda Bassey .pcm_ops = &qpw_pcm_ops, 845c2d3d1c2SDorinda Bassey .max_voices_out = INT_MAX, 846c2d3d1c2SDorinda Bassey .max_voices_in = INT_MAX, 847c2d3d1c2SDorinda Bassey .voice_size_out = sizeof(PWVoiceOut), 848c2d3d1c2SDorinda Bassey .voice_size_in = sizeof(PWVoiceIn), 849c2d3d1c2SDorinda Bassey }; 850c2d3d1c2SDorinda Bassey 851c2d3d1c2SDorinda Bassey static void 852c2d3d1c2SDorinda Bassey register_audio_pw(void) 853c2d3d1c2SDorinda Bassey { 854c2d3d1c2SDorinda Bassey audio_driver_register(&pw_audio_driver); 855c2d3d1c2SDorinda Bassey } 856c2d3d1c2SDorinda Bassey 857c2d3d1c2SDorinda Bassey type_init(register_audio_pw); 858