1 From 1b1f884a165ed7b2147affbdddf85a641d4cf180 Mon Sep 17 00:00:00 2001
2 From: George Kiagiadakis <george.kiagiadakis@collabora.com>
3 Date: Tue, 19 Feb 2019 18:23:19 +0200
4 Subject: [PATCH] gst: Implement new pwaudio{src,sink} elements, based on
7 These are much more reliable elements to use for audio data.
8 * GstAudioBaseSink provides a reliable clock implementation based
9 on the number of samples read/written
10 * on the pipewire side we make sure to dequeue, fill and enqueue
11 a single buffer inside the process() function, which avoids
14 Both elements share a common ringbuffer that actually implements
15 the pipewire integration.
17 Upstream-Status: Denied
18 See https://gitlab.freedesktop.org/pipewire/pipewire/merge_requests/140
20 src/gst/gstpipewire.c | 8 +-
21 src/gst/gstpwaudioringbuffer.c | 565 +++++++++++++++++++++++++++++++++
22 src/gst/gstpwaudioringbuffer.h | 83 +++++
23 src/gst/gstpwaudiosink.c | 207 ++++++++++++
24 src/gst/gstpwaudiosink.h | 48 +++
25 src/gst/gstpwaudiosrc.c | 200 ++++++++++++
26 src/gst/gstpwaudiosrc.h | 48 +++
27 src/gst/meson.build | 6 +
28 8 files changed, 1164 insertions(+), 1 deletion(-)
29 create mode 100644 src/gst/gstpwaudioringbuffer.c
30 create mode 100644 src/gst/gstpwaudioringbuffer.h
31 create mode 100644 src/gst/gstpwaudiosink.c
32 create mode 100644 src/gst/gstpwaudiosink.h
33 create mode 100644 src/gst/gstpwaudiosrc.c
34 create mode 100644 src/gst/gstpwaudiosrc.h
36 diff --git a/src/gst/gstpipewire.c b/src/gst/gstpipewire.c
37 index 4040264b..68fd446f 100644
38 --- a/src/gst/gstpipewire.c
39 +++ b/src/gst/gstpipewire.c
41 #include "gstpipewiresrc.h"
42 #include "gstpipewiresink.h"
43 #include "gstpipewiredeviceprovider.h"
44 +#include "gstpwaudiosrc.h"
45 +#include "gstpwaudiosink.h"
47 GST_DEBUG_CATEGORY (pipewire_debug);
49 @@ -52,12 +54,16 @@ plugin_init (GstPlugin *plugin)
50 GST_TYPE_PIPEWIRE_SRC);
51 gst_element_register (plugin, "pipewiresink", GST_RANK_NONE,
52 GST_TYPE_PIPEWIRE_SINK);
53 + gst_element_register (plugin, "pwaudiosrc", GST_RANK_NONE,
54 + GST_TYPE_PW_AUDIO_SRC);
55 + gst_element_register (plugin, "pwaudiosink", GST_RANK_NONE,
56 + GST_TYPE_PW_AUDIO_SINK);
58 if (!gst_device_provider_register (plugin, "pipewiredeviceprovider",
59 GST_RANK_PRIMARY + 1, GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
62 - GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWirie elements");
63 + GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWire elements");
67 diff --git a/src/gst/gstpwaudioringbuffer.c b/src/gst/gstpwaudioringbuffer.c
69 index 00000000..babf2d83
71 +++ b/src/gst/gstpwaudioringbuffer.c
75 + * Copyright © 2018 Wim Taymans
76 + * Copyright © 2019 Collabora Ltd.
77 + * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
79 + * Permission is hereby granted, free of charge, to any person obtaining a
80 + * copy of this software and associated documentation files (the "Software"),
81 + * to deal in the Software without restriction, including without limitation
82 + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
83 + * and/or sell copies of the Software, and to permit persons to whom the
84 + * Software is furnished to do so, subject to the following conditions:
86 + * The above copyright notice and this permission notice (including the next
87 + * paragraph) shall be included in all copies or substantial portions of the
90 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
91 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
92 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
93 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
94 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
95 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
96 + * DEALINGS IN THE SOFTWARE.
103 +#include "gstpwaudioringbuffer.h"
105 +#include <spa/param/audio/format-utils.h>
106 +#include <spa/pod/builder.h>
108 +GST_DEBUG_CATEGORY_STATIC (pw_audio_ring_buffer_debug);
109 +#define GST_CAT_DEFAULT pw_audio_ring_buffer_debug
111 +#define gst_pw_audio_ring_buffer_parent_class parent_class
112 +G_DEFINE_TYPE (GstPwAudioRingBuffer, gst_pw_audio_ring_buffer, GST_TYPE_AUDIO_RING_BUFFER);
123 +gst_pw_audio_ring_buffer_init (GstPwAudioRingBuffer * self)
125 + self->loop = pw_loop_new (NULL);
126 + self->main_loop = pw_thread_loop_new (self->loop, "pw-audioringbuffer-loop");
127 + self->core = pw_core_new (self->loop, NULL, 0);
131 +gst_pw_audio_ring_buffer_finalize (GObject * object)
133 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (object);
135 + pw_core_destroy (self->core);
136 + pw_thread_loop_destroy (self->main_loop);
137 + pw_loop_destroy (self->loop);
141 +gst_pw_audio_ring_buffer_set_property (GObject * object, guint prop_id,
142 + const GValue * value, GParamSpec * pspec)
144 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (object);
148 + self->elem = g_value_get_object (value);
151 + case PROP_DIRECTION:
152 + self->direction = g_value_get_int (value);
156 + self->props = g_value_get_pointer (value);
160 + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
166 +on_remote_state_changed (void *data, enum pw_remote_state old,
167 + enum pw_remote_state state, const char *error)
169 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data);
171 + GST_DEBUG_OBJECT (self->elem, "got remote state %d", state);
174 + case PW_REMOTE_STATE_UNCONNECTED:
175 + case PW_REMOTE_STATE_CONNECTING:
176 + case PW_REMOTE_STATE_CONNECTED:
178 + case PW_REMOTE_STATE_ERROR:
179 + GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED,
180 + ("remote error: %s", error), (NULL));
183 + pw_thread_loop_signal (self->main_loop, FALSE);
186 +static const struct pw_remote_events remote_events = {
187 + PW_VERSION_REMOTE_EVENTS,
188 + .state_changed = on_remote_state_changed,
192 +wait_for_remote_state (GstPwAudioRingBuffer *self,
193 + enum pw_remote_state target)
196 + enum pw_remote_state state = pw_remote_get_state (self->remote, NULL);
197 + if (state == target)
199 + if (state == PW_REMOTE_STATE_ERROR)
201 + pw_thread_loop_wait (self->main_loop);
206 +gst_pw_audio_ring_buffer_open_device (GstAudioRingBuffer *buf)
208 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
210 + GST_DEBUG_OBJECT (self->elem, "open device");
212 + if (pw_thread_loop_start (self->main_loop) < 0)
213 + goto mainloop_error;
215 + pw_thread_loop_lock (self->main_loop);
217 + self->remote = pw_remote_new (self->core, NULL, 0);
218 + pw_remote_add_listener (self->remote, &self->remote_listener, &remote_events,
221 + if (self->props->fd == -1)
222 + pw_remote_connect (self->remote);
224 + pw_remote_connect_fd (self->remote, self->props->fd);
226 + GST_DEBUG_OBJECT (self->elem, "waiting for connection");
228 + if (!wait_for_remote_state (self, PW_REMOTE_STATE_CONNECTED))
229 + goto connect_error;
231 + pw_thread_loop_unlock (self->main_loop);
238 + GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED,
239 + ("Failed to start mainloop"), (NULL));
244 + pw_thread_loop_unlock (self->main_loop);
250 +gst_pw_audio_ring_buffer_close_device (GstAudioRingBuffer *buf)
252 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
254 + GST_DEBUG_OBJECT (self->elem, "closing device");
256 + pw_thread_loop_lock (self->main_loop);
257 + if (self->remote) {
258 + pw_remote_disconnect (self->remote);
259 + wait_for_remote_state (self, PW_REMOTE_STATE_UNCONNECTED);
261 + pw_thread_loop_unlock (self->main_loop);
263 + pw_thread_loop_stop (self->main_loop);
265 + if (self->remote) {
266 + pw_remote_destroy (self->remote);
267 + self->remote = NULL;
273 +on_stream_state_changed (void *data, enum pw_stream_state old,
274 + enum pw_stream_state state, const char *error)
276 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data);
279 + GST_DEBUG_OBJECT (self->elem, "got stream state: %s",
280 + pw_stream_state_as_string (state));
283 + case PW_STREAM_STATE_ERROR:
284 + GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED,
285 + ("stream error: %s", error), (NULL));
287 + case PW_STREAM_STATE_UNCONNECTED:
288 + GST_ELEMENT_ERROR (self->elem, RESOURCE, FAILED,
289 + ("stream disconnected unexpectedly"), (NULL));
291 + case PW_STREAM_STATE_CONNECTING:
293 + case PW_STREAM_STATE_PAUSED:
294 + if (old == PW_STREAM_STATE_STREAMING) {
295 + if (GST_STATE (self->elem) != GST_STATE_PAUSED &&
296 + GST_STATE_TARGET (self->elem) != GST_STATE_PAUSED) {
297 + GST_DEBUG_OBJECT (self->elem, "requesting GST_STATE_PAUSED");
298 + msg = gst_message_new_request_state (GST_OBJECT (self->elem),
300 + gst_element_post_message (self->elem, msg);
304 + case PW_STREAM_STATE_STREAMING:
305 + if (GST_STATE (self->elem) != GST_STATE_PLAYING &&
306 + GST_STATE_TARGET (self->elem) != GST_STATE_PLAYING) {
307 + GST_DEBUG_OBJECT (self->elem, "requesting GST_STATE_PLAYING");
308 + msg = gst_message_new_request_state (GST_OBJECT (self->elem),
309 + GST_STATE_PLAYING);
310 + gst_element_post_message (self->elem, msg);
314 + pw_thread_loop_signal (self->main_loop, FALSE);
318 +wait_for_stream_state (GstPwAudioRingBuffer *self,
319 + enum pw_stream_state target)
322 + enum pw_stream_state state = pw_stream_get_state (self->stream, NULL);
323 + if (state >= target)
325 + if (state == PW_STREAM_STATE_ERROR || state == PW_STREAM_STATE_UNCONNECTED)
327 + pw_thread_loop_wait (self->main_loop);
332 +on_stream_param_changed (void *data, uint32_t id, const struct spa_pod *format)
334 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data);
335 + const struct spa_pod *params[1];
336 + struct spa_pod_builder b = { NULL };
337 + uint8_t buffer[512];
339 + if (format == NULL || id != SPA_PARAM_Format)
342 + spa_pod_builder_init (&b, buffer, sizeof (buffer));
343 + params[0] = spa_pod_builder_add_object (&b,
344 + SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
345 + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 1, INT32_MAX),
346 + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
347 + SPA_PARAM_BUFFERS_size, SPA_POD_Int(self->segsize),
348 + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(self->bpf),
349 + SPA_PARAM_BUFFERS_align, SPA_POD_Int(16));
351 + GST_DEBUG_OBJECT (self->elem, "doing finish format, buffer size:%d", self->segsize);
352 + pw_stream_update_params (self->stream, params, 1);
356 +on_stream_process (void *data)
358 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (data);
359 + GstAudioRingBuffer *buf = GST_AUDIO_RING_BUFFER (data);
360 + struct pw_buffer *b;
361 + struct spa_data *d;
362 + gint size; /*< size to read/write from/to the spa buffer */
363 + gint offset; /*< offset to read/write from/to in the spa buffer */
364 + gint segment; /*< the current segment number in the ringbuffer */
365 + guint8 *ringptr; /*< pointer to the beginning of the current segment */
366 + gint segsize; /*< the size of one segment in the ringbuffer */
367 + gint copy_size; /*< the bytes to copy in one memcpy() invocation */
368 + gint remain; /*< remainder of bytes available in the spa buffer */
370 + if (g_atomic_int_get (&buf->state) != GST_AUDIO_RING_BUFFER_STATE_STARTED) {
371 + GST_LOG_OBJECT (self->elem, "ring buffer is not started");
375 + b = pw_stream_dequeue_buffer (self->stream);
377 + GST_INFO_OBJECT (self->elem, "no pipewire buffer available");
381 + d = &b->buffer->datas[0];
383 + if (self->direction == PW_DIRECTION_OUTPUT) {
384 + /* in output mode, always fill the entire spa buffer */
385 + offset = d->chunk->offset = 0;
386 + size = d->chunk->size = d->maxsize;
387 + b->size = size / self->bpf;
389 + offset = SPA_MIN (d->chunk->offset, d->maxsize);
390 + size = SPA_MIN (d->chunk->size, d->maxsize - offset);
394 + gst_audio_ring_buffer_prepare_read (buf, &segment, &ringptr, &segsize);
396 + /* in INPUT (src) mode, it is possible that the skew algorithm
397 + * advances the ringbuffer behind our back */
398 + if (self->segoffset > 0 && self->cur_segment != segment)
399 + self->segoffset = 0;
401 + copy_size = SPA_MIN (size, segsize - self->segoffset);
403 + if (self->direction == PW_DIRECTION_OUTPUT) {
404 + memcpy (((guint8*) d->data) + offset, ringptr + self->segoffset,
407 + memcpy (ringptr + self->segoffset, ((guint8*) d->data) + offset,
411 + remain = size - (segsize - self->segoffset);
413 + GST_TRACE_OBJECT (self->elem,
414 + "seg %d: %s %d bytes remained:%d offset:%d segoffset:%d", segment,
415 + self->direction == PW_DIRECTION_INPUT ? "INPUT" : "OUTPUT",
416 + copy_size, remain, offset, self->segoffset);
419 + offset += (segsize - self->segoffset);
422 + /* write silence on the segment we just read */
423 + if (self->direction == PW_DIRECTION_OUTPUT)
424 + gst_audio_ring_buffer_clear (buf, segment);
426 + /* notify that we have read a complete segment */
427 + gst_audio_ring_buffer_advance (buf, 1);
428 + self->segoffset = 0;
430 + self->segoffset += size;
431 + self->cur_segment = segment;
433 + } while (remain > 0);
435 + pw_stream_queue_buffer (self->stream, b);
438 +static const struct pw_stream_events stream_events = {
439 + PW_VERSION_STREAM_EVENTS,
440 + .state_changed = on_stream_state_changed,
441 + .param_changed = on_stream_param_changed,
442 + .process = on_stream_process,
446 +copy_properties (GQuark field_id, const GValue *value, gpointer user_data)
448 + struct pw_properties *properties = user_data;
450 + if (G_VALUE_HOLDS_STRING (value))
451 + pw_properties_set (properties,
452 + g_quark_to_string (field_id),
453 + g_value_get_string (value));
458 +gst_pw_audio_ring_buffer_acquire (GstAudioRingBuffer *buf,
459 + GstAudioRingBufferSpec *spec)
461 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
462 + struct pw_properties *props;
463 + struct spa_pod_builder b = { NULL };
464 + uint8_t buffer[512];
465 + const struct spa_pod *params[1];
467 + g_return_val_if_fail (spec, FALSE);
468 + g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (&spec->info), FALSE);
469 + g_return_val_if_fail (!self->stream, TRUE); /* already acquired */
471 + g_return_val_if_fail (spec->type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW, FALSE);
472 + g_return_val_if_fail (GST_AUDIO_INFO_IS_FLOAT (&spec->info), FALSE);
474 + GST_DEBUG_OBJECT (self->elem, "acquire");
476 + /* construct param & props objects */
478 + props = pw_properties_new (NULL, NULL);
479 + if (self->props->properties) {
480 + gst_structure_foreach (self->props->properties, copy_properties, props);
483 + spa_pod_builder_init (&b, buffer, sizeof (buffer));
484 + params[0] = spa_pod_builder_add_object (&b,
485 + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
486 + SPA_FORMAT_mediaType, SPA_POD_Id (SPA_MEDIA_TYPE_audio),
487 + SPA_FORMAT_mediaSubtype, SPA_POD_Id (SPA_MEDIA_SUBTYPE_raw),
488 + SPA_FORMAT_AUDIO_format, SPA_POD_Id (SPA_AUDIO_FORMAT_F32),
489 + SPA_FORMAT_AUDIO_rate, SPA_POD_Int (GST_AUDIO_INFO_RATE (&spec->info)),
490 + SPA_FORMAT_AUDIO_channels, SPA_POD_Int (GST_AUDIO_INFO_CHANNELS (&spec->info)));
492 + self->segsize = spec->segsize;
493 + self->bpf = GST_AUDIO_INFO_BPF (&spec->info);
494 + self->rate = GST_AUDIO_INFO_RATE (&spec->info);
495 + self->segoffset = 0;
497 + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u",
498 + self->segsize / self->bpf, self->rate);
499 + GST_DEBUG_OBJECT (self->elem, "segsize:%u, bpf:%u, node.latency = %s",
500 + self->segsize, self->bpf, pw_properties_get (props, PW_KEY_NODE_LATENCY));
502 + /* connect stream */
504 + pw_thread_loop_lock (self->main_loop);
506 + GST_DEBUG_OBJECT (self->elem, "creating stream");
508 + self->stream = pw_stream_new (self->remote, self->props->client_name, props);
509 + pw_stream_add_listener(self->stream, &self->stream_listener, &stream_events,
512 + if (pw_stream_connect (self->stream,
514 + self->props->path ? (uint32_t)atoi(self->props->path) : SPA_ID_INVALID,
515 + PW_STREAM_FLAG_AUTOCONNECT |
516 + PW_STREAM_FLAG_MAP_BUFFERS |
517 + PW_STREAM_FLAG_RT_PROCESS,
521 + GST_DEBUG_OBJECT (self->elem, "waiting for stream CONFIGURE");
523 + if (!wait_for_stream_state (self, PW_STREAM_STATE_PAUSED))
526 + pw_thread_loop_unlock (self->main_loop);
528 + /* allocate the internal ringbuffer */
530 + spec->seglatency = spec->segtotal + 1;
531 + buf->size = spec->segtotal * spec->segsize;
532 + buf->memory = g_malloc (buf->size);
534 + gst_audio_format_fill_silence (buf->spec.info.finfo, buf->memory,
537 + GST_DEBUG_OBJECT (self->elem, "acquire done");
543 + GST_ERROR_OBJECT (self->elem, "could not start stream");
544 + pw_stream_destroy (self->stream);
545 + self->stream = NULL;
546 + pw_thread_loop_unlock (self->main_loop);
552 +gst_pw_audio_ring_buffer_release (GstAudioRingBuffer *buf)
554 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
556 + GST_DEBUG_OBJECT (self->elem, "release");
558 + pw_thread_loop_lock (self->main_loop);
559 + if (self->stream) {
560 + spa_hook_remove (&self->stream_listener);
561 + pw_stream_disconnect (self->stream);
562 + pw_stream_destroy (self->stream);
563 + self->stream = NULL;
565 + pw_thread_loop_unlock (self->main_loop);
567 + /* free the buffer */
568 + g_free (buf->memory);
569 + buf->memory = NULL;
575 +gst_pw_audio_ring_buffer_delay (GstAudioRingBuffer *buf)
577 + GstPwAudioRingBuffer *self = GST_PW_AUDIO_RING_BUFFER (buf);
580 + if (!self->stream || pw_stream_get_time (self->stream, &t) < 0)
583 + if (self->direction == PW_DIRECTION_OUTPUT) {
584 + /* on output streams, we set the pw_buffer.size in frames,
585 + so no conversion is necessary */
588 + /* on input streams, pw_buffer.size is set by pw_stream in ticks,
589 + so we need to convert it to frames and also add segoffset, which
590 + is the number of bytes we have read but not advertised yet, as
591 + the segment is incomplete */
592 + if (t.rate.denom > 0)
594 + gst_util_uint64_scale (t.queued, self->rate * t.rate.num, t.rate.denom)
595 + + self->segoffset / self->bpf;
597 + return self->segoffset / self->bpf;
604 +gst_pw_audio_ring_buffer_class_init (GstPwAudioRingBufferClass * klass)
606 + GObjectClass *gobject_class;
607 + GstAudioRingBufferClass *gstaudiorbuf_class;
609 + gobject_class = (GObjectClass *) klass;
610 + gstaudiorbuf_class = (GstAudioRingBufferClass *) klass;
612 + gobject_class->finalize = gst_pw_audio_ring_buffer_finalize;
613 + gobject_class->set_property = gst_pw_audio_ring_buffer_set_property;
615 + gstaudiorbuf_class->open_device = gst_pw_audio_ring_buffer_open_device;
616 + gstaudiorbuf_class->acquire = gst_pw_audio_ring_buffer_acquire;
617 + gstaudiorbuf_class->release = gst_pw_audio_ring_buffer_release;
618 + gstaudiorbuf_class->close_device = gst_pw_audio_ring_buffer_close_device;
619 + gstaudiorbuf_class->delay = gst_pw_audio_ring_buffer_delay;
621 + g_object_class_install_property (gobject_class, PROP_ELEMENT,
622 + g_param_spec_object ("element", "Element", "The audio source or sink",
624 + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
626 + g_object_class_install_property (gobject_class, PROP_DIRECTION,
627 + g_param_spec_int ("direction", "Direction", "The stream direction",
628 + PW_DIRECTION_INPUT, PW_DIRECTION_OUTPUT, PW_DIRECTION_INPUT,
629 + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
631 + g_object_class_install_property (gobject_class, PROP_PROPS,
632 + g_param_spec_pointer ("props", "Properties", "The properties struct",
633 + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
635 + GST_DEBUG_CATEGORY_INIT (pw_audio_ring_buffer_debug, "pwaudioringbuffer", 0,
636 + "PipeWire Audio Ring Buffer");
638 diff --git a/src/gst/gstpwaudioringbuffer.h b/src/gst/gstpwaudioringbuffer.h
640 index 00000000..f47f668a
642 +++ b/src/gst/gstpwaudioringbuffer.h
646 + * Copyright © 2018 Wim Taymans
647 + * Copyright © 2019 Collabora Ltd.
648 + * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
650 + * Permission is hereby granted, free of charge, to any person obtaining a
651 + * copy of this software and associated documentation files (the "Software"),
652 + * to deal in the Software without restriction, including without limitation
653 + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
654 + * and/or sell copies of the Software, and to permit persons to whom the
655 + * Software is furnished to do so, subject to the following conditions:
657 + * The above copyright notice and this permission notice (including the next
658 + * paragraph) shall be included in all copies or substantial portions of the
661 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
662 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
663 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
664 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
665 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
666 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
667 + * DEALINGS IN THE SOFTWARE.
670 +#ifndef __GST_PW_AUDIO_RING_BUFFER_H__
671 +#define __GST_PW_AUDIO_RING_BUFFER_H__
673 +#include <gst/gst.h>
674 +#include <gst/audio/audio.h>
675 +#include <pipewire/pipewire.h>
679 +#define GST_TYPE_PW_AUDIO_RING_BUFFER \
680 + (gst_pw_audio_ring_buffer_get_type ())
682 +G_DECLARE_FINAL_TYPE(GstPwAudioRingBuffer, gst_pw_audio_ring_buffer,
683 + GST, PW_AUDIO_RING_BUFFER, GstAudioRingBuffer);
685 +typedef struct _GstPwAudioRingBufferProps GstPwAudioRingBufferProps;
687 +struct _GstPwAudioRingBuffer
689 + GstAudioRingBuffer parent;
693 + enum pw_direction direction;
694 + GstPwAudioRingBufferProps *props;
697 + struct pw_loop *loop;
698 + struct pw_thread_loop *main_loop;
700 + struct pw_core *core;
701 + struct pw_remote *remote;
702 + struct spa_hook remote_listener;
704 + struct pw_stream *stream;
705 + struct spa_hook stream_listener;
711 + /* on_stream_process() state */
716 +struct _GstPwAudioRingBufferProps
719 + gchar *client_name;
720 + GstStructure *properties;
727 diff --git a/src/gst/gstpwaudiosink.c b/src/gst/gstpwaudiosink.c
729 index 00000000..069996c3
731 +++ b/src/gst/gstpwaudiosink.c
735 + * Copyright © 2018 Wim Taymans
736 + * Copyright © 2019 Collabora Ltd.
737 + * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
739 + * Permission is hereby granted, free of charge, to any person obtaining a
740 + * copy of this software and associated documentation files (the "Software"),
741 + * to deal in the Software without restriction, including without limitation
742 + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
743 + * and/or sell copies of the Software, and to permit persons to whom the
744 + * Software is furnished to do so, subject to the following conditions:
746 + * The above copyright notice and this permission notice (including the next
747 + * paragraph) shall be included in all copies or substantial portions of the
750 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
751 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
752 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
753 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
754 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
755 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
756 + * DEALINGS IN THE SOFTWARE.
759 +#ifdef HAVE_CONFIG_H
763 +#include "gstpwaudiosink.h"
765 +GST_DEBUG_CATEGORY_STATIC (pw_audio_sink_debug);
766 +#define GST_CAT_DEFAULT pw_audio_sink_debug
768 +G_DEFINE_TYPE (GstPwAudioSink, gst_pw_audio_sink, GST_TYPE_AUDIO_BASE_SINK);
775 + PROP_STREAM_PROPERTIES,
779 +static GstStaticPadTemplate gst_pw_audio_sink_template =
780 +GST_STATIC_PAD_TEMPLATE ("sink",
783 + GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F32))
784 + ", layout = (string)\"interleaved\"")
789 +gst_pw_audio_sink_init (GstPwAudioSink * self)
791 + self->props.fd = -1;
793 + /* Bump the default buffer size up to 21.3 ms, which is the default on most
794 + * sound cards, in hope to match the alsa buffer size on the pipewire server.
795 + * This may not always happen, but it still sounds better than the 10ms
796 + * default latency. This is temporary until we have a better mechanism to
797 + * select the appropriate latency */
798 + GST_AUDIO_BASE_SINK (self)->latency_time = 21333;
802 +gst_pw_audio_sink_finalize (GObject * object)
804 + GstPwAudioSink *pwsink = GST_PW_AUDIO_SINK (object);
806 + g_free (pwsink->props.path);
807 + g_free (pwsink->props.client_name);
808 + if (pwsink->props.properties)
809 + gst_structure_free (pwsink->props.properties);
813 +gst_pw_audio_sink_set_property (GObject * object, guint prop_id,
814 + const GValue * value, GParamSpec * pspec)
816 + GstPwAudioSink *pwsink = GST_PW_AUDIO_SINK (object);
820 + g_free (pwsink->props.path);
821 + pwsink->props.path = g_value_dup_string (value);
824 + case PROP_CLIENT_NAME:
825 + g_free (pwsink->props.client_name);
826 + pwsink->props.client_name = g_value_dup_string (value);
829 + case PROP_STREAM_PROPERTIES:
830 + if (pwsink->props.properties)
831 + gst_structure_free (pwsink->props.properties);
832 + pwsink->props.properties =
833 + gst_structure_copy (gst_value_get_structure (value));
837 + pwsink->props.fd = g_value_get_int (value);
841 + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
847 +gst_pw_audio_sink_get_property (GObject * object, guint prop_id,
848 + GValue * value, GParamSpec * pspec)
850 + GstPwAudioSink *pwsink = GST_PW_AUDIO_SINK (object);
854 + g_value_set_string (value, pwsink->props.path);
857 + case PROP_CLIENT_NAME:
858 + g_value_set_string (value, pwsink->props.client_name);
861 + case PROP_STREAM_PROPERTIES:
862 + gst_value_set_structure (value, pwsink->props.properties);
866 + g_value_set_int (value, pwsink->props.fd);
870 + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
875 +static GstAudioRingBuffer *
876 +gst_pw_audio_sink_create_ringbuffer (GstAudioBaseSink * sink)
878 + GstPwAudioSink *self = GST_PW_AUDIO_SINK (sink);
879 + GstAudioRingBuffer *buffer;
881 + GST_DEBUG_OBJECT (sink, "creating ringbuffer");
882 + buffer = g_object_new (GST_TYPE_PW_AUDIO_RING_BUFFER,
884 + "direction", PW_DIRECTION_OUTPUT,
885 + "props", &self->props,
887 + GST_DEBUG_OBJECT (sink, "created ringbuffer @%p", buffer);
893 +gst_pw_audio_sink_class_init (GstPwAudioSinkClass * klass)
895 + GObjectClass *gobject_class;
896 + GstElementClass *gstelement_class;
897 + GstAudioBaseSinkClass *gstaudiobsink_class;
899 + gobject_class = (GObjectClass *) klass;
900 + gstelement_class = (GstElementClass *) klass;
901 + gstaudiobsink_class = (GstAudioBaseSinkClass *) klass;
903 + gobject_class->finalize = gst_pw_audio_sink_finalize;
904 + gobject_class->set_property = gst_pw_audio_sink_set_property;
905 + gobject_class->get_property = gst_pw_audio_sink_get_property;
907 + gstaudiobsink_class->create_ringbuffer = gst_pw_audio_sink_create_ringbuffer;
909 + g_object_class_install_property (gobject_class, PROP_PATH,
910 + g_param_spec_string ("path", "Path",
911 + "The sink path to connect to (NULL = default)", NULL,
912 + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
914 + g_object_class_install_property (gobject_class, PROP_CLIENT_NAME,
915 + g_param_spec_string ("client-name", "Client Name",
916 + "The client name to use (NULL = default)", NULL,
917 + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
919 + g_object_class_install_property (gobject_class, PROP_STREAM_PROPERTIES,
920 + g_param_spec_boxed ("stream-properties", "Stream properties",
921 + "List of PipeWire stream properties", GST_TYPE_STRUCTURE,
922 + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
924 + g_object_class_install_property (gobject_class, PROP_FD,
925 + g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1,
926 + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
928 + gst_element_class_set_static_metadata (gstelement_class,
929 + "PipeWire Audio sink", "Sink/Audio",
930 + "Send audio to PipeWire",
931 + "George Kiagiadakis <george.kiagiadakis@collabora.com>");
933 + gst_element_class_add_pad_template (gstelement_class,
934 + gst_static_pad_template_get (&gst_pw_audio_sink_template));
936 + GST_DEBUG_CATEGORY_INIT (pw_audio_sink_debug, "pwaudiosink", 0,
937 + "PipeWire Audio Sink");
940 diff --git a/src/gst/gstpwaudiosink.h b/src/gst/gstpwaudiosink.h
942 index 00000000..7ed0de7b
944 +++ b/src/gst/gstpwaudiosink.h
948 + * Copyright © 2018 Wim Taymans
949 + * Copyright © 2019 Collabora Ltd.
950 + * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
952 + * Permission is hereby granted, free of charge, to any person obtaining a
953 + * copy of this software and associated documentation files (the "Software"),
954 + * to deal in the Software without restriction, including without limitation
955 + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
956 + * and/or sell copies of the Software, and to permit persons to whom the
957 + * Software is furnished to do so, subject to the following conditions:
959 + * The above copyright notice and this permission notice (including the next
960 + * paragraph) shall be included in all copies or substantial portions of the
963 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
964 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
965 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
966 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
967 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
968 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
969 + * DEALINGS IN THE SOFTWARE.
972 +#ifndef __GST_PW_AUDIO_SINK_H__
973 +#define __GST_PW_AUDIO_SINK_H__
975 +#include "gstpwaudioringbuffer.h"
979 +#define GST_TYPE_PW_AUDIO_SINK \
980 + (gst_pw_audio_sink_get_type ())
982 +G_DECLARE_FINAL_TYPE(GstPwAudioSink, gst_pw_audio_sink,
983 + GST, PW_AUDIO_SINK, GstAudioBaseSink);
985 +struct _GstPwAudioSink
987 + GstAudioBaseSink parent;
988 + GstPwAudioRingBufferProps props;
994 diff --git a/src/gst/gstpwaudiosrc.c b/src/gst/gstpwaudiosrc.c
996 index 00000000..6c522982
998 +++ b/src/gst/gstpwaudiosrc.c
1002 + * Copyright © 2018 Wim Taymans
1003 + * Copyright © 2019 Collabora Ltd.
1004 + * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
1006 + * Permission is hereby granted, free of charge, to any person obtaining a
1007 + * copy of this software and associated documentation files (the "Software"),
1008 + * to deal in the Software without restriction, including without limitation
1009 + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
1010 + * and/or sell copies of the Software, and to permit persons to whom the
1011 + * Software is furnished to do so, subject to the following conditions:
1013 + * The above copyright notice and this permission notice (including the next
1014 + * paragraph) shall be included in all copies or substantial portions of the
1017 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1018 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1019 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1020 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1021 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
1022 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
1023 + * DEALINGS IN THE SOFTWARE.
1026 +#ifdef HAVE_CONFIG_H
1027 +#include "config.h"
1030 +#include "gstpwaudiosrc.h"
1032 +GST_DEBUG_CATEGORY_STATIC (pw_audio_src_debug);
1033 +#define GST_CAT_DEFAULT pw_audio_src_debug
1035 +G_DEFINE_TYPE (GstPwAudioSrc, gst_pw_audio_src, GST_TYPE_AUDIO_BASE_SRC);
1042 + PROP_STREAM_PROPERTIES,
1046 +static GstStaticPadTemplate gst_pw_audio_src_template =
1047 +GST_STATIC_PAD_TEMPLATE ("src",
1050 + GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_NE (F32))
1051 + ", layout = (string)\"interleaved\"")
1056 +gst_pw_audio_src_init (GstPwAudioSrc * self)
1058 + self->props.fd = -1;
1062 +gst_pw_audio_src_finalize (GObject * object)
1064 + GstPwAudioSrc *self = GST_PW_AUDIO_SRC (object);
1066 + g_free (self->props.path);
1067 + g_free (self->props.client_name);
1068 + if (self->props.properties)
1069 + gst_structure_free (self->props.properties);
1073 +gst_pw_audio_src_set_property (GObject * object, guint prop_id,
1074 + const GValue * value, GParamSpec * pspec)
1076 + GstPwAudioSrc *self = GST_PW_AUDIO_SRC (object);
1078 + switch (prop_id) {
1080 + g_free (self->props.path);
1081 + self->props.path = g_value_dup_string (value);
1084 + case PROP_CLIENT_NAME:
1085 + g_free (self->props.client_name);
1086 + self->props.client_name = g_value_dup_string (value);
1089 + case PROP_STREAM_PROPERTIES:
1090 + if (self->props.properties)
1091 + gst_structure_free (self->props.properties);
1092 + self->props.properties =
1093 + gst_structure_copy (gst_value_get_structure (value));
1097 + self->props.fd = g_value_get_int (value);
1101 + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1107 +gst_pw_audio_src_get_property (GObject * object, guint prop_id,
1108 + GValue * value, GParamSpec * pspec)
1110 + GstPwAudioSrc *self = GST_PW_AUDIO_SRC (object);
1112 + switch (prop_id) {
1114 + g_value_set_string (value, self->props.path);
1117 + case PROP_CLIENT_NAME:
1118 + g_value_set_string (value, self->props.client_name);
1121 + case PROP_STREAM_PROPERTIES:
1122 + gst_value_set_structure (value, self->props.properties);
1126 + g_value_set_int (value, self->props.fd);
1130 + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1135 +static GstAudioRingBuffer *
1136 +gst_pw_audio_src_create_ringbuffer (GstAudioBaseSrc * sink)
1138 + GstPwAudioSrc *self = GST_PW_AUDIO_SRC (sink);
1139 + GstAudioRingBuffer *buffer;
1141 + GST_DEBUG_OBJECT (sink, "creating ringbuffer");
1142 + buffer = g_object_new (GST_TYPE_PW_AUDIO_RING_BUFFER,
1144 + "direction", PW_DIRECTION_INPUT,
1145 + "props", &self->props,
1147 + GST_DEBUG_OBJECT (sink, "created ringbuffer @%p", buffer);
1153 +gst_pw_audio_src_class_init (GstPwAudioSrcClass * klass)
1155 + GObjectClass *gobject_class;
1156 + GstElementClass *gstelement_class;
1157 + GstAudioBaseSrcClass *gstaudiobsrc_class;
1159 + gobject_class = (GObjectClass *) klass;
1160 + gstelement_class = (GstElementClass *) klass;
1161 + gstaudiobsrc_class = (GstAudioBaseSrcClass *) klass;
1163 + gobject_class->finalize = gst_pw_audio_src_finalize;
1164 + gobject_class->set_property = gst_pw_audio_src_set_property;
1165 + gobject_class->get_property = gst_pw_audio_src_get_property;
1167 + gstaudiobsrc_class->create_ringbuffer = gst_pw_audio_src_create_ringbuffer;
1169 + g_object_class_install_property (gobject_class, PROP_PATH,
1170 + g_param_spec_string ("path", "Path",
1171 + "The sink path to connect to (NULL = default)", NULL,
1172 + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1174 + g_object_class_install_property (gobject_class, PROP_CLIENT_NAME,
1175 + g_param_spec_string ("client-name", "Client Name",
1176 + "The client name to use (NULL = default)", NULL,
1177 + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1179 + g_object_class_install_property (gobject_class, PROP_STREAM_PROPERTIES,
1180 + g_param_spec_boxed ("stream-properties", "Stream properties",
1181 + "List of PipeWire stream properties", GST_TYPE_STRUCTURE,
1182 + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1184 + g_object_class_install_property (gobject_class, PROP_FD,
1185 + g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1,
1186 + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1188 + gst_element_class_set_static_metadata (gstelement_class,
1189 + "PipeWire Audio source", "Source/Audio",
1190 + "Receive audio from PipeWire",
1191 + "George Kiagiadakis <george.kiagiadakis@collabora.com>");
1193 + gst_element_class_add_pad_template (gstelement_class,
1194 + gst_static_pad_template_get (&gst_pw_audio_src_template));
1196 + GST_DEBUG_CATEGORY_INIT (pw_audio_src_debug, "pwaudiosrc", 0,
1197 + "PipeWire Audio Src");
1200 diff --git a/src/gst/gstpwaudiosrc.h b/src/gst/gstpwaudiosrc.h
1201 new file mode 100644
1202 index 00000000..c46e644c
1204 +++ b/src/gst/gstpwaudiosrc.h
1208 + * Copyright © 2018 Wim Taymans
1209 + * Copyright © 2019 Collabora Ltd.
1210 + * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
1212 + * Permission is hereby granted, free of charge, to any person obtaining a
1213 + * copy of this software and associated documentation files (the "Software"),
1214 + * to deal in the Software without restriction, including without limitation
1215 + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
1216 + * and/or sell copies of the Software, and to permit persons to whom the
1217 + * Software is furnished to do so, subject to the following conditions:
1219 + * The above copyright notice and this permission notice (including the next
1220 + * paragraph) shall be included in all copies or substantial portions of the
1223 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1224 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1225 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1226 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1227 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
1228 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
1229 + * DEALINGS IN THE SOFTWARE.
1232 +#ifndef __GST_PW_AUDIO_SRC_H__
1233 +#define __GST_PW_AUDIO_SRC_H__
1235 +#include "gstpwaudioringbuffer.h"
1239 +#define GST_TYPE_PW_AUDIO_SRC \
1240 + (gst_pw_audio_src_get_type ())
1242 +G_DECLARE_FINAL_TYPE(GstPwAudioSrc, gst_pw_audio_src,
1243 + GST, PW_AUDIO_SRC, GstAudioBaseSrc);
1245 +struct _GstPwAudioSrc
1247 + GstAudioBaseSrc parent;
1248 + GstPwAudioRingBufferProps props;
1254 diff --git a/src/gst/meson.build b/src/gst/meson.build
1255 index ad0e0801..0e922347 100644
1256 --- a/src/gst/meson.build
1257 +++ b/src/gst/meson.build
1258 @@ -6,6 +6,9 @@ pipewire_gst_sources = [
1259 'gstpipewirepool.c',
1260 'gstpipewiresink.c',
1262 + 'gstpwaudioringbuffer.c',
1263 + 'gstpwaudiosink.c',
1264 + 'gstpwaudiosrc.c',
1267 pipewire_gst_headers = [
1268 @@ -15,6 +18,9 @@ pipewire_gst_headers = [
1269 'gstpipewirepool.h',
1270 'gstpipewiresink.h',
1272 + 'gstpwaudioringbuffer.h',
1273 + 'gstpwaudiosink.h',
1274 + 'gstpwaudiosrc.h',
1277 pipewire_gst_c_args = [