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