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