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 <errno.h> 15c2d3d1c2SDorinda Bassey #include "qemu/error-report.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 69c2d3d1c2SDorinda Bassey static void 70c2d3d1c2SDorinda Bassey stream_destroy(void *data) 71c2d3d1c2SDorinda Bassey { 72c2d3d1c2SDorinda Bassey PWVoice *v = (PWVoice *) data; 73c2d3d1c2SDorinda Bassey spa_hook_remove(&v->stream_listener); 74c2d3d1c2SDorinda Bassey v->stream = NULL; 75c2d3d1c2SDorinda Bassey } 76c2d3d1c2SDorinda Bassey 77c2d3d1c2SDorinda Bassey /* output data processing function to read stuffs from the buffer */ 78c2d3d1c2SDorinda Bassey static void 79c2d3d1c2SDorinda Bassey playback_on_process(void *data) 80c2d3d1c2SDorinda Bassey { 81c2d3d1c2SDorinda Bassey PWVoice *v = data; 82c2d3d1c2SDorinda Bassey void *p; 83c2d3d1c2SDorinda Bassey struct pw_buffer *b; 84c2d3d1c2SDorinda Bassey struct spa_buffer *buf; 85c2d3d1c2SDorinda Bassey uint32_t req, index, n_bytes; 86c2d3d1c2SDorinda Bassey int32_t avail; 87c2d3d1c2SDorinda Bassey 88c2d3d1c2SDorinda Bassey assert(v->stream); 89c2d3d1c2SDorinda Bassey 90c2d3d1c2SDorinda Bassey /* obtain a buffer to read from */ 91c2d3d1c2SDorinda Bassey b = pw_stream_dequeue_buffer(v->stream); 92c2d3d1c2SDorinda Bassey if (b == NULL) { 93c2d3d1c2SDorinda Bassey error_report("out of buffers: %s", strerror(errno)); 94c2d3d1c2SDorinda Bassey return; 95c2d3d1c2SDorinda Bassey } 96c2d3d1c2SDorinda Bassey 97c2d3d1c2SDorinda Bassey buf = b->buffer; 98c2d3d1c2SDorinda Bassey p = buf->datas[0].data; 99c2d3d1c2SDorinda Bassey if (p == NULL) { 100c2d3d1c2SDorinda Bassey return; 101c2d3d1c2SDorinda Bassey } 102c2d3d1c2SDorinda Bassey /* calculate the total no of bytes to read data from buffer */ 103c2d3d1c2SDorinda Bassey req = b->requested * v->frame_size; 104c2d3d1c2SDorinda Bassey if (req == 0) { 105c2d3d1c2SDorinda Bassey req = v->req; 106c2d3d1c2SDorinda Bassey } 107c2d3d1c2SDorinda Bassey n_bytes = SPA_MIN(req, buf->datas[0].maxsize); 108c2d3d1c2SDorinda Bassey 109c2d3d1c2SDorinda Bassey /* get no of available bytes to read data from buffer */ 110c2d3d1c2SDorinda Bassey avail = spa_ringbuffer_get_read_index(&v->ring, &index); 111c2d3d1c2SDorinda Bassey 112c2d3d1c2SDorinda Bassey if (avail <= 0) { 113c2d3d1c2SDorinda Bassey PWVoiceOut *vo = container_of(data, PWVoiceOut, v); 114c2d3d1c2SDorinda Bassey audio_pcm_info_clear_buf(&vo->hw.info, p, n_bytes / v->frame_size); 115c2d3d1c2SDorinda Bassey } else { 116c2d3d1c2SDorinda Bassey if ((uint32_t) avail < n_bytes) { 117c2d3d1c2SDorinda Bassey /* 118c2d3d1c2SDorinda Bassey * PipeWire immediately calls this callback again if we provide 119c2d3d1c2SDorinda Bassey * less than n_bytes. Then audio_pcm_info_clear_buf() fills the 120c2d3d1c2SDorinda Bassey * rest of the buffer with silence. 121c2d3d1c2SDorinda Bassey */ 122c2d3d1c2SDorinda Bassey n_bytes = avail; 123c2d3d1c2SDorinda Bassey } 124c2d3d1c2SDorinda Bassey 125c2d3d1c2SDorinda Bassey spa_ringbuffer_read_data(&v->ring, 126c2d3d1c2SDorinda Bassey v->buffer, RINGBUFFER_SIZE, 127c2d3d1c2SDorinda Bassey index & RINGBUFFER_MASK, p, n_bytes); 128c2d3d1c2SDorinda Bassey 129c2d3d1c2SDorinda Bassey index += n_bytes; 130c2d3d1c2SDorinda Bassey spa_ringbuffer_read_update(&v->ring, index); 131c2d3d1c2SDorinda Bassey 132c2d3d1c2SDorinda Bassey } 133c2d3d1c2SDorinda Bassey buf->datas[0].chunk->offset = 0; 134c2d3d1c2SDorinda Bassey buf->datas[0].chunk->stride = v->frame_size; 135c2d3d1c2SDorinda Bassey buf->datas[0].chunk->size = n_bytes; 136c2d3d1c2SDorinda Bassey 137c2d3d1c2SDorinda Bassey /* queue the buffer for playback */ 138c2d3d1c2SDorinda Bassey pw_stream_queue_buffer(v->stream, b); 139c2d3d1c2SDorinda Bassey } 140c2d3d1c2SDorinda Bassey 141c2d3d1c2SDorinda Bassey /* output data processing function to generate stuffs in the buffer */ 142c2d3d1c2SDorinda Bassey static void 143c2d3d1c2SDorinda Bassey capture_on_process(void *data) 144c2d3d1c2SDorinda Bassey { 145c2d3d1c2SDorinda Bassey PWVoice *v = (PWVoice *) data; 146c2d3d1c2SDorinda Bassey void *p; 147c2d3d1c2SDorinda Bassey struct pw_buffer *b; 148c2d3d1c2SDorinda Bassey struct spa_buffer *buf; 149c2d3d1c2SDorinda Bassey int32_t filled; 150c2d3d1c2SDorinda Bassey uint32_t index, offs, n_bytes; 151c2d3d1c2SDorinda Bassey 152c2d3d1c2SDorinda Bassey assert(v->stream); 153c2d3d1c2SDorinda Bassey 154c2d3d1c2SDorinda Bassey /* obtain a buffer */ 155c2d3d1c2SDorinda Bassey b = pw_stream_dequeue_buffer(v->stream); 156c2d3d1c2SDorinda Bassey if (b == NULL) { 157c2d3d1c2SDorinda Bassey error_report("out of buffers: %s", strerror(errno)); 158c2d3d1c2SDorinda Bassey return; 159c2d3d1c2SDorinda Bassey } 160c2d3d1c2SDorinda Bassey 161c2d3d1c2SDorinda Bassey /* Write data into buffer */ 162c2d3d1c2SDorinda Bassey buf = b->buffer; 163c2d3d1c2SDorinda Bassey p = buf->datas[0].data; 164c2d3d1c2SDorinda Bassey if (p == NULL) { 165c2d3d1c2SDorinda Bassey return; 166c2d3d1c2SDorinda Bassey } 167c2d3d1c2SDorinda Bassey offs = SPA_MIN(buf->datas[0].chunk->offset, buf->datas[0].maxsize); 168c2d3d1c2SDorinda Bassey n_bytes = SPA_MIN(buf->datas[0].chunk->size, buf->datas[0].maxsize - offs); 169c2d3d1c2SDorinda Bassey 170c2d3d1c2SDorinda Bassey filled = spa_ringbuffer_get_write_index(&v->ring, &index); 171c2d3d1c2SDorinda Bassey 172c2d3d1c2SDorinda Bassey 173c2d3d1c2SDorinda Bassey if (filled < 0) { 174c2d3d1c2SDorinda Bassey error_report("%p: underrun write:%u filled:%d", p, index, filled); 175c2d3d1c2SDorinda Bassey } else { 176c2d3d1c2SDorinda Bassey if ((uint32_t) filled + n_bytes > RINGBUFFER_SIZE) { 177c2d3d1c2SDorinda Bassey error_report("%p: overrun write:%u filled:%d + size:%u > max:%u", 178c2d3d1c2SDorinda Bassey p, index, filled, n_bytes, RINGBUFFER_SIZE); 179c2d3d1c2SDorinda Bassey } 180c2d3d1c2SDorinda Bassey } 181c2d3d1c2SDorinda Bassey spa_ringbuffer_write_data(&v->ring, 182c2d3d1c2SDorinda Bassey v->buffer, RINGBUFFER_SIZE, 183c2d3d1c2SDorinda Bassey index & RINGBUFFER_MASK, 184c2d3d1c2SDorinda Bassey SPA_PTROFF(p, offs, void), n_bytes); 185c2d3d1c2SDorinda Bassey index += n_bytes; 186c2d3d1c2SDorinda Bassey spa_ringbuffer_write_update(&v->ring, index); 187c2d3d1c2SDorinda Bassey 188c2d3d1c2SDorinda Bassey /* queue the buffer for playback */ 189c2d3d1c2SDorinda Bassey pw_stream_queue_buffer(v->stream, b); 190c2d3d1c2SDorinda Bassey } 191c2d3d1c2SDorinda Bassey 192c2d3d1c2SDorinda Bassey static void 193c2d3d1c2SDorinda Bassey on_stream_state_changed(void *data, enum pw_stream_state old, 194c2d3d1c2SDorinda Bassey enum pw_stream_state state, const char *error) 195c2d3d1c2SDorinda Bassey { 196c2d3d1c2SDorinda Bassey PWVoice *v = (PWVoice *) data; 197c2d3d1c2SDorinda Bassey 198c2d3d1c2SDorinda Bassey trace_pw_state_changed(pw_stream_get_node_id(v->stream), 199c2d3d1c2SDorinda Bassey pw_stream_state_as_string(state)); 200c2d3d1c2SDorinda Bassey } 201c2d3d1c2SDorinda Bassey 202c2d3d1c2SDorinda Bassey static const struct pw_stream_events capture_stream_events = { 203c2d3d1c2SDorinda Bassey PW_VERSION_STREAM_EVENTS, 204c2d3d1c2SDorinda Bassey .destroy = stream_destroy, 205c2d3d1c2SDorinda Bassey .state_changed = on_stream_state_changed, 206c2d3d1c2SDorinda Bassey .process = capture_on_process 207c2d3d1c2SDorinda Bassey }; 208c2d3d1c2SDorinda Bassey 209c2d3d1c2SDorinda Bassey static const struct pw_stream_events playback_stream_events = { 210c2d3d1c2SDorinda Bassey PW_VERSION_STREAM_EVENTS, 211c2d3d1c2SDorinda Bassey .destroy = stream_destroy, 212c2d3d1c2SDorinda Bassey .state_changed = on_stream_state_changed, 213c2d3d1c2SDorinda Bassey .process = playback_on_process 214c2d3d1c2SDorinda Bassey }; 215c2d3d1c2SDorinda Bassey 216c2d3d1c2SDorinda Bassey static size_t 217c2d3d1c2SDorinda Bassey qpw_read(HWVoiceIn *hw, void *data, size_t len) 218c2d3d1c2SDorinda Bassey { 219c2d3d1c2SDorinda Bassey PWVoiceIn *pw = (PWVoiceIn *) hw; 220c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 221c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 222c2d3d1c2SDorinda Bassey const char *error = NULL; 223c2d3d1c2SDorinda Bassey size_t l; 224c2d3d1c2SDorinda Bassey int32_t avail; 225c2d3d1c2SDorinda Bassey uint32_t index; 226c2d3d1c2SDorinda Bassey 227c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 228c2d3d1c2SDorinda Bassey if (pw_stream_get_state(v->stream, &error) != PW_STREAM_STATE_STREAMING) { 229c2d3d1c2SDorinda Bassey /* wait for stream to become ready */ 230c2d3d1c2SDorinda Bassey l = 0; 231c2d3d1c2SDorinda Bassey goto done_unlock; 232c2d3d1c2SDorinda Bassey } 233c2d3d1c2SDorinda Bassey /* get no of available bytes to read data from buffer */ 234c2d3d1c2SDorinda Bassey avail = spa_ringbuffer_get_read_index(&v->ring, &index); 235c2d3d1c2SDorinda Bassey 236c2d3d1c2SDorinda Bassey trace_pw_read(avail, index, len); 237c2d3d1c2SDorinda Bassey 238c2d3d1c2SDorinda Bassey if (avail < (int32_t) len) { 239c2d3d1c2SDorinda Bassey len = avail; 240c2d3d1c2SDorinda Bassey } 241c2d3d1c2SDorinda Bassey 242c2d3d1c2SDorinda Bassey spa_ringbuffer_read_data(&v->ring, 243c2d3d1c2SDorinda Bassey v->buffer, RINGBUFFER_SIZE, 244c2d3d1c2SDorinda Bassey index & RINGBUFFER_MASK, data, len); 245c2d3d1c2SDorinda Bassey index += len; 246c2d3d1c2SDorinda Bassey spa_ringbuffer_read_update(&v->ring, index); 247c2d3d1c2SDorinda Bassey l = len; 248c2d3d1c2SDorinda Bassey 249c2d3d1c2SDorinda Bassey done_unlock: 250c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 251c2d3d1c2SDorinda Bassey return l; 252c2d3d1c2SDorinda Bassey } 253c2d3d1c2SDorinda Bassey 254c2d3d1c2SDorinda Bassey static size_t qpw_buffer_get_free(HWVoiceOut *hw) 255c2d3d1c2SDorinda Bassey { 256c2d3d1c2SDorinda Bassey PWVoiceOut *pw = (PWVoiceOut *)hw; 257c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 258c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 259c2d3d1c2SDorinda Bassey const char *error = NULL; 260c2d3d1c2SDorinda Bassey int32_t filled, avail; 261c2d3d1c2SDorinda Bassey uint32_t index; 262c2d3d1c2SDorinda Bassey 263c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 264c2d3d1c2SDorinda Bassey if (pw_stream_get_state(v->stream, &error) != PW_STREAM_STATE_STREAMING) { 265c2d3d1c2SDorinda Bassey /* wait for stream to become ready */ 266c2d3d1c2SDorinda Bassey avail = 0; 267c2d3d1c2SDorinda Bassey goto done_unlock; 268c2d3d1c2SDorinda Bassey } 269c2d3d1c2SDorinda Bassey 270c2d3d1c2SDorinda Bassey filled = spa_ringbuffer_get_write_index(&v->ring, &index); 271c2d3d1c2SDorinda Bassey avail = v->highwater_mark - filled; 272c2d3d1c2SDorinda Bassey 273c2d3d1c2SDorinda Bassey done_unlock: 274c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 275c2d3d1c2SDorinda Bassey return avail; 276c2d3d1c2SDorinda Bassey } 277c2d3d1c2SDorinda Bassey 278c2d3d1c2SDorinda Bassey static size_t 279c2d3d1c2SDorinda Bassey qpw_write(HWVoiceOut *hw, void *data, size_t len) 280c2d3d1c2SDorinda Bassey { 281c2d3d1c2SDorinda Bassey PWVoiceOut *pw = (PWVoiceOut *) hw; 282c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 283c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 284c2d3d1c2SDorinda Bassey const char *error = NULL; 285c2d3d1c2SDorinda Bassey int32_t filled, avail; 286c2d3d1c2SDorinda Bassey uint32_t index; 287c2d3d1c2SDorinda Bassey 288c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 289c2d3d1c2SDorinda Bassey if (pw_stream_get_state(v->stream, &error) != PW_STREAM_STATE_STREAMING) { 290c2d3d1c2SDorinda Bassey /* wait for stream to become ready */ 291c2d3d1c2SDorinda Bassey len = 0; 292c2d3d1c2SDorinda Bassey goto done_unlock; 293c2d3d1c2SDorinda Bassey } 294c2d3d1c2SDorinda Bassey filled = spa_ringbuffer_get_write_index(&v->ring, &index); 295c2d3d1c2SDorinda Bassey avail = v->highwater_mark - filled; 296c2d3d1c2SDorinda Bassey 297c2d3d1c2SDorinda Bassey trace_pw_write(filled, avail, index, len); 298c2d3d1c2SDorinda Bassey 299c2d3d1c2SDorinda Bassey if (len > avail) { 300c2d3d1c2SDorinda Bassey len = avail; 301c2d3d1c2SDorinda Bassey } 302c2d3d1c2SDorinda Bassey 303c2d3d1c2SDorinda Bassey if (filled < 0) { 304c2d3d1c2SDorinda Bassey error_report("%p: underrun write:%u filled:%d", pw, index, filled); 305c2d3d1c2SDorinda Bassey } else { 306c2d3d1c2SDorinda Bassey if ((uint32_t) filled + len > RINGBUFFER_SIZE) { 307c2d3d1c2SDorinda Bassey error_report("%p: overrun write:%u filled:%d + size:%zu > max:%u", 308c2d3d1c2SDorinda Bassey pw, index, filled, len, RINGBUFFER_SIZE); 309c2d3d1c2SDorinda Bassey } 310c2d3d1c2SDorinda Bassey } 311c2d3d1c2SDorinda Bassey 312c2d3d1c2SDorinda Bassey spa_ringbuffer_write_data(&v->ring, 313c2d3d1c2SDorinda Bassey v->buffer, RINGBUFFER_SIZE, 314c2d3d1c2SDorinda Bassey index & RINGBUFFER_MASK, data, len); 315c2d3d1c2SDorinda Bassey index += len; 316c2d3d1c2SDorinda Bassey spa_ringbuffer_write_update(&v->ring, index); 317c2d3d1c2SDorinda Bassey 318c2d3d1c2SDorinda Bassey done_unlock: 319c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 320c2d3d1c2SDorinda Bassey return len; 321c2d3d1c2SDorinda Bassey } 322c2d3d1c2SDorinda Bassey 323c2d3d1c2SDorinda Bassey static int 324c2d3d1c2SDorinda Bassey audfmt_to_pw(AudioFormat fmt, int endianness) 325c2d3d1c2SDorinda Bassey { 326c2d3d1c2SDorinda Bassey int format; 327c2d3d1c2SDorinda Bassey 328c2d3d1c2SDorinda Bassey switch (fmt) { 329c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_S8: 330c2d3d1c2SDorinda Bassey format = SPA_AUDIO_FORMAT_S8; 331c2d3d1c2SDorinda Bassey break; 332c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_U8: 333c2d3d1c2SDorinda Bassey format = SPA_AUDIO_FORMAT_U8; 334c2d3d1c2SDorinda Bassey break; 335c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_S16: 336c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_S16_BE : SPA_AUDIO_FORMAT_S16_LE; 337c2d3d1c2SDorinda Bassey break; 338c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_U16: 339c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_U16_BE : SPA_AUDIO_FORMAT_U16_LE; 340c2d3d1c2SDorinda Bassey break; 341c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_S32: 342c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_S32_BE : SPA_AUDIO_FORMAT_S32_LE; 343c2d3d1c2SDorinda Bassey break; 344c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_U32: 345c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_U32_BE : SPA_AUDIO_FORMAT_U32_LE; 346c2d3d1c2SDorinda Bassey break; 347c2d3d1c2SDorinda Bassey case AUDIO_FORMAT_F32: 348c2d3d1c2SDorinda Bassey format = endianness ? SPA_AUDIO_FORMAT_F32_BE : SPA_AUDIO_FORMAT_F32_LE; 349c2d3d1c2SDorinda Bassey break; 350c2d3d1c2SDorinda Bassey default: 351c2d3d1c2SDorinda Bassey dolog("Internal logic error: Bad audio format %d\n", fmt); 352c2d3d1c2SDorinda Bassey format = SPA_AUDIO_FORMAT_U8; 353c2d3d1c2SDorinda Bassey break; 354c2d3d1c2SDorinda Bassey } 355c2d3d1c2SDorinda Bassey return format; 356c2d3d1c2SDorinda Bassey } 357c2d3d1c2SDorinda Bassey 358c2d3d1c2SDorinda Bassey static AudioFormat 359c2d3d1c2SDorinda Bassey pw_to_audfmt(enum spa_audio_format fmt, int *endianness, 360c2d3d1c2SDorinda Bassey uint32_t *sample_size) 361c2d3d1c2SDorinda Bassey { 362c2d3d1c2SDorinda Bassey switch (fmt) { 363c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S8: 364c2d3d1c2SDorinda Bassey *sample_size = 1; 365c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S8; 366c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U8: 367c2d3d1c2SDorinda Bassey *sample_size = 1; 368c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U8; 369c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S16_BE: 370c2d3d1c2SDorinda Bassey *sample_size = 2; 371c2d3d1c2SDorinda Bassey *endianness = 1; 372c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S16; 373c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S16_LE: 374c2d3d1c2SDorinda Bassey *sample_size = 2; 375c2d3d1c2SDorinda Bassey *endianness = 0; 376c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S16; 377c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U16_BE: 378c2d3d1c2SDorinda Bassey *sample_size = 2; 379c2d3d1c2SDorinda Bassey *endianness = 1; 380c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U16; 381c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U16_LE: 382c2d3d1c2SDorinda Bassey *sample_size = 2; 383c2d3d1c2SDorinda Bassey *endianness = 0; 384c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U16; 385c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S32_BE: 386c2d3d1c2SDorinda Bassey *sample_size = 4; 387c2d3d1c2SDorinda Bassey *endianness = 1; 388c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S32; 389c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_S32_LE: 390c2d3d1c2SDorinda Bassey *sample_size = 4; 391c2d3d1c2SDorinda Bassey *endianness = 0; 392c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_S32; 393c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U32_BE: 394c2d3d1c2SDorinda Bassey *sample_size = 4; 395c2d3d1c2SDorinda Bassey *endianness = 1; 396c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U32; 397c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_U32_LE: 398c2d3d1c2SDorinda Bassey *sample_size = 4; 399c2d3d1c2SDorinda Bassey *endianness = 0; 400c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U32; 401c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_F32_BE: 402c2d3d1c2SDorinda Bassey *sample_size = 4; 403c2d3d1c2SDorinda Bassey *endianness = 1; 404c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_F32; 405c2d3d1c2SDorinda Bassey case SPA_AUDIO_FORMAT_F32_LE: 406c2d3d1c2SDorinda Bassey *sample_size = 4; 407c2d3d1c2SDorinda Bassey *endianness = 0; 408c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_F32; 409c2d3d1c2SDorinda Bassey default: 410c2d3d1c2SDorinda Bassey *sample_size = 1; 411c2d3d1c2SDorinda Bassey dolog("Internal logic error: Bad spa_audio_format %d\n", fmt); 412c2d3d1c2SDorinda Bassey return AUDIO_FORMAT_U8; 413c2d3d1c2SDorinda Bassey } 414c2d3d1c2SDorinda Bassey } 415c2d3d1c2SDorinda Bassey 416c2d3d1c2SDorinda Bassey static int 417c2d3d1c2SDorinda Bassey create_stream(pwaudio *c, PWVoice *v, const char *stream_name, 418c2d3d1c2SDorinda Bassey const char *name, enum spa_direction dir) 419c2d3d1c2SDorinda Bassey { 420c2d3d1c2SDorinda Bassey int res; 421c2d3d1c2SDorinda Bassey uint32_t n_params; 422c2d3d1c2SDorinda Bassey const struct spa_pod *params[2]; 423c2d3d1c2SDorinda Bassey uint8_t buffer[1024]; 424c2d3d1c2SDorinda Bassey struct spa_pod_builder b; 425c2d3d1c2SDorinda Bassey uint64_t buf_samples; 426c2d3d1c2SDorinda Bassey struct pw_properties *props; 427c2d3d1c2SDorinda Bassey 428c2d3d1c2SDorinda Bassey props = pw_properties_new(NULL, NULL); 429c2d3d1c2SDorinda Bassey 430c2d3d1c2SDorinda Bassey /* 75% of the timer period for faster updates */ 431c2d3d1c2SDorinda Bassey buf_samples = (uint64_t)v->g->dev->timer_period * v->info.rate 432c2d3d1c2SDorinda Bassey * 3 / 4 / 1000000; 433c2d3d1c2SDorinda Bassey pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%" PRIu64 "/%u", 434c2d3d1c2SDorinda Bassey buf_samples, v->info.rate); 435c2d3d1c2SDorinda Bassey 436c2d3d1c2SDorinda Bassey trace_pw_period(buf_samples, v->info.rate); 437c2d3d1c2SDorinda Bassey if (name) { 438c2d3d1c2SDorinda Bassey pw_properties_set(props, PW_KEY_TARGET_OBJECT, name); 439c2d3d1c2SDorinda Bassey } 440c2d3d1c2SDorinda Bassey v->stream = pw_stream_new(c->core, stream_name, props); 441c2d3d1c2SDorinda Bassey 442c2d3d1c2SDorinda Bassey if (v->stream == NULL) { 443c2d3d1c2SDorinda Bassey return -1; 444c2d3d1c2SDorinda Bassey } 445c2d3d1c2SDorinda Bassey 446c2d3d1c2SDorinda Bassey if (dir == SPA_DIRECTION_INPUT) { 447c2d3d1c2SDorinda Bassey pw_stream_add_listener(v->stream, 448c2d3d1c2SDorinda Bassey &v->stream_listener, &capture_stream_events, v); 449c2d3d1c2SDorinda Bassey } else { 450c2d3d1c2SDorinda Bassey pw_stream_add_listener(v->stream, 451c2d3d1c2SDorinda Bassey &v->stream_listener, &playback_stream_events, v); 452c2d3d1c2SDorinda Bassey } 453c2d3d1c2SDorinda Bassey 454c2d3d1c2SDorinda Bassey n_params = 0; 455c2d3d1c2SDorinda Bassey spa_pod_builder_init(&b, buffer, sizeof(buffer)); 456c2d3d1c2SDorinda Bassey params[n_params++] = spa_format_audio_raw_build(&b, 457c2d3d1c2SDorinda Bassey SPA_PARAM_EnumFormat, 458c2d3d1c2SDorinda Bassey &v->info); 459c2d3d1c2SDorinda Bassey 460c2d3d1c2SDorinda Bassey /* connect the stream to a sink or source */ 461c2d3d1c2SDorinda Bassey res = pw_stream_connect(v->stream, 462c2d3d1c2SDorinda Bassey dir == 463c2d3d1c2SDorinda Bassey SPA_DIRECTION_INPUT ? PW_DIRECTION_INPUT : 464c2d3d1c2SDorinda Bassey PW_DIRECTION_OUTPUT, PW_ID_ANY, 465c2d3d1c2SDorinda Bassey PW_STREAM_FLAG_AUTOCONNECT | 466c2d3d1c2SDorinda Bassey PW_STREAM_FLAG_INACTIVE | 467c2d3d1c2SDorinda Bassey PW_STREAM_FLAG_MAP_BUFFERS | 468c2d3d1c2SDorinda Bassey PW_STREAM_FLAG_RT_PROCESS, params, n_params); 469c2d3d1c2SDorinda Bassey if (res < 0) { 470c2d3d1c2SDorinda Bassey pw_stream_destroy(v->stream); 471c2d3d1c2SDorinda Bassey return -1; 472c2d3d1c2SDorinda Bassey } 473c2d3d1c2SDorinda Bassey 474c2d3d1c2SDorinda Bassey return 0; 475c2d3d1c2SDorinda Bassey } 476c2d3d1c2SDorinda Bassey 477c2d3d1c2SDorinda Bassey static int 478c2d3d1c2SDorinda Bassey qpw_stream_new(pwaudio *c, PWVoice *v, const char *stream_name, 479c2d3d1c2SDorinda Bassey const char *name, enum spa_direction dir) 480c2d3d1c2SDorinda Bassey { 481c2d3d1c2SDorinda Bassey int r; 482c2d3d1c2SDorinda Bassey 483c2d3d1c2SDorinda Bassey switch (v->info.channels) { 484c2d3d1c2SDorinda Bassey case 8: 485c2d3d1c2SDorinda Bassey v->info.position[0] = SPA_AUDIO_CHANNEL_FL; 486c2d3d1c2SDorinda Bassey v->info.position[1] = SPA_AUDIO_CHANNEL_FR; 487c2d3d1c2SDorinda Bassey v->info.position[2] = SPA_AUDIO_CHANNEL_FC; 488c2d3d1c2SDorinda Bassey v->info.position[3] = SPA_AUDIO_CHANNEL_LFE; 489c2d3d1c2SDorinda Bassey v->info.position[4] = SPA_AUDIO_CHANNEL_RL; 490c2d3d1c2SDorinda Bassey v->info.position[5] = SPA_AUDIO_CHANNEL_RR; 491c2d3d1c2SDorinda Bassey v->info.position[6] = SPA_AUDIO_CHANNEL_SL; 492c2d3d1c2SDorinda Bassey v->info.position[7] = SPA_AUDIO_CHANNEL_SR; 493c2d3d1c2SDorinda Bassey break; 494c2d3d1c2SDorinda Bassey case 6: 495c2d3d1c2SDorinda Bassey v->info.position[0] = SPA_AUDIO_CHANNEL_FL; 496c2d3d1c2SDorinda Bassey v->info.position[1] = SPA_AUDIO_CHANNEL_FR; 497c2d3d1c2SDorinda Bassey v->info.position[2] = SPA_AUDIO_CHANNEL_FC; 498c2d3d1c2SDorinda Bassey v->info.position[3] = SPA_AUDIO_CHANNEL_LFE; 499c2d3d1c2SDorinda Bassey v->info.position[4] = SPA_AUDIO_CHANNEL_RL; 500c2d3d1c2SDorinda Bassey v->info.position[5] = SPA_AUDIO_CHANNEL_RR; 501c2d3d1c2SDorinda Bassey break; 502c2d3d1c2SDorinda Bassey case 5: 503c2d3d1c2SDorinda Bassey v->info.position[0] = SPA_AUDIO_CHANNEL_FL; 504c2d3d1c2SDorinda Bassey v->info.position[1] = SPA_AUDIO_CHANNEL_FR; 505c2d3d1c2SDorinda Bassey v->info.position[2] = SPA_AUDIO_CHANNEL_FC; 506c2d3d1c2SDorinda Bassey v->info.position[3] = SPA_AUDIO_CHANNEL_LFE; 507c2d3d1c2SDorinda Bassey v->info.position[4] = SPA_AUDIO_CHANNEL_RC; 508c2d3d1c2SDorinda Bassey break; 509c2d3d1c2SDorinda Bassey case 4: 510c2d3d1c2SDorinda Bassey v->info.position[0] = SPA_AUDIO_CHANNEL_FL; 511c2d3d1c2SDorinda Bassey v->info.position[1] = SPA_AUDIO_CHANNEL_FR; 512c2d3d1c2SDorinda Bassey v->info.position[2] = SPA_AUDIO_CHANNEL_FC; 513c2d3d1c2SDorinda Bassey v->info.position[3] = SPA_AUDIO_CHANNEL_RC; 514c2d3d1c2SDorinda Bassey break; 515c2d3d1c2SDorinda Bassey case 3: 516c2d3d1c2SDorinda Bassey v->info.position[0] = SPA_AUDIO_CHANNEL_FL; 517c2d3d1c2SDorinda Bassey v->info.position[1] = SPA_AUDIO_CHANNEL_FR; 518c2d3d1c2SDorinda Bassey v->info.position[2] = SPA_AUDIO_CHANNEL_LFE; 519c2d3d1c2SDorinda Bassey break; 520c2d3d1c2SDorinda Bassey case 2: 521c2d3d1c2SDorinda Bassey v->info.position[0] = SPA_AUDIO_CHANNEL_FL; 522c2d3d1c2SDorinda Bassey v->info.position[1] = SPA_AUDIO_CHANNEL_FR; 523c2d3d1c2SDorinda Bassey break; 524c2d3d1c2SDorinda Bassey case 1: 525c2d3d1c2SDorinda Bassey v->info.position[0] = SPA_AUDIO_CHANNEL_MONO; 526c2d3d1c2SDorinda Bassey break; 527c2d3d1c2SDorinda Bassey default: 528c2d3d1c2SDorinda Bassey for (size_t i = 0; i < v->info.channels; i++) { 529c2d3d1c2SDorinda Bassey v->info.position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; 530c2d3d1c2SDorinda Bassey } 531c2d3d1c2SDorinda Bassey break; 532c2d3d1c2SDorinda Bassey } 533c2d3d1c2SDorinda Bassey 534c2d3d1c2SDorinda Bassey /* create a new unconnected pwstream */ 535c2d3d1c2SDorinda Bassey r = create_stream(c, v, stream_name, name, dir); 536c2d3d1c2SDorinda Bassey if (r < 0) { 537c2d3d1c2SDorinda Bassey AUD_log(AUDIO_CAP, "Failed to create stream."); 538c2d3d1c2SDorinda Bassey return -1; 539c2d3d1c2SDorinda Bassey } 540c2d3d1c2SDorinda Bassey 541c2d3d1c2SDorinda Bassey return r; 542c2d3d1c2SDorinda Bassey } 543c2d3d1c2SDorinda Bassey 544c2d3d1c2SDorinda Bassey static int 545c2d3d1c2SDorinda Bassey qpw_init_out(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque) 546c2d3d1c2SDorinda Bassey { 547c2d3d1c2SDorinda Bassey PWVoiceOut *pw = (PWVoiceOut *) hw; 548c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 549c2d3d1c2SDorinda Bassey struct audsettings obt_as = *as; 550c2d3d1c2SDorinda Bassey pwaudio *c = v->g = drv_opaque; 551c2d3d1c2SDorinda Bassey AudiodevPipewireOptions *popts = &c->dev->u.pipewire; 552c2d3d1c2SDorinda Bassey AudiodevPipewirePerDirectionOptions *ppdo = popts->out; 553c2d3d1c2SDorinda Bassey int r; 554c2d3d1c2SDorinda Bassey 555c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 556c2d3d1c2SDorinda Bassey 557c2d3d1c2SDorinda Bassey v->info.format = audfmt_to_pw(as->fmt, as->endianness); 558c2d3d1c2SDorinda Bassey v->info.channels = as->nchannels; 559c2d3d1c2SDorinda Bassey v->info.rate = as->freq; 560c2d3d1c2SDorinda Bassey 561c2d3d1c2SDorinda Bassey obt_as.fmt = 562c2d3d1c2SDorinda Bassey pw_to_audfmt(v->info.format, &obt_as.endianness, &v->frame_size); 563c2d3d1c2SDorinda Bassey v->frame_size *= as->nchannels; 564c2d3d1c2SDorinda Bassey 565c2d3d1c2SDorinda Bassey v->req = (uint64_t)c->dev->timer_period * v->info.rate 566c2d3d1c2SDorinda Bassey * 1 / 2 / 1000000 * v->frame_size; 567c2d3d1c2SDorinda Bassey 568c2d3d1c2SDorinda Bassey /* call the function that creates a new stream for playback */ 569c2d3d1c2SDorinda Bassey r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id, 570c2d3d1c2SDorinda Bassey ppdo->name, SPA_DIRECTION_OUTPUT); 571c2d3d1c2SDorinda Bassey if (r < 0) { 572c2d3d1c2SDorinda Bassey error_report("qpw_stream_new for playback failed"); 573c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 574c2d3d1c2SDorinda Bassey return -1; 575c2d3d1c2SDorinda Bassey } 576c2d3d1c2SDorinda Bassey 577c2d3d1c2SDorinda Bassey /* report the audio format we support */ 578c2d3d1c2SDorinda Bassey audio_pcm_init_info(&hw->info, &obt_as); 579c2d3d1c2SDorinda Bassey 580c2d3d1c2SDorinda Bassey /* report the buffer size to qemu */ 581c2d3d1c2SDorinda Bassey hw->samples = audio_buffer_frames( 582c2d3d1c2SDorinda Bassey qapi_AudiodevPipewirePerDirectionOptions_base(ppdo), &obt_as, 46440); 583c2d3d1c2SDorinda Bassey v->highwater_mark = MIN(RINGBUFFER_SIZE, 584c2d3d1c2SDorinda Bassey (ppdo->has_latency ? ppdo->latency : 46440) 585c2d3d1c2SDorinda Bassey * (uint64_t)v->info.rate / 1000000 * v->frame_size); 586c2d3d1c2SDorinda Bassey 587c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 588c2d3d1c2SDorinda Bassey return 0; 589c2d3d1c2SDorinda Bassey } 590c2d3d1c2SDorinda Bassey 591c2d3d1c2SDorinda Bassey static int 592c2d3d1c2SDorinda Bassey qpw_init_in(HWVoiceIn *hw, struct audsettings *as, void *drv_opaque) 593c2d3d1c2SDorinda Bassey { 594c2d3d1c2SDorinda Bassey PWVoiceIn *pw = (PWVoiceIn *) hw; 595c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 596c2d3d1c2SDorinda Bassey struct audsettings obt_as = *as; 597c2d3d1c2SDorinda Bassey pwaudio *c = v->g = drv_opaque; 598c2d3d1c2SDorinda Bassey AudiodevPipewireOptions *popts = &c->dev->u.pipewire; 599c2d3d1c2SDorinda Bassey AudiodevPipewirePerDirectionOptions *ppdo = popts->in; 600c2d3d1c2SDorinda Bassey int r; 601c2d3d1c2SDorinda Bassey 602c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 603c2d3d1c2SDorinda Bassey 604c2d3d1c2SDorinda Bassey v->info.format = audfmt_to_pw(as->fmt, as->endianness); 605c2d3d1c2SDorinda Bassey v->info.channels = as->nchannels; 606c2d3d1c2SDorinda Bassey v->info.rate = as->freq; 607c2d3d1c2SDorinda Bassey 608c2d3d1c2SDorinda Bassey obt_as.fmt = 609c2d3d1c2SDorinda Bassey pw_to_audfmt(v->info.format, &obt_as.endianness, &v->frame_size); 610c2d3d1c2SDorinda Bassey v->frame_size *= as->nchannels; 611c2d3d1c2SDorinda Bassey 612c2d3d1c2SDorinda Bassey /* call the function that creates a new stream for recording */ 613c2d3d1c2SDorinda Bassey r = qpw_stream_new(c, v, ppdo->stream_name ? : c->dev->id, 614c2d3d1c2SDorinda Bassey ppdo->name, SPA_DIRECTION_INPUT); 615c2d3d1c2SDorinda Bassey if (r < 0) { 616c2d3d1c2SDorinda Bassey error_report("qpw_stream_new for recording failed"); 617c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 618c2d3d1c2SDorinda Bassey return -1; 619c2d3d1c2SDorinda Bassey } 620c2d3d1c2SDorinda Bassey 621c2d3d1c2SDorinda Bassey /* report the audio format we support */ 622c2d3d1c2SDorinda Bassey audio_pcm_init_info(&hw->info, &obt_as); 623c2d3d1c2SDorinda Bassey 624c2d3d1c2SDorinda Bassey /* report the buffer size to qemu */ 625c2d3d1c2SDorinda Bassey hw->samples = audio_buffer_frames( 626c2d3d1c2SDorinda Bassey qapi_AudiodevPipewirePerDirectionOptions_base(ppdo), &obt_as, 46440); 627c2d3d1c2SDorinda Bassey 628c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 629c2d3d1c2SDorinda Bassey return 0; 630c2d3d1c2SDorinda Bassey } 631c2d3d1c2SDorinda Bassey 632c2d3d1c2SDorinda Bassey static void 633c2d3d1c2SDorinda Bassey qpw_fini_out(HWVoiceOut *hw) 634c2d3d1c2SDorinda Bassey { 635c2d3d1c2SDorinda Bassey PWVoiceOut *pw = (PWVoiceOut *) hw; 636c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 637c2d3d1c2SDorinda Bassey 638c2d3d1c2SDorinda Bassey if (v->stream) { 639c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 640c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 641c2d3d1c2SDorinda Bassey pw_stream_destroy(v->stream); 642c2d3d1c2SDorinda Bassey v->stream = NULL; 643c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 644c2d3d1c2SDorinda Bassey } 645c2d3d1c2SDorinda Bassey } 646c2d3d1c2SDorinda Bassey 647c2d3d1c2SDorinda Bassey static void 648c2d3d1c2SDorinda Bassey qpw_fini_in(HWVoiceIn *hw) 649c2d3d1c2SDorinda Bassey { 650c2d3d1c2SDorinda Bassey PWVoiceIn *pw = (PWVoiceIn *) hw; 651c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 652c2d3d1c2SDorinda Bassey 653c2d3d1c2SDorinda Bassey if (v->stream) { 654c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 655c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 656c2d3d1c2SDorinda Bassey pw_stream_destroy(v->stream); 657c2d3d1c2SDorinda Bassey v->stream = NULL; 658c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 659c2d3d1c2SDorinda Bassey } 660c2d3d1c2SDorinda Bassey } 661c2d3d1c2SDorinda Bassey 662c2d3d1c2SDorinda Bassey static void 663c2d3d1c2SDorinda Bassey qpw_enable_out(HWVoiceOut *hw, bool enable) 664c2d3d1c2SDorinda Bassey { 665c2d3d1c2SDorinda Bassey PWVoiceOut *po = (PWVoiceOut *) hw; 666c2d3d1c2SDorinda Bassey PWVoice *v = &po->v; 667c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 668c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 669c2d3d1c2SDorinda Bassey pw_stream_set_active(v->stream, enable); 670c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 671c2d3d1c2SDorinda Bassey } 672c2d3d1c2SDorinda Bassey 673c2d3d1c2SDorinda Bassey static void 674c2d3d1c2SDorinda Bassey qpw_enable_in(HWVoiceIn *hw, bool enable) 675c2d3d1c2SDorinda Bassey { 676c2d3d1c2SDorinda Bassey PWVoiceIn *pi = (PWVoiceIn *) hw; 677c2d3d1c2SDorinda Bassey PWVoice *v = &pi->v; 678c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 679c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 680c2d3d1c2SDorinda Bassey pw_stream_set_active(v->stream, enable); 681c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 682c2d3d1c2SDorinda Bassey } 683c2d3d1c2SDorinda Bassey 684c2d3d1c2SDorinda Bassey static void 685c2d3d1c2SDorinda Bassey qpw_volume_out(HWVoiceOut *hw, Volume *vol) 686c2d3d1c2SDorinda Bassey { 687c2d3d1c2SDorinda Bassey PWVoiceOut *pw = (PWVoiceOut *) hw; 688c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 689c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 690c2d3d1c2SDorinda Bassey int i, ret; 691c2d3d1c2SDorinda Bassey 692c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 693c2d3d1c2SDorinda Bassey v->volume.channels = vol->channels; 694c2d3d1c2SDorinda Bassey 695c2d3d1c2SDorinda Bassey for (i = 0; i < vol->channels; ++i) { 696c2d3d1c2SDorinda Bassey v->volume.values[i] = (float)vol->vol[i] / 255; 697c2d3d1c2SDorinda Bassey } 698c2d3d1c2SDorinda Bassey 699c2d3d1c2SDorinda Bassey ret = pw_stream_set_control(v->stream, 700c2d3d1c2SDorinda Bassey SPA_PROP_channelVolumes, v->volume.channels, v->volume.values, 0); 701c2d3d1c2SDorinda Bassey trace_pw_vol(ret == 0 ? "success" : "failed"); 702c2d3d1c2SDorinda Bassey 703c2d3d1c2SDorinda Bassey v->muted = vol->mute; 704c2d3d1c2SDorinda Bassey float val = v->muted ? 1.f : 0.f; 705c2d3d1c2SDorinda Bassey ret = pw_stream_set_control(v->stream, SPA_PROP_mute, 1, &val, 0); 706c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 707c2d3d1c2SDorinda Bassey } 708c2d3d1c2SDorinda Bassey 709c2d3d1c2SDorinda Bassey static void 710c2d3d1c2SDorinda Bassey qpw_volume_in(HWVoiceIn *hw, Volume *vol) 711c2d3d1c2SDorinda Bassey { 712c2d3d1c2SDorinda Bassey PWVoiceIn *pw = (PWVoiceIn *) hw; 713c2d3d1c2SDorinda Bassey PWVoice *v = &pw->v; 714c2d3d1c2SDorinda Bassey pwaudio *c = v->g; 715c2d3d1c2SDorinda Bassey int i, ret; 716c2d3d1c2SDorinda Bassey 717c2d3d1c2SDorinda Bassey pw_thread_loop_lock(c->thread_loop); 718c2d3d1c2SDorinda Bassey v->volume.channels = vol->channels; 719c2d3d1c2SDorinda Bassey 720c2d3d1c2SDorinda Bassey for (i = 0; i < vol->channels; ++i) { 721c2d3d1c2SDorinda Bassey v->volume.values[i] = (float)vol->vol[i] / 255; 722c2d3d1c2SDorinda Bassey } 723c2d3d1c2SDorinda Bassey 724c2d3d1c2SDorinda Bassey ret = pw_stream_set_control(v->stream, 725c2d3d1c2SDorinda Bassey SPA_PROP_channelVolumes, v->volume.channels, v->volume.values, 0); 726c2d3d1c2SDorinda Bassey trace_pw_vol(ret == 0 ? "success" : "failed"); 727c2d3d1c2SDorinda Bassey 728c2d3d1c2SDorinda Bassey v->muted = vol->mute; 729c2d3d1c2SDorinda Bassey float val = v->muted ? 1.f : 0.f; 730c2d3d1c2SDorinda Bassey ret = pw_stream_set_control(v->stream, SPA_PROP_mute, 1, &val, 0); 731c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(c->thread_loop); 732c2d3d1c2SDorinda Bassey } 733c2d3d1c2SDorinda Bassey 734c2d3d1c2SDorinda Bassey static int wait_resync(pwaudio *pw) 735c2d3d1c2SDorinda Bassey { 736c2d3d1c2SDorinda Bassey int res; 737c2d3d1c2SDorinda Bassey pw->pending_seq = pw_core_sync(pw->core, PW_ID_CORE, pw->pending_seq); 738c2d3d1c2SDorinda Bassey 739c2d3d1c2SDorinda Bassey while (true) { 740c2d3d1c2SDorinda Bassey pw_thread_loop_wait(pw->thread_loop); 741c2d3d1c2SDorinda Bassey 742c2d3d1c2SDorinda Bassey res = pw->error; 743c2d3d1c2SDorinda Bassey if (res < 0) { 744c2d3d1c2SDorinda Bassey pw->error = 0; 745c2d3d1c2SDorinda Bassey return res; 746c2d3d1c2SDorinda Bassey } 747c2d3d1c2SDorinda Bassey if (pw->pending_seq == pw->last_seq) { 748c2d3d1c2SDorinda Bassey break; 749c2d3d1c2SDorinda Bassey } 750c2d3d1c2SDorinda Bassey } 751c2d3d1c2SDorinda Bassey return 0; 752c2d3d1c2SDorinda Bassey } 753*24a9095cSMarc-André Lureau 754c2d3d1c2SDorinda Bassey static void 755c2d3d1c2SDorinda Bassey on_core_error(void *data, uint32_t id, int seq, int res, const char *message) 756c2d3d1c2SDorinda Bassey { 757c2d3d1c2SDorinda Bassey pwaudio *pw = data; 758c2d3d1c2SDorinda Bassey 759c2d3d1c2SDorinda Bassey error_report("error id:%u seq:%d res:%d (%s): %s", 760c2d3d1c2SDorinda Bassey id, seq, res, spa_strerror(res), message); 761c2d3d1c2SDorinda Bassey 762c2d3d1c2SDorinda Bassey /* stop and exit the thread loop */ 763c2d3d1c2SDorinda Bassey pw_thread_loop_signal(pw->thread_loop, FALSE); 764c2d3d1c2SDorinda Bassey } 765c2d3d1c2SDorinda Bassey 766c2d3d1c2SDorinda Bassey static void 767c2d3d1c2SDorinda Bassey on_core_done(void *data, uint32_t id, int seq) 768c2d3d1c2SDorinda Bassey { 769c2d3d1c2SDorinda Bassey pwaudio *pw = data; 770c2d3d1c2SDorinda Bassey assert(id == PW_ID_CORE); 771c2d3d1c2SDorinda Bassey pw->last_seq = seq; 772c2d3d1c2SDorinda Bassey if (pw->pending_seq == seq) { 773c2d3d1c2SDorinda Bassey /* stop and exit the thread loop */ 774c2d3d1c2SDorinda Bassey pw_thread_loop_signal(pw->thread_loop, FALSE); 775c2d3d1c2SDorinda Bassey } 776c2d3d1c2SDorinda Bassey } 777c2d3d1c2SDorinda Bassey 778c2d3d1c2SDorinda Bassey static const struct pw_core_events core_events = { 779c2d3d1c2SDorinda Bassey PW_VERSION_CORE_EVENTS, 780c2d3d1c2SDorinda Bassey .done = on_core_done, 781c2d3d1c2SDorinda Bassey .error = on_core_error, 782c2d3d1c2SDorinda Bassey }; 783c2d3d1c2SDorinda Bassey 784c2d3d1c2SDorinda Bassey static void * 785c2d3d1c2SDorinda Bassey qpw_audio_init(Audiodev *dev) 786c2d3d1c2SDorinda Bassey { 787c2d3d1c2SDorinda Bassey g_autofree pwaudio *pw = g_new0(pwaudio, 1); 788c2d3d1c2SDorinda Bassey 789c2d3d1c2SDorinda Bassey assert(dev->driver == AUDIODEV_DRIVER_PIPEWIRE); 79087048d20SMarc-André Lureau trace_pw_audio_init(); 79187048d20SMarc-André Lureau 79287048d20SMarc-André Lureau pw_init(NULL, NULL); 793c2d3d1c2SDorinda Bassey 794c2d3d1c2SDorinda Bassey pw->dev = dev; 79520c51248SMarc-André Lureau pw->thread_loop = pw_thread_loop_new("PipeWire thread loop", NULL); 796c2d3d1c2SDorinda Bassey if (pw->thread_loop == NULL) { 797*24a9095cSMarc-André Lureau error_report("Could not create PipeWire loop: %s", g_strerror(errno)); 798c2d3d1c2SDorinda Bassey goto fail; 799c2d3d1c2SDorinda Bassey } 800c2d3d1c2SDorinda Bassey 801c2d3d1c2SDorinda Bassey pw->context = 802c2d3d1c2SDorinda Bassey pw_context_new(pw_thread_loop_get_loop(pw->thread_loop), NULL, 0); 803c2d3d1c2SDorinda Bassey if (pw->context == NULL) { 804*24a9095cSMarc-André Lureau error_report("Could not create PipeWire context: %s", g_strerror(errno)); 805c2d3d1c2SDorinda Bassey goto fail; 806c2d3d1c2SDorinda Bassey } 807c2d3d1c2SDorinda Bassey 808c2d3d1c2SDorinda Bassey if (pw_thread_loop_start(pw->thread_loop) < 0) { 809*24a9095cSMarc-André Lureau error_report("Could not start PipeWire loop: %s", g_strerror(errno)); 810c2d3d1c2SDorinda Bassey goto fail; 811c2d3d1c2SDorinda Bassey } 812c2d3d1c2SDorinda Bassey 813c2d3d1c2SDorinda Bassey pw_thread_loop_lock(pw->thread_loop); 814c2d3d1c2SDorinda Bassey 815c2d3d1c2SDorinda Bassey pw->core = pw_context_connect(pw->context, NULL, 0); 816c2d3d1c2SDorinda Bassey if (pw->core == NULL) { 817c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(pw->thread_loop); 818c2d3d1c2SDorinda Bassey goto fail; 819c2d3d1c2SDorinda Bassey } 820c2d3d1c2SDorinda Bassey 821c2d3d1c2SDorinda Bassey if (pw_core_add_listener(pw->core, &pw->core_listener, 822c2d3d1c2SDorinda Bassey &core_events, pw) < 0) { 823c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(pw->thread_loop); 824c2d3d1c2SDorinda Bassey goto fail; 825c2d3d1c2SDorinda Bassey } 826c2d3d1c2SDorinda Bassey if (wait_resync(pw) < 0) { 827c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(pw->thread_loop); 828c2d3d1c2SDorinda Bassey } 829c2d3d1c2SDorinda Bassey 830c2d3d1c2SDorinda Bassey pw_thread_loop_unlock(pw->thread_loop); 831c2d3d1c2SDorinda Bassey 832c2d3d1c2SDorinda Bassey return g_steal_pointer(&pw); 833c2d3d1c2SDorinda Bassey 834c2d3d1c2SDorinda Bassey fail: 835c2d3d1c2SDorinda Bassey AUD_log(AUDIO_CAP, "Failed to initialize PW context"); 836c2d3d1c2SDorinda Bassey if (pw->thread_loop) { 837c2d3d1c2SDorinda Bassey pw_thread_loop_stop(pw->thread_loop); 838c2d3d1c2SDorinda Bassey } 839c2d3d1c2SDorinda Bassey g_clear_pointer(&pw->context, pw_context_destroy); 840c2d3d1c2SDorinda Bassey g_clear_pointer(&pw->thread_loop, pw_thread_loop_destroy); 841c2d3d1c2SDorinda Bassey return NULL; 842c2d3d1c2SDorinda Bassey } 843c2d3d1c2SDorinda Bassey 844c2d3d1c2SDorinda Bassey static void 845c2d3d1c2SDorinda Bassey qpw_audio_fini(void *opaque) 846c2d3d1c2SDorinda Bassey { 847c2d3d1c2SDorinda Bassey pwaudio *pw = opaque; 848c2d3d1c2SDorinda Bassey 849c2d3d1c2SDorinda Bassey if (pw->thread_loop) { 850c2d3d1c2SDorinda Bassey pw_thread_loop_stop(pw->thread_loop); 851c2d3d1c2SDorinda Bassey } 852c2d3d1c2SDorinda Bassey 853c2d3d1c2SDorinda Bassey if (pw->core) { 854c2d3d1c2SDorinda Bassey spa_hook_remove(&pw->core_listener); 855c2d3d1c2SDorinda Bassey spa_zero(pw->core_listener); 856c2d3d1c2SDorinda Bassey pw_core_disconnect(pw->core); 857c2d3d1c2SDorinda Bassey } 858c2d3d1c2SDorinda Bassey 859c2d3d1c2SDorinda Bassey if (pw->context) { 860c2d3d1c2SDorinda Bassey pw_context_destroy(pw->context); 861c2d3d1c2SDorinda Bassey } 862c2d3d1c2SDorinda Bassey pw_thread_loop_destroy(pw->thread_loop); 863c2d3d1c2SDorinda Bassey 864c2d3d1c2SDorinda Bassey g_free(pw); 865c2d3d1c2SDorinda Bassey } 866c2d3d1c2SDorinda Bassey 867c2d3d1c2SDorinda Bassey static struct audio_pcm_ops qpw_pcm_ops = { 868c2d3d1c2SDorinda Bassey .init_out = qpw_init_out, 869c2d3d1c2SDorinda Bassey .fini_out = qpw_fini_out, 870c2d3d1c2SDorinda Bassey .write = qpw_write, 871c2d3d1c2SDorinda Bassey .buffer_get_free = qpw_buffer_get_free, 872c2d3d1c2SDorinda Bassey .run_buffer_out = audio_generic_run_buffer_out, 873c2d3d1c2SDorinda Bassey .enable_out = qpw_enable_out, 874c2d3d1c2SDorinda Bassey .volume_out = qpw_volume_out, 875c2d3d1c2SDorinda Bassey .volume_in = qpw_volume_in, 876c2d3d1c2SDorinda Bassey 877c2d3d1c2SDorinda Bassey .init_in = qpw_init_in, 878c2d3d1c2SDorinda Bassey .fini_in = qpw_fini_in, 879c2d3d1c2SDorinda Bassey .read = qpw_read, 880c2d3d1c2SDorinda Bassey .run_buffer_in = audio_generic_run_buffer_in, 881c2d3d1c2SDorinda Bassey .enable_in = qpw_enable_in 882c2d3d1c2SDorinda Bassey }; 883c2d3d1c2SDorinda Bassey 884c2d3d1c2SDorinda Bassey static struct audio_driver pw_audio_driver = { 885c2d3d1c2SDorinda Bassey .name = "pipewire", 886c2d3d1c2SDorinda Bassey .descr = "http://www.pipewire.org/", 887c2d3d1c2SDorinda Bassey .init = qpw_audio_init, 888c2d3d1c2SDorinda Bassey .fini = qpw_audio_fini, 889c2d3d1c2SDorinda Bassey .pcm_ops = &qpw_pcm_ops, 890c2d3d1c2SDorinda Bassey .can_be_default = 1, 891c2d3d1c2SDorinda Bassey .max_voices_out = INT_MAX, 892c2d3d1c2SDorinda Bassey .max_voices_in = INT_MAX, 893c2d3d1c2SDorinda Bassey .voice_size_out = sizeof(PWVoiceOut), 894c2d3d1c2SDorinda Bassey .voice_size_in = sizeof(PWVoiceIn), 895c2d3d1c2SDorinda Bassey }; 896c2d3d1c2SDorinda Bassey 897c2d3d1c2SDorinda Bassey static void 898c2d3d1c2SDorinda Bassey register_audio_pw(void) 899c2d3d1c2SDorinda Bassey { 900c2d3d1c2SDorinda Bassey audio_driver_register(&pw_audio_driver); 901c2d3d1c2SDorinda Bassey } 902c2d3d1c2SDorinda Bassey 903c2d3d1c2SDorinda Bassey type_init(register_audio_pw); 904