Blob Blame History Raw

# HG changeset patch
# User stransky <stransky@redhat.com>
# Date 1604009732 0
# Node ID e5e706b532041baa3ef46420c9bf10629b0cb3ba
# Parent  7dad14e6de1a80a5c6a194df8a7b16504fb3d617
Bug 1672945 Ship PipeWire 0.3 headers and library wrapper to build PW support out of the box, r=dminor

- Ship PipeWire 0.3 headers at third_party/pipewire/pipewire
- Ship SPA 0.2 headers at third_party/pipewire/spa
- Ship PipeWire library wrapper at third_party/pipewire/libpipewire

Differential Revision: https://phabricator.services.mozilla.com/D94580

diff --git a/third_party/pipewire/README b/third_party/pipewire/README
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/README
@@ -0,0 +1,4 @@
+Libpipewire is a pipewire library wrapper needed to build and run Firefox with
+Pipewire support on Linux (https://pipewire.org/).
+
+Pipewire directory stores headers of pipewire-0.2 and spa-0.2 needed for build only.
diff --git a/third_party/pipewire/libpipewire/moz.build b/third_party/pipewire/libpipewire/moz.build
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/libpipewire/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+    'mozpipewire.cpp',
+]
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
+
+LOCAL_INCLUDES += ['/third_party/pipewire']
+
+FINAL_LIBRARY = 'xul'
diff --git a/third_party/pipewire/libpipewire/mozpipewire.cpp b/third_party/pipewire/libpipewire/mozpipewire.cpp
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/libpipewire/mozpipewire.cpp
@@ -0,0 +1,312 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <glib.h>
+#include "mozilla/Types.h"
+#include "prlink.h"
+
+#include <pipewire/pipewire.h>
+
+#define GET_FUNC(func, lib)                                  \
+    func##_fn =                                              \
+      (decltype(func##_fn))PR_FindFunctionSymbol(lib, #func) \
+
+#define IS_FUNC_LOADED(func)                                          \
+    (func != nullptr)                                                 \
+
+struct GUnixFDList;
+
+extern "C" gint
+g_unix_fd_list_get(struct GUnixFDList *list,
+                   gint index_,
+                   GError **error)
+{
+  static PRLibrary* gioLib = nullptr;
+  static bool gioInitialized = false;
+  static gint (*g_unix_fd_list_get_fn)(struct GUnixFDList *list,
+               gint index_, GError **error) = nullptr;
+
+  if (!gioInitialized) {
+    gioInitialized = true;
+    gioLib = PR_LoadLibrary("libgio-2.0.so.0");
+    if (!gioLib) {
+      return -1;
+    }
+    GET_FUNC(g_unix_fd_list_get, gioLib);
+  }
+
+  if (!g_unix_fd_list_get_fn) {
+    return -1;
+  }
+
+  return g_unix_fd_list_get_fn(list, index_, error);
+}
+
+static struct pw_core * (*pw_context_connect_fn)(struct pw_context *context,
+                                              struct pw_properties *properties,
+                                              size_t user_data_size);
+static void (*pw_context_destroy_fn)(struct pw_context *context);
+struct pw_context * (*pw_context_new_fn)(struct pw_loop *main_loop,
+                                      struct pw_properties *props,
+                                      size_t user_data_size);
+static int (*pw_core_disconnect_fn)(struct pw_core *core);
+static void (*pw_init_fn)(int *argc, char **argv[]);
+static void (*pw_stream_add_listener_fn)(struct pw_stream *stream,
+                                      struct spa_hook *listener,
+                                      const struct pw_stream_events *events,
+                                      void *data);
+static int (*pw_stream_connect_fn)(struct pw_stream *stream,
+                                enum pw_direction direction,
+                                uint32_t target_id,
+                                enum pw_stream_flags flags,
+                                const struct spa_pod **params,
+                                uint32_t n_params);
+static struct pw_buffer* (*pw_stream_dequeue_buffer_fn)(struct pw_stream *stream);
+static void (*pw_stream_destroy_fn)(struct pw_stream *stream);
+static struct pw_stream* (*pw_stream_new_fn)(struct pw_core *core,
+                                          const char *name,
+                                          struct pw_properties *props);
+static int (*pw_stream_queue_buffer_fn)(struct pw_stream *stream,
+                                     struct pw_buffer *buffer);
+static int (*pw_stream_update_params_fn)(struct pw_stream *stream,
+                                      const struct spa_pod **params,
+                                      uint32_t n_params);
+static void (*pw_thread_loop_destroy_fn)(struct pw_thread_loop *loop);
+static struct pw_loop* (*pw_thread_loop_get_loop_fn)(struct pw_thread_loop *loop);
+static struct pw_thread_loop* (*pw_thread_loop_new_fn)(const char *name,
+                                                const struct spa_dict *props);
+static int (*pw_thread_loop_start_fn)(struct pw_thread_loop *loop);
+static void (*pw_thread_loop_stop_fn)(struct pw_thread_loop *loop);
+
+bool IsPwLibraryLoaded() {
+  static bool isLoaded =
+         (IS_FUNC_LOADED(pw_context_connect_fn) &&
+          IS_FUNC_LOADED(pw_context_destroy_fn) &&
+          IS_FUNC_LOADED(pw_context_new_fn) &&
+          IS_FUNC_LOADED(pw_core_disconnect_fn) &&
+          IS_FUNC_LOADED(pw_init_fn) &&
+          IS_FUNC_LOADED(pw_stream_add_listener_fn) &&
+          IS_FUNC_LOADED(pw_stream_connect_fn) &&
+          IS_FUNC_LOADED(pw_stream_dequeue_buffer_fn) &&
+          IS_FUNC_LOADED(pw_stream_destroy_fn) &&
+          IS_FUNC_LOADED(pw_stream_new_fn) &&
+          IS_FUNC_LOADED(pw_stream_queue_buffer_fn) &&
+          IS_FUNC_LOADED(pw_stream_update_params_fn) &&
+          IS_FUNC_LOADED(pw_thread_loop_destroy_fn) &&
+          IS_FUNC_LOADED(pw_thread_loop_get_loop_fn) &&
+          IS_FUNC_LOADED(pw_thread_loop_new_fn) &&
+          IS_FUNC_LOADED(pw_thread_loop_start_fn) &&
+          IS_FUNC_LOADED(pw_thread_loop_stop_fn));
+
+  return isLoaded;
+}
+
+bool LoadPWLibrary() {
+  static PRLibrary* pwLib = nullptr;
+  static bool pwInitialized = false;
+
+  //TODO Thread safe
+  if (!pwInitialized) {
+    pwInitialized = true;
+    pwLib = PR_LoadLibrary("libpipewire-0.3.so.0");
+    if (!pwLib) {
+      return false;
+    }
+
+    GET_FUNC(pw_context_connect, pwLib);
+    GET_FUNC(pw_context_destroy, pwLib);
+    GET_FUNC(pw_context_new, pwLib);
+    GET_FUNC(pw_core_disconnect, pwLib);
+    GET_FUNC(pw_init, pwLib);
+    GET_FUNC(pw_stream_add_listener, pwLib);
+    GET_FUNC(pw_stream_connect, pwLib);
+    GET_FUNC(pw_stream_dequeue_buffer, pwLib);
+    GET_FUNC(pw_stream_destroy, pwLib);
+    GET_FUNC(pw_stream_new, pwLib);
+    GET_FUNC(pw_stream_queue_buffer, pwLib);
+    GET_FUNC(pw_stream_update_params, pwLib);
+    GET_FUNC(pw_thread_loop_destroy, pwLib);
+    GET_FUNC(pw_thread_loop_get_loop, pwLib);
+    GET_FUNC(pw_thread_loop_new, pwLib);
+    GET_FUNC(pw_thread_loop_start, pwLib);
+    GET_FUNC(pw_thread_loop_stop, pwLib);
+  }
+
+  return IsPwLibraryLoaded();
+}
+
+struct pw_core *
+pw_context_connect(struct pw_context *context,
+                   struct pw_properties *properties,
+                   size_t user_data_size)
+{
+  if (!LoadPWLibrary()) {
+    return nullptr;
+  }
+  return pw_context_connect_fn(context, properties, user_data_size);
+}
+
+void
+pw_context_destroy(struct pw_context *context)
+{
+  if (!LoadPWLibrary()) {
+    return;
+  }
+  pw_context_destroy_fn(context);
+}
+
+struct pw_context *
+pw_context_new(struct pw_loop *main_loop,
+               struct pw_properties *props,
+               size_t user_data_size)
+{
+  if (!LoadPWLibrary()) {
+    return nullptr;
+  }
+  return pw_context_new_fn(main_loop, props, user_data_size);
+}
+
+int
+pw_core_disconnect(struct pw_core *core)
+{
+  if (!LoadPWLibrary()) {
+    return 0;
+  }
+  return pw_core_disconnect_fn(core);
+}
+
+void
+pw_init(int *argc, char **argv[])
+{
+  if (!LoadPWLibrary()) {
+    return;
+  }
+  return pw_init_fn(argc, argv);
+}
+
+void
+pw_stream_add_listener(struct pw_stream *stream,
+                       struct spa_hook *listener,
+                       const struct pw_stream_events *events,
+                       void *data)
+{
+  if (!LoadPWLibrary()) {
+    return;
+  }
+  return pw_stream_add_listener_fn(stream, listener, events, data);
+}
+
+int
+pw_stream_connect(struct pw_stream *stream,
+                  enum pw_direction direction,
+                  uint32_t target_id,
+                  enum pw_stream_flags flags,
+                  const struct spa_pod **params,
+                  uint32_t n_params)
+{
+  if (!LoadPWLibrary()) {
+    return 0;
+  }
+  return pw_stream_connect_fn(stream, direction, target_id, flags,
+                              params, n_params);
+}
+
+struct pw_buffer *
+pw_stream_dequeue_buffer(struct pw_stream *stream)
+{
+  if (!LoadPWLibrary()) {
+    return nullptr;
+  }
+  return pw_stream_dequeue_buffer_fn(stream);
+}
+
+void
+pw_stream_destroy(struct pw_stream *stream)
+{
+  if (!LoadPWLibrary()) {
+    return;
+  }
+  return pw_stream_destroy_fn(stream);
+}
+
+struct pw_stream *
+pw_stream_new(struct pw_core *core,
+              const char *name,
+              struct pw_properties *props)
+{
+  if (!LoadPWLibrary()) {
+    return nullptr;
+  }
+  return pw_stream_new_fn(core, name, props);
+}
+
+int
+pw_stream_queue_buffer(struct pw_stream *stream,
+                       struct pw_buffer *buffer)
+{
+  if (!LoadPWLibrary()) {
+    return 0;
+  }
+  return pw_stream_queue_buffer_fn(stream, buffer);
+}
+
+int
+pw_stream_update_params(struct pw_stream *stream,
+                        const struct spa_pod **params,
+                        uint32_t n_params)
+{
+  if (!LoadPWLibrary()) {
+    return 0;
+  }
+  return pw_stream_update_params_fn(stream, params, n_params);
+}
+
+void
+pw_thread_loop_destroy(struct pw_thread_loop *loop)
+{
+  if (!LoadPWLibrary()) {
+    return;
+  }
+  return pw_thread_loop_destroy_fn(loop);
+}
+
+struct pw_loop *
+pw_thread_loop_get_loop(struct pw_thread_loop *loop)
+{
+  if (!LoadPWLibrary()) {
+    return nullptr;
+  }
+  return pw_thread_loop_get_loop_fn(loop);
+}
+
+struct pw_thread_loop *
+pw_thread_loop_new(const char *name,
+                   const struct spa_dict *props)
+{
+  if (!LoadPWLibrary()) {
+    return nullptr;
+  }
+  return pw_thread_loop_new_fn(name, props);
+}
+
+int
+pw_thread_loop_start(struct pw_thread_loop *loop)
+{
+  if (!LoadPWLibrary()) {
+    return 0;
+  }
+  return pw_thread_loop_start_fn(loop);
+}
+
+void
+pw_thread_loop_stop(struct pw_thread_loop *loop)
+{
+  if (!LoadPWLibrary()) {
+    return;
+  }
+  return pw_thread_loop_stop_fn(loop);
+}
diff --git a/third_party/pipewire/pipewire/array.h b/third_party/pipewire/pipewire/array.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/array.h
@@ -0,0 +1,165 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_ARRAY_H
+#define PIPEWIRE_ARRAY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+
+/** \class pw_array
+ *
+ * \brief An array object
+ *
+ * The array is a dynamically resizable data structure that can
+ * hold items of the same size.
+ */
+struct pw_array {
+	void *data;		/**< pointer to array data */
+	size_t size;		/**< length of array in bytes */
+	size_t alloc;		/**< number of allocated memory in \a data */
+	size_t extend;		/**< number of bytes to extend with */
+};
+
+#define PW_ARRAY_INIT(extend) (struct pw_array) { NULL, 0, 0, extend }
+
+#define pw_array_get_len_s(a,s)			((a)->size / (s))
+#define pw_array_get_unchecked_s(a,idx,s,t)	SPA_MEMBER((a)->data,(idx)*(s),t)
+#define pw_array_check_index_s(a,idx,s)		((idx) < pw_array_get_len_s(a,s))
+
+/** Get the number of items of type \a t in array \memberof pw_array */
+#define pw_array_get_len(a,t)			pw_array_get_len_s(a,sizeof(t))
+/** Get the item with index \a idx and type \a t from array \memberof pw_array */
+#define pw_array_get_unchecked(a,idx,t)		pw_array_get_unchecked_s(a,idx,sizeof(t),t)
+/** Check if an item with index \a idx and type \a t exist in array \memberof pw_array */
+#define pw_array_check_index(a,idx,t)		pw_array_check_index_s(a,idx,sizeof(t))
+
+#define pw_array_first(a)	((a)->data)
+#define pw_array_end(a)		SPA_MEMBER((a)->data, (a)->size, void)
+#define pw_array_check(a,p)	(SPA_MEMBER(p,sizeof(*p),void) <= pw_array_end(a))
+
+#define pw_array_for_each(pos, array)					\
+	for (pos = (__typeof__(pos)) pw_array_first(array);		\
+	     pw_array_check(array, pos);				\
+	     (pos)++)
+
+#define pw_array_consume(pos, array)					\
+	for (pos = (__typeof__(pos)) pw_array_first(array);		\
+	     pw_array_check(array, pos);				\
+	     pos = (__typeof__(pos)) pw_array_first(array))
+
+#define pw_array_remove(a,p)						\
+({									\
+	(a)->size -= sizeof(*(p));					\
+	memmove(p, SPA_MEMBER((p), sizeof(*(p)), void),			\
+                SPA_PTRDIFF(pw_array_end(a),(p)));			\
+})
+
+/** Initialize the array with given extend \memberof pw_array */
+static inline void pw_array_init(struct pw_array *arr, size_t extend)
+{
+	arr->data = NULL;
+	arr->size = arr->alloc = 0;
+	arr->extend = extend;
+}
+
+/** Clear the array */
+static inline void pw_array_clear(struct pw_array *arr)
+{
+	free(arr->data);
+}
+
+/** Reset the array */
+static inline void pw_array_reset(struct pw_array *arr)
+{
+	arr->size = 0;
+}
+
+/** Make sure \a size bytes can be added to the array \memberof pw_array */
+static inline int pw_array_ensure_size(struct pw_array *arr, size_t size)
+{
+	size_t alloc, need;
+
+	alloc = arr->alloc;
+	need = arr->size + size;
+
+	if (SPA_UNLIKELY(alloc < need)) {
+		void *data;
+		alloc = SPA_MAX(alloc, arr->extend);
+		while (alloc < need)
+			alloc *= 2;
+		if (SPA_UNLIKELY((data = realloc(arr->data, alloc)) == NULL))
+			return -errno;
+		arr->data = data;
+		arr->alloc = alloc;
+	}
+	return 0;
+}
+
+/** Add \a ref size bytes to \a arr. A pointer to memory that can
+ * hold at least \a size bytes is returned \memberof pw_array */
+static inline void *pw_array_add(struct pw_array *arr, size_t size)
+{
+	void *p;
+
+	if (pw_array_ensure_size(arr, size) < 0)
+		return NULL;
+
+	p = SPA_MEMBER(arr->data, arr->size, void);
+	arr->size += size;
+
+	return p;
+}
+
+/** Add \a ref size bytes to \a arr. When there is not enough memory to
+ * hold \a size bytes, NULL is returned \memberof pw_array */
+static inline void *pw_array_add_fixed(struct pw_array *arr, size_t size)
+{
+	void *p;
+
+	if (SPA_UNLIKELY(arr->alloc < arr->size + size)) {
+		errno = ENOSPC;
+		return NULL;
+	}
+
+	p = SPA_MEMBER(arr->data, arr->size, void);
+	arr->size += size;
+
+	return p;
+}
+
+/** Add a pointer to array \memberof pw_array */
+#define pw_array_add_ptr(a,p)					\
+	*((void**) pw_array_add(a, sizeof(void*))) = (p)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_ARRAY_H */
diff --git a/third_party/pipewire/pipewire/buffers.h b/third_party/pipewire/pipewire/buffers.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/buffers.h
@@ -0,0 +1,60 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_BUFFERS_H
+#define PIPEWIRE_BUFFERS_H
+
+#include <spa/node/node.h>
+
+#include <pipewire/context.h>
+#include <pipewire/mem.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PW_BUFFERS_FLAG_NONE		0
+#define PW_BUFFERS_FLAG_NO_MEM		(1<<0)	/**< don't allocate buffer memory */
+#define PW_BUFFERS_FLAG_SHARED		(1<<1)	/**< buffers can be shared */
+#define PW_BUFFERS_FLAG_DYNAMIC		(1<<2)	/**< buffers have dynamic data */
+
+struct pw_buffers {
+	struct pw_memblock *mem;	/**< allocated buffer memory */
+	struct spa_buffer **buffers;	/**< port buffers */
+	uint32_t n_buffers;		/**< number of port buffers */
+	uint32_t flags;			/**< flags */
+};
+
+int pw_buffers_negotiate(struct pw_context *context, uint32_t flags,
+		struct spa_node *outnode, uint32_t out_port_id,
+		struct spa_node *innode, uint32_t in_port_id,
+		struct pw_buffers *result);
+
+void pw_buffers_clear(struct pw_buffers *buffers);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_BUFFERS_H */
diff --git a/third_party/pipewire/pipewire/client.h b/third_party/pipewire/pipewire/client.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/client.h
@@ -0,0 +1,171 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CLIENT_H
+#define PIPEWIRE_CLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/param/param.h>
+
+#include <pipewire/proxy.h>
+#include <pipewire/permission.h>
+
+#define PW_TYPE_INTERFACE_Client	PW_TYPE_INFO_INTERFACE_BASE "Client"
+
+#define PW_VERSION_CLIENT		3
+struct pw_client;
+
+/* default ID of the current client after connect */
+#define PW_ID_CLIENT			1
+
+/** The client information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_client_info {
+	uint32_t id;			/**< id of the global */
+#define PW_CLIENT_CHANGE_MASK_PROPS	(1 << 0)
+#define PW_CLIENT_CHANGE_MASK_ALL	((1 << 1)-1)
+	uint64_t change_mask;		/**< bitfield of changed fields since last call */
+	struct spa_dict *props;		/**< extra properties */
+};
+
+/** Update and existing \ref pw_client_info with \a update \memberof pw_introspect */
+struct pw_client_info *
+pw_client_info_update(struct pw_client_info *info,
+		      const struct pw_client_info *update);
+
+/** Free a \ref pw_client_info \memberof pw_introspect */
+void pw_client_info_free(struct pw_client_info *info);
+
+
+#define PW_CLIENT_EVENT_INFO		0
+#define PW_CLIENT_EVENT_PERMISSIONS	1
+#define PW_CLIENT_EVENT_NUM		2
+
+/** Client events */
+struct pw_client_events {
+#define PW_VERSION_CLIENT_EVENTS	0
+	uint32_t version;
+	/**
+	 * Notify client info
+	 *
+	 * \param info info about the client
+	 */
+	void (*info) (void *object, const struct pw_client_info *info);
+	/**
+	 * Notify a client permission
+	 *
+	 * Event emitted as a result of the get_permissions method.
+	 *
+	 * \param default_permissions the default permissions
+	 * \param index the index of the first permission entry
+	 * \param n_permissions the number of permissions
+	 * \param permissions the permissions
+	 */
+	void (*permissions) (void *object,
+			     uint32_t index,
+			     uint32_t n_permissions,
+			     const struct pw_permission *permissions);
+};
+
+
+#define PW_CLIENT_METHOD_ADD_LISTENER		0
+#define PW_CLIENT_METHOD_ERROR			1
+#define PW_CLIENT_METHOD_UPDATE_PROPERTIES	2
+#define PW_CLIENT_METHOD_GET_PERMISSIONS	3
+#define PW_CLIENT_METHOD_UPDATE_PERMISSIONS	4
+#define PW_CLIENT_METHOD_NUM			5
+
+/** Client methods */
+struct pw_client_methods {
+#define PW_VERSION_CLIENT_METHODS	0
+	uint32_t version;
+
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct pw_client_events *events,
+			void *data);
+	/**
+	 * Send an error to a client
+	 *
+	 * \param id the global id to report the error on
+	 * \param res an errno style error code
+	 * \param message an error string
+	 */
+	int (*error) (void *object, uint32_t id, int res, const char *message);
+	/**
+	 * Update client properties
+	 *
+	 * \param props new properties
+	 */
+	int (*update_properties) (void *object, const struct spa_dict *props);
+
+	/**
+	 * Get client permissions
+	 *
+	 * A permissions event will be emitted with the permissions.
+	 *
+	 * \param index the first index to query, 0 for first
+	 * \param num the maximum number of items to get
+	 */
+	int (*get_permissions) (void *object, uint32_t index, uint32_t num);
+	/**
+	 * Manage the permissions of the global objects for this
+	 * client
+	 *
+	 * Update the permissions of the global objects using the
+	 * provided array with permissions
+	 *
+	 * Globals can use the default permissions or can have specific
+	 * permissions assigned to them.
+	 *
+	 * \param n_permissions number of permissions
+	 * \param permissions array of permissions
+	 */
+	int (*update_permissions) (void *object, uint32_t n_permissions,
+			const struct pw_permission *permissions);
+};
+
+#define pw_client_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)o,		\
+			struct pw_client_methods, _res,			\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define pw_client_add_listener(c,...)		pw_client_method(c,add_listener,0,__VA_ARGS__)
+#define pw_client_error(c,...)			pw_client_method(c,error,0,__VA_ARGS__)
+#define pw_client_update_properties(c,...)	pw_client_method(c,update_properties,0,__VA_ARGS__)
+#define pw_client_get_permissions(c,...)	pw_client_method(c,get_permissions,0,__VA_ARGS__)
+#define pw_client_update_permissions(c,...)	pw_client_method(c,update_permissions,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_CLIENT_H */
diff --git a/third_party/pipewire/pipewire/context.h b/third_party/pipewire/pipewire/context.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/context.h
@@ -0,0 +1,188 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CONTEXT_H
+#define PIPEWIRE_CONTEXT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+/** \class pw_context
+ *
+ * \brief the PipeWire context
+ *
+ * The context object manages all locally available resources. It
+ * is used by both clients and servers.
+ *
+ * The context is used to:
+ *
+ *  - Load modules and extend the functionality. This includes
+ *    extending the protocol with new object types or creating
+ *    any of the available objects.
+ *
+ *  - Create implementations of various objects like nodes,
+ *    devices, factories, modules, etc.. This will usually also
+ *    create pw_global objects that can then be shared with
+ *    clients.
+ *
+ *  - Connect to another PipeWire instance (the main daemon, for
+ *    example) and interact with it (See \subpage page_core_api).
+ *
+ *  - Export a local implementation of an object to another
+ *    instance.
+ */
+struct pw_context;
+
+struct pw_global;
+struct pw_impl_client;
+
+#include <pipewire/core.h>
+#include <pipewire/loop.h>
+#include <pipewire/properties.h>
+
+/** \page page_context_api Core API
+ *
+ * \section page_context_overview Overview
+ *
+ * \subpage page_context
+ *
+ * \subpage page_global
+ *
+ * \subpage page_client
+ *
+ * \subpage page_resource
+ *
+ * \subpage page_node
+ *
+ * \subpage page_port
+ *
+ * \subpage page_link
+ */
+
+/** \page page_context Context
+ *
+ * \section page_context_overview Overview
+ *
+ * The context object is an object that manages the state and
+ * resources of a PipeWire instance.
+ */
+
+/** context events emitted by the context object added with \ref pw_context_add_listener */
+struct pw_context_events {
+#define PW_VERSION_CONTEXT_EVENTS	0
+	uint32_t version;
+
+	/** The context is being destroyed */
+	void (*destroy) (void *data);
+	/** The context is being freed */
+	void (*free) (void *data);
+	/** a new client object is added */
+	void (*check_access) (void *data, struct pw_impl_client *client);
+	/** a new global object was added */
+	void (*global_added) (void *data, struct pw_global *global);
+	/** a global object was removed */
+	void (*global_removed) (void *data, struct pw_global *global);
+};
+
+/** Make a new context object for a given main_loop. Ownership of the properties is taken */
+struct pw_context * pw_context_new(struct pw_loop *main_loop,		/**< a main loop to run in */
+			     struct pw_properties *props,	/**< extra properties */
+			     size_t user_data_size		/**< extra user data size */);
+
+/** destroy a context object, all resources except the main_loop will be destroyed */
+void pw_context_destroy(struct pw_context *context);
+
+/** Get the context user data */
+void *pw_context_get_user_data(struct pw_context *context);
+
+/** Add a new event listener to a context */
+void pw_context_add_listener(struct pw_context *context,
+			  struct spa_hook *listener,
+			  const struct pw_context_events *events,
+			  void *data);
+
+/** Get the context properties */
+const struct pw_properties *pw_context_get_properties(struct pw_context *context);
+
+/** Update the context properties */
+int pw_context_update_properties(struct pw_context *context, const struct spa_dict *dict);
+
+/** Get the context support objects */
+const struct spa_support *pw_context_get_support(struct pw_context *context, uint32_t *n_support);
+
+/** get the context main loop */
+struct pw_loop *pw_context_get_main_loop(struct pw_context *context);
+
+/** Iterate the globals of the context. The callback should return
+ * 0 to fetch the next item, any other value stops the iteration and returns
+ * the value. When all callbacks return 0, this function returns 0 when all
+ * globals are iterated. */
+int pw_context_for_each_global(struct pw_context *context,	/**< the context */
+			    int (*callback) (void *data, struct pw_global *global),
+			    void *data);
+
+/** Find a context global by id */
+struct pw_global *pw_context_find_global(struct pw_context *context,	/**< the context */
+				      uint32_t id		/**< the global id */);
+
+/** add a spa library for the given factory_name regex */
+int pw_context_add_spa_lib(struct pw_context *context, const char *factory_regex, const char *lib);
+
+/** find the library name for a spa factory */
+const char * pw_context_find_spa_lib(struct pw_context *context, const char *factory_name);
+
+struct spa_handle *pw_context_load_spa_handle(struct pw_context *context,
+		const char *factory_name,
+		const struct spa_dict *info);
+
+
+/** data for registering export functions */
+struct pw_export_type {
+	struct spa_list link;
+	const char *type;
+	struct pw_proxy * (*func) (struct pw_core *core,
+		const char *type, const struct spa_dict *props, void *object,
+		size_t user_data_size);
+};
+
+/** register a type that can be exported on a context_proxy. This is usually used by
+ * extension modules */
+int pw_context_register_export_type(struct pw_context *context, struct pw_export_type *type);
+/** find information about registered export type */
+const struct pw_export_type *pw_context_find_export_type(struct pw_context *context, const char *type);
+
+/** add an object to the context */
+int pw_context_set_object(struct pw_context *context, const char *type, void *value);
+/** get an object from the context */
+void *pw_context_get_object(struct pw_context *context, const char *type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_CONTEXT_H */
diff --git a/third_party/pipewire/pipewire/control.h b/third_party/pipewire/pipewire/control.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/control.h
@@ -0,0 +1,79 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CONTROL_H
+#define PIPEWIRE_CONTROL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \page page_control Control
+ *
+ * \section page_control_overview Overview
+ *
+ * A control can be used to control a port property.
+ */
+/** \class pw_control
+ *
+ * The control object
+ */
+struct pw_control;
+
+#include <pipewire/impl.h>
+
+/** Port events, use \ref pw_control_add_listener */
+struct pw_control_events {
+#define PW_VERSION_CONTROL_EVENTS 0
+	uint32_t version;
+
+	/** The control is destroyed */
+	void (*destroy) (void *data);
+
+	/** The control is freed */
+	void (*free) (void *data);
+
+	/** control is linked to another control */
+	void (*linked) (void *data, struct pw_control *other);
+	/** control is unlinked from another control */
+	void (*unlinked) (void *data, struct pw_control *other);
+
+};
+
+/** Get the control parent port or NULL when not set */
+struct pw_impl_port *pw_control_get_port(struct pw_control *control);
+
+/** Add an event listener on the control */
+void pw_control_add_listener(struct pw_control *control,
+			     struct spa_hook *listener,
+			     const struct pw_control_events *events,
+			     void *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_CONTROL_H */
diff --git a/third_party/pipewire/pipewire/core.h b/third_party/pipewire/pipewire/core.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/core.h
@@ -0,0 +1,584 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CORE_H
+#define PIPEWIRE_CORE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <errno.h>
+
+#include <spa/utils/hook.h>
+
+#define PW_TYPE_INTERFACE_Core		PW_TYPE_INFO_INTERFACE_BASE "Core"
+#define PW_TYPE_INTERFACE_Registry	PW_TYPE_INFO_INTERFACE_BASE "Registry"
+
+#define PW_VERSION_CORE		3
+struct pw_core;
+#define PW_VERSION_REGISTRY	3
+struct pw_registry;
+
+/* the default remote name to connect to */
+#define PW_DEFAULT_REMOTE	"pipewire-0"
+
+/* default ID for the core object after connect */
+#define PW_ID_CORE		0
+
+/* invalid ID that matches any object when used for permissions */
+#define PW_ID_ANY		(uint32_t)(0xffffffff)
+
+/**  The core information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_core_info {
+	uint32_t id;			/**< id of the global */
+	uint32_t cookie;		/**< a random cookie for identifying this instance of PipeWire */
+	const char *user_name;		/**< name of the user that started the core */
+	const char *host_name;		/**< name of the machine the core is running on */
+	const char *version;		/**< version of the core */
+	const char *name;		/**< name of the core */
+#define PW_CORE_CHANGE_MASK_PROPS      (1 << 0)
+#define PW_CORE_CHANGE_MASK_ALL        ((1 << 1)-1)
+	uint64_t change_mask;		/**< bitfield of changed fields since last call */
+	struct spa_dict *props;		/**< extra properties */
+};
+
+#include <pipewire/context.h>
+#include <pipewire/properties.h>
+#include <pipewire/proxy.h>
+
+/** Update and existing \ref pw_core_info with \a update  \memberof pw_introspect */
+struct pw_core_info *
+pw_core_info_update(struct pw_core_info *info,
+		    const struct pw_core_info *update);
+
+/** Free a \ref pw_core_info  \memberof pw_introspect */
+void pw_core_info_free(struct pw_core_info *info);
+
+/**
+ * \page page_iface_pw_core pw_core
+ * \section page_iface_pw_core_desc Description
+ *
+ * The core global object.  This is a special singleton object.  It
+ * is used for internal PipeWire protocol features.
+ * \section page_iface_pw_core API
+ */
+
+/** Core */
+
+#define PW_CORE_EVENT_INFO	0
+#define PW_CORE_EVENT_DONE	1
+#define PW_CORE_EVENT_PING	2
+#define PW_CORE_EVENT_ERROR	3
+#define PW_CORE_EVENT_REMOVE_ID	4
+#define PW_CORE_EVENT_BOUND_ID	5
+#define PW_CORE_EVENT_ADD_MEM	6
+#define PW_CORE_EVENT_REMOVE_MEM	7
+#define PW_CORE_EVENT_NUM		8
+
+/** \struct pw_core_events
+ *  \brief Core events
+ *  \ingroup pw_core_interface The pw_core interface
+ */
+struct pw_core_events {
+#define PW_VERSION_CORE_EVENTS	0
+	uint32_t version;
+
+	/**
+	 * Notify new core info
+	 *
+	 * This event is emitted when first bound to the core or when the
+	 * hello method is called.
+	 *
+	 * \param info new core info
+	 */
+	void (*info) (void *object, const struct pw_core_info *info);
+	/**
+	 * Emit a done event
+	 *
+	 * The done event is emitted as a result of a sync method with the
+	 * same seq number.
+	 *
+	 * \param seq the seq number passed to the sync method call
+	 */
+	void (*done) (void *object, uint32_t id, int seq);
+
+	/** Emit a ping event
+	 *
+	 * The client should reply with a pong reply with the same seq
+	 * number.
+	 */
+	void (*ping) (void *object, uint32_t id, int seq);
+
+	/**
+	 * Fatal error event
+         *
+         * The error event is sent out when a fatal (non-recoverable)
+         * error has occurred. The id argument is the proxy object where
+         * the error occurred, most often in response to a request to that
+         * object. The message is a brief description of the error,
+         * for (debugging) convenience.
+	 *
+	 * This event is usually also emitted on the proxy object with
+	 * \a id.
+	 *
+         * \param id object where the error occurred
+         * \param seq the sequence number that generated the error
+         * \param res error code
+         * \param message error description
+	 */
+	void (*error) (void *object, uint32_t id, int seq, int res, const char *message);
+	/**
+	 * Remove an object ID
+         *
+         * This event is used internally by the object ID management
+         * logic. When a client deletes an object, the server will send
+         * this event to acknowledge that it has seen the delete request.
+         * When the client receives this event, it will know that it can
+         * safely reuse the object ID.
+	 *
+         * \param id deleted object ID
+	 */
+	void (*remove_id) (void *object, uint32_t id);
+
+	/**
+	 * Notify an object binding
+	 *
+	 * This event is emitted when a local object ID is bound to a
+	 * global ID. It is emitted before the global becomes visible in the
+	 * registry.
+	 *
+	 * \param id bound object ID
+	 * \param global_id the global id bound to
+	 */
+	void (*bound_id) (void *object, uint32_t id, uint32_t global_id);
+
+	/**
+	 * Add memory for a client
+	 *
+	 * Memory is given to a client as \a fd of a certain
+	 * memory \a type.
+	 *
+	 * Further references to this fd will be made with the per memory
+	 * unique identifier \a id.
+	 *
+	 * \param id the unique id of the memory
+	 * \param type the memory type, one of enum spa_data_type
+	 * \param fd the file descriptor
+	 * \param flags extra flags
+	 */
+	void (*add_mem) (void *object, uint32_t id, uint32_t type, int fd, uint32_t flags);
+
+	/**
+	 * Remove memory for a client
+	 *
+	 * \param id the memory id to remove
+	 */
+	void (*remove_mem) (void *object, uint32_t id);
+};
+
+#define PW_CORE_METHOD_ADD_LISTENER	0
+#define PW_CORE_METHOD_HELLO		1
+#define PW_CORE_METHOD_SYNC		2
+#define PW_CORE_METHOD_PONG		3
+#define PW_CORE_METHOD_ERROR		4
+#define PW_CORE_METHOD_GET_REGISTRY	5
+#define PW_CORE_METHOD_CREATE_OBJECT	6
+#define PW_CORE_METHOD_DESTROY		7
+#define PW_CORE_METHOD_NUM		8
+
+/**
+ * \struct pw_core_methods
+ * \brief Core methods
+ *
+ * The core global object. This is a singleton object used for
+ * creating new objects in the remote PipeWire instance. It is
+ * also used for internal features.
+ */
+struct pw_core_methods {
+#define PW_VERSION_CORE_METHODS	0
+	uint32_t version;
+
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct pw_core_events *events,
+			void *data);
+	/**
+	 * Start a conversation with the server. This will send
+	 * the core info and will destroy all resources for the client
+	 * (except the core and client resource).
+	 */
+	int (*hello) (void *object, uint32_t version);
+	/**
+	 * Do server roundtrip
+	 *
+	 * Ask the server to emit the 'done' event with \a seq.
+	 *
+	 * Since methods are handled in-order and events are delivered
+	 * in-order, this can be used as a barrier to ensure all previous
+	 * methods and the resulting events have been handled.
+	 *
+	 * \param seq the seq number passed to the done event
+	 */
+	int (*sync) (void *object, uint32_t id, int seq);
+	/**
+	 * Reply to a server ping event.
+	 *
+	 * Reply to the server ping event with the same seq.
+	 *
+	 * \param seq the seq number received in the ping event
+	 */
+	int (*pong) (void *object, uint32_t id, int seq);
+	/**
+	 * Fatal error event
+         *
+         * The error method is sent out when a fatal (non-recoverable)
+         * error has occurred. The id argument is the proxy object where
+         * the error occurred, most often in response to an event on that
+         * object. The message is a brief description of the error,
+         * for (debugging) convenience.
+	 *
+	 * This method is usually also emitted on the resource object with
+	 * \a id.
+	 *
+         * \param id object where the error occurred
+         * \param res error code
+         * \param message error description
+	 */
+	int (*error) (void *object, uint32_t id, int seq, int res, const char *message);
+	/**
+	 * Get the registry object
+	 *
+	 * Create a registry object that allows the client to list and bind
+	 * the global objects available from the PipeWire server
+	 * \param version the client version
+	 * \param user_data_size extra size
+	 */
+	struct pw_registry * (*get_registry) (void *object, uint32_t version,
+			size_t user_data_size);
+
+	/**
+	 * Create a new object on the PipeWire server from a factory.
+	 *
+	 * \param factory_name the factory name to use
+	 * \param type the interface to bind to
+	 * \param version the version of the interface
+	 * \param props extra properties
+	 * \param user_data_size extra size
+	 */
+	void * (*create_object) (void *object,
+			       const char *factory_name,
+			       const char *type,
+			       uint32_t version,
+			       const struct spa_dict *props,
+			       size_t user_data_size);
+	/**
+	 * Destroy an resource
+	 *
+	 * Destroy the server resource for the given proxy.
+	 *
+	 * \param obj the proxy to destroy
+	 */
+	int (*destroy) (void *object, void *proxy);
+};
+
+#define pw_core_method(o,method,version,...)			\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)o,		\
+			struct pw_core_methods, _res,		\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define pw_core_add_listener(c,...)	pw_core_method(c,add_listener,0,__VA_ARGS__)
+#define pw_core_hello(c,...)		pw_core_method(c,hello,0,__VA_ARGS__)
+#define pw_core_sync(c,...)		pw_core_method(c,sync,0,__VA_ARGS__)
+#define pw_core_pong(c,...)		pw_core_method(c,pong,0,__VA_ARGS__)
+#define pw_core_error(c,...)		pw_core_method(c,error,0,__VA_ARGS__)
+
+
+static inline
+SPA_PRINTF_FUNC(5, 0) int
+pw_core_errorv(struct pw_core *core, uint32_t id, int seq,
+		int res, const char *message, va_list args)
+{
+	char buffer[1024];
+	vsnprintf(buffer, sizeof(buffer), message, args);
+	buffer[1023] = '\0';
+	return pw_core_error(core, id, seq, res, buffer);
+}
+
+static inline
+SPA_PRINTF_FUNC(5, 6) int
+pw_core_errorf(struct pw_core *core, uint32_t id, int seq,
+		int res, const char *message, ...)
+{
+        va_list args;
+	int r;
+	va_start(args, message);
+	r = pw_core_errorv(core, id, seq, res, message, args);
+	va_end(args);
+	return r;
+}
+
+static inline struct pw_registry *
+pw_core_get_registry(struct pw_core *core, uint32_t version, size_t user_data_size)
+{
+	struct pw_registry *res = NULL;
+	spa_interface_call_res((struct spa_interface*)core,
+			struct pw_core_methods, res,
+			get_registry, 0, version, user_data_size);
+	return res;
+}
+
+static inline void *
+pw_core_create_object(struct pw_core *core,
+			    const char *factory_name,
+			    const char *type,
+			    uint32_t version,
+			    const struct spa_dict *props,
+			    size_t user_data_size)
+{
+	void *res = NULL;
+	spa_interface_call_res((struct spa_interface*)core,
+			struct pw_core_methods, res,
+			create_object, 0, factory_name,
+			type, version, props, user_data_size);
+	return res;
+}
+
+#define pw_core_destroy(c,...)		pw_core_method(c,destroy,0,__VA_ARGS__)
+
+/** \page page_registry Registry
+ *
+ * \section page_registry_overview Overview
+ *
+ * The registry object is a singleton object that keeps track of
+ * global objects on the PipeWire instance. See also \ref page_global.
+ *
+ * Global objects typically represent an actual object in PipeWire
+ * (for example, a module or node) or they are singleton
+ * objects such as the core.
+ *
+ * When a client creates a registry object, the registry object
+ * will emit a global event for each global currently in the
+ * registry.  Globals come and go as a result of device hotplugs or
+ * reconfiguration or other events, and the registry will send out
+ * global and global_remove events to keep the client up to date
+ * with the changes.  To mark the end of the initial burst of
+ * events, the client can use the pw_core.sync methosd immediately
+ * after calling pw_core.get_registry.
+ *
+ * A client can bind to a global object by using the bind
+ * request.  This creates a client-side proxy that lets the object
+ * emit events to the client and lets the client invoke methods on
+ * the object. See \ref page_proxy
+ *
+ * Clients can also change the permissions of the global objects that
+ * it can see. This is interesting when you want to configure a
+ * pipewire session before handing it to another application. You
+ * can, for example, hide certain existing or new objects or limit
+ * the access permissions on an object.
+ */
+
+#define PW_REGISTRY_EVENT_GLOBAL             0
+#define PW_REGISTRY_EVENT_GLOBAL_REMOVE      1
+#define PW_REGISTRY_EVENT_NUM                2
+
+/** Registry events */
+struct pw_registry_events {
+#define PW_VERSION_REGISTRY_EVENTS	0
+	uint32_t version;
+	/**
+	 * Notify of a new global object
+	 *
+	 * The registry emits this event when a new global object is
+	 * available.
+	 *
+	 * \param id the global object id
+	 * \param permissions the permissions of the object
+	 * \param type the type of the interface
+	 * \param version the version of the interface
+	 * \param props extra properties of the global
+	 */
+	void (*global) (void *object, uint32_t id,
+		       uint32_t permissions, const char *type, uint32_t version,
+		       const struct spa_dict *props);
+	/**
+	 * Notify of a global object removal
+	 *
+	 * Emitted when a global object was removed from the registry.
+	 * If the client has any bindings to the global, it should destroy
+	 * those.
+	 *
+	 * \param id the id of the global that was removed
+	 */
+	void (*global_remove) (void *object, uint32_t id);
+};
+
+#define PW_REGISTRY_METHOD_ADD_LISTENER	0
+#define PW_REGISTRY_METHOD_BIND		1
+#define PW_REGISTRY_METHOD_DESTROY	2
+#define PW_REGISTRY_METHOD_NUM		3
+
+/** Registry methods */
+struct pw_registry_methods {
+#define PW_VERSION_REGISTRY_METHODS	0
+	uint32_t version;
+
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct pw_registry_events *events,
+			void *data);
+	/**
+	 * Bind to a global object
+	 *
+	 * Bind to the global object with \a id and use the client proxy
+	 * with new_id as the proxy. After this call, methods can be
+	 * send to the remote global object and events can be received
+	 *
+	 * \param id the global id to bind to
+	 * \param type the interface type to bind to
+	 * \param version the interface version to use
+	 * \returns the new object
+	 */
+	void * (*bind) (void *object, uint32_t id, const char *type, uint32_t version,
+			size_t use_data_size);
+
+	/**
+	 * Attempt to destroy a global object
+	 *
+	 * Try to destroy the global object.
+	 *
+	 * \param id the global id to destroy
+	 */
+	int (*destroy) (void *object, uint32_t id);
+};
+
+#define pw_registry_method(o,method,version,...)			\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)o,		\
+			struct pw_registry_methods, _res,		\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+/** Registry */
+#define pw_registry_add_listener(p,...)	pw_registry_method(p,add_listener,0,__VA_ARGS__)
+
+static inline void *
+pw_registry_bind(struct pw_registry *registry,
+		       uint32_t id, const char *type, uint32_t version,
+		       size_t user_data_size)
+{
+	void *res = NULL;
+	spa_interface_call_res((struct spa_interface*)registry,
+			struct pw_registry_methods, res,
+			bind, 0, id, type, version, user_data_size);
+	return res;
+}
+
+#define pw_registry_destroy(p,...)	pw_registry_method(p,destroy,0,__VA_ARGS__)
+
+
+/** Connect to a PipeWire instance \memberof pw_core
+ * \return a pw_core on success or NULL with errno set on error. The core
+ * will have an id of PW_ID_CORE (0) */
+struct pw_core *
+pw_context_connect(struct pw_context *context,		/**< a \ref pw_context */
+	      struct pw_properties *properties,	/**< optional properties, ownership of
+						  *  the properties is taken.*/
+	      size_t user_data_size		/**< extra user data size */);
+
+/** Connect to a PipeWire instance on the given socket \memberof pw_core
+ * \param fd the connected socket to use, the socket will be closed
+ *	automatically on disconnect or error.
+ * \return a pw_core on success or NULL with errno set on error */
+struct pw_core *
+pw_context_connect_fd(struct pw_context *context,	/**< a \ref pw_context */
+	      int fd,				/**< an fd */
+	      struct pw_properties *properties,	/**< optional properties, ownership of
+						  *  the properties is taken.*/
+	      size_t user_data_size		/**< extra user data size */);
+
+/** Connect to a given PipeWire instance \memberof pw_core
+ * \return a pw_core on success or NULL with errno set on error */
+struct pw_core *
+pw_context_connect_self(struct pw_context *context,	/**< a \ref pw_context to connect to */
+	      struct pw_properties *properties,	/**< optional properties, ownership of
+						  *  the properties is taken.*/
+	      size_t user_data_size		/**< extra user data size */);
+
+/** Steal the fd of the core connection or < 0 on error. The core
+  * will be disconnected after this call. */
+int pw_core_steal_fd(struct pw_core *core);
+
+/** Pause or resume the core. When the core is paused, no new events
+ *  will be dispatched until the core is resumed again. */
+int pw_core_set_paused(struct pw_core *core, bool paused);
+
+/** disconnect and destroy a core */
+int pw_core_disconnect(struct pw_core *core);
+
+/** Get the user_data. It is of the size specified when this object was
+ * constructed */
+void *pw_core_get_user_data(struct pw_core *core);
+
+/** Get the client proxy of the connected core. This will have the id
+ * of PW_ID_CLIENT (1) */
+struct pw_client * pw_core_get_client(struct pw_core *core);
+
+/** Get the context object used to created this core */
+struct pw_context * pw_core_get_context(struct pw_core *core);
+
+/** Get properties from the core */
+const struct pw_properties *pw_core_get_properties(struct pw_core *core);
+
+/** Update the core properties. This updates the properties
+ * of the associated client.
+ * \return the number of properties that were updated */
+int pw_core_update_properties(struct pw_core *core, const struct spa_dict *dict);
+
+/** Get the core mempool object */
+struct pw_mempool * pw_core_get_mempool(struct pw_core *core);
+
+/** Get the proxy with the given id */
+struct pw_proxy *pw_core_find_proxy(struct pw_core *core, uint32_t id);
+
+/** Export an object into the PipeWire instance associated with core */
+struct pw_proxy *pw_core_export(struct pw_core *core,			/**< the core */
+				  const char *type,			/**< the type of object */
+				  const struct spa_dict *props,		/**< extra properties */
+				  void *object,				/**< object to export */
+				  size_t user_data_size			/**< extra user data */);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_CORE_H */
diff --git a/third_party/pipewire/pipewire/data-loop.h b/third_party/pipewire/pipewire/data-loop.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/data-loop.h
@@ -0,0 +1,95 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_DATA_LOOP_H
+#define PIPEWIRE_DATA_LOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \class pw_data_loop
+ *
+ * PipeWire rt-loop object. This loop starts a new real-time thread that
+ * is designed to run the processing graph.
+ */
+struct pw_data_loop;
+
+#include <pipewire/loop.h>
+#include <pipewire/properties.h>
+
+/** Loop events, use \ref pw_data_loop_add_listener to add a listener */
+struct pw_data_loop_events {
+#define PW_VERSION_DATA_LOOP_EVENTS		0
+	uint32_t version;
+	/** The loop is destroyed */
+	void (*destroy) (void *data);
+};
+
+/** Make a new loop. */
+struct pw_data_loop *
+pw_data_loop_new(const struct spa_dict *props);
+
+/** Add an event listener to loop */
+void pw_data_loop_add_listener(struct pw_data_loop *loop,
+			       struct spa_hook *listener,
+			       const struct pw_data_loop_events *events,
+			       void *data);
+
+/** wait for activity on the loop up to \a timeout milliseconds.
+ * Should be called from the loop function */
+int pw_data_loop_wait(struct pw_data_loop *loop, int timeout);
+
+/** make sure the thread will exit. Can be called from a loop callback */
+void pw_data_loop_exit(struct pw_data_loop *loop);
+
+/** Get the loop implementation of this data loop */
+struct pw_loop *
+pw_data_loop_get_loop(struct pw_data_loop *loop);
+
+/** Destroy the loop */
+void pw_data_loop_destroy(struct pw_data_loop *loop);
+
+/** Start the processing thread */
+int pw_data_loop_start(struct pw_data_loop *loop);
+
+/** Stop the processing thread */
+int pw_data_loop_stop(struct pw_data_loop *loop);
+
+/** Check if the current thread is the processing thread */
+bool pw_data_loop_in_thread(struct pw_data_loop *loop);
+
+/** invoke func in the context of the thread or in the caller thread when
+ * the loop is not running. Since 0.3.3 */
+int pw_data_loop_invoke(struct pw_data_loop *loop,
+		spa_invoke_func_t func, uint32_t seq, const void *data, size_t size,
+		bool block, void *user_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_DATA_LOOP_H */
diff --git a/third_party/pipewire/pipewire/device.h b/third_party/pipewire/pipewire/device.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/device.h
@@ -0,0 +1,162 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_DEVICE_H
+#define PIPEWIRE_DEVICE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include <pipewire/proxy.h>
+
+#define PW_TYPE_INTERFACE_Device	PW_TYPE_INFO_INTERFACE_BASE "Device"
+
+#define PW_VERSION_DEVICE		3
+struct pw_device;
+
+/** The device information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_device_info {
+	uint32_t id;			/**< id of the global */
+#define PW_DEVICE_CHANGE_MASK_PROPS	(1 << 0)
+#define PW_DEVICE_CHANGE_MASK_PARAMS	(1 << 1)
+#define PW_DEVICE_CHANGE_MASK_ALL	((1 << 2)-1)
+	uint64_t change_mask;		/**< bitfield of changed fields since last call */
+	struct spa_dict *props;		/**< extra properties */
+	struct spa_param_info *params;	/**< parameters */
+	uint32_t n_params;		/**< number of items in \a params */
+};
+
+/** Update and existing \ref pw_device_info with \a update \memberof pw_introspect */
+struct pw_device_info *
+pw_device_info_update(struct pw_device_info *info,
+		      const struct pw_device_info *update);
+
+/** Free a \ref pw_device_info \memberof pw_introspect */
+void pw_device_info_free(struct pw_device_info *info);
+
+#define PW_DEVICE_EVENT_INFO	0
+#define PW_DEVICE_EVENT_PARAM	1
+#define PW_DEVICE_EVENT_NUM	2
+
+/** Device events */
+struct pw_device_events {
+#define PW_VERSION_DEVICE_EVENTS	0
+	uint32_t version;
+	/**
+	 * Notify device info
+	 *
+	 * \param info info about the device
+	 */
+	void (*info) (void *object, const struct pw_device_info *info);
+	/**
+	 * Notify a device param
+	 *
+	 * Event emitted as a result of the enum_params method.
+	 *
+	 * \param seq the sequence number of the request
+	 * \param id the param id
+	 * \param index the param index
+	 * \param next the param index of the next param
+	 * \param param the parameter
+	 */
+	void (*param) (void *object, int seq,
+		      uint32_t id, uint32_t index, uint32_t next,
+		      const struct spa_pod *param);
+};
+
+
+#define PW_DEVICE_METHOD_ADD_LISTENER		0
+#define PW_DEVICE_METHOD_SUBSCRIBE_PARAMS	1
+#define PW_DEVICE_METHOD_ENUM_PARAMS		2
+#define PW_DEVICE_METHOD_SET_PARAM		3
+#define PW_DEVICE_METHOD_NUM			4
+
+/** Device methods */
+struct pw_device_methods {
+#define PW_VERSION_DEVICE_METHODS	0
+	uint32_t version;
+
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct pw_device_events *events,
+			void *data);
+	/**
+	 * Subscribe to parameter changes
+	 *
+	 * Automatically emit param events for the given ids when
+	 * they are changed.
+	 *
+	 * \param ids an array of param ids
+	 * \param n_ids the number of ids in \a ids
+	 */
+	int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+	/**
+	 * Enumerate device parameters
+	 *
+	 * Start enumeration of device parameters. For each param, a
+	 * param event will be emitted.
+	 *
+	 * \param seq a sequence number to place in the reply
+	 * \param id the parameter id to enum or PW_ID_ANY for all
+	 * \param start the start index or 0 for the first param
+	 * \param num the maximum number of params to retrieve
+	 * \param filter a param filter or NULL
+	 */
+	int (*enum_params) (void *object, int seq, uint32_t id, uint32_t start, uint32_t num,
+			    const struct spa_pod *filter);
+	/**
+	 * Set a parameter on the device
+	 *
+	 * \param id the parameter id to set
+	 * \param flags extra parameter flags
+	 * \param param the parameter to set
+	 */
+	int (*set_param) (void *object, uint32_t id, uint32_t flags,
+			  const struct spa_pod *param);
+};
+
+#define pw_device_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)o,		\
+			struct pw_device_methods, _res,			\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define pw_device_add_listener(c,...)		pw_device_method(c,add_listener,0,__VA_ARGS__)
+#define pw_device_subscribe_params(c,...)	pw_device_method(c,subscribe_params,0,__VA_ARGS__)
+#define pw_device_enum_params(c,...)		pw_device_method(c,enum_params,0,__VA_ARGS__)
+#define pw_device_set_param(c,...)		pw_device_method(c,set_param,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_DEVICE_H */
diff --git a/third_party/pipewire/pipewire/factory.h b/third_party/pipewire/pipewire/factory.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/factory.h
@@ -0,0 +1,109 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_FACTORY_H
+#define PIPEWIRE_FACTORY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include <pipewire/proxy.h>
+
+#define PW_TYPE_INTERFACE_Factory	PW_TYPE_INFO_INTERFACE_BASE "Factory"
+
+#define PW_VERSION_FACTORY		3
+struct pw_factory;
+
+/** The factory information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_factory_info {
+	uint32_t id;			/**< id of the global */
+	const char *name;		/**< name the factory */
+	const char *type;		/**< type of the objects created by this factory */
+	uint32_t version;		/**< version of the objects */
+#define PW_FACTORY_CHANGE_MASK_PROPS	(1 << 0)
+#define PW_FACTORY_CHANGE_MASK_ALL	((1 << 1)-1)
+	uint64_t change_mask;		/**< bitfield of changed fields since last call */
+	struct spa_dict *props;		/**< the properties of the factory */
+};
+
+struct pw_factory_info *
+pw_factory_info_update(struct pw_factory_info *info,
+		       const struct pw_factory_info *update);
+
+void
+pw_factory_info_free(struct pw_factory_info *info);
+
+
+#define PW_FACTORY_EVENT_INFO		0
+#define PW_FACTORY_EVENT_NUM		1
+
+/** Factory events */
+struct pw_factory_events {
+#define PW_VERSION_FACTORY_EVENTS	0
+	uint32_t version;
+	/**
+	 * Notify factory info
+	 *
+	 * \param info info about the factory
+	 */
+	void (*info) (void *object, const struct pw_factory_info *info);
+};
+
+#define PW_FACTORY_METHOD_ADD_LISTENER	0
+#define PW_FACTORY_METHOD_NUM		1
+
+/** Factory methods */
+struct pw_factory_methods {
+#define PW_VERSION_FACTORY_METHODS	0
+	uint32_t version;
+
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct pw_factory_events *events,
+			void *data);
+};
+
+#define pw_factory_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)o,		\
+			struct pw_factory_methods, _res,		\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define pw_factory_add_listener(c,...)	pw_factory_method(c,add_listener,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_FACTORY_H */
diff --git a/third_party/pipewire/pipewire/filter.h b/third_party/pipewire/pipewire/filter.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/filter.h
@@ -0,0 +1,240 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_FILTER_H
+#define PIPEWIRE_FILTER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_filter
+ *
+ * \brief PipeWire filter object class
+ *
+ * The filter object provides a convenient way to implement
+ * processing filters.
+ *
+ * See also \ref page_filters and \ref page_core_api
+ */
+struct pw_filter;
+
+#include <spa/buffer/buffer.h>
+#include <spa/node/io.h>
+#include <spa/param/param.h>
+
+#include <pipewire/core.h>
+#include <pipewire/stream.h>
+
+/** \enum pw_filter_state The state of a filter \memberof pw_filter */
+enum pw_filter_state {
+	PW_FILTER_STATE_ERROR = -1,		/**< the strean is in error */
+	PW_FILTER_STATE_UNCONNECTED = 0,	/**< unconnected */
+	PW_FILTER_STATE_CONNECTING = 1,		/**< connection is in progress */
+	PW_FILTER_STATE_PAUSED = 2,		/**< filter is connected and paused */
+	PW_FILTER_STATE_STREAMING = 3		/**< filter is streaming */
+};
+
+#if 0
+struct pw_buffer {
+	struct spa_buffer *buffer;	/**< the spa buffer */
+	void *user_data;		/**< user data attached to the buffer */
+	uint64_t size;			/**< For input ports, this field is set by pw_filter
+					  *  with the duration of the buffer in ticks.
+					  *  For output ports, this field is set by the user.
+					  *  This field is added for all queued buffers and
+					  *  returned in the time info. */
+};
+#endif
+
+/** Events for a filter. These events are always called from the mainloop
+ * unless explicitly documented otherwise. */
+struct pw_filter_events {
+#define PW_VERSION_FILTER_EVENTS	0
+	uint32_t version;
+
+	void (*destroy) (void *data);
+	/** when the filter state changes */
+	void (*state_changed) (void *data, enum pw_filter_state old,
+				enum pw_filter_state state, const char *error);
+
+	/** when io changed on a port of the filter (when port_data is NULL). */
+	void (*io_changed) (void *data, void *port_data,
+			uint32_t id, void *area, uint32_t size);
+	/** when a parameter changed on a port of the filter (when port_data is NULL). */
+	void (*param_changed) (void *data, void *port_data,
+			uint32_t id, const struct spa_pod *param);
+
+        /** when a new buffer was created for a port */
+        void (*add_buffer) (void *data, void *port_data, struct pw_buffer *buffer);
+        /** when a buffer was destroyed for a port */
+        void (*remove_buffer) (void *data, void *port_data, struct pw_buffer *buffer);
+
+        /** do processing. This is normally called from the
+	 *  mainloop but can also be called directly from the realtime data
+	 *  thread if the user is prepared to deal with this. */
+        void (*process) (void *data, struct spa_io_position *position);
+
+	/** The filter is drained */
+        void (*drained) (void *data);
+};
+
+/** Convert a filter state to a readable string \memberof pw_filter */
+const char * pw_filter_state_as_string(enum pw_filter_state state);
+
+/** \enum pw_filter_flags Extra flags that can be used in \ref pw_filter_connect() \memberof pw_filter */
+enum pw_filter_flags {
+	PW_FILTER_FLAG_NONE = 0,			/**< no flags */
+	PW_FILTER_FLAG_INACTIVE		= (1 << 0),	/**< start the filter inactive,
+							  *  pw_filter_set_active() needs to be
+							  *  called explicitly */
+	PW_FILTER_FLAG_DRIVER		= (1 << 1),	/**< be a driver */
+	PW_FILTER_FLAG_RT_PROCESS	= (1 << 2),	/**< call process from the realtime
+							  *  thread */
+};
+
+enum pw_filter_port_flags {
+	PW_FILTER_PORT_FLAG_NONE		= 0,		/**< no flags */
+	PW_FILTER_PORT_FLAG_MAP_BUFFERS		= (1 << 0),	/**< mmap the buffers */
+	PW_FILTER_PORT_FLAG_ALLOC_BUFFERS	= (1 << 1),	/**< the application will allocate buffer
+								  *  memory. In the add_buffer event, the
+								  *  data of the buffer should be set */
+};
+
+/** Create a new unconneced \ref pw_filter \memberof pw_filter
+ * \return a newly allocated \ref pw_filter */
+struct pw_filter *
+pw_filter_new(struct pw_core *core,		/**< a \ref pw_core */
+	      const char *name,			/**< a filter media name */
+	      struct pw_properties *props	/**< filter properties, ownership is taken */);
+
+struct pw_filter *
+pw_filter_new_simple(struct pw_loop *loop,		/**< a \ref pw_loop to use */
+		     const char *name,			/**< a filter media name */
+		     struct pw_properties *props,	/**< filter properties, ownership is taken */
+		     const struct pw_filter_events *events,	/**< filter events */
+		     void *data					/**< data passed to events */);
+
+/** Destroy a filter \memberof pw_filter */
+void pw_filter_destroy(struct pw_filter *filter);
+
+void pw_filter_add_listener(struct pw_filter *filter,
+			    struct spa_hook *listener,
+			    const struct pw_filter_events *events,
+			    void *data);
+
+enum pw_filter_state pw_filter_get_state(struct pw_filter *filter, const char **error);
+
+const char *pw_filter_get_name(struct pw_filter *filter);
+
+struct pw_core *pw_filter_get_core(struct pw_filter *filter);
+
+/** Connect a filter for processing. \memberof pw_filter
+ * \return 0 on success < 0 on error.
+ *
+ * You should connect to the process event and use pw_filter_dequeue_buffer()
+ * to get the latest metadata and data. */
+int
+pw_filter_connect(struct pw_filter *filter,		/**< a \ref pw_filter */
+		  enum pw_filter_flags flags,		/**< filter flags */
+		  const struct spa_pod **params,	/**< an array with params. */
+		  uint32_t n_params			/**< number of items in \a params */);
+
+/** Get the node ID of the filter. \memberof pw_filter
+ * \return node ID. */
+uint32_t
+pw_filter_get_node_id(struct pw_filter *filter);
+
+/** Disconnect \a filter \memberof pw_filter */
+int pw_filter_disconnect(struct pw_filter *filter);
+
+/** add a port to the filter, returns user data of port_data_size. */
+void *pw_filter_add_port(struct pw_filter *filter,
+		enum pw_direction direction,		/**< port direction */
+		enum pw_filter_port_flags flags,	/**< port flags */
+		size_t port_data_size,			/**< allocated and given to the user as port_data */
+		struct pw_properties *props,		/**< port properties, ownership is taken */
+		const struct spa_pod **params,		/**< an array of params. The params should
+							  *  ideally contain the supported formats */
+		uint32_t n_params			/**< number of elements in \a params */);
+
+/** remove a port from the filter */
+int pw_filter_remove_port(void *port_data		/**< data associated with port */);
+
+/** get properties, port_data of NULL will give global properties */
+const struct pw_properties *pw_filter_get_properties(struct pw_filter *filter,
+		void *port_data);
+
+/** Update properties, use NULL port_data for global filter properties */
+int pw_filter_update_properties(struct pw_filter *filter,
+		void *port_data, const struct spa_dict *dict);
+
+/** Set the filter in error state */
+int pw_filter_set_error(struct pw_filter *filter,	/**< a \ref pw_filter */
+			int res,			/**< a result code */
+			const char *error, ...		/**< an error message */) SPA_PRINTF_FUNC(3, 4);
+
+/** Update params, use NULL port_data for global filter params */
+int
+pw_filter_update_params(struct pw_filter *filter,	/**< a \ref pw_filter */
+			void *port_data,		/**< data associated with port */
+			const struct spa_pod **params,	/**< an array of params. */
+			uint32_t n_params		/**< number of elements in \a params */);
+
+
+#if 0
+/** A time structure \memberof pw_filter */
+struct pw_time {
+	int64_t now;			/**< the monotonic time */
+	struct spa_fraction rate;	/**< the rate of \a ticks and delay */
+	uint64_t ticks;			/**< the ticks at \a now. This is the current time that
+					  *  the remote end is reading/writing. */
+};
+#endif
+
+/** Query the time on the filter \memberof pw_filter */
+int pw_filter_get_time(struct pw_filter *filter, struct pw_time *time);
+
+/** Get a buffer that can be filled for output ports or consumed
+ * for input ports.  */
+struct pw_buffer *pw_filter_dequeue_buffer(void *port_data);
+
+/** Submit a buffer for playback or recycle a buffer for capture. */
+int pw_filter_queue_buffer(void *port_data, struct pw_buffer *buffer);
+
+/** Get a data pointer to the buffer data */
+void *pw_filter_get_dsp_buffer(void *port_data, uint32_t n_samples);
+
+/** Activate or deactivate the filter \memberof pw_filter */
+int pw_filter_set_active(struct pw_filter *filter, bool active);
+
+/** Flush a filter. When \a drain is true, the drained callback will
+ * be called when all data is played or recorded */
+int pw_filter_flush(struct pw_filter *filter, bool drain);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_FILTER_H */
diff --git a/third_party/pipewire/pipewire/global.h b/third_party/pipewire/pipewire/global.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/global.h
@@ -0,0 +1,155 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_GLOBAL_H
+#define PIPEWIRE_GLOBAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \page page_global Global
+ *
+ * Global objects represent resources that are available on the PipeWire
+ * context and are accessible to remote clients.
+ * Globals come and go when devices or other resources become available for
+ * clients.
+ *
+ * Remote clients receives a list of globals when it binds to the registry
+ * object. See \ref page_registry.
+ *
+ * A client can bind to a global to send methods or receive events from
+ * the global.
+ */
+/** \class pw_global
+ *
+ * \brief A global object visible to remote clients
+ *
+ * A global object is visible to remote clients and represents a resource
+ * that can be used or inspected.
+ *
+ * See \ref page_remote_api
+ */
+struct pw_global;
+
+#include <pipewire/impl.h>
+
+typedef int (*pw_global_bind_func_t) (void *object,
+		      struct pw_impl_client *client,	/**< client that binds */
+		      uint32_t permissions,	/**< permissions for the bind */
+		      uint32_t version,		/**< client interface version */
+		      uint32_t id		/**< client proxy id */);
+
+/** Global events, use \ref pw_global_add_listener */
+struct pw_global_events {
+#define PW_VERSION_GLOBAL_EVENTS 0
+	uint32_t version;
+
+	/** The global is destroyed */
+	void (*destroy) (void *data);
+	/** The global is freed */
+	void (*free) (void *data);
+	/** The permissions changed for a client */
+	void (*permissions_changed) (void *data,
+			struct pw_impl_client *client,
+			uint32_t old_permissions,
+			uint32_t new_permissions);
+};
+
+/** Create a new global object */
+struct pw_global *
+pw_global_new(struct pw_context *context,	/**< the context */
+	      const char *type,			/**< the interface type of the global */
+	      uint32_t version,			/**< the interface version of the global */
+	      struct pw_properties *properties,	/**< extra properties */
+	      pw_global_bind_func_t func,	/**< function to bind */
+	      void *object			/**< global object */);
+
+/** Register a global object to the context registry */
+int pw_global_register(struct pw_global *global);
+
+/** Add an event listener on the global */
+void pw_global_add_listener(struct pw_global *global,
+			    struct spa_hook *listener,
+			    const struct pw_global_events *events,
+			    void *data);
+
+/** Get the permissions of the global for a given client */
+uint32_t pw_global_get_permissions(struct pw_global *global, struct pw_impl_client *client);
+
+/** Get the context object of this global */
+struct pw_context *pw_global_get_context(struct pw_global *global);
+
+/** Get the global type */
+const char *pw_global_get_type(struct pw_global *global);
+
+/** Check a global type */
+bool pw_global_is_type(struct pw_global *global, const char *type);
+
+/** Get the global version */
+uint32_t pw_global_get_version(struct pw_global *global);
+
+/** Get the global properties */
+const struct pw_properties *pw_global_get_properties(struct pw_global *global);
+
+/** Update the global properties, must be done when unregistered */
+int pw_global_update_keys(struct pw_global *global,
+		     const struct spa_dict *dict, const char *keys[]);
+
+/** Get the object associated with the global. This depends on the type of the
+  * global */
+void *pw_global_get_object(struct pw_global *global);
+
+/** Get the unique id of the global */
+uint32_t pw_global_get_id(struct pw_global *global);
+
+/** Add a resource to a global */
+int pw_global_add_resource(struct pw_global *global, struct pw_resource *resource);
+
+/** Iterate all resources added to the global The callback should return
+ * 0 to fetch the next item, any other value stops the iteration and returns
+ * the value. When all callbacks return 0, this function returns 0 when all
+ * items are iterated. */
+int pw_global_for_each_resource(struct pw_global *global,
+			   int (*callback) (void *data, struct pw_resource *resource),
+			   void *data);
+
+/** Let a client bind to a global */
+int pw_global_bind(struct pw_global *global,
+		   struct pw_impl_client *client,
+		   uint32_t permissions,
+		   uint32_t version,
+		   uint32_t id);
+
+int pw_global_update_permissions(struct pw_global *global, struct pw_impl_client *client,
+		uint32_t old_permissions, uint32_t new_permissions);
+
+/** Destroy a global */
+void pw_global_destroy(struct pw_global *global);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_GLOBAL_H */
diff --git a/third_party/pipewire/pipewire/impl-client.h b/third_party/pipewire/pipewire/impl-client.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/impl-client.h
@@ -0,0 +1,174 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_CLIENT_H
+#define PIPEWIRE_IMPL_CLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \class pw_impl_client
+ *
+ * \brief PipeWire client object class.
+ *
+ * The client object represents a client connection with the PipeWire
+ * server.
+ *
+ * Each client has its own list of resources it is bound to along with
+ * a mapping between the client types and server types.
+ */
+struct pw_impl_client;
+
+#include <pipewire/context.h>
+#include <pipewire/global.h>
+#include <pipewire/properties.h>
+#include <pipewire/resource.h>
+#include <pipewire/permission.h>
+
+/** \page page_client Client
+ *
+ * \section sec_page_client_overview Overview
+ *
+ * The \ref pw_impl_client object is created by a protocol implementation when
+ * a new client connects.
+ *
+ * The client is used to keep track of all resources belonging to one
+ * connection with the PipeWire server.
+ *
+ * \section sec_page_client_credentials Credentials
+ *
+ * The client object will have its credentials filled in by the protocol.
+ * This information is used to check if a resource or action is available
+ * for this client. See also \ref page_access
+ *
+ * \section sec_page_client_types Types
+ *
+ * The client and server maintain a mapping between the client and server
+ * types. All type ids that are in messages exchanged between the client
+ * and server will automatically be remapped. See also \ref page_types.
+ *
+ * \section sec_page_client_resources Resources
+ *
+ * When a client binds to context global object, a resource is made for this
+ * binding and a unique id is assigned to the resources. The client and
+ * server will use this id as the destination when exchanging messages.
+ * See also \ref page_resource
+ */
+
+/** The events that a client can emit */
+struct pw_impl_client_events {
+#define PW_VERSION_IMPL_CLIENT_EVENTS	0
+        uint32_t version;
+
+	/** emitted when the client is destroyed */
+	void (*destroy) (void *data);
+
+	/** emitted right before the client is freed */
+	void (*free) (void *data);
+
+	/** the client is initialized */
+	void (*initialized) (void *data);
+
+	/** emitted when the client info changed */
+	void (*info_changed) (void *data, const struct pw_client_info *info);
+
+	/** emitted when a new resource is added for client */
+	void (*resource_added) (void *data, struct pw_resource *resource);
+
+	/** emitted when a resource is removed */
+	void (*resource_removed) (void *data, struct pw_resource *resource);
+
+	/** emitted when the client becomes busy processing an asynchronous
+	 * message. In the busy state no messages should be processed.
+	 * Processing should resume when the client becomes not busy */
+	void (*busy_changed) (void *data, bool busy);
+};
+
+/** Create a new client. This is mainly used by protocols. */
+struct pw_impl_client *
+pw_context_create_client(struct pw_impl_core *core,		/**< the core object */
+			struct pw_protocol *prototol,		/**< the client protocol */
+			struct pw_properties *properties,	/**< client properties */
+			size_t user_data_size			/**< extra user data size */);
+
+/** Destroy a previously created client */
+void pw_impl_client_destroy(struct pw_impl_client *client);
+
+/** Finish configuration and register a client */
+int pw_impl_client_register(struct pw_impl_client *client,	/**< the client to register */
+		       struct pw_properties *properties/**< extra properties */);
+
+/** Get the client user data */
+void *pw_impl_client_get_user_data(struct pw_impl_client *client);
+
+/** Get the client information */
+const struct pw_client_info *pw_impl_client_get_info(struct pw_impl_client *client);
+
+/** Update the client properties */
+int pw_impl_client_update_properties(struct pw_impl_client *client, const struct spa_dict *dict);
+
+/** Update the client permissions */
+int pw_impl_client_update_permissions(struct pw_impl_client *client, uint32_t n_permissions,
+		const struct pw_permission *permissions);
+
+/** check if a client has permissions for global_id, Since 0.3.9 */
+int pw_impl_client_check_permissions(struct pw_impl_client *client,
+		uint32_t global_id, uint32_t permissions);
+
+/** Get the client properties */
+const struct pw_properties *pw_impl_client_get_properties(struct pw_impl_client *client);
+
+/** Get the context used to create this client */
+struct pw_context *pw_impl_client_get_context(struct pw_impl_client *client);
+/** Get the protocol used to create this client */
+struct pw_protocol *pw_impl_client_get_protocol(struct pw_impl_client *client);
+
+/** Get the client core resource */
+struct pw_resource *pw_impl_client_get_core_resource(struct pw_impl_client *client);
+
+/** Get a resource with the given id */
+struct pw_resource *pw_impl_client_find_resource(struct pw_impl_client *client, uint32_t id);
+
+/** Get the global associated with this client */
+struct pw_global *pw_impl_client_get_global(struct pw_impl_client *client);
+
+/** listen to events from this client */
+void pw_impl_client_add_listener(struct pw_impl_client *client,
+			    struct spa_hook *listener,
+			    const struct pw_impl_client_events *events,
+			    void *data);
+
+
+/** Mark the client busy. This can be used when an asynchronous operation is
+  * started and no further processing is allowed to happen for the client */
+void pw_impl_client_set_busy(struct pw_impl_client *client, bool busy);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_CLIENT_H */
diff --git a/third_party/pipewire/pipewire/impl-core.h b/third_party/pipewire/pipewire/impl-core.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/impl-core.h
@@ -0,0 +1,94 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_CORE_H
+#define PIPEWIRE_IMPL_CORE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_impl_core
+ *
+ * \brief PipeWire core interface.
+ *
+ * The core is used to make objects on demand.
+ */
+struct pw_impl_core;
+
+#include <pipewire/context.h>
+#include <pipewire/global.h>
+#include <pipewire/properties.h>
+#include <pipewire/resource.h>
+
+/** Factory events, listen to them with \ref pw_impl_core_add_listener */
+struct pw_impl_core_events {
+#define PW_VERSION_IMPL_CORE_EVENTS	0
+	uint32_t version;
+
+	/** the core is destroyed */
+        void (*destroy) (void *data);
+	/** the core is freed */
+        void (*free) (void *data);
+	/** the core is initialized */
+        void (*initialized) (void *data);
+};
+
+struct pw_impl_core *pw_context_create_core(struct pw_context *context,
+				  struct pw_properties *properties,
+				  size_t user_data_size);
+
+/* get the default core in a context */
+struct pw_impl_core *pw_context_get_default_core(struct pw_context *context);
+
+/** Get the core properties */
+const struct pw_properties *pw_impl_core_get_properties(struct pw_impl_core *core);
+
+/** Get the core information */
+const struct pw_core_info *pw_impl_core_get_info(struct pw_impl_core *core);
+
+/** Update the core properties */
+int pw_impl_core_update_properties(struct pw_impl_core *core, const struct spa_dict *dict);
+
+int pw_impl_core_register(struct pw_impl_core *core,
+			struct pw_properties *properties);
+
+void pw_impl_core_destroy(struct pw_impl_core *core);
+
+void *pw_impl_core_get_user_data(struct pw_impl_core *core);
+
+/** Get the global of this core */
+struct pw_global *pw_impl_core_get_global(struct pw_impl_core *core);
+
+/** Add an event listener */
+void pw_impl_core_add_listener(struct pw_impl_core *core,
+			     struct spa_hook *listener,
+			     const struct pw_impl_core_events *events,
+			     void *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_CORE_H */
diff --git a/third_party/pipewire/pipewire/impl-device.h b/third_party/pipewire/pipewire/impl-device.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/impl-device.h
@@ -0,0 +1,109 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_DEVICE_H
+#define PIPEWIRE_IMPL_DEVICE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_impl_device
+ *
+ * \brief PipeWire device interface.
+ *
+ * The device is an object that manages nodes. It typically
+ * corresponds to a physical hardware device but it does not
+ * have to be.
+ *
+ * The purpose of the device is to provide an interface to
+ * dynamically create/remove/configure the nodes it manages.
+ */
+struct pw_impl_device;
+
+#include <spa/monitor/device.h>
+
+#include <pipewire/context.h>
+#include <pipewire/global.h>
+#include <pipewire/properties.h>
+#include <pipewire/resource.h>
+
+/** Device events, listen to them with \ref pw_impl_device_add_listener */
+struct pw_impl_device_events {
+#define PW_VERSION_IMPL_DEVICE_EVENTS	0
+	uint32_t version;
+
+	/** the device is destroyed */
+        void (*destroy) (void *data);
+	/** the device is freed */
+        void (*free) (void *data);
+	/** the device is initialized */
+        void (*initialized) (void *data);
+
+	/** the device info changed */
+	void (*info_changed) (void *data, const struct pw_device_info *info);
+};
+
+struct pw_impl_device *pw_context_create_device(struct pw_context *context,
+				struct pw_properties *properties,
+				size_t user_data_size);
+
+int pw_impl_device_register(struct pw_impl_device *device,
+		       struct pw_properties *properties);
+
+void pw_impl_device_destroy(struct pw_impl_device *device);
+
+void *pw_impl_device_get_user_data(struct pw_impl_device *device);
+
+/** Set the device implementation */
+int pw_impl_device_set_implementation(struct pw_impl_device *device, struct spa_device *spa_device);
+/** Get the device implementation */
+struct spa_device *pw_impl_device_get_implementation(struct pw_impl_device *device);
+
+/** Get the global of this device */
+struct pw_global *pw_impl_device_get_global(struct pw_impl_device *device);
+
+/** Add an event listener */
+void pw_impl_device_add_listener(struct pw_impl_device *device,
+			    struct spa_hook *listener,
+			    const struct pw_impl_device_events *events,
+			    void *data);
+
+int pw_impl_device_update_properties(struct pw_impl_device *device, const struct spa_dict *dict);
+
+const struct pw_properties *pw_impl_device_get_properties(struct pw_impl_device *device);
+
+int pw_impl_device_for_each_param(struct pw_impl_device *device,
+			     int seq, uint32_t param_id,
+			     uint32_t index, uint32_t max,
+			     const struct spa_pod *filter,
+			     int (*callback) (void *data, int seq,
+					      uint32_t id, uint32_t index, uint32_t next,
+					      struct spa_pod *param),
+			     void *data);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_DEVICE_H */
diff --git a/third_party/pipewire/pipewire/impl-factory.h b/third_party/pipewire/pipewire/impl-factory.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/impl-factory.h
@@ -0,0 +1,124 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_FACTORY_H
+#define PIPEWIRE_IMPL_FACTORY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_impl_factory
+ *
+ * \brief PipeWire factory interface.
+ *
+ * The factory is used to make objects on demand.
+ */
+struct pw_impl_factory;
+
+#include <pipewire/context.h>
+#include <pipewire/impl-client.h>
+#include <pipewire/global.h>
+#include <pipewire/properties.h>
+#include <pipewire/resource.h>
+
+/** Factory events, listen to them with \ref pw_impl_factory_add_listener */
+struct pw_impl_factory_events {
+#define PW_VERSION_IMPL_FACTORY_EVENTS	0
+	uint32_t version;
+
+	/** the factory is destroyed */
+        void (*destroy) (void *data);
+	/** the factory is freed */
+        void (*free) (void *data);
+	/** the factory is initialized */
+        void (*initialized) (void *data);
+};
+
+struct pw_impl_factory_implementation {
+#define PW_VERSION_IMPL_FACTORY_IMPLEMENTATION	0
+	uint32_t version;
+
+	/** The function to create an object from this factory */
+	void *(*create_object) (void *data,
+				struct pw_resource *resource,
+				const char *type,
+				uint32_t version,
+				struct pw_properties *properties,
+				uint32_t new_id);
+};
+
+struct pw_impl_factory *pw_context_create_factory(struct pw_context *context,
+				  const char *name,
+				  const char *type,
+				  uint32_t version,
+				  struct pw_properties *properties,
+				  size_t user_data_size);
+
+/** Get the factory properties */
+const struct pw_properties *pw_impl_factory_get_properties(struct pw_impl_factory *factory);
+
+/** Get the factory info */
+const struct pw_factory_info *pw_impl_factory_get_info(struct pw_impl_factory *factory);
+
+/** Update the factory properties */
+int pw_impl_factory_update_properties(struct pw_impl_factory *factory, const struct spa_dict *dict);
+
+int pw_impl_factory_register(struct pw_impl_factory *factory,
+			struct pw_properties *properties);
+
+void pw_impl_factory_destroy(struct pw_impl_factory *factory);
+
+void *pw_impl_factory_get_user_data(struct pw_impl_factory *factory);
+
+/** Get the global of this factory */
+struct pw_global *pw_impl_factory_get_global(struct pw_impl_factory *factory);
+
+/** Add an event listener */
+void pw_impl_factory_add_listener(struct pw_impl_factory *factory,
+			     struct spa_hook *listener,
+			     const struct pw_impl_factory_events *events,
+			     void *data);
+
+void pw_impl_factory_set_implementation(struct pw_impl_factory *factory,
+				   const struct pw_impl_factory_implementation *implementation,
+				   void *data);
+
+void *pw_impl_factory_create_object(struct pw_impl_factory *factory,
+			       struct pw_resource *resource,
+			       const char *type,
+			       uint32_t version,
+			       struct pw_properties *properties,
+			       uint32_t new_id);
+
+/** Find a factory by name */
+struct pw_impl_factory *
+pw_context_find_factory(struct pw_context *context	/**< the context */,
+		     const char *name			/**< the factory name */);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_FACTORY_H */
diff --git a/third_party/pipewire/pipewire/impl-link.h b/third_party/pipewire/pipewire/impl-link.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/impl-link.h
@@ -0,0 +1,128 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_LINK_H
+#define PIPEWIRE_IMPL_LINK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_impl_link
+ *
+ * PipeWire link object.
+ */
+struct pw_impl_link;
+struct pw_impl_port;
+
+#include <pipewire/impl.h>
+
+/** \page page_link Link
+ *
+ * \section page_link_overview Overview
+ *
+ * A link is the connection between 2 nodes (\ref page_node). Nodes are
+ * linked together on ports.
+ *
+ * The link is responsible for negotiating the format and buffers for
+ * the nodes.
+ */
+
+/** link events added with \ref pw_impl_link_add_listener */
+struct pw_impl_link_events {
+#define PW_VERSION_IMPL_LINK_EVENTS	0
+	uint32_t version;
+
+	/** A link is destroyed */
+	void (*destroy) (void *data);
+
+	/** A link is freed */
+	void (*free) (void *data);
+
+	/** a Link is initialized */
+	void (*initialized) (void *data);
+
+	/** The info changed on a link */
+	void (*info_changed) (void *data, const struct pw_link_info *info);
+
+	/** The link state changed, \a error is only valid when the state is
+	  * in error. */
+	void (*state_changed) (void *data, enum pw_link_state old,
+					   enum pw_link_state state, const char *error);
+
+	/** A port is unlinked */
+	void (*port_unlinked) (void *data, struct pw_impl_port *port);
+};
+
+
+/** Make a new link between two ports \memberof pw_impl_link
+ * \return a newly allocated link */
+struct pw_impl_link *
+pw_context_create_link(struct pw_context *context,		/**< the context object */
+	    struct pw_impl_port *output,		/**< an output port */
+	    struct pw_impl_port *input,		/**< an input port */
+	    struct spa_pod *format_filter,	/**< an optional format filter */
+	    struct pw_properties *properties	/**< extra properties */,
+	    size_t user_data_size		/**< extra user data size */);
+
+/** Destroy a link \memberof pw_impl_link */
+void pw_impl_link_destroy(struct pw_impl_link *link);
+
+/** Add an event listener to \a link */
+void pw_impl_link_add_listener(struct pw_impl_link *link,
+			  struct spa_hook *listener,
+			  const struct pw_impl_link_events *events,
+			  void *data);
+
+/** Finish link configuration and register */
+int pw_impl_link_register(struct pw_impl_link *link,		/**< the link to register */
+		     struct pw_properties *properties	/**< extra properties */);
+
+/** Get the context of a link */
+struct pw_context *pw_impl_link_get_context(struct pw_impl_link *link);
+
+/** Get the user_data of a link, the size of the memory is given when
+  * constructing the link */
+void *pw_impl_link_get_user_data(struct pw_impl_link *link);
+
+/** Get the link info */
+const struct pw_link_info *pw_impl_link_get_info(struct pw_impl_link *link);
+
+/** Get the global of the link */
+struct pw_global *pw_impl_link_get_global(struct pw_impl_link *link);
+
+/** Get the output port of the link */
+struct pw_impl_port *pw_impl_link_get_output(struct pw_impl_link *link);
+
+/** Get the input port of the link */
+struct pw_impl_port *pw_impl_link_get_input(struct pw_impl_link *link);
+
+/** Find the link between 2 ports \memberof pw_impl_link */
+struct pw_impl_link *pw_impl_link_find(struct pw_impl_port *output, struct pw_impl_port *input);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_LINK_H */
diff --git a/third_party/pipewire/pipewire/impl-module.h b/third_party/pipewire/pipewire/impl-module.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/impl-module.h
@@ -0,0 +1,110 @@
+/* PipeWire
+ * Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
+ *	@author Linus Svensson <linus.svensson@axis.com>
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_MODULE_H
+#define PIPEWIRE_IMPL_MODULE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+#include <pipewire/context.h>
+
+#define PIPEWIRE_SYMBOL_MODULE_INIT "pipewire__module_init"
+#define PIPEWIRE_MODULE_PREFIX "libpipewire-"
+
+/** \class pw_impl_module
+ *
+ * A dynamically loadable module
+ */
+struct pw_impl_module;
+
+/** Module init function signature
+ *
+ * \param module A \ref pw_impl_module
+ * \param args Arguments to the module
+ * \return 0 on success, < 0 otherwise with an errno style error
+ *
+ * A module should provide an init function with this signature. This function
+ * will be called when a module is loaded.
+ *
+ * \memberof pw_impl_module
+ */
+typedef int (*pw_impl_module_init_func_t) (struct pw_impl_module *module, const char *args);
+
+/** Module events added with \ref pw_impl_module_add_listener */
+struct pw_impl_module_events {
+#define PW_VERSION_IMPL_MODULE_EVENTS	0
+	uint32_t version;
+
+	/** The module is destroyed */
+	void (*destroy) (void *data);
+	/** The module is freed */
+	void (*free) (void *data);
+	/** The module is initialized */
+	void (*initialized) (void *data);
+
+	/** The module is registered. This is a good time to register
+	 * objectes created from the module. */
+	void (*registered) (void *data);
+};
+
+struct pw_impl_module *
+pw_context_load_module(struct pw_context *context,
+	       const char *name,		/**< name of the module */
+	       const char *args			/**< arguments of the module */,
+	       struct pw_properties *properties	/**< extra global properties */);
+
+/** Get the context of a module */
+struct pw_context * pw_impl_module_get_context(struct pw_impl_module *module);
+
+/** Get the global of a module */
+struct pw_global * pw_impl_module_get_global(struct pw_impl_module *module);
+
+/** Get the node properties */
+const struct pw_properties *pw_impl_module_get_properties(struct pw_impl_module *module);
+
+/** Update the module properties */
+int pw_impl_module_update_properties(struct pw_impl_module *module, const struct spa_dict *dict);
+
+/** Get the module info */
+const struct pw_module_info *pw_impl_module_get_info(struct pw_impl_module *module);
+
+/** Add an event listener to a module */
+void pw_impl_module_add_listener(struct pw_impl_module *module,
+			    struct spa_hook *listener,
+			    const struct pw_impl_module_events *events,
+			    void *data);
+
+/** Destroy a module */
+void pw_impl_module_destroy(struct pw_impl_module *module);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_MODULE_H */
diff --git a/third_party/pipewire/pipewire/impl-node.h b/third_party/pipewire/pipewire/impl-node.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/impl-node.h
@@ -0,0 +1,182 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_NODE_H
+#define PIPEWIRE_IMPL_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \page page_node Node
+ *
+ * \section page_node_overview Overview
+ *
+ * The node object processes data. The node has a list of
+ * input and output ports (\ref page_port) on which it
+ * will receive and send out buffers respectively.
+ */
+/** \class pw_impl_node
+ *
+ * PipeWire node class.
+ */
+struct pw_impl_node;
+struct pw_impl_port;
+
+#include <spa/node/node.h>
+#include <spa/node/event.h>
+
+#include <pipewire/impl.h>
+
+/** Node events, listen to them with \ref pw_impl_node_add_listener */
+struct pw_impl_node_events {
+#define PW_VERSION_IMPL_NODE_EVENTS	0
+	uint32_t version;
+
+	/** the node is destroyed */
+        void (*destroy) (void *data);
+	/** the node is about to be freed */
+        void (*free) (void *data);
+	/** the node is initialized */
+        void (*initialized) (void *data);
+
+	/** a port is being initialized on the node */
+        void (*port_init) (void *data, struct pw_impl_port *port);
+	/** a port was added */
+        void (*port_added) (void *data, struct pw_impl_port *port);
+	/** a port was removed */
+        void (*port_removed) (void *data, struct pw_impl_port *port);
+
+	/** the node info changed */
+	void (*info_changed) (void *data, const struct pw_node_info *info);
+	/** a port on the node changed info */
+	void (*port_info_changed) (void *data, struct pw_impl_port *port,
+			const struct pw_port_info *info);
+	/** the node active state changed */
+	void (*active_changed) (void *data, bool active);
+
+	/** a new state is requested on the node */
+	void (*state_request) (void *data, enum pw_node_state state);
+	/** the state of the node changed */
+	void (*state_changed) (void *data, enum pw_node_state old,
+			       enum pw_node_state state, const char *error);
+
+        /** a result was received */
+	void (*result) (void *data, int seq, int res, uint32_t type, const void *result);
+
+        /** an event is emitted */
+	void (*event) (void *data, const struct spa_event *event);
+
+	/** the driver of the node changed */
+	void (*driver_changed) (void *data, struct pw_impl_node *old, struct pw_impl_node *driver);
+
+	/** a peer was added */
+	void (*peer_added) (void *data, struct pw_impl_node *peer);
+	/** a peer was removed */
+	void (*peer_removed) (void *data, struct pw_impl_node *peer);
+};
+
+/** Create a new node \memberof pw_impl_node */
+struct pw_impl_node *
+pw_context_create_node(struct pw_context *context,	/**< the context */
+	    struct pw_properties *properties,	/**< extra properties */
+	    size_t user_data_size		/**< user data size */);
+
+/** Complete initialization of the node and register */
+int pw_impl_node_register(struct pw_impl_node *node,		/**< node to register */
+		     struct pw_properties *properties	/**< extra properties */);
+
+/** Destroy a node */
+void pw_impl_node_destroy(struct pw_impl_node *node);
+
+/** Get the node info */
+const struct pw_node_info *pw_impl_node_get_info(struct pw_impl_node *node);
+
+/** Get node user_data. The size of the memory was given in \ref pw_context_create_node */
+void * pw_impl_node_get_user_data(struct pw_impl_node *node);
+
+/** Get the context of this node */
+struct pw_context *pw_impl_node_get_context(struct pw_impl_node *node);
+
+/** Get the global of this node */
+struct pw_global *pw_impl_node_get_global(struct pw_impl_node *node);
+
+/** Get the node properties */
+const struct pw_properties *pw_impl_node_get_properties(struct pw_impl_node *node);
+
+/** Update the node properties */
+int pw_impl_node_update_properties(struct pw_impl_node *node, const struct spa_dict *dict);
+
+/** Set the node implementation */
+int pw_impl_node_set_implementation(struct pw_impl_node *node, struct spa_node *spa_node);
+
+/** Get the node implementation */
+struct spa_node *pw_impl_node_get_implementation(struct pw_impl_node *node);
+
+/** Add an event listener */
+void pw_impl_node_add_listener(struct pw_impl_node *node,
+			  struct spa_hook *listener,
+			  const struct pw_impl_node_events *events,
+			  void *data);
+
+/** Iterate the ports in the given direction. The callback should return
+ * 0 to fetch the next item, any other value stops the iteration and returns
+ * the value. When all callbacks return 0, this function returns 0 when all
+ * items are iterated. */
+int pw_impl_node_for_each_port(struct pw_impl_node *node,
+			  enum pw_direction direction,
+			  int (*callback) (void *data, struct pw_impl_port *port),
+			  void *data);
+
+int pw_impl_node_for_each_param(struct pw_impl_node *node,
+			   int seq, uint32_t param_id,
+			   uint32_t index, uint32_t max,
+			   const struct spa_pod *filter,
+			   int (*callback) (void *data, int seq,
+					    uint32_t id, uint32_t index, uint32_t next,
+					    struct spa_pod *param),
+			   void *data);
+
+/** Find the port with direction and port_id or NULL when not found. Passing
+ * PW_ID_ANY for port_id will return any port, preferably an unlinked one. */
+struct pw_impl_port *
+pw_impl_node_find_port(struct pw_impl_node *node, enum pw_direction direction, uint32_t port_id);
+
+/** Get a free unused port_id from the node */
+uint32_t pw_impl_node_get_free_port_id(struct pw_impl_node *node, enum pw_direction direction);
+
+int pw_impl_node_initialized(struct pw_impl_node *node);
+
+/** Set a node active. This will start negotiation with all linked active
+  * nodes and start data transport */
+int pw_impl_node_set_active(struct pw_impl_node *node, bool active);
+
+/** Check if a node is active */
+bool pw_impl_node_is_active(struct pw_impl_node *node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_NODE_H */
diff --git a/third_party/pipewire/pipewire/impl-port.h b/third_party/pipewire/pipewire/impl-port.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/impl-port.h
@@ -0,0 +1,138 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_PORT_H
+#define PIPEWIRE_IMPL_PORT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \page page_port Port
+ *
+ * \section page_node_overview Overview
+ *
+ * A port can be used to link two nodes.
+ */
+/** \class pw_impl_port
+ *
+ * The port object
+ */
+struct pw_impl_port;
+struct pw_impl_link;
+struct pw_control;
+
+#include <pipewire/impl.h>
+
+enum pw_impl_port_state {
+	PW_IMPL_PORT_STATE_ERROR = -1,	/**< the port is in error */
+	PW_IMPL_PORT_STATE_INIT = 0,		/**< the port is being created */
+	PW_IMPL_PORT_STATE_CONFIGURE = 1,	/**< the port is ready for format negotiation */
+	PW_IMPL_PORT_STATE_READY = 2,	/**< the port is ready for buffer allocation */
+	PW_IMPL_PORT_STATE_PAUSED = 3,	/**< the port is paused */
+};
+
+/** Port events, use \ref pw_impl_port_add_listener */
+struct pw_impl_port_events {
+#define PW_VERSION_IMPL_PORT_EVENTS 1
+	uint32_t version;
+
+	/** The port is destroyed */
+	void (*destroy) (void *data);
+
+	/** The port is freed */
+	void (*free) (void *data);
+
+	/** The port is initialized */
+	void (*initialized) (void *data);
+
+	/** the port info changed */
+	void (*info_changed) (void *data, const struct pw_port_info *info);
+
+	/** a new link is added on this port */
+	void (*link_added) (void *data, struct pw_impl_link *link);
+
+	/** a link is removed from this port */
+	void (*link_removed) (void *data, struct pw_impl_link *link);
+
+	/** the state of the port changed */
+	void (*state_changed) (void *data, enum pw_impl_port_state old,
+			enum pw_impl_port_state state, const char *error);
+
+	/** a control was added to the port */
+	void (*control_added) (void *data, struct pw_control *control);
+
+	/** a control was removed from the port */
+	void (*control_removed) (void *data, struct pw_control *control);
+
+	/** a parameter changed, since version 1 */
+	void (*param_changed) (void *data, uint32_t id);
+};
+
+/** Create a new port \memberof pw_impl_port
+ * \return a newly allocated port */
+struct pw_impl_port *
+pw_context_create_port(struct pw_context *context,
+	enum pw_direction direction,
+	uint32_t port_id,
+	const struct spa_port_info *info,
+	size_t user_data_size);
+
+/** Get the port direction */
+enum pw_direction pw_impl_port_get_direction(struct pw_impl_port *port);
+
+/** Get the port properties */
+const struct pw_properties *pw_impl_port_get_properties(struct pw_impl_port *port);
+
+/** Update the port properties */
+int pw_impl_port_update_properties(struct pw_impl_port *port, const struct spa_dict *dict);
+
+/** Get the port info */
+const struct pw_port_info *pw_impl_port_get_info(struct pw_impl_port *port);
+
+/** Get the port id */
+uint32_t pw_impl_port_get_id(struct pw_impl_port *port);
+
+/** Get the port parent node or NULL when not yet set */
+struct pw_impl_node *pw_impl_port_get_node(struct pw_impl_port *port);
+
+/** check is a port has links, return 0 if not, 1 if it is linked */
+int pw_impl_port_is_linked(struct pw_impl_port *port);
+
+/** Add a port to a node \memberof pw_impl_port */
+int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node);
+
+/** Add an event listener on the port */
+void pw_impl_port_add_listener(struct pw_impl_port *port,
+			  struct spa_hook *listener,
+			  const struct pw_impl_port_events *events,
+			  void *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_PORT_H */
diff --git a/third_party/pipewire/pipewire/impl.h b/third_party/pipewire/pipewire/impl.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/impl.h
@@ -0,0 +1,57 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_H
+#define PIPEWIRE_IMPL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pw_impl_client;
+struct pw_impl_module;
+struct pw_global;
+struct pw_node;
+struct pw_impl_port;
+struct pw_resource;
+
+#include <pipewire/pipewire.h>
+#include <pipewire/control.h>
+#include <pipewire/impl-core.h>
+#include <pipewire/impl-client.h>
+#include <pipewire/impl-device.h>
+#include <pipewire/impl-factory.h>
+#include <pipewire/global.h>
+#include <pipewire/impl-link.h>
+#include <pipewire/impl-module.h>
+#include <pipewire/impl-node.h>
+#include <pipewire/impl-port.h>
+#include <pipewire/resource.h>
+#include <pipewire/work-queue.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_H */
diff --git a/third_party/pipewire/pipewire/keys.h b/third_party/pipewire/pipewire/keys.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/keys.h
@@ -0,0 +1,278 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_KEYS_H
+#define PIPEWIRE_KEYS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * A collection of keys that are used to add extra information on objects.
+ *
+ * Keys that start with "pipewire." are in general set-once and then
+ * read-only. They are usually used for security sensitive information that
+ * needs to be fixed.
+ *
+ * Properties from other objects can also appear. This usually suggests some
+ * sort of parent/child or owner/owned relationship.
+ */
+#define PW_KEY_PROTOCOL			"pipewire.protocol"	/**< protocol used for connection */
+#define PW_KEY_ACCESS			"pipewire.access"	/**< how the client access is controlled */
+
+/** Various keys related to the identity of a client process and its security.
+ * Must be obtained from trusted sources by the protocol and placed as
+ * read-only properties. */
+#define PW_KEY_SEC_PID			"pipewire.sec.pid"	/**< Client pid, set by protocol */
+#define PW_KEY_SEC_UID			"pipewire.sec.uid"	/**< Client uid, set by protocol*/
+#define PW_KEY_SEC_GID			"pipewire.sec.gid"	/**< client gid, set by protocol*/
+#define PW_KEY_SEC_LABEL		"pipewire.sec.label"	/**< client security label, set by protocol*/
+
+#define PW_KEY_LIBRARY_NAME_SYSTEM	"library.name.system"	/**< name of the system library to use */
+#define PW_KEY_LIBRARY_NAME_LOOP	"library.name.loop"	/**< name of the loop library to use */
+#define PW_KEY_LIBRARY_NAME_DBUS	"library.name.dbus"	/**< name of the dbus library to use */
+
+#define PW_KEY_OBJECT_PATH		"object.path"		/**< unique path to construct the object */
+#define PW_KEY_OBJECT_ID		"object.id"		/**< a global object id */
+
+/* context */
+#define PW_KEY_CONTEXT_PROFILE_MODULES	"context.profile.modules"	/**< a context profile for modules */
+#define PW_KEY_USER_NAME		"context.user-name"	/**< The user name that runs pipewire */
+#define PW_KEY_HOST_NAME		"context.host-name"	/**< The host name of the machine */
+
+/* core */
+#define PW_KEY_CORE_NAME		"core.name"		/**< The name of the core. Default is
+								  *  pipewire-<user-name>-<pid>, overwritten
+								  *  by env(PIPEWIRE_CORE) */
+#define PW_KEY_CORE_VERSION		"core.version"		/**< The version of the core. */
+#define PW_KEY_CORE_DAEMON		"core.daemon"		/**< If the core is listening for connections. */
+
+#define PW_KEY_CORE_ID			"core.id"		/**< the core id */
+#define PW_KEY_CORE_MONITORS		"core.monitors"		/**< the apis monitored by core. */
+
+/* cpu */
+#define PW_KEY_CPU_MAX_ALIGN		"cpu.max-align"		/**< maximum alignment needed to support
+								  *  all CPU optimizations */
+#define PW_KEY_CPU_CORES		"cpu.cores"		/**< number of cores */
+
+/* priorities */
+#define PW_KEY_PRIORITY_SESSION		"priority.session"	/**< priority in session manager */
+#define PW_KEY_PRIORITY_DRIVER		"priority.driver"	/**< priority to be a driver */
+
+/* remote keys */
+#define PW_KEY_REMOTE_NAME		"remote.name"		/**< The name of the remote to connect to,
+								  *  default pipewire-0, overwritten by
+								  *  env(PIPEWIRE_REMOTE) */
+#define PW_KEY_REMOTE_INTENTION		"remote.intention"	/**< The intention of the remote connection,
+								  *  "generic", "screencast" */
+
+/** application keys */
+#define PW_KEY_APP_NAME			"application.name"	/**< application name. Ex: "Totem Music Player" */
+#define PW_KEY_APP_ID			"application.id"	/**< a textual id for identifying an
+								  *  application logically. Ex: "org.gnome.Totem" */
+#define PW_KEY_APP_VERSION		"application.version"
+#define PW_KEY_APP_ICON			"application.icon"	/**< aa base64 blob with PNG image data */
+#define PW_KEY_APP_ICON_NAME		"application.icon-name"	/**< an XDG icon name for the application.
+								  *  Ex: "totem" */
+#define PW_KEY_APP_LANGUAGE		"application.language"	/**< application language if applicable, in
+								  *  standard POSIX format. Ex: "en_GB" */
+
+#define PW_KEY_APP_PROCESS_ID		"application.process.id"	/**< process id  (pid)*/
+#define PW_KEY_APP_PROCESS_BINARY	"application.process.binary"	/**< binary name */
+#define PW_KEY_APP_PROCESS_USER		"application.process.user"	/**< user name */
+#define PW_KEY_APP_PROCESS_HOST		"application.process.host"	/**< host name */
+#define PW_KEY_APP_PROCESS_MACHINE_ID	"application.process.machine-id" /**< the D-Bus host id the
+									   *  application runs on */
+#define PW_KEY_APP_PROCESS_SESSION_ID	"application.process.session-id" /**< login session of the
+									   *  application, on Unix the
+									   *  value of $XDG_SESSION_ID. */
+/** window system */
+#define PW_KEY_WINDOW_X11_DISPLAY	"window.x11.display"	/**< the X11 display string. Ex. ":0.0" */
+
+/** Client properties */
+#define PW_KEY_CLIENT_ID		"client.id"		/**< a client id */
+#define PW_KEY_CLIENT_NAME		"client.name"		/**< the client name */
+#define PW_KEY_CLIENT_API		"client.api"		/**< the client api used to access
+								  *  PipeWire */
+
+/** Node keys */
+#define PW_KEY_NODE_ID			"node.id"		/**< node id */
+#define PW_KEY_NODE_NAME		"node.name"		/**< node name */
+#define PW_KEY_NODE_NICK		"node.nick"		/**< short node name */
+#define PW_KEY_NODE_DESCRIPTION		"node.description"	/**< localized human readable node one-line
+								  *  description. Ex. "Foobar USB Headset" */
+#define PW_KEY_NODE_PLUGGED		"node.plugged"		/**< when the node was created. As a uint64 in
+								  *  nanoseconds. */
+
+#define PW_KEY_NODE_SESSION		"node.session"		/**< the session id this node is part of */
+#define PW_KEY_NODE_GROUP		"node.group"		/**< the group id this node is part of. Nodes
+								  *  in the same group are always scheduled
+								  *  with the same driver. */
+#define PW_KEY_NODE_EXCLUSIVE		"node.exclusive"	/**< node wants exclusive access to resources */
+#define PW_KEY_NODE_AUTOCONNECT		"node.autoconnect"	/**< node wants to be automatically connected
+								  *  to a compatible node */
+#define PW_KEY_NODE_TARGET		"node.target"		/**< node wants to be connected to the target
+								  *  node/session */
+#define PW_KEY_NODE_LATENCY		"node.latency"		/**< the requested latency of the node as
+								  *  a fraction. Ex: 128/48000 */
+#define PW_KEY_NODE_DONT_RECONNECT	"node.dont-reconnect"	/**< don't reconnect this node */
+#define PW_KEY_NODE_ALWAYS_PROCESS	"node.always-process"	/**< process even when unlinked */
+#define PW_KEY_NODE_PAUSE_ON_IDLE	"node.pause-on-idle"	/**< pause the node when idle */
+#define PW_KEY_NODE_DRIVER		"node.driver"		/**< node can drive the graph */
+#define PW_KEY_NODE_STREAM		"node.stream"		/**< node is a stream, the server side should
+								  *  add a converter */
+/** Port keys */
+#define PW_KEY_PORT_ID			"port.id"		/**< port id */
+#define PW_KEY_PORT_NAME		"port.name"		/**< port name */
+#define PW_KEY_PORT_DIRECTION		"port.direction"	/**< the port direction, one of "in" or "out"
+								  *  or "control" and "notify" for control ports */
+#define PW_KEY_PORT_ALIAS		"port.alias"		/**< port alias */
+#define PW_KEY_PORT_PHYSICAL		"port.physical"		/**< if this is a physical port */
+#define PW_KEY_PORT_TERMINAL		"port.terminal"		/**< if this port consumes the data */
+#define PW_KEY_PORT_CONTROL		"port.control"		/**< if this port is a control port */
+#define PW_KEY_PORT_MONITOR		"port.monitor"		/**< if this port is a monitor port */
+
+/** link properties */
+#define PW_KEY_LINK_ID			"link.id"		/**< a link id */
+#define PW_KEY_LINK_INPUT_NODE		"link.input.node"	/**< input node id of a link */
+#define PW_KEY_LINK_INPUT_PORT		"link.input.port"	/**< input port id of a link */
+#define PW_KEY_LINK_OUTPUT_NODE		"link.output.node"	/**< output node id of a link */
+#define PW_KEY_LINK_OUTPUT_PORT		"link.output.port"	/**< output port id of a link */
+#define PW_KEY_LINK_PASSIVE		"link.passive"		/**< indicate that a link is passive and
+								  *  does not cause the graph to be
+								  *  runnable. */
+/** device properties */
+#define PW_KEY_DEVICE_ID		"device.id"		/**< device id */
+#define PW_KEY_DEVICE_NAME		"device.name"		/**< device name */
+#define PW_KEY_DEVICE_PLUGGED		"device.plugged"	/**< when the device was created. As a uint64 in
+								  *  nanoseconds. */
+#define PW_KEY_DEVICE_NICK		"device.nick"		/**< a short device nickname */
+#define PW_KEY_DEVICE_STRING		"device.string"		/**< device string in the underlying layer's
+								  *  format. Ex. "surround51:0" */
+#define PW_KEY_DEVICE_API		"device.api"		/**< API this device is accessed with.
+								  *  Ex. "alsa", "v4l2" */
+#define PW_KEY_DEVICE_DESCRIPTION	"device.description"	/**< localized human readable device one-line
+								  *  description. Ex. "Foobar USB Headset" */
+#define PW_KEY_DEVICE_BUS_PATH		"device.bus-path"	/**< bus path to the device in the OS'
+								  *  format. Ex. "pci-0000:00:14.0-usb-0:3.2:1.0" */
+#define PW_KEY_DEVICE_SERIAL		"device.serial"		/**< Serial number if applicable */
+#define PW_KEY_DEVICE_VENDOR_ID		"device.vendor.id"	/**< vendor ID if applicable */
+#define PW_KEY_DEVICE_VENDOR_NAME	"device.vendor.name"	/**< vendor name if applicable */
+#define PW_KEY_DEVICE_PRODUCT_ID	"device.product.id"	/**< product ID if applicable */
+#define PW_KEY_DEVICE_PRODUCT_NAME	"device.product.name"	/**< product name if applicable */
+#define PW_KEY_DEVICE_CLASS		"device.class"		/**< device class */
+#define PW_KEY_DEVICE_FORM_FACTOR	"device.form-factor"	/**< form factor if applicable. One of
+								  *  "internal", "speaker", "handset", "tv",
+								  *  "webcam", "microphone", "headset",
+								  *  "headphone", "hands-free", "car", "hifi",
+								  *  "computer", "portable" */
+#define PW_KEY_DEVICE_BUS		"device.bus"		/**< bus of the device if applicable. One of
+								  *  "isa", "pci", "usb", "firewire",
+								  *  "bluetooth" */
+#define PW_KEY_DEVICE_SUBSYSTEM		"device.subsystem"	/**< device subsystem */
+#define PW_KEY_DEVICE_ICON		"device.icon"		/**< icon for the device. A base64 blob
+								  *  containing PNG image data */
+#define PW_KEY_DEVICE_ICON_NAME		"device.icon-name"	/**< an XDG icon name for the device.
+								  *  Ex. "sound-card-speakers-usb" */
+#define PW_KEY_DEVICE_INTENDED_ROLES	"device.intended-roles"	/**< intended use. A space separated list of
+								  *  roles (see PW_KEY_MEDIA_ROLE) this device
+								  *  is particularly well suited for, due to
+								  *  latency, quality or form factor. */
+
+/** module properties */
+#define PW_KEY_MODULE_ID		"module.id"		/**< the module id */
+#define PW_KEY_MODULE_NAME		"module.name"		/**< the name of the module */
+#define PW_KEY_MODULE_AUTHOR		"module.author"		/**< the author's name */
+#define PW_KEY_MODULE_DESCRIPTION	"module.description"	/**< a human readable one-line description
+								  *  of the module's purpose.*/
+#define PW_KEY_MODULE_USAGE		"module.usage"		/**< a human readable usage description of
+								  *  the module's arguments. */
+#define PW_KEY_MODULE_VERSION		"module.version"	/**< a version string for the module. */
+
+/** Factory properties */
+#define PW_KEY_FACTORY_ID		"factory.id"		/**< the factory id */
+#define PW_KEY_FACTORY_NAME		"factory.name"		/**< the name of the factory */
+#define PW_KEY_FACTORY_USAGE		"factory.usage"		/**< the usage of the factory */
+#define PW_KEY_FACTORY_TYPE_NAME	"factory.type.name"	/**< the name of the type created by a factory */
+#define PW_KEY_FACTORY_TYPE_VERSION	"factory.type.version"	/**< the version of the type created by a factory */
+
+/** Stream properties */
+#define PW_KEY_STREAM_IS_LIVE		"stream.is-live"	/**< Indicates that the stream is live. */
+#define PW_KEY_STREAM_LATENCY_MIN	"stream.latency.min"	/**< The minimum latency of the stream. */
+#define PW_KEY_STREAM_LATENCY_MAX	"stream.latency.max"	/**< The maximum latency of the stream */
+#define PW_KEY_STREAM_MONITOR		"stream.monitor"	/**< Indicates that the stream is monitoring
+								  *  and might select a less accurate but faster
+								  *  conversion algorithm. */
+#define PW_KEY_STREAM_DONT_REMIX	"stream.dont-remix"	/**< don't remix channels */
+
+/** object properties */
+#define PW_KEY_OBJECT_LINGER		"object.linger"		/**< the object lives on even after the client
+								  *  that created it has been destroyed */
+
+/** Media */
+#define PW_KEY_MEDIA_TYPE		"media.type"		/**< Media type, one of
+								  *  Audio, Video, Midi */
+#define PW_KEY_MEDIA_CATEGORY		"media.category"	/**< Media Category:
+								  *  Playback, Capture, Duplex, Monitor */
+#define PW_KEY_MEDIA_ROLE		"media.role"		/**< Role: Movie, Music, Camera,
+								  *  Screen, Communication, Game,
+								  *  Notification, DSP, Production,
+								  *  Accessibility, Test */
+#define PW_KEY_MEDIA_CLASS		"media.class"		/**< class Ex: "Video/Source" */
+#define PW_KEY_MEDIA_NAME		"media.name"		/**< media name. Ex: "Pink Floyd: Time" */
+#define PW_KEY_MEDIA_TITLE		"media.title"		/**< title. Ex: "Time" */
+#define PW_KEY_MEDIA_ARTIST		"media.artist"		/**< artist. Ex: "Pink Floyd" */
+#define PW_KEY_MEDIA_COPYRIGHT		"media.copyright"	/**< copyright string */
+#define PW_KEY_MEDIA_SOFTWARE		"media.software"	/**< generator software */
+#define PW_KEY_MEDIA_LANGUAGE		"media.language"	/**< language in POSIX format. Ex: en_GB */
+#define PW_KEY_MEDIA_FILENAME		"media.filename"	/**< filename */
+#define PW_KEY_MEDIA_ICON		"media.icon"		/**< icon for the media, a base64 blob with
+								  *  PNG image data */
+#define PW_KEY_MEDIA_ICON_NAME		"media.icon-name"	/**< an XDG icon name for the media.
+								  *  Ex: "audio-x-mp3" */
+
+/** format related properties */
+#define PW_KEY_FORMAT_DSP		"format.dsp"		/**< a dsp format.
+								  *  Ex: "32 bit float mono audio" */
+/** audio related properties */
+#define PW_KEY_AUDIO_CHANNEL		"audio.channel"		/**< an audio channel. Ex: "FL" */
+#define PW_KEY_AUDIO_RATE		"audio.samplerate"	/**< an audio samplerate */
+#define PW_KEY_AUDIO_CHANNELS		"audio.channels"	/**< number of audio channels */
+#define PW_KEY_AUDIO_FORMAT		"audio.format"		/**< an audio format. Ex: "S16LE" */
+
+/** video related properties */
+#define PW_KEY_VIDEO_RATE		"video.framerate"	/**< a video framerate */
+#define PW_KEY_VIDEO_FORMAT		"video.format"		/**< a video format */
+#define PW_KEY_VIDEO_SIZE		"video.size"		/**< a video size as "<width>x<height" */
+
+#ifdef PW_ENABLE_DEPRECATED
+#define PW_KEY_PRIORITY_MASTER		"priority.master"	/**< deprecated */
+#endif /* PW_ENABLE_DEPRECATED */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_KEYS_H */
diff --git a/third_party/pipewire/pipewire/link.h b/third_party/pipewire/pipewire/link.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/link.h
@@ -0,0 +1,124 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_LINK_H
+#define PIPEWIRE_LINK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include <pipewire/proxy.h>
+
+#define PW_TYPE_INTERFACE_Link	PW_TYPE_INFO_INTERFACE_BASE "Link"
+
+#define PW_VERSION_LINK		3
+struct pw_link;
+
+/** \enum pw_link_state The different link states \memberof pw_link */
+enum pw_link_state {
+	PW_LINK_STATE_ERROR = -2,	/**< the link is in error */
+	PW_LINK_STATE_UNLINKED = -1,	/**< the link is unlinked */
+	PW_LINK_STATE_INIT = 0,		/**< the link is initialized */
+	PW_LINK_STATE_NEGOTIATING = 1,	/**< the link is negotiating formats */
+	PW_LINK_STATE_ALLOCATING = 2,	/**< the link is allocating buffers */
+	PW_LINK_STATE_PAUSED = 3,	/**< the link is paused */
+};
+
+/** Convert a \ref pw_link_state to a readable string \memberof pw_link */
+const char * pw_link_state_as_string(enum pw_link_state state);
+/** The link information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_link_info {
+	uint32_t id;			/**< id of the global */
+	uint32_t output_node_id;	/**< server side output node id */
+	uint32_t output_port_id;	/**< output port id */
+	uint32_t input_node_id;		/**< server side input node id */
+	uint32_t input_port_id;		/**< input port id */
+#define PW_LINK_CHANGE_MASK_STATE	(1 << 0)
+#define PW_LINK_CHANGE_MASK_FORMAT	(1 << 1)
+#define PW_LINK_CHANGE_MASK_PROPS	(1 << 2)
+#define PW_LINK_CHANGE_MASK_ALL		((1 << 3)-1)
+	uint64_t change_mask;		/**< bitfield of changed fields since last call */
+	enum pw_link_state state;	/**< the current state of the link */
+	const char *error;		/**< an error reason if \a state is error */
+	struct spa_pod *format;		/**< format over link */
+	struct spa_dict *props;		/**< the properties of the link */
+};
+
+struct pw_link_info *
+pw_link_info_update(struct pw_link_info *info,
+		    const struct pw_link_info *update);
+
+void
+pw_link_info_free(struct pw_link_info *info);
+
+
+#define PW_LINK_EVENT_INFO	0
+#define PW_LINK_EVENT_NUM	1
+
+/** Link events */
+struct pw_link_events {
+#define PW_VERSION_LINK_EVENTS	0
+	uint32_t version;
+	/**
+	 * Notify link info
+	 *
+	 * \param info info about the link
+	 */
+	void (*info) (void *object, const struct pw_link_info *info);
+};
+
+#define PW_LINK_METHOD_ADD_LISTENER	0
+#define PW_LINK_METHOD_NUM		1
+
+/** Link methods */
+struct pw_link_methods {
+#define PW_VERSION_LINK_METHODS		0
+	uint32_t version;
+
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct pw_link_events *events,
+			void *data);
+};
+
+#define pw_link_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)o,		\
+			struct pw_link_methods, _res,			\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define pw_link_add_listener(c,...)		pw_link_method(c,add_listener,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_LINK_H */
diff --git a/third_party/pipewire/pipewire/log.h b/third_party/pipewire/pipewire/log.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/log.h
@@ -0,0 +1,97 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_LOG_H
+#define PIPEWIRE_LOG_H
+
+#include <spa/support/log.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_log
+ *
+ * Logging functions of PipeWire
+ *
+ * Logging is performed to stdout and stderr. Trace logging is performed
+ * in a lockfree ringbuffer and written out from the main thread as to not
+ * block the realtime threads.
+ */
+
+/** The global log level */
+extern enum spa_log_level pw_log_level;
+
+/** Configure a logging module. This is usually done automatically
+ * in pw_init() but you can install a custom logger before calling
+ * pw_init(). */
+void pw_log_set(struct spa_log *log);
+
+/** Get the log interface */
+struct spa_log *pw_log_get(void);
+
+/** Configure the logging level */
+void pw_log_set_level(enum spa_log_level level);
+
+
+/** Log a message */
+void
+pw_log_log(enum spa_log_level level,
+	   const char *file,
+	   int line, const char *func,
+	   const char *fmt, ...) SPA_PRINTF_FUNC(5, 6);
+
+/** Log a message */
+void
+pw_log_logv(enum spa_log_level level,
+	    const char *file,
+	    int line, const char *func,
+	    const char *fmt, va_list args) SPA_PRINTF_FUNC(5, 0);
+
+
+/** Check if a loglevel is enabled \memberof pw_log */
+#define pw_log_level_enabled(lev) (pw_log_level >= (lev))
+
+#define pw_log(lev,...)							\
+({									\
+	if (SPA_UNLIKELY(pw_log_level_enabled (lev)))			\
+		pw_log_log(lev,__FILE__,__LINE__,__func__,__VA_ARGS__);	\
+})
+
+#define pw_log_error(...)   pw_log(SPA_LOG_LEVEL_ERROR,__VA_ARGS__)
+#define pw_log_warn(...)    pw_log(SPA_LOG_LEVEL_WARN,__VA_ARGS__)
+#define pw_log_info(...)    pw_log(SPA_LOG_LEVEL_INFO,__VA_ARGS__)
+#define pw_log_debug(...)   pw_log(SPA_LOG_LEVEL_DEBUG,__VA_ARGS__)
+#define pw_log_trace(...)   pw_log(SPA_LOG_LEVEL_TRACE,__VA_ARGS__)
+
+#ifndef FASTPATH
+#define pw_log_trace_fp(...)   pw_log(SPA_LOG_LEVEL_TRACE,__VA_ARGS__)
+#else
+#define pw_log_trace_fp(...)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* PIPEWIRE_LOG_H */
diff --git a/third_party/pipewire/pipewire/loop.h b/third_party/pipewire/pipewire/loop.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/loop.h
@@ -0,0 +1,80 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_LOOP_H
+#define PIPEWIRE_LOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/support/loop.h>
+#include <spa/utils/dict.h>
+
+/** \class pw_loop
+ *
+ * PipeWire loop object provides an implementation of
+ * the spa loop interfaces. It can be used to implement various
+ * event loops.
+ */
+struct pw_loop {
+	struct spa_system *system;		/**< system utils */
+	struct spa_loop *loop;			/**< wrapped loop */
+	struct spa_loop_control *control;	/**< loop control */
+	struct spa_loop_utils *utils;		/**< loop utils */
+};
+
+struct pw_loop *
+pw_loop_new(const struct spa_dict *props);
+
+void
+pw_loop_destroy(struct pw_loop *loop);
+
+#define pw_loop_add_source(l,...)	spa_loop_add_source((l)->loop,__VA_ARGS__)
+#define pw_loop_update_source(l,...)	spa_loop_update_source(__VA_ARGS__)
+#define pw_loop_remove_source(l,...)	spa_loop_remove_source(__VA_ARGS__)
+#define pw_loop_invoke(l,...)		spa_loop_invoke((l)->loop,__VA_ARGS__)
+
+#define pw_loop_get_fd(l)		spa_loop_control_get_fd((l)->control)
+#define pw_loop_add_hook(l,...)		spa_loop_control_add_hook((l)->control,__VA_ARGS__)
+#define pw_loop_enter(l)		spa_loop_control_enter((l)->control)
+#define pw_loop_iterate(l,...)		spa_loop_control_iterate((l)->control,__VA_ARGS__)
+#define pw_loop_leave(l)		spa_loop_control_leave((l)->control)
+
+#define pw_loop_add_io(l,...)		spa_loop_utils_add_io((l)->utils,__VA_ARGS__)
+#define pw_loop_update_io(l,...)	spa_loop_utils_update_io((l)->utils,__VA_ARGS__)
+#define pw_loop_add_idle(l,...)		spa_loop_utils_add_idle((l)->utils,__VA_ARGS__)
+#define pw_loop_enable_idle(l,...)	spa_loop_utils_enable_idle((l)->utils,__VA_ARGS__)
+#define pw_loop_add_event(l,...)	spa_loop_utils_add_event((l)->utils,__VA_ARGS__)
+#define pw_loop_signal_event(l,...)	spa_loop_utils_signal_event((l)->utils,__VA_ARGS__)
+#define pw_loop_add_timer(l,...)	spa_loop_utils_add_timer((l)->utils,__VA_ARGS__)
+#define pw_loop_update_timer(l,...)	spa_loop_utils_update_timer((l)->utils,__VA_ARGS__)
+#define pw_loop_add_signal(l,...)	spa_loop_utils_add_signal((l)->utils,__VA_ARGS__)
+#define pw_loop_destroy_source(l,...)	spa_loop_utils_destroy_source((l)->utils,__VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_LOOP_H */
diff --git a/third_party/pipewire/pipewire/main-loop.h b/third_party/pipewire/pipewire/main-loop.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/main-loop.h
@@ -0,0 +1,78 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_MAIN_LOOP_H
+#define PIPEWIRE_MAIN_LOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_main_loop
+ *
+ * \brief PipeWire main-loop interface.
+ *
+ * A main loop object
+ */
+/** A main loop object \memberof pw_main_loop */
+struct pw_main_loop;
+
+#include <pipewire/loop.h>
+
+/** Events of the main loop */
+struct pw_main_loop_events {
+#define PW_VERSION_MAIN_LOOP_EVENTS	0
+	uint32_t version;
+
+	/** Emitted when the main loop is destroyed */
+	void (*destroy) (void *data);
+};
+
+/** Create a new main loop. */
+struct pw_main_loop *
+pw_main_loop_new(const struct spa_dict *props);
+
+/** Add an event listener */
+void pw_main_loop_add_listener(struct pw_main_loop *loop,
+			       struct spa_hook *listener,
+			       const struct pw_main_loop_events *events,
+			       void *data);
+
+/** Get the loop implementation */
+struct pw_loop * pw_main_loop_get_loop(struct pw_main_loop *loop);
+
+/** Destroy a loop */
+void pw_main_loop_destroy(struct pw_main_loop *loop);
+
+/** Run a main loop. This blocks until \ref pw_main_loop_quit is called */
+int pw_main_loop_run(struct pw_main_loop *loop);
+
+/** Quit a main loop */
+int pw_main_loop_quit(struct pw_main_loop *loop);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_MAIN_LOOP_H */
diff --git a/third_party/pipewire/pipewire/map.h b/third_party/pipewire/pipewire/map.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/map.h
@@ -0,0 +1,206 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_MAP_H
+#define PIPEWIRE_MAP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+#include <pipewire/array.h>
+
+/** \class pw_map
+ *
+ * A map that holds objects indexed by id
+ */
+
+/** An entry in the map \memberof pw_map */
+union pw_map_item {
+	uint32_t next;	/**< next free index */
+	void *data;	/**< data of this item, must be an even address */
+};
+
+/** A map \memberof pw_map */
+struct pw_map {
+	struct pw_array items;	/**< an array with the map items */
+	uint32_t free_list;	/**< the free items */
+};
+
+#define PW_MAP_INIT(extend) (struct pw_map) { PW_ARRAY_INIT(extend), 0 }
+
+#define pw_map_get_size(m)            pw_array_get_len(&(m)->items, union pw_map_item)
+#define pw_map_get_item(m,id)         pw_array_get_unchecked(&(m)->items,id,union pw_map_item)
+#define pw_map_item_is_free(item)     ((item)->next & 0x1)
+#define pw_map_id_is_free(m,id)       (pw_map_item_is_free(pw_map_get_item(m,id)))
+#define pw_map_check_id(m,id)         ((id) < pw_map_get_size(m))
+#define pw_map_has_item(m,id)         (pw_map_check_id(m,id) && !pw_map_id_is_free(m, id))
+#define pw_map_lookup_unchecked(m,id) pw_map_get_item(m,id)->data
+
+/** Convert an id to a pointer that can be inserted into the map \memberof pw_map */
+#define PW_MAP_ID_TO_PTR(id)          (SPA_UINT32_TO_PTR((id)<<1))
+/** Convert a pointer to an id that can be retrieved from the map \memberof pw_map */
+#define PW_MAP_PTR_TO_ID(p)           (SPA_PTR_TO_UINT32(p)>>1)
+
+/** Initialize a map
+ * \param map the map to initialize
+ * \param size the initial size of the map
+ * \param extend the amount to bytes to grow the map with when needed
+ * \memberof pw_map
+ */
+static inline void pw_map_init(struct pw_map *map, size_t size, size_t extend)
+{
+	pw_array_init(&map->items, extend);
+	pw_array_ensure_size(&map->items, size * sizeof(union pw_map_item));
+	map->free_list = SPA_ID_INVALID;
+}
+
+/** Clear a map
+ * \param map the map to clear
+ * \memberof pw_map
+ */
+static inline void pw_map_clear(struct pw_map *map)
+{
+	pw_array_clear(&map->items);
+}
+
+static inline void pw_map_reset(struct pw_map *map)
+{
+	pw_array_reset(&map->items);
+	map->free_list = SPA_ID_INVALID;
+}
+
+/** Insert data in the map
+ * \param map the map to insert into
+ * \param data the item to add
+ * \return the id where the item was inserted or SPA_ID_INVALID when the
+ *	item can not be inserted.
+ * \memberof pw_map
+ */
+static inline uint32_t pw_map_insert_new(struct pw_map *map, void *data)
+{
+	union pw_map_item *start, *item;
+	uint32_t id;
+
+	if (map->free_list != SPA_ID_INVALID) {
+		start = (union pw_map_item *) map->items.data;
+		item = &start[map->free_list >> 1];
+		map->free_list = item->next;
+	} else {
+		item = (union pw_map_item *) pw_array_add(&map->items, sizeof(union pw_map_item));
+		if (item == NULL)
+			return SPA_ID_INVALID;
+		start = (union pw_map_item *) map->items.data;
+	}
+	item->data = data;
+	id = (item - start);
+	return id;
+}
+
+/** Insert data in the map at an index
+ * \param map the map to inser into
+ * \param id the index to insert at
+ * \param data the data to insert
+ * \return 0 on success, -ENOSPC value when the index is invalid or a < 0
+ *	errno value.
+ * \memberof pw_map
+ */
+static inline int pw_map_insert_at(struct pw_map *map, uint32_t id, void *data)
+{
+	size_t size = pw_map_get_size(map);
+	union pw_map_item *item;
+
+	if (id > size)
+		return -ENOSPC;
+	else if (id == size) {
+		item = (union pw_map_item *) pw_array_add(&map->items, sizeof(union pw_map_item));
+		if (item == NULL)
+			return -errno;
+	}
+	else {
+		item = pw_map_get_item(map, id);
+	}
+	item->data = data;
+	return 0;
+}
+
+/** Remove an item at index
+ * \param map the map to remove from
+ * \param id the index to remove
+ * \memberof pw_map
+ */
+static inline void pw_map_remove(struct pw_map *map, uint32_t id)
+{
+	pw_map_get_item(map, id)->next = map->free_list;
+	map->free_list = (id << 1) | 1;
+}
+
+/** Find an item in the map
+ * \param map the map to use
+ * \param id the index to look at
+ * \return the item at \a id or NULL when no such item exists
+ * \memberof pw_map
+ */
+static inline void *pw_map_lookup(struct pw_map *map, uint32_t id)
+{
+	if (SPA_LIKELY(pw_map_check_id(map, id))) {
+		union pw_map_item *item = pw_map_get_item(map, id);
+		if (!pw_map_item_is_free(item))
+			return item->data;
+	}
+	return NULL;
+}
+
+/** Iterate all map items
+ * \param map the map to iterate
+ * \param func the function to call for each item, the item data and \a data is
+ *		passed to the function. When \a func returns a non-zero result,
+ *		iteration ends and the result is returned.
+ * \param data data to pass to \a func
+ * \return the result of the last call to \a func or 0 when all callbacks returned 0.
+ * \memberof pw_map
+ */
+static inline int pw_map_for_each(struct pw_map *map,
+				   int (*func) (void *item_data, void *data), void *data)
+{
+	union pw_map_item *item;
+	int res = 0;
+
+	pw_array_for_each(item, &map->items) {
+		if (!pw_map_item_is_free(item))
+			if ((res = func(item->data, data)) != 0)
+				break;
+	}
+	return res;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_MAP_H */
diff --git a/third_party/pipewire/pipewire/mem.h b/third_party/pipewire/pipewire/mem.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/mem.h
@@ -0,0 +1,199 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_MEM_H
+#define PIPEWIRE_MEM_H
+
+#include <pipewire/properties.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Flags passed to \ref pw_mempool_alloc() \memberof pw_memblock */
+enum pw_memblock_flags {
+	PW_MEMBLOCK_FLAG_NONE =		0,
+	PW_MEMBLOCK_FLAG_READABLE =	(1 << 0),	/**< memory is readable */
+	PW_MEMBLOCK_FLAG_WRITABLE =	(1 << 1),	/**< memory is writable */
+	PW_MEMBLOCK_FLAG_SEAL =		(1 << 2),	/**< seal the fd */
+	PW_MEMBLOCK_FLAG_MAP =		(1 << 3),	/**< mmap the fd */
+	PW_MEMBLOCK_FLAG_DONT_CLOSE =	(1 << 4),	/**< don't close fd */
+	PW_MEMBLOCK_FLAG_DONT_NOTIFY =	(1 << 5),	/**< don't notify events */
+
+	PW_MEMBLOCK_FLAG_READWRITE = PW_MEMBLOCK_FLAG_READABLE | PW_MEMBLOCK_FLAG_WRITABLE,
+};
+
+enum pw_memmap_flags {
+	PW_MEMMAP_FLAG_NONE =		0,
+	PW_MEMMAP_FLAG_READ =		(1 << 0),	/**< map in read mode */
+	PW_MEMMAP_FLAG_WRITE =		(1 << 1),	/**< map in write mode */
+	PW_MEMMAP_FLAG_TWICE =		(1 << 2),	/**< map the same area twice after each other,
+							  *  creating a circular ringbuffer */
+	PW_MEMMAP_FLAG_PRIVATE =	(1 << 3),	/**< writes will be private */
+	PW_MEMMAP_FLAG_READWRITE = PW_MEMMAP_FLAG_READ | PW_MEMMAP_FLAG_WRITE,
+};
+
+struct pw_memchunk;
+
+/** \class pw_memblock
+ *
+ * A memory pool is a collection of pw_memblocks */
+struct pw_mempool {
+	struct pw_properties *props;
+};
+
+/** \class pw_memblock
+ * Memory block structure */
+struct pw_memblock {
+	struct pw_mempool *pool;	/**< owner pool */
+	uint32_t id;			/**< unique id */
+	int ref;			/**< refcount */
+	uint32_t flags;			/**< flags for the memory block on of enum pw_memblock_flags */
+	uint32_t type;			/**< type of the fd, one of enum spa_data_type */
+	int fd;				/**< fd */
+	uint32_t size;			/**< size of memory */
+	struct pw_memmap *map;		/**< optional map when PW_MEMBLOCK_FLAG_MAP was given */
+};
+
+/** a mapped region of a pw_memblock */
+struct pw_memmap {
+	struct pw_memblock *block;	/**< owner memblock */
+	void *ptr;			/**< mapped pointer */
+	uint32_t flags;			/**< flags for the mapping on of enum pw_memmap_flags */
+	uint32_t offset;		/**< offset in memblock */
+	uint32_t size;			/**< size in memblock */
+	uint32_t tag[5];		/**< user tag */
+};
+
+struct pw_mempool_events {
+#define PW_VERSION_MEMPOOL_EVENTS	0
+	uint32_t version;
+
+	/** the pool is destroyed */
+	void (*destroy) (void *data);
+
+	/** a new memory block is added to the pool */
+	void (*added) (void *data, struct pw_memblock *block);
+
+	/** a memory block is removed from the pool */
+	void (*removed) (void *data, struct pw_memblock *block);
+};
+
+/** Create a new memory pool */
+struct pw_mempool *pw_mempool_new(struct pw_properties *props);
+
+/** Listen for events */
+void pw_mempool_add_listener(struct pw_mempool *pool,
+                            struct spa_hook *listener,
+                            const struct pw_mempool_events *events,
+                            void *data);
+
+/** Clear a pool */
+void pw_mempool_clear(struct pw_mempool *pool);
+
+/** Clear and destroy a pool */
+void pw_mempool_destroy(struct pw_mempool *pool);
+
+
+/** Allocate a memory block from the pool */
+struct pw_memblock * pw_mempool_alloc(struct pw_mempool *pool,
+		enum pw_memblock_flags flags, uint32_t type, size_t size);
+
+/** Import a block from another pool */
+struct pw_memblock * pw_mempool_import_block(struct pw_mempool *pool,
+		struct pw_memblock *mem);
+
+/** Import an fd into the pool */
+struct pw_memblock * pw_mempool_import(struct pw_mempool *pool,
+		enum pw_memblock_flags flags, uint32_t type, int fd);
+
+/** Free a memblock regardless of the refcount and destroy all mappings */
+void pw_memblock_free(struct pw_memblock *mem);
+
+/** Unref a memblock */
+static inline void pw_memblock_unref(struct pw_memblock *mem)
+{
+	if (--mem->ref == 0)
+		pw_memblock_free(mem);
+}
+
+/** Remove a memblock for given \a id */
+int pw_mempool_remove_id(struct pw_mempool *pool, uint32_t id);
+
+/** Find memblock for given \a ptr */
+struct pw_memblock * pw_mempool_find_ptr(struct pw_mempool *pool, const void *ptr);
+
+/** Find memblock for given \a id */
+struct pw_memblock * pw_mempool_find_id(struct pw_mempool *pool, uint32_t id);
+
+/** Find memblock for given \a fd */
+struct pw_memblock * pw_mempool_find_fd(struct pw_mempool *pool, int fd);
+
+
+/** Map a region of a memory block */
+struct pw_memmap * pw_memblock_map(struct pw_memblock *block,
+		enum pw_memmap_flags flags, uint32_t offset, uint32_t size,
+		uint32_t tag[5]);
+
+/** Map a region of a memory block with \a id */
+struct pw_memmap * pw_mempool_map_id(struct pw_mempool *pool, uint32_t id,
+		enum pw_memmap_flags flags, uint32_t offset, uint32_t size,
+		uint32_t tag[5]);
+
+struct pw_memmap * pw_mempool_import_map(struct pw_mempool *pool,
+		struct pw_mempool *other, void *data, uint32_t size, uint32_t tag[5]);
+
+/** find a map with the given tag */
+struct pw_memmap * pw_mempool_find_tag(struct pw_mempool *pool, uint32_t tag[5], size_t size);
+
+/** Unmap a region */
+int pw_memmap_free(struct pw_memmap *map);
+
+
+/** parameters to map a memory range */
+struct pw_map_range {
+	uint32_t start;		/** offset in first page with start of data */
+	uint32_t offset;	/** page aligned offset to map */
+	uint32_t size;		/** size to map */
+};
+
+#define PW_MAP_RANGE_INIT (struct pw_map_range){ 0, }
+
+/** Calculate parameters to mmap() memory into \a range so that
+ * \a size bytes at \a offset can be mapped with mmap().  */
+static inline void pw_map_range_init(struct pw_map_range *range,
+				     uint32_t offset, uint32_t size,
+				     uint32_t page_size)
+{
+	range->offset = SPA_ROUND_DOWN_N(offset, page_size);
+	range->start = offset - range->offset;
+	range->size = SPA_ROUND_UP_N(range->start + size, page_size);
+}
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_MEM_H */
diff --git a/third_party/pipewire/pipewire/module.h b/third_party/pipewire/pipewire/module.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/module.h
@@ -0,0 +1,106 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_MODULE_H
+#define PIPEWIRE_MODULE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include <pipewire/proxy.h>
+
+#define PW_TYPE_INTERFACE_Module	PW_TYPE_INFO_INTERFACE_BASE "Module"
+
+#define PW_VERSION_MODULE		3
+struct pw_module;
+
+/** The module information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_module_info {
+	uint32_t id;		/**< id of the global */
+	const char *name;	/**< name of the module */
+	const char *filename;	/**< filename of the module */
+	const char *args;	/**< arguments passed to the module */
+#define PW_MODULE_CHANGE_MASK_PROPS	(1 << 0)
+#define PW_MODULE_CHANGE_MASK_ALL	((1 << 1)-1)
+	uint64_t change_mask;	/**< bitfield of changed fields since last call */
+	struct spa_dict *props;	/**< extra properties */
+};
+
+/** Update and existing \ref pw_module_info with \a update \memberof pw_introspect */
+struct pw_module_info *
+pw_module_info_update(struct pw_module_info *info,
+		      const struct pw_module_info *update);
+
+/** Free a \ref pw_module_info \memberof pw_introspect */
+void pw_module_info_free(struct pw_module_info *info);
+
+#define PW_MODULE_EVENT_INFO		0
+#define PW_MODULE_EVENT_NUM		1
+
+/** Module events */
+struct pw_module_events {
+#define PW_VERSION_MODULE_EVENTS	0
+	uint32_t version;
+	/**
+	 * Notify module info
+	 *
+	 * \param info info about the module
+	 */
+	void (*info) (void *object, const struct pw_module_info *info);
+};
+
+#define PW_MODULE_METHOD_ADD_LISTENER	0
+#define PW_MODULE_METHOD_NUM		1
+
+/** Module methods */
+struct pw_module_methods {
+#define PW_VERSION_MODULE_METHODS	0
+	uint32_t version;
+
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct pw_module_events *events,
+			void *data);
+};
+
+#define pw_module_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)o,		\
+			struct pw_module_methods, _res,			\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define pw_module_add_listener(c,...)	pw_module_method(c,add_listener,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_MODULE_H */
diff --git a/third_party/pipewire/pipewire/node.h b/third_party/pipewire/pipewire/node.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/node.h
@@ -0,0 +1,200 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_NODE_H
+#define PIPEWIRE_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+#include <spa/node/command.h>
+#include <spa/param/param.h>
+
+#include <pipewire/proxy.h>
+
+#define PW_TYPE_INTERFACE_Node	PW_TYPE_INFO_INTERFACE_BASE "Node"
+
+#define PW_VERSION_NODE		3
+struct pw_node;
+
+/** \enum pw_node_state The different node states \memberof pw_node */
+enum pw_node_state {
+	PW_NODE_STATE_ERROR = -1,	/**< error state */
+	PW_NODE_STATE_CREATING = 0,	/**< the node is being created */
+	PW_NODE_STATE_SUSPENDED = 1,	/**< the node is suspended, the device might
+					 *   be closed */
+	PW_NODE_STATE_IDLE = 2,		/**< the node is running but there is no active
+					 *   port */
+	PW_NODE_STATE_RUNNING = 3,	/**< the node is running */
+};
+
+/** Convert a \ref pw_node_state to a readable string \memberof pw_node */
+const char * pw_node_state_as_string(enum pw_node_state state);
+
+/** The node information. Extra information can be added in later versions \memberof pw_introspect */
+struct pw_node_info {
+	uint32_t id;				/**< id of the global */
+	uint32_t max_input_ports;		/**< maximum number of inputs */
+	uint32_t max_output_ports;		/**< maximum number of outputs */
+#define PW_NODE_CHANGE_MASK_INPUT_PORTS		(1 << 0)
+#define PW_NODE_CHANGE_MASK_OUTPUT_PORTS	(1 << 1)
+#define PW_NODE_CHANGE_MASK_STATE		(1 << 2)
+#define PW_NODE_CHANGE_MASK_PROPS		(1 << 3)
+#define PW_NODE_CHANGE_MASK_PARAMS		(1 << 4)
+#define PW_NODE_CHANGE_MASK_ALL			((1 << 5)-1)
+	uint64_t change_mask;			/**< bitfield of changed fields since last call */
+	uint32_t n_input_ports;			/**< number of inputs */
+	uint32_t n_output_ports;		/**< number of outputs */
+	enum pw_node_state state;		/**< the current state of the node */
+	const char *error;			/**< an error reason if \a state is error */
+	struct spa_dict *props;			/**< the properties of the node */
+	struct spa_param_info *params;		/**< parameters */
+	uint32_t n_params;			/**< number of items in \a params */
+};
+
+struct pw_node_info *
+pw_node_info_update(struct pw_node_info *info,
+		    const struct pw_node_info *update);
+
+void
+pw_node_info_free(struct pw_node_info *info);
+
+#define PW_NODE_EVENT_INFO	0
+#define PW_NODE_EVENT_PARAM	1
+#define PW_NODE_EVENT_NUM	2
+
+/** Node events */
+struct pw_node_events {
+#define PW_VERSION_NODE_EVENTS	0
+	uint32_t version;
+	/**
+	 * Notify node info
+	 *
+	 * \param info info about the node
+	 */
+	void (*info) (void *object, const struct pw_node_info *info);
+	/**
+	 * Notify a node param
+	 *
+	 * Event emitted as a result of the enum_params method.
+	 *
+	 * \param seq the sequence number of the request
+	 * \param id the param id
+	 * \param index the param index
+	 * \param next the param index of the next param
+	 * \param param the parameter
+	 */
+	void (*param) (void *object, int seq,
+		      uint32_t id, uint32_t index, uint32_t next,
+		      const struct spa_pod *param);
+};
+
+#define PW_NODE_METHOD_ADD_LISTENER	0
+#define PW_NODE_METHOD_SUBSCRIBE_PARAMS	1
+#define PW_NODE_METHOD_ENUM_PARAMS	2
+#define PW_NODE_METHOD_SET_PARAM	3
+#define PW_NODE_METHOD_SEND_COMMAND	4
+#define PW_NODE_METHOD_NUM		5
+
+/** Node methods */
+struct pw_node_methods {
+#define PW_VERSION_NODE_METHODS		0
+	uint32_t version;
+
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct pw_node_events *events,
+			void *data);
+	/**
+	 * Subscribe to parameter changes
+	 *
+	 * Automatically emit param events for the given ids when
+	 * they are changed.
+	 *
+	 * \param ids an array of param ids
+	 * \param n_ids the number of ids in \a ids
+	 */
+	int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+	/**
+	 * Enumerate node parameters
+	 *
+	 * Start enumeration of node parameters. For each param, a
+	 * param event will be emitted.
+	 *
+	 * \param seq a sequence number to place in the reply
+	 * \param id the parameter id to enum or PW_ID_ANY for all
+	 * \param start the start index or 0 for the first param
+	 * \param num the maximum number of params to retrieve
+	 * \param filter a param filter or NULL
+	 */
+	int (*enum_params) (void *object, int seq, uint32_t id,
+			uint32_t start, uint32_t num,
+			const struct spa_pod *filter);
+
+	/**
+	 * Set a parameter on the node
+	 *
+	 * \param id the parameter id to set
+	 * \param flags extra parameter flags
+	 * \param param the parameter to set
+	 */
+	int (*set_param) (void *object, uint32_t id, uint32_t flags,
+			const struct spa_pod *param);
+
+	/**
+	 * Send a command to the node
+	 *
+	 * \param command the command to send
+	 */
+	int (*send_command) (void *object, const struct spa_command *command);
+};
+
+#define pw_node_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)o,		\
+			struct pw_node_methods, _res,			\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+/** Node */
+#define pw_node_add_listener(c,...)	pw_node_method(c,add_listener,0,__VA_ARGS__)
+#define pw_node_subscribe_params(c,...)	pw_node_method(c,subscribe_params,0,__VA_ARGS__)
+#define pw_node_enum_params(c,...)	pw_node_method(c,enum_params,0,__VA_ARGS__)
+#define pw_node_set_param(c,...)	pw_node_method(c,set_param,0,__VA_ARGS__)
+#define pw_node_send_command(c,...)	pw_node_method(c,send_command,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_NODE_H */
diff --git a/third_party/pipewire/pipewire/permission.h b/third_party/pipewire/pipewire/permission.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/permission.h
@@ -0,0 +1,79 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PERMISSION_H
+#define PIPEWIRE_PERMISSION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+
+/** \class pw_permission
+ *
+ * \brief a PipeWire permission
+ *
+ * Permissions are kept for a client and describe what the client is
+ * allowed to do with an object.
+ *
+ * See \ref page_core_api
+ */
+
+#define PW_PERM_R	0400	/**< object can be seen and events can be received */
+#define PW_PERM_W	0200	/**< methods can be called that modify the object */
+#define PW_PERM_X	0100	/**< methods can be called on the object. The W flag must be
+				  *  present in order to call methods that modify the object. */
+#define PW_PERM_M	0010	/**< metadata can be set on object, Since 0.3.9 */
+
+#define PW_PERM_RWX	(PW_PERM_R|PW_PERM_W|PW_PERM_X)
+#define PW_PERM_RWXM	(PW_PERM_RWX|PW_PERM_M)
+
+#define PW_PERM_IS_R(p) (((p)&PW_PERM_R) == PW_PERM_R)
+#define PW_PERM_IS_W(p) (((p)&PW_PERM_W) == PW_PERM_W)
+#define PW_PERM_IS_X(p) (((p)&PW_PERM_X) == PW_PERM_X)
+#define PW_PERM_IS_M(p) (((p)&PW_PERM_M) == PW_PERM_M)
+
+#define PW_PERM_ALL	PW_PERM_RWXM
+#define PW_PERM_INVALID	(uint32_t)(0xffffffff)
+
+struct pw_permission {
+	uint32_t id;		/**< id of object, PW_ID_ANY for default permission */
+	uint32_t permissions;	/**< bitmask of above permissions */
+};
+
+#define PW_PERMISSION_INIT(id,p) (struct pw_permission){ (id), (p) }
+
+#define PW_PERMISSION_FORMAT "%c%c%c%c"
+#define PW_PERMISSION_ARGS(permission)		\
+	(permission) & PW_PERM_R ? 'r' : '-',	\
+	(permission) & PW_PERM_W ? 'w' : '-',	\
+	(permission) & PW_PERM_X ? 'x' : '-',	\
+	(permission) & PW_PERM_M ? 'm' : '-'
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_PERMISSION_H */
diff --git a/third_party/pipewire/pipewire/pipewire.h b/third_party/pipewire/pipewire/pipewire.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/pipewire.h
@@ -0,0 +1,158 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_H
+#define PIPEWIRE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/support/plugin.h>
+
+#include <pipewire/array.h>
+#include <pipewire/client.h>
+#include <pipewire/context.h>
+#include <pipewire/device.h>
+#include <pipewire/buffers.h>
+#include <pipewire/core.h>
+#include <pipewire/factory.h>
+#include <pipewire/keys.h>
+#include <pipewire/log.h>
+#include <pipewire/loop.h>
+#include <pipewire/link.h>
+#include <pipewire/main-loop.h>
+#include <pipewire/map.h>
+#include <pipewire/mem.h>
+#include <pipewire/module.h>
+#include <pipewire/node.h>
+#include <pipewire/properties.h>
+#include <pipewire/proxy.h>
+#include <pipewire/permission.h>
+#include <pipewire/protocol.h>
+#include <pipewire/port.h>
+#include <pipewire/stream.h>
+#include <pipewire/filter.h>
+#include <pipewire/thread-loop.h>
+#include <pipewire/data-loop.h>
+#include <pipewire/type.h>
+#include <pipewire/utils.h>
+#include <pipewire/version.h>
+
+/** \mainpage
+ *
+ * \section sec_intro Introduction
+ *
+ * This document describes the API for the PipeWire multimedia framework.
+ * The API consists of two parts:
+ *
+ * \li The core API to access a PipeWire instance.
+ * (See \subpage page_core_api)
+ * \li The implementation API and tools to build new objects and
+ * modules (See \subpage page_implementation_api)
+ *
+ * \section sec_errors Error reporting
+ *
+ * Functions return either NULL with errno set or a negative int error
+ * code when an error occurs. Error codes are used from the SPA plugin
+ * library on which PipeWire is built.
+ *
+ * Some functions might return asynchronously. The error code for such
+ * functions is positive and SPA_RESULT_IS_ASYNC() will return true.
+ * SPA_RESULT_ASYNC_SEQ() can be used to get the unique sequence number
+ * associated with the async operation.
+ *
+ * The object returning the async result code will have some way to
+ * signal the completion of the async operation (with, for example, a
+ * callback). The sequence number can be used to see which operation
+ * completed.
+ *
+ * \section sec_logging Logging
+ *
+ * The 'PIPEWIRE_DEBUG' environment variable can be used to enable
+ * more debugging. The format is:
+ *
+ *    &lt;level&gt;[:&lt;category&gt;,...]
+ *
+ * - &lt;level&gt;: specifies the log level:
+ *   + `0`: no logging is enabled
+ *   + `1`: Error logging is enabled
+ *   + `2`: Warnings are enabled
+ *   + `3`: Informational messages are enabled
+ *   + `4`: Debug messages are enabled
+ *   + `5`: Trace messages are enabled. These messages can be logged
+ *		from the realtime threads.
+ *
+ * - &lt;category&gt;:  Specifies a string category to enable. Many categories
+ *		  can be separated by commas. Current categories are:
+ *   + `connection`: to log connection messages
+ */
+
+/** \class pw_pipewire
+ *
+ * \brief PipeWire initialization and infrastructure functions
+ */
+void
+pw_init(int *argc, char **argv[]);
+
+void pw_deinit(void);
+
+bool
+pw_debug_is_category_enabled(const char *name);
+
+const char *
+pw_get_application_name(void);
+
+const char *
+pw_get_prgname(void);
+
+const char *
+pw_get_user_name(void);
+
+const char *
+pw_get_host_name(void);
+
+const char *
+pw_get_client_name(void);
+
+bool pw_in_valgrind(void);
+
+enum pw_direction
+pw_direction_reverse(enum pw_direction direction);
+
+uint32_t pw_get_support(struct spa_support *support, uint32_t max_support);
+
+struct spa_handle *pw_load_spa_handle(const char *lib,
+		const char *factory_name,
+		const struct spa_dict *info,
+		uint32_t n_support,
+		const struct spa_support support[]);
+
+int pw_unload_spa_handle(struct spa_handle *handle);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_H */
diff --git a/third_party/pipewire/pipewire/port.h b/third_party/pipewire/pipewire/port.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/port.h
@@ -0,0 +1,169 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PORT_H
+#define PIPEWIRE_PORT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+#include <spa/param/param.h>
+
+#include <pipewire/proxy.h>
+
+#define PW_TYPE_INTERFACE_Port	PW_TYPE_INFO_INTERFACE_BASE "Port"
+
+#define PW_VERSION_PORT		3
+struct pw_port;
+
+/** \enum pw_direction The direction of a port \memberof pw_introspect */
+#define pw_direction spa_direction
+#define PW_DIRECTION_INPUT SPA_DIRECTION_INPUT
+#define PW_DIRECTION_OUTPUT SPA_DIRECTION_OUTPUT
+
+/** Convert a \ref pw_direction to a readable string \memberof pw_introspect */
+const char * pw_direction_as_string(enum pw_direction direction);
+
+
+/** \class pw_introspect
+ *
+ * The introspection methods and structures are used to get information
+ * about the object in the PipeWire server
+ */
+
+struct pw_port_info {
+	uint32_t id;				/**< id of the global */
+	enum pw_direction direction;		/**< port direction */
+#define PW_PORT_CHANGE_MASK_PROPS		(1 << 0)
+#define PW_PORT_CHANGE_MASK_PARAMS		(1 << 1)
+#define PW_PORT_CHANGE_MASK_ALL			((1 << 2)-1)
+	uint64_t change_mask;			/**< bitfield of changed fields since last call */
+	struct spa_dict *props;			/**< the properties of the port */
+	struct spa_param_info *params;		/**< parameters */
+	uint32_t n_params;			/**< number of items in \a params */
+};
+
+struct pw_port_info *
+pw_port_info_update(struct pw_port_info *info,
+		    const struct pw_port_info *update);
+
+void
+pw_port_info_free(struct pw_port_info *info);
+
+#define PW_PORT_EVENT_INFO	0
+#define PW_PORT_EVENT_PARAM	1
+#define PW_PORT_EVENT_NUM	2
+
+/** Port events */
+struct pw_port_events {
+#define PW_VERSION_PORT_EVENTS	0
+	uint32_t version;
+	/**
+	 * Notify port info
+	 *
+	 * \param info info about the port
+	 */
+	void (*info) (void *object, const struct pw_port_info *info);
+	/**
+	 * Notify a port param
+	 *
+	 * Event emitted as a result of the enum_params method.
+	 *
+	 * \param seq the sequence number of the request
+	 * \param id the param id
+	 * \param index the param index
+	 * \param next the param index of the next param
+	 * \param param the parameter
+	 */
+	void (*param) (void *object, int seq,
+		       uint32_t id, uint32_t index, uint32_t next,
+		       const struct spa_pod *param);
+};
+
+#define PW_PORT_METHOD_ADD_LISTENER	0
+#define PW_PORT_METHOD_SUBSCRIBE_PARAMS	1
+#define PW_PORT_METHOD_ENUM_PARAMS	2
+#define PW_PORT_METHOD_NUM		3
+
+/** Port methods */
+struct pw_port_methods {
+#define PW_VERSION_PORT_METHODS		0
+	uint32_t version;
+
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct pw_port_events *events,
+			void *data);
+	/**
+	 * Subscribe to parameter changes
+	 *
+	 * Automatically emit param events for the given ids when
+	 * they are changed.
+	 *
+	 * \param ids an array of param ids
+	 * \param n_ids the number of ids in \a ids
+	 */
+	int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+	/**
+	 * Enumerate port parameters
+	 *
+	 * Start enumeration of port parameters. For each param, a
+	 * param event will be emitted.
+	 *
+	 * \param seq a sequence number returned in the reply
+	 * \param id the parameter id to enumerate
+	 * \param start the start index or 0 for the first param
+	 * \param num the maximum number of params to retrieve
+	 * \param filter a param filter or NULL
+	 */
+	int (*enum_params) (void *object, int seq,
+			uint32_t id, uint32_t start, uint32_t num,
+			const struct spa_pod *filter);
+};
+
+#define pw_port_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)o,		\
+			struct pw_port_methods, _res,			\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define pw_port_add_listener(c,...)	pw_port_method(c,add_listener,0,__VA_ARGS__)
+#define pw_port_subscribe_params(c,...)	pw_port_method(c,subscribe_params,0,__VA_ARGS__)
+#define pw_port_enum_params(c,...)	pw_port_method(c,enum_params,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_PORT_H */
diff --git a/third_party/pipewire/pipewire/properties.h b/third_party/pipewire/pipewire/properties.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/properties.h
@@ -0,0 +1,121 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PROPERTIES_H
+#define PIPEWIRE_PROPERTIES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+	
+#include <spa/utils/dict.h>
+
+/** \class pw_properties
+ *
+ * \brief A collection of key/value pairs
+ *
+ * Properties are used to pass around arbitrary key/value pairs.
+ * Both keys and values are strings which keeps things simple.
+ * Encoding of arbitrary values should be done by using a string
+ * serialization such as base64 for binary blobs.
+ */
+struct pw_properties {
+	struct spa_dict dict;	/**< dictionary of key/values */
+	uint32_t flags;		/**< extra flags */
+};
+
+struct pw_properties *
+pw_properties_new(const char *key, ...) SPA_SENTINEL;
+
+struct pw_properties *
+pw_properties_new_dict(const struct spa_dict *dict);
+
+struct pw_properties *
+pw_properties_new_string(const char *args);
+
+struct pw_properties *
+pw_properties_copy(const struct pw_properties *properties);
+
+int pw_properties_update_keys(struct pw_properties *props,
+		     const struct spa_dict *dict, const char *keys[]);
+
+int pw_properties_update(struct pw_properties *oldprops,
+		     const struct spa_dict *dict);
+
+int pw_properties_add(struct pw_properties *oldprops,
+		     const struct spa_dict *dict);
+int pw_properties_add_keys(struct pw_properties *oldprops,
+		     const struct spa_dict *dict, const char *keys[]);
+
+void pw_properties_clear(struct pw_properties *properties);
+
+void
+pw_properties_free(struct pw_properties *properties);
+
+int
+pw_properties_set(struct pw_properties *properties, const char *key, const char *value);
+
+int
+pw_properties_setf(struct pw_properties *properties,
+		   const char *key, const char *format, ...) SPA_PRINTF_FUNC(3, 4);
+int
+pw_properties_setva(struct pw_properties *properties,
+		   const char *key, const char *format, va_list args) SPA_PRINTF_FUNC(3,0);
+const char *
+pw_properties_get(const struct pw_properties *properties, const char *key);
+
+const char *
+pw_properties_iterate(const struct pw_properties *properties, void **state);
+
+static inline bool pw_properties_parse_bool(const char *value) {
+	return (strcmp(value, "true") == 0 || atoi(value) == 1);
+}
+
+static inline int pw_properties_parse_int(const char *value) {
+	return strtol(value, NULL, 0);
+}
+
+static inline int64_t pw_properties_parse_int64(const char *value) {
+	return strtoll(value, NULL, 0);
+}
+
+static inline uint64_t pw_properties_parse_uint64(const char *value) {
+	return strtoull(value, NULL, 0);
+}
+
+static inline float pw_properties_parse_float(const char *value) {
+	return strtof(value, NULL);
+}
+
+static inline double pw_properties_parse_double(const char *value) {
+	return strtod(value, NULL);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_PROPERTIES_H */
diff --git a/third_party/pipewire/pipewire/protocol.h b/third_party/pipewire/pipewire/protocol.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/protocol.h
@@ -0,0 +1,152 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PROTOCOL_H
+#define PIPEWIRE_PROTOCOL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/list.h>
+
+struct pw_protocol;
+
+#include <pipewire/context.h>
+#include <pipewire/properties.h>
+#include <pipewire/utils.h>
+
+#define PW_TYPE_INFO_Protocol		"PipeWire:Protocol"
+#define PW_TYPE_INFO_PROTOCOL_BASE	PW_TYPE_INFO_Protocol ":"
+
+struct pw_protocol_client {
+	struct spa_list link;		/**< link in protocol client_list */
+	struct pw_protocol *protocol;	/**< the owner protocol */
+
+	struct pw_core *core;
+
+	int (*connect) (struct pw_protocol_client *client,
+			const struct spa_dict *props,
+			void (*done_callback) (void *data, int result),
+			void *data);
+	int (*connect_fd) (struct pw_protocol_client *client, int fd, bool close);
+	int (*steal_fd) (struct pw_protocol_client *client);
+	void (*disconnect) (struct pw_protocol_client *client);
+	void (*destroy) (struct pw_protocol_client *client);
+	int (*set_paused) (struct pw_protocol_client *client, bool paused);
+};
+
+#define pw_protocol_client_connect(c,p,cb,d)	((c)->connect(c,p,cb,d))
+#define pw_protocol_client_connect_fd(c,fd,cl)	((c)->connect_fd(c,fd,cl))
+#define pw_protocol_client_steal_fd(c)		((c)->steal_fd(c))
+#define pw_protocol_client_disconnect(c)	((c)->disconnect(c))
+#define pw_protocol_client_destroy(c)		((c)->destroy(c))
+#define pw_protocol_client_set_paused(c,p)	((c)->set_paused(c,p))
+
+struct pw_protocol_server {
+	struct spa_list link;		/**< link in protocol server_list */
+	struct pw_protocol *protocol;	/**< the owner protocol */
+
+	struct pw_impl_core *core;
+
+	struct spa_list client_list;	/**< list of clients of this protocol */
+
+	void (*destroy) (struct pw_protocol_server *listen);
+};
+
+#define pw_protocol_server_destroy(l)	((l)->destroy(l))
+
+struct pw_protocol_marshal {
+	const char *type;		/**< interface type */
+	uint32_t version;		/**< version */
+#define PW_PROTOCOL_MARSHAL_FLAG_IMPL	(1 << 0)	/**< marshal for implementations */
+	uint32_t flags;			/**< version */
+	uint32_t n_client_methods;	/**< number of client methods */
+	uint32_t n_server_methods;	/**< number of server methods */
+	const void *client_marshal;
+	const void *server_demarshal;
+	const void *server_marshal;
+	const void *client_demarshal;
+};
+
+struct pw_protocol_implementaton {
+#define PW_VERSION_PROTOCOL_IMPLEMENTATION	0
+	uint32_t version;
+
+	struct pw_protocol_client * (*new_client) (struct pw_protocol *protocol,
+						   struct pw_core *core,
+						   const struct spa_dict *props);
+	struct pw_protocol_server * (*add_server) (struct pw_protocol *protocol,
+						   struct pw_impl_core *core,
+						   const struct spa_dict *props);
+};
+
+struct pw_protocol_events {
+#define PW_VERSION_PROTOCOL_EVENTS		0
+	uint32_t version;
+
+	void (*destroy) (void *data);
+};
+
+#define pw_protocol_new_client(p,...)	(pw_protocol_get_implementation(p)->new_client(p,__VA_ARGS__))
+#define pw_protocol_add_server(p,...)	(pw_protocol_get_implementation(p)->add_server(p,__VA_ARGS__))
+#define pw_protocol_ext(p,type,method,...)	(((type*)pw_protocol_get_extension(p))->method( __VA_ARGS__))
+
+struct pw_protocol *pw_protocol_new(struct pw_context *context, const char *name, size_t user_data_size);
+
+void pw_protocol_destroy(struct pw_protocol *protocol);
+
+struct pw_context *pw_protocol_get_context(struct pw_protocol *protocol);
+
+void *pw_protocol_get_user_data(struct pw_protocol *protocol);
+
+const struct pw_protocol_implementaton *
+pw_protocol_get_implementation(struct pw_protocol *protocol);
+
+const void *
+pw_protocol_get_extension(struct pw_protocol *protocol);
+
+
+void pw_protocol_add_listener(struct pw_protocol *protocol,
+                              struct spa_hook *listener,
+                              const struct pw_protocol_events *events,
+                              void *data);
+
+/** \class pw_protocol
+ *
+ * \brief Manages protocols and their implementation
+ */
+int pw_protocol_add_marshal(struct pw_protocol *protocol,
+			    const struct pw_protocol_marshal *marshal);
+
+const struct pw_protocol_marshal *
+pw_protocol_get_marshal(struct pw_protocol *protocol, const char *type, uint32_t version, uint32_t flags);
+
+struct pw_protocol * pw_context_find_protocol(struct pw_context *context, const char *name);
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_PROTOCOL_H */
diff --git a/third_party/pipewire/pipewire/proxy.h b/third_party/pipewire/pipewire/proxy.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/proxy.h
@@ -0,0 +1,207 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PROXY_H
+#define PIPEWIRE_PROXY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \page page_proxy Proxy
+ *
+ * \section sec_page_proxy_overview Overview
+ *
+ * The proxy object is a client side representation of a resource
+ * that lives on a remote PipeWire instance.
+ *
+ * It is used to communicate with the remote object.
+ *
+ * \section sec_page_proxy_core Core proxy
+ *
+ * A proxy for a remote core object can be obtained by making
+ * a remote connection with \ref pw_core_connect.
+ * See \ref pw_page_remote_api
+ *
+ * Some methods on proxy object allow creation of more proxy objects or
+ * create a binding between a local proxy and global resource.
+ *
+ * \section sec_page_proxy_create Create
+ *
+ * A client first creates a new proxy object with pw_proxy_new(). A
+ * type must be provided for this object.
+ *
+ * The protocol of the context will usually install an interface to
+ * translate method calls and events to the wire format.
+ *
+ * The creator of the proxy will usually also install an event
+ * implementation of the particular object type.
+ *
+ * \section sec_page_proxy_bind Bind
+ *
+ * To actually use the proxy object, one needs to create a server
+ * side resource for it. This can be done by, for example, binding
+ * to a global object or by calling a method that creates and binds
+ * to a new remote object. In all cases, the local id is passed to
+ * the server and is used to create a resource with the same id.
+ *
+ * \section sec_page_proxy_methods Methods
+ *
+ * To call a method on the proxy use the interface methods. Calling
+ * any interface method will result in a request to the server to
+ * perform the requested action on the corresponding resource.
+ *
+ * \section sec_page_proxy_events Events
+ *
+ * Events send from the server to the proxy will be demarshalled by
+ * the protocol and will then result in a call to the installed
+ * implementation of the proxy.
+ *
+ * \section sec_page_proxy_destroy Destroy
+ *
+ * Use pw_proxy_destroy() to destroy the client side object. This
+ * is usually done automatically when the server removes the resource
+ * associated to the proxy.
+ */
+
+/** \class pw_proxy
+ *
+ * \brief Represents an object on the client side.
+ *
+ * A pw_proxy acts as a client side proxy to an object existing in a remote
+ * pipewire instance. The proxy is responsible for converting interface functions
+ * invoked by the client to PipeWire messages. Events will call the handlers
+ * set in listener.
+ *
+ * See \ref page_proxy
+ */
+struct pw_proxy;
+
+#include <pipewire/protocol.h>
+
+/** Proxy events, use \ref pw_proxy_add_listener */
+struct pw_proxy_events {
+#define PW_VERSION_PROXY_EVENTS		0
+        uint32_t version;
+
+	/** The proxy is destroyed */
+        void (*destroy) (void *data);
+
+	/** a proxy is bound to a global id */
+        void (*bound) (void *data, uint32_t global_id);
+
+	/** a proxy is removed from the server. Use pw_proxy_destroy to
+	 * free the proxy. */
+        void (*removed) (void *data);
+
+	/** a reply to a sync method completed */
+        void (*done) (void *data, int seq);
+
+	/** an error occurred on the proxy */
+        void (*error) (void *data, int seq, int res, const char *message);
+};
+
+/** Make a new proxy object. The id can be used to bind to a remote object and
+  * can be retrieved with \ref pw_proxy_get_id . */
+struct pw_proxy *
+pw_proxy_new(struct pw_proxy *factory,	/**< factory */
+	     const char *type,		/**< interface type */
+	     uint32_t version,		/**< interface version */
+	     size_t user_data_size	/**< size of user data */);
+
+/** Add an event listener to proxy */
+void pw_proxy_add_listener(struct pw_proxy *proxy,
+			   struct spa_hook *listener,
+			   const struct pw_proxy_events *events,
+			   void *data);
+
+/** Add a listener for the events received from the remote object. The
+  * events depend on the type of the remote object type. */
+void pw_proxy_add_object_listener(struct pw_proxy *proxy,	/**< the proxy */
+				 struct spa_hook *listener,	/**< listener */
+				 const void *funcs,		/**< proxied functions */
+				 void *data			/**< data passed to events */);
+
+/** destroy a proxy */
+void pw_proxy_destroy(struct pw_proxy *proxy);
+
+/** Get the user_data. The size was given in \ref pw_proxy_new */
+void *pw_proxy_get_user_data(struct pw_proxy *proxy);
+
+/** Get the local id of the proxy */
+uint32_t pw_proxy_get_id(struct pw_proxy *proxy);
+
+/** Get the type and version of the proxy */
+const char *pw_proxy_get_type(struct pw_proxy *proxy, uint32_t *version);
+
+/** Get the protocol used for the proxy */
+struct pw_protocol *pw_proxy_get_protocol(struct pw_proxy *proxy);
+
+/** Generate an sync method for a proxy. This will generate a done event
+ * with the same seq number of the reply. */
+int pw_proxy_sync(struct pw_proxy *proxy, int seq);
+
+/** Set the global id this proxy is bound to. This is usually used internally
+ * and will also emit the bound event */
+int pw_proxy_set_bound_id(struct pw_proxy *proxy, uint32_t global_id);
+/** Get the global id bound to this proxy of SPA_ID_INVALID when not bound
+ * to a global */
+uint32_t pw_proxy_get_bound_id(struct pw_proxy *proxy);
+
+/** Generate an error for a proxy */
+int pw_proxy_error(struct pw_proxy *proxy, int res, const char *error);
+int pw_proxy_errorf(struct pw_proxy *proxy, int res, const char *error, ...) SPA_PRINTF_FUNC(3, 4);
+
+/** Get the listener of proxy */
+struct spa_hook_list *pw_proxy_get_object_listeners(struct pw_proxy *proxy);
+
+/** Get the marshal functions for the proxy */
+const struct pw_protocol_marshal *pw_proxy_get_marshal(struct pw_proxy *proxy);
+
+/** Install a marshal function on a proxy */
+int pw_proxy_install_marshal(struct pw_proxy *proxy, bool implementor);
+
+#define pw_proxy_notify(p,type,event,version,...)			\
+	spa_hook_list_call(pw_proxy_get_object_listeners(p),		\
+			type, event, version, ## __VA_ARGS__)
+
+#define pw_proxy_call(p,type,method,version,...)			\
+	spa_interface_call((struct spa_interface*)p,			\
+			type, method, version, ##__VA_ARGS__)
+
+#define pw_proxy_call_res(p,type,method,version,...)			\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)p,		\
+			type, _res, method, version, ##__VA_ARGS__);	\
+	_res;								\
+})
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_PROXY_H */
diff --git a/third_party/pipewire/pipewire/resource.h b/third_party/pipewire/pipewire/resource.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/resource.h
@@ -0,0 +1,168 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_RESOURCE_H
+#define PIPEWIRE_RESOURCE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \page page_resource Resource
+ *
+ * \section sec_page_resource Overview
+ *
+ * Resources represent objects owned by a \ref pw_impl_client. They are
+ * the result of binding to a global resource or by calling API that
+ * creates client owned objects.
+ *
+ * The client usually has a proxy object associated with the resource
+ * that it can use to communicate with the resource. See \ref page_proxy.
+ *
+ * Resources are destroyed when the client or the bound object is
+ * destroyed.
+ *
+ */
+
+/** \class pw_resource
+ *
+ * \brief Client owned objects
+ *
+ * Resources are objects owned by a client and are destroyed when the
+ * client disappears.
+ *
+ * See also \ref page_resource
+ */
+struct pw_resource;
+
+#include <pipewire/impl-client.h>
+
+/** Resource events */
+struct pw_resource_events {
+#define PW_VERSION_RESOURCE_EVENTS	0
+	uint32_t version;
+
+	/** The resource is destroyed */
+	void (*destroy) (void *data);
+
+	/** a reply to a ping event completed */
+        void (*pong) (void *data, int seq);
+
+	/** an error occurred on the resource */
+        void (*error) (void *data, int seq, int res, const char *message);
+};
+
+/** Make a new resource for client */
+struct pw_resource *
+pw_resource_new(struct pw_impl_client *client,	/**< the client owning the resource */
+		uint32_t id,			/**< the remote per client id */
+		uint32_t permissions,		/**< permissions on this resource */
+		const char *type,		/**< interface of the resource */
+		uint32_t version,		/**< requested interface version */
+		size_t user_data_size		/**< extra user data size */);
+
+/** Destroy a resource */
+void pw_resource_destroy(struct pw_resource *resource);
+
+/** Remove a resource, like pw_resource_destroy but without sending a
+ * remove_id message to the client */
+void pw_resource_remove(struct pw_resource *resource);
+
+/** Get the client owning this resource */
+struct pw_impl_client *pw_resource_get_client(struct pw_resource *resource);
+
+/** Get the unique id of this resource */
+uint32_t pw_resource_get_id(struct pw_resource *resource);
+
+/** Get the permissions of this resource */
+uint32_t pw_resource_get_permissions(struct pw_resource *resource);
+
+/** Get the type and optionally the version of this resource */
+const char *pw_resource_get_type(struct pw_resource *resource, uint32_t *version);
+
+/** Get the protocol used for this resource */
+struct pw_protocol *pw_resource_get_protocol(struct pw_resource *resource);
+
+/** Get the user data for the resource, the size was given in \ref pw_resource_new */
+void *pw_resource_get_user_data(struct pw_resource *resource);
+
+/** Add an event listener */
+void pw_resource_add_listener(struct pw_resource *resource,
+			      struct spa_hook *listener,
+			      const struct pw_resource_events *events,
+			      void *data);
+
+/** Set the resource implementation. */
+void pw_resource_add_object_listener(struct pw_resource *resource,
+				struct spa_hook *listener,
+				const void *funcs,
+				void *data);
+
+/** Generate an ping event for a resource. This will generate a pong event
+ * with the same \a sequence number in the return value. */
+int pw_resource_ping(struct pw_resource *resource, int seq);
+
+/** Notify global id this resource is bound to */
+int pw_resource_set_bound_id(struct pw_resource *resource, uint32_t global_id);
+
+/** Get the global id this resource is bound to or SPA_ID_INVALID when not bound */
+uint32_t pw_resource_get_bound_id(struct pw_resource *resource);
+
+/** Generate an error for a resource */
+void pw_resource_error(struct pw_resource *resource, int res, const char *error);
+void pw_resource_errorf(struct pw_resource *resource, int res, const char *error, ...) SPA_PRINTF_FUNC(3, 4);
+void pw_resource_errorf_id(struct pw_resource *resource, uint32_t id, int res, const char *error, ...) SPA_PRINTF_FUNC(4, 5);
+
+/** Get the list of object listeners from a resource */
+struct spa_hook_list *pw_resource_get_object_listeners(struct pw_resource *resource);
+
+/** Get the marshal functions for the resource */
+const struct pw_protocol_marshal *pw_resource_get_marshal(struct pw_resource *resource);
+
+/** install a marshal function on a resource */
+int pw_resource_install_marshal(struct pw_resource *resource, bool implementor);
+
+#define pw_resource_notify(r,type,event,version,...)			\
+	spa_hook_list_call(pw_resource_get_object_listeners(r),		\
+			type, event, version, ## __VA_ARGS__)
+
+#define pw_resource_call(r,type,method,version,...)			\
+	spa_interface_call((struct spa_interface*)r,			\
+			type, method, version, ##__VA_ARGS__)
+
+#define pw_resource_call_res(r,type,method,version,...)			\
+({									\
+	int _res = -ENOTSUP;						\
+	spa_interface_call_res((struct spa_interface*)r,		\
+			type, _res, method, version, ##__VA_ARGS__);	\
+	_res;								\
+})
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_RESOURCE_H */
diff --git a/third_party/pipewire/pipewire/stream.h b/third_party/pipewire/pipewire/stream.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/stream.h
@@ -0,0 +1,358 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_STREAM_H
+#define PIPEWIRE_STREAM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \page page_streams Media Streams
+ *
+ * \section sec_overview Overview
+ *
+ * Media streams are used to exchange data with the PipeWire server. A
+ * stream is a wrapper around a proxy for a \ref pw_client_node with
+ * an adapter. This means the stream will automatically do conversion
+ * to the type required by the server.
+ *
+ * Streams can be used to:
+ *
+ * \li Consume a stream from PipeWire. This is a PW_DIRECTION_INPUT stream.
+ * \li Produce a stream to PipeWire. This is a PW_DIRECTION_OUTPUT stream
+ *
+ * You can connect the stream port to a specific server port or let PipeWire
+ * choose a port for you.
+ *
+ * For more complicated nodes such as filters or ports with multiple
+ * inputs and/or outputs you will need to use the pw_filter or make
+ * a pw_node yourself and export it with \ref pw_core_export.
+ *
+ * \section sec_create Create
+ *
+ * Make a new stream with \ref pw_stream_new(). You will need to specify
+ * a name for the stream and extra properties. You can use \ref
+ * pw_fill_stream_properties() to get a basic set of properties for the
+ * stream.
+ *
+ * Once the stream is created, the state_changed event should be used to
+ * track the state of the stream.
+ *
+ * \section sec_connect Connect
+ *
+ * The stream is initially unconnected. To connect the stream, use
+ * \ref pw_stream_connect(). Pass the desired direction as an argument.
+ *
+ * \subsection ssec_stream_target Stream target
+ *
+ * To make the newly connected stream automatically connect to an existing
+ * PipeWire node, use the \ref PW_STREAM_FLAG_AUTOCONNECT and the port_path
+ * argument while connecting.
+ *
+ * \subsection ssec_stream_formats Stream formats
+ *
+ * An array of possible formats that this stream can consume or provide
+ * must be specified.
+ *
+ * \section sec_format Format negotiation
+ *
+ * After connecting the stream, the server will want to configure some
+ * parameters on the stream. You will be notified of these changes
+ * with the param_changed event.
+ *
+ * When a format param change is emitted, the client should now prepare
+ * itself to deal with the format and complete the negotiation procedure
+ * with a call to \ref pw_stream_update_params().
+ *
+ * As arguments to \ref pw_stream_update_params() an array of spa_param
+ * structures must be given. They contain parameters such as buffer size,
+ * number of buffers, required metadata and other parameters for the
+ * media buffers.
+ *
+ * \section sec_buffers Buffer negotiation
+ *
+ * After completing the format negotiation, PipeWire will allocate and
+ * notify the stream of the buffers that will be used to exchange data
+ * between client and server.
+ *
+ * With the add_buffer event, a stream will be notified of a new buffer
+ * that can be used for data transport. You can attach user_data to these
+ * buffers.
+ *
+ * After the buffers are negotiated, the stream will transition to the
+ * \ref PW_STREAM_STATE_PAUSED state.
+ *
+ * \section sec_streaming Streaming
+ *
+ * From the \ref PW_STREAM_STATE_PAUSED state, the stream can be set to
+ * the \ref PW_STREAM_STATE_STREAMING state by the PipeWire server when
+ * data transport is started.
+ *
+ * Depending on how the stream was connected it will need to Produce or
+ * Consume data for/from PipeWire as explained in the following
+ * subsections.
+ *
+ * \subsection ssec_consume Consume data
+ *
+ * The process event is emitted for each new buffer that can can be
+ * consumed.
+ *
+ * \ref pw_stream_dequeue_buffer() should be used to get the data and
+ * metadata of the buffer.
+ *
+ * When the buffer is no longer in use, call \ref pw_stream_queue_buffer()
+ * to let PipeWire reuse the buffer.
+ *
+ * \subsection ssec_produce Produce data
+ *
+ * \ref pw_stream_dequeue_buffer() gives an empty buffer that can be filled.
+ *
+ * Filled buffers should be queued with \ref pw_stream_queue_buffer().
+ *
+ * The process event is emitted when PipeWire has emptied a buffer that
+ * can now be refilled.
+ *
+ * \section sec_stream_disconnect Disconnect
+ *
+ * Use \ref pw_stream_disconnect() to disconnect a stream after use.
+ */
+/** \class pw_stream
+ *
+ * \brief PipeWire stream object class
+ *
+ * The stream object provides a convenient way to send and
+ * receive data streams from/to PipeWire.
+ *
+ * See also \ref page_streams and \ref page_context_api
+ */
+struct pw_stream;
+
+#include <spa/buffer/buffer.h>
+#include <spa/param/param.h>
+
+/** \enum pw_stream_state The state of a stream \memberof pw_stream */
+enum pw_stream_state {
+	PW_STREAM_STATE_ERROR = -1,		/**< the stream is in error */
+	PW_STREAM_STATE_UNCONNECTED = 0,	/**< unconnected */
+	PW_STREAM_STATE_CONNECTING = 1,		/**< connection is in progress */
+	PW_STREAM_STATE_PAUSED = 2,		/**< paused */
+	PW_STREAM_STATE_STREAMING = 3		/**< streaming */
+};
+
+struct pw_buffer {
+	struct spa_buffer *buffer;	/**< the spa buffer */
+	void *user_data;		/**< user data attached to the buffer */
+	uint64_t size;			/**< This field is set by the user and the sum of
+					  *  all queued buffer is returned in the time info */
+};
+
+struct pw_stream_control {
+	const char *name;		/**< name of the control */
+	uint32_t flags;			/**< extra flags (unused) */
+	float def;			/**< default value */
+	float min;			/**< min value */
+	float max;			/**< max value */
+	float *values;			/**< array of values */
+	uint32_t n_values;		/**< number of values in array */
+	uint32_t max_values;		/**< max values that can be set on this control */
+};
+
+/** A time structure \memberof pw_stream */
+struct pw_time {
+	int64_t now;			/**< the monotonic time */
+	struct spa_fraction rate;	/**< the rate of \a ticks and delay */
+	uint64_t ticks;			/**< the ticks at \a now. This is the current time that
+					  *  the remote end is reading/writing. */
+	int64_t delay;			/**< delay to device, add to ticks to get the time of the
+					  *  device. Positive for INPUT streams and
+					  *  negative for OUTPUT streams. */
+	uint64_t queued;		/**< data queued in the stream, this is the sum
+					  *  of the size fields in the pw_buffer that are
+					  *  currently queued */
+};
+
+#include <pipewire/pipewire.h>
+
+/** Events for a stream. These events are always called from the mainloop
+ * unless explicitly documented otherwise. */
+struct pw_stream_events {
+#define PW_VERSION_STREAM_EVENTS	0
+	uint32_t version;
+
+	void (*destroy) (void *data);
+	/** when the stream state changes */
+	void (*state_changed) (void *data, enum pw_stream_state old,
+				enum pw_stream_state state, const char *error);
+
+	/** Notify information about a control.  */
+	void (*control_info) (void *data, uint32_t id, const struct pw_stream_control *control);
+
+	/** when io changed on the stream. */
+	void (*io_changed) (void *data, uint32_t id, void *area, uint32_t size);
+	/** when a parameter changed */
+	void (*param_changed) (void *data, uint32_t id, const struct spa_pod *param);
+
+        /** when a new buffer was created for this stream */
+        void (*add_buffer) (void *data, struct pw_buffer *buffer);
+        /** when a buffer was destroyed for this stream */
+        void (*remove_buffer) (void *data, struct pw_buffer *buffer);
+
+        /** when a buffer can be queued (for playback streams) or
+         *  dequeued (for capture streams). This is normally called from the
+	 *  mainloop but can also be called directly from the realtime data
+	 *  thread if the user is prepared to deal with this. */
+        void (*process) (void *data);
+
+	/** The stream is drained */
+        void (*drained) (void *data);
+
+};
+
+/** Convert a stream state to a readable string \memberof pw_stream */
+const char * pw_stream_state_as_string(enum pw_stream_state state);
+
+/** \enum pw_stream_flags Extra flags that can be used in \ref pw_stream_connect() \memberof pw_stream */
+enum pw_stream_flags {
+	PW_STREAM_FLAG_NONE = 0,			/**< no flags */
+	PW_STREAM_FLAG_AUTOCONNECT	= (1 << 0),	/**< try to automatically connect
+							  *  this stream */
+	PW_STREAM_FLAG_INACTIVE		= (1 << 1),	/**< start the stream inactive,
+							  *  pw_stream_set_active() needs to be
+							  *  called explicitly */
+	PW_STREAM_FLAG_MAP_BUFFERS	= (1 << 2),	/**< mmap the buffers */
+	PW_STREAM_FLAG_DRIVER		= (1 << 3),	/**< be a driver */
+	PW_STREAM_FLAG_RT_PROCESS	= (1 << 4),	/**< call process from the realtime
+							  *  thread. You MUST use RT safe functions
+							  *  in the process callback. */
+	PW_STREAM_FLAG_NO_CONVERT	= (1 << 5),	/**< don't convert format */
+	PW_STREAM_FLAG_EXCLUSIVE	= (1 << 6),	/**< require exclusive access to the
+							  *  device */
+	PW_STREAM_FLAG_DONT_RECONNECT	= (1 << 7),	/**< don't try to reconnect this stream
+							  *  when the sink/source is removed */
+	PW_STREAM_FLAG_ALLOC_BUFFERS	= (1 << 8),	/**< the application will allocate buffer
+							  *  memory. In the add_buffer event, the
+							  *  data of the buffer should be set */
+};
+
+/** Create a new unconneced \ref pw_stream \memberof pw_stream
+ * \return a newly allocated \ref pw_stream */
+struct pw_stream *
+pw_stream_new(struct pw_core *core,		/**< a \ref pw_core */
+	      const char *name,			/**< a stream media name */
+	      struct pw_properties *props	/**< stream properties, ownership is taken */);
+
+struct pw_stream *
+pw_stream_new_simple(struct pw_loop *loop,	/**< a \ref pw_loop to use */
+		     const char *name,		/**< a stream media name */
+		     struct pw_properties *props,/**< stream properties, ownership is taken */
+		     const struct pw_stream_events *events,	/**< stream events */
+		     void *data					/**< data passed to events */);
+
+/** Destroy a stream \memberof pw_stream */
+void pw_stream_destroy(struct pw_stream *stream);
+
+void pw_stream_add_listener(struct pw_stream *stream,
+			    struct spa_hook *listener,
+			    const struct pw_stream_events *events,
+			    void *data);
+
+enum pw_stream_state pw_stream_get_state(struct pw_stream *stream, const char **error);
+
+const char *pw_stream_get_name(struct pw_stream *stream);
+
+struct pw_core *pw_stream_get_core(struct pw_stream *stream);
+
+const struct pw_properties *pw_stream_get_properties(struct pw_stream *stream);
+
+int pw_stream_update_properties(struct pw_stream *stream, const struct spa_dict *dict);
+
+/** Connect a stream for input or output on \a port_path. \memberof pw_stream
+ * \return 0 on success < 0 on error.
+ *
+ * You should connect to the process event and use pw_stream_dequeue_buffer()
+ * to get the latest metadata and data. */
+int
+pw_stream_connect(struct pw_stream *stream,		/**< a \ref pw_stream */
+		  enum pw_direction direction,		/**< the stream direction */
+		  uint32_t target_id,			/**< the target object id to connect to or
+							  *  PW_ID_ANY to let the manager
+							  *  select a target. */
+		  enum pw_stream_flags flags,		/**< stream flags */
+		  const struct spa_pod **params,	/**< an array with params. The params
+							  *  should ideally contain supported
+							  *  formats. */
+		  uint32_t n_params			/**< number of items in \a params */);
+
+/** Get the node ID of the stream. \memberof pw_stream
+ * \return node ID. */
+uint32_t
+pw_stream_get_node_id(struct pw_stream *stream);
+
+/** Disconnect \a stream \memberof pw_stream */
+int pw_stream_disconnect(struct pw_stream *stream);
+
+/** Set the stream in error state */
+int pw_stream_set_error(struct pw_stream *stream,	/**< a \ref pw_stream */
+			int res,			/**< a result code */
+			const char *error, ...		/**< an error message */) SPA_PRINTF_FUNC(3, 4);
+
+/** Complete the negotiation process with result code \a res \memberof pw_stream
+ *
+ * This function should be called after notification of the format.
+
+ * When \a res indicates success, \a params contain the parameters for the
+ * allocation state.  */
+int
+pw_stream_update_params(struct pw_stream *stream,	/**< a \ref pw_stream */
+			const struct spa_pod **params,	/**< an array of params. The params should
+							  *  ideally contain parameters for doing
+							  *  buffer allocation. */
+			uint32_t n_params		/**< number of elements in \a params */);
+
+/** Set control values */
+int pw_stream_set_control(struct pw_stream *stream, uint32_t id, uint32_t n_values, float *values, ...);
+
+/** Query the time on the stream \memberof pw_stream */
+int pw_stream_get_time(struct pw_stream *stream, struct pw_time *time);
+
+/** Get a buffer that can be filled for playback streams or consumed
+ * for capture streams.  */
+struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream);
+
+/** Submit a buffer for playback or recycle a buffer for capture. */
+int pw_stream_queue_buffer(struct pw_stream *stream, struct pw_buffer *buffer);
+
+/** Activate or deactivate the stream \memberof pw_stream */
+int pw_stream_set_active(struct pw_stream *stream, bool active);
+
+/** Flush a stream. When \a drain is true, the drained callback will
+ * be called when all data is played or recorded */
+int pw_stream_flush(struct pw_stream *stream, bool drain);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_STREAM_H */
diff --git a/third_party/pipewire/pipewire/thread-loop.h b/third_party/pipewire/pipewire/thread-loop.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/thread-loop.h
@@ -0,0 +1,168 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_THREAD_LOOP_H
+#define PIPEWIRE_THREAD_LOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/loop.h>
+
+/** \page page_thread_loop Threaded Loop
+ *
+ * \section sec_thread_loop_overview Overview
+ *
+ * The threaded loop implementation is a special wrapper around the
+ * regular \ref pw_loop implementation.
+ *
+ * The added feature in the threaded loop is that it spawns a new thread
+ * that runs the wrapped loop. This allows a synchronous application to use
+ * the asynchronous API without risking to stall the PipeWire library.
+ *
+ * \section sec_thread_loop_create Creation
+ *
+ * A \ref pw_thread_loop object is created using pw_thread_loop_new().
+ * The \ref pw_loop to wrap must be given as an argument along with the name
+ * for the thread that will be spawned.
+ *
+ * After allocating the object, the thread must be started with
+ * pw_thread_loop_start()
+ *
+ * \section sec_thread_loop_destruction Destruction
+ *
+ * When the PipeWire connection has been terminated, the thread must be
+ * stopped and the resources freed. Stopping the thread is done using
+ * pw_thread_loop_stop(), which must be called without the lock (see
+ * below) held. When that function returns, the thread is stopped and the
+ * \ref pw_thread_loop object can be freed using pw_thread_loop_destroy().
+ *
+ * \section sec_thread_loop_locking Locking
+ *
+ * Since the PipeWire API doesn't allow concurrent accesses to objects,
+ * a locking scheme must be used to guarantee safe usage. The threaded
+ * loop API provides such a scheme through the functions
+ * pw_thread_loop_lock() and pw_thread_loop_unlock().
+ *
+ * The lock is recursive, so it's safe to use it multiple times from the same
+ * thread. Just make sure you call pw_thread_loop_unlock() the same
+ * number of times you called pw_thread_loop_lock().
+ *
+ * The lock needs to be held whenever you call any PipeWire function that
+ * uses an object associated with this loop. Make sure you do not hold
+ * on to the lock more than necessary though, as the threaded loop stops
+ * while the lock is held.
+ *
+ * \section sec_thread_loop_events Events and Callbacks
+ *
+ * All events and callbacks are called with the thread lock held.
+ *
+ */
+/** \class pw_thread_loop
+ *
+ * \brief PipeWire threaded loop object
+ *
+ * The threaded loop object runs a \ref pw_loop in a separate thread
+ * and ensures proper locking is done.
+ *
+ * All of the loop callbacks will be executed with the loop
+ * lock held.
+ *
+ * See also \ref page_thread_loop
+ */
+struct pw_thread_loop;
+
+/** Thread loop events */
+struct pw_thread_loop_events {
+#define PW_VERSION_THREAD_LOOP_EVENTS	0
+        uint32_t version;
+
+	/** the loop is destroyed */
+        void (*destroy) (void *data);
+};
+
+/** Make a new thread loop with the given name and optional properties. */
+struct pw_thread_loop *
+pw_thread_loop_new(const char *name, const struct spa_dict *props);
+
+/** Make a new thread loop with the given loop, name and optional properties.
+ * When \a loop is NULL, a new loop will be created. */
+struct pw_thread_loop *
+pw_thread_loop_new_full(struct pw_loop *loop, const char *name, const struct spa_dict *props);
+
+/** Destroy a thread loop */
+void pw_thread_loop_destroy(struct pw_thread_loop *loop);
+
+/** Add an event listener */
+void pw_thread_loop_add_listener(struct pw_thread_loop *loop,
+				 struct spa_hook *listener,
+				 const struct pw_thread_loop_events *events,
+				 void *data);
+
+/** Get the loop implementation of the thread loop */
+struct pw_loop * pw_thread_loop_get_loop(struct pw_thread_loop *loop);
+
+/** Start the thread loop */
+int pw_thread_loop_start(struct pw_thread_loop *loop);
+
+/** Stop the thread loop */
+void pw_thread_loop_stop(struct pw_thread_loop *loop);
+
+/** Lock the loop. This ensures exclusive ownership of the loop */
+void pw_thread_loop_lock(struct pw_thread_loop *loop);
+
+/** Unlock the loop */
+void pw_thread_loop_unlock(struct pw_thread_loop *loop);
+
+/** Release the lock and wait until some thread calls \ref pw_thread_loop_signal */
+void pw_thread_loop_wait(struct pw_thread_loop *loop);
+
+/** Release the lock and wait a maximum of 'wait_max_sec' seconds
+ *  until some thread calls \ref pw_thread_loop_signal or time out */
+int pw_thread_loop_timed_wait(struct pw_thread_loop *loop, int wait_max_sec);
+
+/** Get a struct timespec suitable for \ref pw_thread_loop_timed_wait_full.
+ * Since: 0.3.7 */
+int pw_thread_loop_get_time(struct pw_thread_loop *loop, struct timespec *abstime, int64_t timeout);
+
+/** Release the lock and wait up to \a abstime until some thread calls
+ * \ref pw_thread_loop_signal. Use \ref pw_thread_loop_get_time to make a timeout.
+ * Since: 0.3.7 */
+int pw_thread_loop_timed_wait_full(struct pw_thread_loop *loop, struct timespec *abstime);
+
+/** Signal all threads waiting with \ref pw_thread_loop_wait */
+void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept);
+
+/** Signal all threads executing \ref pw_thread_loop_signal with wait_for_accept */
+void pw_thread_loop_accept(struct pw_thread_loop *loop);
+
+/** Check if inside the thread */
+bool pw_thread_loop_in_thread(struct pw_thread_loop *loop);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_THREAD_LOOP_H */
diff --git a/third_party/pipewire/pipewire/type.h b/third_party/pipewire/pipewire/type.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/type.h
@@ -0,0 +1,52 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_TYPE_H
+#define PIPEWIRE_TYPE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/type.h>
+
+enum {
+	PW_TYPE_FIRST = SPA_TYPE_VENDOR_PipeWire,
+};
+
+#define PW_TYPE_INFO_BASE		"PipeWire:"
+
+#define PW_TYPE_INFO_Object		PW_TYPE_INFO_BASE "Object"
+#define PW_TYPE_INFO_OBJECT_BASE	PW_TYPE_INFO_Object ":"
+
+#define PW_TYPE_INFO_Interface		PW_TYPE_INFO_BASE "Interface"
+#define PW_TYPE_INFO_INTERFACE_BASE	PW_TYPE_INFO_Interface ":"
+
+const struct spa_type_info * pw_type_info(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_TYPE_H */
diff --git a/third_party/pipewire/pipewire/utils.h b/third_party/pipewire/pipewire/utils.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/utils.h
@@ -0,0 +1,59 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_UTILS_H
+#define PIPEWIRE_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/pod/pod.h>
+
+/** \class pw_utils
+ *
+ * Various utility functions
+ */
+
+/** a function to destroy an item \memberof pw_utils */
+typedef void (*pw_destroy_t) (void *object);
+
+const char *
+pw_split_walk(const char *str, const char *delimiter, size_t *len, const char **state);
+
+char **
+pw_split_strv(const char *str, const char *delimiter, int max_tokens, int *n_tokens);
+
+void
+pw_free_strv(char **str);
+
+char *
+pw_strip(char *str, const char *whitespace);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_UTILS_H */
diff --git a/third_party/pipewire/pipewire/version.h b/third_party/pipewire/pipewire/version.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/version.h
@@ -0,0 +1,68 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_VERSION_H
+#define PIPEWIRE_VERSION_H
+
+/* WARNING: Make sure to edit the real source file version.h.in! */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Return the version of the header files. Keep in mind that this is
+a macro and not a function, so it is impossible to get the pointer of
+it. */
+#define pw_get_headers_version() ("0.3.13")
+
+/** Return the version of the library the current application is
+ * linked to. */
+const char* pw_get_library_version(void);
+
+/** The current API version. Versions prior to 0.2.0 have
+ * PW_API_VERSION undefined. Please note that this is only ever
+ * increased on incompatible API changes!  */
+#define PW_API_VERSION "0.3"
+
+/** The major version of PipeWire. \since 0.2.0 */
+#define PW_MAJOR 0
+
+/** The minor version of PipeWire. \since 0.2.0 */
+#define PW_MINOR 3
+
+/** The micro version of PipeWire. \since 0.2.0 */
+#define PW_MICRO 13
+
+/** Evaluates to TRUE if the PipeWire library version is equal or
+ * newer than the specified. \since 0.2.0 */
+#define PW_CHECK_VERSION(major,minor,micro)                             \
+    ((PW_MAJOR > (major)) ||                                            \
+     (PW_MAJOR == (major) && PW_MINOR > (minor)) ||                     \
+     (PW_MAJOR == (major) && PW_MINOR == (minor) && PW_MICRO >= (micro)))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_VERION_H */
diff --git a/third_party/pipewire/pipewire/work-queue.h b/third_party/pipewire/pipewire/work-queue.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/pipewire/work-queue.h
@@ -0,0 +1,63 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_WORK_QUEUE_H
+#define PIPEWIRE_WORK_QUEUE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_work_queue
+ *
+ * PipeWire work queue object
+ */
+struct pw_work_queue;
+
+#include <pipewire/loop.h>
+
+typedef void (*pw_work_func_t) (void *obj, void *data, int res, uint32_t id);
+
+struct pw_work_queue *
+pw_work_queue_new(struct pw_loop *loop);
+
+void
+pw_work_queue_destroy(struct pw_work_queue *queue);
+
+uint32_t
+pw_work_queue_add(struct pw_work_queue *queue,
+		  void *obj, int res,
+		  pw_work_func_t func, void *data);
+
+int
+pw_work_queue_cancel(struct pw_work_queue *queue, void *obj, uint32_t id);
+
+int
+pw_work_queue_complete(struct pw_work_queue *queue, void *obj, uint32_t seq, int res);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_WORK_QUEUE_H */
diff --git a/third_party/pipewire/spa/buffer/alloc.h b/third_party/pipewire/spa/buffer/alloc.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/buffer/alloc.h
@@ -0,0 +1,338 @@
+/* Simple Plugin API
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SPA_BUFFER_ALLOC_H
+#define SPA_BUFFER_ALLOC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/buffer/buffer.h>
+
+/** information about the buffer layout */
+struct spa_buffer_alloc_info {
+#define SPA_BUFFER_ALLOC_FLAG_INLINE_META	(1<<0)	/**< add metadata data in the skeleton */
+#define SPA_BUFFER_ALLOC_FLAG_INLINE_CHUNK	(1<<1)	/**< add chunk data in the skeleton */
+#define SPA_BUFFER_ALLOC_FLAG_INLINE_DATA	(1<<2)	/**< add buffer data to the skeleton */
+#define SPA_BUFFER_ALLOC_FLAG_INLINE_ALL	0b111
+#define SPA_BUFFER_ALLOC_FLAG_NO_DATA		(1<<3)	/**< don't set data pointers */
+	uint32_t flags;
+	uint32_t max_align;	/**< max of all alignments */
+	uint32_t n_metas;
+	uint32_t n_datas;
+	struct spa_meta *metas;
+	struct spa_data *datas;
+	uint32_t *data_aligns;
+	size_t skel_size;	/**< size of the struct spa_buffer and inlined meta/chunk/data */
+	size_t meta_size;	/**< size of the meta if not inlined */
+	size_t chunk_size;	/**< size of the chunk if not inlined */
+	size_t data_size;	/**< size of the data if not inlined */
+	size_t mem_size;	/**< size of the total memory if not inlined */
+};
+
+/**
+ * Fill buffer allocation information
+ *
+ * Fill \a info with allocation information needed to allocate buffers
+ * with the given number of metadata and data members.
+ *
+ * The required size of the skeleton (the struct spa_buffer) information
+ * and the memory (for the metadata, chunk and buffer memory) will be
+ * calculated.
+ *
+ * The flags member in \a info should be configured before calling this
+ * functions.
+ *
+ * \param info the information to fill
+ * \param n_metas the number of metadatas for the buffer
+ * \param metas an array of metadata items
+ * \param n_datas the number of datas for the buffer
+ * \param datas an array of \a n_datas items
+ * \param data_aligns \a n_datas alignments
+ * \return 0 on success.
+ * */
+static inline int spa_buffer_alloc_fill_info(struct spa_buffer_alloc_info *info,
+					     uint32_t n_metas, struct spa_meta metas[],
+					     uint32_t n_datas, struct spa_data datas[],
+					     uint32_t data_aligns[])
+{
+	size_t size, *target;
+	uint32_t i;
+
+	info->n_metas = n_metas;
+	info->metas = metas;
+	info->n_datas = n_datas;
+	info->datas = datas;
+	info->data_aligns = data_aligns;
+	info->max_align = 16;
+	info->mem_size = 0;
+	/*
+	 * The buffer skeleton is placed in memory like below and can
+	 * be accessed as a regular structure.
+	 *
+	 *      +==============================+
+	 *      | struct spa_buffer            |
+	 *      |   uint32_t n_metas           | number of metas
+	 *      |   uint32_t n_datas           | number of datas
+	 *    +-|   struct spa_meta *metas     | pointer to array of metas
+	 *   +|-|   struct spa_data *datas     | pointer to array of datas
+	 *   || +------------------------------+
+	 *   |+>| struct spa_meta              |
+	 *   |  |   uint32_t type              | metadata
+	 *   |  |   uint32_t size              | size of metadata
+	 *  +|--|   void *data                 | pointer to metadata
+	 *  ||  | ... <n_metas>                | more spa_meta follow
+	 *  ||  +------------------------------+
+	 *  |+->| struct spa_data              |
+	 *  |   |   uint32_t type              | memory type
+	 *  |   |   uint32_t flags             |
+	 *  |   |   int fd                     | fd of shared memory block
+	 *  |   |   uint32_t mapoffset         | offset in shared memory of data
+	 *  |   |   uint32_t maxsize           | size of data block
+	 *  | +-|   void *data                 | pointer to data
+	 *  |+|-|   struct spa_chunk *chunk    | pointer to chunk
+	 *  ||| | ... <n_datas>                | more spa_data follow
+	 *  ||| +==============================+
+	 *  VVV
+	 *
+	 * metadata, chunk and memory can either be placed right
+	 * after the skeleton (inlined) or in a separate piece of memory.
+	 *
+	 *  vvv
+	 *  ||| +==============================+
+	 *  +-->| meta data memory             | metadata memory, 8 byte aligned
+	 *   || | ... <n_metas>                |
+	 *   || +------------------------------+
+	 *   +->| struct spa_chunk             | memory for n_datas chunks
+	 *    | |   uint32_t offset            |
+	 *    | |   uint32_t size              |
+	 *    | |   int32_t stride             |
+	 *    | |   int32_t dummy              |
+	 *    | | ... <n_datas> chunks         |
+	 *    | +------------------------------+
+	 *    +>| data                         | memory for n_datas data, aligned
+	 *      | ... <n_datas> blocks         | according to alignments
+	 *      +==============================+
+	 */
+	info->skel_size = sizeof(struct spa_buffer);
+        info->skel_size += n_metas * sizeof(struct spa_meta);
+        info->skel_size += n_datas * sizeof(struct spa_data);
+
+	for (i = 0, size = 0; i < n_metas; i++)
+		size += SPA_ROUND_UP_N(metas[i].size, 8);
+	info->meta_size = size;
+
+	if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_META))
+		target = &info->skel_size;
+	else
+		target = &info->mem_size;
+	*target += info->meta_size;
+
+	info->chunk_size = n_datas * sizeof(struct spa_chunk);
+	if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_CHUNK))
+		target = &info->skel_size;
+	else
+	        target = &info->mem_size;
+	*target += info->chunk_size;
+
+	for (i = 0, size = 0; i < n_datas; i++) {
+		info->max_align = SPA_MAX(info->max_align, data_aligns[i]);
+		size = SPA_ROUND_UP_N(size, data_aligns[i]);
+		size += datas[i].maxsize;
+	}
+	info->data_size = size;
+
+	if (!SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_NO_DATA) &&
+	    SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_DATA))
+		target = &info->skel_size;
+	else
+		target = &info->mem_size;
+
+	*target = SPA_ROUND_UP_N(*target, n_datas ? data_aligns[0] : 1);
+	*target += info->data_size;
+	*target = SPA_ROUND_UP_N(*target, info->max_align);
+
+	return 0;
+}
+
+/**
+ * Fill skeleton and data according to the allocation info
+ *
+ * Use the allocation info to create a \ref struct spa_buffer into
+ * \a skel_mem and \a data_mem.
+ *
+ * Depending on the flags given when calling \ref
+ * spa_buffer_alloc_fill_info(), the buffer meta, chunk and memory
+ * will be referenced in either skel_mem or data_mem.
+ *
+ * \param info an allocation info
+ * \param skel_mem memory to hold the \ref struct spa_buffer and the
+ *  pointers to meta, chunk and memory.
+ * \param data_mem memory to hold the meta, chunk and memory
+ * \return a \ref struct spa_buffer in \a skel_mem
+ */
+static inline struct spa_buffer *
+spa_buffer_alloc_layout(struct spa_buffer_alloc_info *info,
+			void *skel_mem, void *data_mem)
+{
+	struct spa_buffer *b = (struct spa_buffer*)skel_mem;
+	size_t size;
+	uint32_t i;
+	void **dp, *skel, *data;
+	struct spa_chunk *cp;
+
+	b->n_metas = info->n_metas;
+	b->metas = SPA_MEMBER(b, sizeof(struct spa_buffer), struct spa_meta);
+	b->n_datas = info->n_datas;
+	b->datas = SPA_MEMBER(b->metas, info->n_metas * sizeof(struct spa_meta), struct spa_data);
+
+	skel = SPA_MEMBER(b->datas, info->n_datas * sizeof(struct spa_data), void);
+	data = data_mem;
+
+	if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_META))
+		dp = &skel;
+	else
+		dp = &data;
+
+	for (i = 0; i < info->n_metas; i++) {
+		struct spa_meta *m = &b->metas[i];
+		*m = info->metas[i];
+		m->data = *dp;
+		*dp = SPA_MEMBER(*dp, SPA_ROUND_UP_N(m->size, 8), void);
+	}
+
+	size = info->n_datas * sizeof(struct spa_chunk);
+	if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_CHUNK)) {
+		cp = (struct spa_chunk*)skel;
+		skel = SPA_MEMBER(skel, size, void);
+	}
+	else {
+		cp = (struct spa_chunk*)data;
+		data = SPA_MEMBER(data, size, void);
+	}
+
+	if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_DATA))
+		dp = &skel;
+	else
+		dp = &data;
+
+	for (i = 0; i < info->n_datas; i++) {
+		struct spa_data *d = &b->datas[i];
+
+		*d = info->datas[i];
+		d->chunk = &cp[i];
+		if (!SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_NO_DATA)) {
+			*dp = SPA_PTR_ALIGN(*dp, info->data_aligns[i], void);
+			d->data = *dp;
+			*dp = SPA_MEMBER(*dp, d->maxsize, void);
+		}
+	}
+	return b;
+}
+
+/**
+ * Layout an array of buffers
+ *
+ * Use the allocation info to layout the memory of an array of buffers.
+ *
+ * \a skel_mem should point to at least info->skel_size * \a n_buffers bytes
+ * of memory.
+ * \a data_mem should point to at least info->mem_size * \a n_buffers bytes
+ * of memory.
+ *
+ * \param info the allocation info for one buffer
+ * \param n_buffers the number of buffers to create
+ * \param buffer a array with space to hold \a n_buffers pointers to buffers
+ * \param skel_mem memory for the \ref struct spa_buffer
+ * \param data_mem memory for the meta, chunk, memory of the buffer if not
+ *		inlined in the skeleton.
+ * \return 0 on success.
+ *
+ */
+static inline int
+spa_buffer_alloc_layout_array(struct spa_buffer_alloc_info *info,
+			      uint32_t n_buffers, struct spa_buffer *buffers[],
+			      void *skel_mem, void *data_mem)
+{
+	uint32_t i;
+	for (i = 0; i < n_buffers; i++) {
+		buffers[i] = spa_buffer_alloc_layout(info, skel_mem, data_mem);
+		skel_mem = SPA_MEMBER(skel_mem, info->skel_size, void);
+		data_mem = SPA_MEMBER(data_mem, info->mem_size, void);
+        }
+	return 0;
+}
+
+/**
+ * Allocate an array of buffers
+ *
+ * Allocate \a n_buffers with the given metadata, memory and alignment
+ * information.
+ *
+ * The buffer array, structures, data and metadata will all be allocated
+ * in one block of memory with the proper requested alignment.
+ *
+ * \param n_buffers the number of buffers to create
+ * \param flags extra flags
+ * \param n_metas number of metadatas
+ * \param metas \a n_metas metadata specification
+ * \param n_datas number of datas
+ * \param datas \a n_datas memory specification
+ * \param data_aligns \a n_datas alignment specifications
+ * \returns an array of \a n_buffers pointers to \ref struct spa_buffer
+ *     with the given metadata, data and alignment or NULL when
+ *     allocation failed.
+ *
+ */
+static inline struct spa_buffer **
+spa_buffer_alloc_array(uint32_t n_buffers, uint32_t flags,
+		       uint32_t n_metas, struct spa_meta metas[],
+		       uint32_t n_datas, struct spa_data datas[],
+		       uint32_t data_aligns[])
+{
+
+	struct spa_buffer **buffers;
+	struct spa_buffer_alloc_info info = { flags | SPA_BUFFER_ALLOC_FLAG_INLINE_ALL, };
+	void *skel;
+
+	spa_buffer_alloc_fill_info(&info, n_metas, metas, n_datas, datas, data_aligns);
+
+	buffers = (struct spa_buffer **)calloc(1, info.max_align +
+			n_buffers * (sizeof(struct spa_buffer *) + info.skel_size));
+	if (buffers == NULL)
+		return NULL;
+
+	skel = SPA_MEMBER(buffers, sizeof(struct spa_buffer *) * n_buffers, void);
+	skel = SPA_PTR_ALIGN(skel, info.max_align, void);
+
+	spa_buffer_alloc_layout_array(&info, n_buffers, buffers, skel, NULL);
+
+	return buffers;
+}
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_BUFFER_ALLOC_H */
diff --git a/third_party/pipewire/spa/buffer/buffer.h b/third_party/pipewire/spa/buffer/buffer.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/buffer/buffer.h
@@ -0,0 +1,114 @@
+/* Simple Plugin API
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_BUFFER_H
+#define SPA_BUFFER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/buffer/meta.h>
+
+/** \page page_buffer Buffers
+ *
+ * Buffers describe the data and metadata that is exchanged between
+ * ports of a node.
+ */
+
+enum spa_data_type {
+	SPA_DATA_Invalid,
+	SPA_DATA_MemPtr,		/**< pointer to memory, the data field in
+					  *  struct spa_data is set. */
+	SPA_DATA_MemFd,			/**< generic fd, mmap to get to memory */
+	SPA_DATA_DmaBuf,		/**< fd to dmabuf memory */
+	SPA_DATA_MemId,			/**< memory is identified with an id */
+
+	SPA_DATA_LAST,			/**< not part of ABI */
+};
+
+/** Chunk of memory, can change for each buffer */
+struct spa_chunk {
+	uint32_t offset;		/**< offset of valid data. Should be taken
+					  *  modulo the data maxsize to get the offset
+					  *  in the data memory. */
+	uint32_t size;			/**< size of valid data. Should be clamped to
+					  *  maxsize. */
+	int32_t stride;			/**< stride of valid data */
+#define SPA_CHUNK_FLAG_NONE		0
+#define SPA_CHUNK_FLAG_CORRUPTED	(1u<<0)	/**< chunk data is corrupted in some way */
+	int32_t flags;			/**< chunk flags */
+};
+
+/** Data for a buffer this stays constant for a buffer */
+struct spa_data {
+	uint32_t type;			/**< memory type, one of enum spa_data_type, when
+					  *  allocating memory, the type contains a bitmask
+					  *  of allowed types */
+#define SPA_DATA_FLAG_NONE	 0
+#define SPA_DATA_FLAG_READABLE	(1u<<0)	/**< data is readable */
+#define SPA_DATA_FLAG_WRITABLE	(1u<<1)	/**< data is writable */
+#define SPA_DATA_FLAG_DYNAMIC	(1u<<2)	/**< data pointer can be changed */
+#define SPA_DATA_FLAG_READWRITE	(SPA_DATA_FLAG_READABLE|SPA_DATA_FLAG_WRITABLE)
+	uint32_t flags;			/**< data flags */
+	int64_t fd;			/**< optional fd for data */
+	uint32_t mapoffset;		/**< offset to map fd at */
+	uint32_t maxsize;		/**< max size of data */
+	void *data;			/**< optional data pointer */
+	struct spa_chunk *chunk;	/**< valid chunk of memory */
+};
+
+/** A Buffer */
+struct spa_buffer {
+	uint32_t n_metas;		/**< number of metadata */
+	uint32_t n_datas;		/**< number of data members */
+	struct spa_meta *metas;		/**< array of metadata */
+	struct spa_data *datas;		/**< array of data members */
+};
+
+/** Find metadata in a buffer */
+static inline struct spa_meta *spa_buffer_find_meta(const struct spa_buffer *b, uint32_t type)
+{
+	uint32_t i;
+
+	for (i = 0; i < b->n_metas; i++)
+		if (b->metas[i].type == type)
+			return &b->metas[i];
+
+	return NULL;
+}
+
+static inline void *spa_buffer_find_meta_data(const struct spa_buffer *b, uint32_t type, size_t size)
+{
+	struct spa_meta *m;
+	if ((m = spa_buffer_find_meta(b, type)) && m->size >= size)
+		return m->data;
+	return NULL;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_BUFFER_H */
diff --git a/third_party/pipewire/spa/buffer/meta.h b/third_party/pipewire/spa/buffer/meta.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/buffer/meta.h
@@ -0,0 +1,151 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_META_H
+#define SPA_META_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/pod/pod.h>
+
+/** \page page_meta Metadata
+ *
+ * Metadata contains extra information on a buffer.
+ */
+enum spa_meta_type {
+	SPA_META_Invalid,
+	SPA_META_Header,	/**< struct spa_meta_header */
+	SPA_META_VideoCrop,	/**< struct spa_meta_region with cropping data */
+	SPA_META_VideoDamage,	/**< array of struct spa_meta_region with damage */
+	SPA_META_Bitmap,	/**< struct spa_meta_bitmap */
+	SPA_META_Cursor,	/**< struct spa_meta_cursor */
+	SPA_META_Control,	/**< metadata contains a spa_meta_control
+				  *  associated with the data */
+
+	SPA_META_LAST,		/**< not part of ABI/API */
+};
+
+/**
+ * A metadata element.
+ *
+ * This structure is available on the buffer structure and contains
+ * the type of the metadata and a pointer/size to the actual metadata
+ * itself.
+ */
+struct spa_meta {
+	uint32_t type;		/**< metadata type, one of enum spa_meta_type */
+	uint32_t size;		/**< size of metadata */
+	void *data;		/**< pointer to metadata */
+};
+
+#define spa_meta_first(m)	((m)->data)
+#define spa_meta_end(m)		SPA_MEMBER((m)->data,(m)->size,void)
+#define spa_meta_check(p,m)	(SPA_MEMBER(p,sizeof(*p),void) <= spa_meta_end(m))
+
+/**
+ * Describes essential buffer header metadata such as flags and
+ * timestamps.
+ */
+struct spa_meta_header {
+#define SPA_META_HEADER_FLAG_DISCONT	(1 << 0)	/**< data is not continuous with previous buffer */
+#define SPA_META_HEADER_FLAG_CORRUPTED	(1 << 1)	/**< data might be corrupted */
+#define SPA_META_HEADER_FLAG_MARKER	(1 << 2)	/**< media specific marker */
+#define SPA_META_HEADER_FLAG_HEADER	(1 << 3)	/**< data contains a codec specific header */
+#define SPA_META_HEADER_FLAG_GAP	(1 << 4)	/**< data contains media neutral data */
+#define SPA_META_HEADER_FLAG_DELTA_UNIT	(1 << 5)	/**< cannot be decoded independently */
+	uint32_t flags;				/**< flags */
+	uint32_t offset;			/**< offset in current cycle */
+	int64_t pts;				/**< presentation timestamp */
+	int64_t dts_offset;			/**< decoding timestamp as a difference with pts */
+	uint64_t seq;				/**< sequence number, increments with a
+						  *  media specific frequency */
+};
+
+/** metadata structure for Region or an array of these for RegionArray */
+struct spa_meta_region {
+	struct spa_region region;
+};
+
+#define spa_meta_region_is_valid(m)	((m)->region.size.width != 0 && (m)->region.size.height != 0)
+
+/** iterate all the items in a metadata */
+#define spa_meta_for_each(pos,meta)					\
+	for (pos = (__typeof(pos))spa_meta_first(meta);			\
+	    spa_meta_check(pos, meta);					\
+            (pos)++)
+
+#define spa_meta_bitmap_is_valid(m)	((m)->format != 0)
+
+/**
+ * Bitmap information
+ *
+ * This metadata contains a bitmap image in the given format and size.
+ * It is typically used for cursor images or other small images that are
+ * better transferred inline.
+ */
+struct spa_meta_bitmap {
+	uint32_t format;		/**< bitmap video format, one of enum spa_video_format. 0 is
+					  *  and invalid format and should be handled as if there is
+					  *  no new bitmap information. */
+	struct spa_rectangle size;	/**< width and height of bitmap */
+	int32_t stride;			/**< stride of bitmap data */
+	uint32_t offset;		/**< offset of bitmap data in this structure. An offset of
+					  *  0 means no image data (invisible), an offset >=
+					  *  sizeof(struct spa_meta_bitmap) contains valid bitmap
+					  *  info. */
+};
+
+#define spa_meta_cursor_is_valid(m)	((m)->id != 0)
+
+/**
+ * Cursor information
+ *
+ * Metadata to describe the position and appearance of a pointing device.
+ */
+struct spa_meta_cursor {
+	uint32_t id;			/**< cursor id. an id of 0 is an invalid id and means that
+					  *  there is no new cursor data */
+	uint32_t flags;			/**< extra flags */
+	struct spa_point position;	/**< position on screen */
+	struct spa_point hotspot;	/**< offsets for hotspot in bitmap, this field has no meaning
+					  *  when there is no valid bitmap (see below) */
+	uint32_t bitmap_offset;		/**< offset of bitmap meta in this structure. When the offset
+					  *  is 0, there is no new bitmap information. When the offset is
+					  *  >= sizeof(struct spa_meta_cursor) there is a
+					  *  struct spa_meta_bitmap at the offset. */
+};
+
+/** a timed set of events associated with the buffer */
+struct spa_meta_control {
+	struct spa_pod_sequence sequence;
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_META_H */
diff --git a/third_party/pipewire/spa/buffer/type-info.h b/third_party/pipewire/spa/buffer/type-info.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/buffer/type-info.h
@@ -0,0 +1,83 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_BUFFER_TYPES_H
+#define SPA_BUFFER_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/buffer/buffer.h>
+#include <spa/buffer/meta.h>
+#include <spa/utils/type.h>
+
+#define SPA_TYPE_INFO_Buffer			SPA_TYPE_INFO_POINTER_BASE "Buffer"
+#define SPA_TYPE_INFO_BUFFER_BASE		SPA_TYPE_INFO_Buffer ":"
+
+/** Buffers contain data of a certain type */
+#define SPA_TYPE_INFO_Data			SPA_TYPE_INFO_ENUM_BASE "Data"
+#define SPA_TYPE_INFO_DATA_BASE			SPA_TYPE_INFO_Data ":"
+
+/** base type for fd based memory */
+#define SPA_TYPE_INFO_DATA_Fd			SPA_TYPE_INFO_DATA_BASE "Fd"
+#define SPA_TYPE_INFO_DATA_FD_BASE		SPA_TYPE_INFO_DATA_Fd ":"
+
+static const struct spa_type_info spa_type_data_type[] = {
+	{ SPA_DATA_Invalid, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_BASE "Invalid", NULL },
+	{ SPA_DATA_MemPtr, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_BASE "MemPtr", NULL },
+	{ SPA_DATA_MemFd, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_FD_BASE "MemFd", NULL },
+	{ SPA_DATA_DmaBuf, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_FD_BASE "DmaBuf", NULL },
+	{ SPA_DATA_MemId, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_BASE "MemId", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_Meta			SPA_TYPE_INFO_POINTER_BASE "Meta"
+#define SPA_TYPE_INFO_META_BASE			SPA_TYPE_INFO_Meta ":"
+
+#define SPA_TYPE_INFO_META_Array		SPA_TYPE_INFO_META_BASE "Array"
+#define SPA_TYPE_INFO_META_ARRAY_BASE		SPA_TYPE_INFO_META_Array ":"
+
+#define SPA_TYPE_INFO_META_Region		SPA_TYPE_INFO_META_BASE "Region"
+#define SPA_TYPE_INFO_META_REGION_BASE		SPA_TYPE_INFO_META_Region ":"
+
+#define SPA_TYPE_INFO_META_ARRAY_Region		SPA_TYPE_INFO_META_ARRAY_BASE "Region"
+#define SPA_TYPE_INFO_META_ARRAY_REGION_BASE	SPA_TYPE_INFO_META_ARRAY_Region ":"
+
+static const struct spa_type_info spa_type_meta_type[] = {
+	{ SPA_META_Invalid, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Invalid", NULL },
+	{ SPA_META_Header, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Header", NULL },
+	{ SPA_META_VideoCrop, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_REGION_BASE "VideoCrop", NULL },
+	{ SPA_META_VideoDamage, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_ARRAY_REGION_BASE "VideoDamage", NULL },
+	{ SPA_META_Bitmap, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Bitmap", NULL },
+	{ SPA_META_Cursor, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Cursor", NULL },
+	{ SPA_META_Control, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Control", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_BUFFER_TYPES_H */
diff --git a/third_party/pipewire/spa/control/control.h b/third_party/pipewire/spa/control/control.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/control/control.h
@@ -0,0 +1,53 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_CONTROL_H
+#define SPA_CONTROL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/pod/pod.h>
+
+/** Controls
+ *
+ */
+
+/** Different Control types */
+enum spa_control_type {
+	SPA_CONTROL_Invalid,
+	SPA_CONTROL_Properties,		/**< data contains a SPA_TYPE_OBJECT_Props */
+	SPA_CONTROL_Midi,		/**< data contains a spa_pod_bytes with raw midi data */
+	SPA_CONTROL_OSC,		/**< data contains a spa_pod_bytes with an OSC packet */
+
+	SPA_CONTROL_LAST,		/**< not part of ABI */
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_CONTROL_H */
diff --git a/third_party/pipewire/spa/control/type-info.h b/third_party/pipewire/spa/control/type-info.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/control/type-info.h
@@ -0,0 +1,52 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_CONTROL_TYPES_H
+#define SPA_CONTROL_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/type-info.h>
+#include <spa/control/control.h>
+
+/* base for parameter object enumerations */
+#define SPA_TYPE_INFO_Control		SPA_TYPE_INFO_ENUM_BASE "Control"
+#define SPA_TYPE_INFO_CONTROL_BASE		SPA_TYPE_INFO_Control ":"
+
+static const struct spa_type_info spa_type_control[] = {
+	{ SPA_CONTROL_Invalid, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "Invalid", NULL },
+	{ SPA_CONTROL_Properties, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "Properties", NULL },
+	{ SPA_CONTROL_Midi, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "Midi", NULL },
+	{ SPA_CONTROL_OSC, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "OSC", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_CONTROL_TYPES_H */
diff --git a/third_party/pipewire/spa/debug/buffer.h b/third_party/pipewire/spa/debug/buffer.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/debug/buffer.h
@@ -0,0 +1,119 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DEBUG_BUFFER_H
+#define SPA_DEBUG_BUFFER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/debug/mem.h>
+#include <spa/debug/types.h>
+#include <spa/buffer/type-info.h>
+
+#ifndef spa_debug
+#define spa_debug(...)	({ fprintf(stderr, __VA_ARGS__);fputc('\n', stderr); })
+#endif
+
+static inline int spa_debug_buffer(int indent, const struct spa_buffer *buffer)
+{
+	uint32_t i;
+
+	spa_debug("%*s" "struct spa_buffer %p:", indent, "", buffer);
+	spa_debug("%*s" " n_metas: %u (at %p)", indent, "", buffer->n_metas, buffer->metas);
+	for (i = 0; i < buffer->n_metas; i++) {
+		struct spa_meta *m = &buffer->metas[i];
+		const char *type_name;
+
+		type_name = spa_debug_type_find_name(spa_type_meta_type, m->type);
+		spa_debug("%*s" "  meta %d: type %d (%s), data %p, size %d:", indent, "", i, m->type,
+			type_name, m->data, m->size);
+
+		switch (m->type) {
+		case SPA_META_Header:
+		{
+			struct spa_meta_header *h = (struct spa_meta_header*)m->data;
+			spa_debug("%*s" "    struct spa_meta_header:", indent, "");
+			spa_debug("%*s" "      flags:      %08x", indent, "", h->flags);
+			spa_debug("%*s" "      offset:     %u", indent, "", h->offset);
+			spa_debug("%*s" "      seq:        %" PRIu64, indent, "", h->seq);
+			spa_debug("%*s" "      pts:        %" PRIi64, indent, "", h->pts);
+			spa_debug("%*s" "      dts_offset: %" PRIi64, indent, "", h->dts_offset);
+			break;
+		}
+		case SPA_META_VideoCrop:
+		{
+			struct spa_meta_region *h = (struct spa_meta_region*)m->data;
+			spa_debug("%*s" "    struct spa_meta_region:", indent, "");
+			spa_debug("%*s" "      x:      %d", indent, "", h->region.position.x);
+			spa_debug("%*s" "      y:      %d", indent, "", h->region.position.y);
+			spa_debug("%*s" "      width:  %d", indent, "", h->region.size.width);
+			spa_debug("%*s" "      height: %d", indent, "", h->region.size.height);
+			break;
+		}
+		case SPA_META_VideoDamage:
+		{
+			struct spa_meta_region *h;
+			spa_meta_for_each(h, m) {
+				spa_debug("%*s" "    struct spa_meta_region:", indent, "");
+				spa_debug("%*s" "      x:      %d", indent, "", h->region.position.x);
+				spa_debug("%*s" "      y:      %d", indent, "", h->region.position.y);
+				spa_debug("%*s" "      width:  %d", indent, "", h->region.size.width);
+				spa_debug("%*s" "      height: %d", indent, "", h->region.size.height);
+			}
+			break;
+		}
+		case SPA_META_Bitmap:
+			break;
+		case SPA_META_Cursor:
+			break;
+		default:
+			spa_debug("%*s" "    Unknown:", indent, "");
+			spa_debug_mem(5, m->data, m->size);
+		}
+	}
+	spa_debug("%*s" " n_datas: \t%u (at %p)", indent, "", buffer->n_datas, buffer->datas);
+	for (i = 0; i < buffer->n_datas; i++) {
+		struct spa_data *d = &buffer->datas[i];
+		spa_debug("%*s" "   type:    %d (%s)", indent, "", d->type,
+			spa_debug_type_find_name(spa_type_data_type, d->type));
+		spa_debug("%*s" "   flags:   %d", indent, "", d->flags);
+		spa_debug("%*s" "   data:    %p", indent, "", d->data);
+		spa_debug("%*s" "   fd:      %" PRIi64, indent, "", d->fd);
+		spa_debug("%*s" "   offset:  %d", indent, "", d->mapoffset);
+		spa_debug("%*s" "   maxsize: %u", indent, "", d->maxsize);
+		spa_debug("%*s" "   chunk:   %p", indent, "", d->chunk);
+		spa_debug("%*s" "    offset: %d", indent, "", d->chunk->offset);
+		spa_debug("%*s" "    size:   %u", indent, "", d->chunk->size);
+		spa_debug("%*s" "    stride: %d", indent, "", d->chunk->stride);
+	}
+	return 0;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DEBUG_BUFFER_H */
diff --git a/third_party/pipewire/spa/debug/dict.h b/third_party/pipewire/spa/debug/dict.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/debug/dict.h
@@ -0,0 +1,52 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DEBUG_DICT_H
+#define SPA_DEBUG_DICT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/dict.h>
+
+#ifndef spa_debug
+#define spa_debug(...)	({ fprintf(stderr, __VA_ARGS__);fputc('\n', stderr); })
+#endif
+
+static inline int spa_debug_dict(int indent, const struct spa_dict *dict)
+{
+	const struct spa_dict_item *item;
+	spa_debug("%*sflags:%08x n_items:%d", indent, "", dict->flags, dict->n_items);
+	spa_dict_for_each(item, dict) {
+		spa_debug("%*s  %s = \"%s\"", indent, "", item->key, item->value);
+	}
+	return 0;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DEBUG_DICT_H */
diff --git a/third_party/pipewire/spa/debug/format.h b/third_party/pipewire/spa/debug/format.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/debug/format.h
@@ -0,0 +1,201 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DEBUG_FORMAT_H
+#define SPA_DEBUG_FORMAT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/pod/parser.h>
+#include <spa/debug/types.h>
+#include <spa/param/type-info.h>
+#include <spa/param/format-utils.h>
+
+static inline int
+spa_debug_format_value(const struct spa_type_info *info,
+		uint32_t type, void *body, uint32_t size)
+{
+	switch (type) {
+	case SPA_TYPE_Bool:
+		fprintf(stderr, "%s", *(int32_t *) body ? "true" : "false");
+		break;
+	case SPA_TYPE_Id:
+	{
+		const char *str = spa_debug_type_find_short_name(info, *(int32_t *) body);
+		char tmp[64];
+		if (str == NULL) {
+			snprintf(tmp, sizeof(tmp), "%d", *(int32_t*)body);
+			str = tmp;
+		}
+		fprintf(stderr, "%s", str);
+		break;
+	}
+	case SPA_TYPE_Int:
+		fprintf(stderr, "%d", *(int32_t *) body);
+		break;
+	case SPA_TYPE_Long:
+		fprintf(stderr, "%" PRIi64, *(int64_t *) body);
+		break;
+	case SPA_TYPE_Float:
+		fprintf(stderr, "%f", *(float *) body);
+		break;
+	case SPA_TYPE_Double:
+		fprintf(stderr, "%g", *(double *) body);
+		break;
+	case SPA_TYPE_String:
+		fprintf(stderr, "%s", (char *) body);
+		break;
+	case SPA_TYPE_Rectangle:
+	{
+		struct spa_rectangle *r = (struct spa_rectangle *)body;
+		fprintf(stderr, "%" PRIu32 "x%" PRIu32, r->width, r->height);
+		break;
+	}
+	case SPA_TYPE_Fraction:
+	{
+		struct spa_fraction *f = (struct spa_fraction *)body;
+		fprintf(stderr, "%" PRIu32 "/%" PRIu32, f->num, f->denom);
+		break;
+	}
+	case SPA_TYPE_Bitmap:
+		fprintf(stderr, "Bitmap");
+		break;
+	case SPA_TYPE_Bytes:
+		fprintf(stderr, "Bytes");
+		break;
+	case SPA_TYPE_Array:
+	{
+		void *p;
+		struct spa_pod_array_body *b = (struct spa_pod_array_body *)body;
+		int i = 0;
+		fprintf(stderr, "< ");
+		SPA_POD_ARRAY_BODY_FOREACH(b, size, p) {
+			if (i++ > 0)
+				fprintf(stderr, ", ");
+			spa_debug_format_value(info, b->child.type, p, b->child.size);
+		}
+		fprintf(stderr, " >");
+		break;
+	}
+	default:
+		fprintf(stderr, "INVALID type %d", type);
+		break;
+	}
+	return 0;
+}
+
+static inline int spa_debug_format(int indent,
+		const struct spa_type_info *info, const struct spa_pod *format)
+{
+	const char *media_type;
+	const char *media_subtype;
+	struct spa_pod_prop *prop;
+	uint32_t mtype, mstype;
+
+	if (info == NULL)
+		info = spa_type_format;
+
+	if (format == NULL || SPA_POD_TYPE(format) != SPA_TYPE_Object)
+		return -EINVAL;
+
+	if (spa_format_parse(format, &mtype, &mstype) < 0)
+		return -EINVAL;
+
+	media_type = spa_debug_type_find_name(spa_type_media_type, mtype);
+	media_subtype = spa_debug_type_find_name(spa_type_media_subtype, mstype);
+
+	fprintf(stderr, "%*s %s/%s\n", indent, "",
+		media_type ? spa_debug_type_short_name(media_type) : "unknown",
+		media_subtype ? spa_debug_type_short_name(media_subtype) : "unknown");
+
+	SPA_POD_OBJECT_FOREACH((struct spa_pod_object*)format, prop) {
+		const char *key;
+		const struct spa_type_info *ti;
+		uint32_t i, type, size, n_vals, choice;
+		const struct spa_pod *val;
+		void *vals;
+
+		if (prop->key == SPA_FORMAT_mediaType ||
+		    prop->key == SPA_FORMAT_mediaSubtype)
+			continue;
+
+		val = spa_pod_get_values(&prop->value, &n_vals, &choice);
+
+		type = val->type;
+		size = val->size;
+		vals = SPA_POD_BODY(val);
+
+		if (type < SPA_TYPE_None || type >= SPA_TYPE_LAST)
+			continue;
+
+		ti = spa_debug_type_find(info, prop->key);
+		key = ti ? ti->name : NULL;
+
+		fprintf(stderr, "%*s %16s : (%s) ", indent, "",
+			key ? spa_debug_type_short_name(key) : "unknown",
+			spa_debug_type_short_name(spa_types[type].name));
+
+		if (choice == SPA_CHOICE_None) {
+			spa_debug_format_value(ti ? ti->values : NULL, type, vals, size);
+		} else {
+			const char *ssep, *esep, *sep;
+
+			switch (choice) {
+			case SPA_CHOICE_Range:
+			case SPA_CHOICE_Step:
+				ssep = "[ ";
+				sep = ", ";
+				esep = " ]";
+				break;
+			default:
+			case SPA_CHOICE_Enum:
+			case SPA_CHOICE_Flags:
+				ssep = "{ ";
+				sep = ", ";
+				esep = " }";
+				break;
+			}
+
+			fprintf(stderr, "%s", ssep);
+
+			for (i = 1; i < n_vals; i++) {
+				vals = SPA_MEMBER(vals, size, void);
+				if (i > 1)
+					fprintf(stderr, "%s", sep);
+				spa_debug_format_value(ti ? ti->values : NULL, type, vals, size);
+			}
+			fprintf(stderr, "%s", esep);
+		}
+		fprintf(stderr, "\n");
+	}
+	return 0;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DEBUG_FORMAT_H */
diff --git a/third_party/pipewire/spa/debug/mem.h b/third_party/pipewire/spa/debug/mem.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/debug/mem.h
@@ -0,0 +1,60 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DEBUG_MEM_H
+#define SPA_DEBUG_MEM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/dict.h>
+
+#ifndef spa_debug
+#define spa_debug(...)	({ fprintf(stderr, __VA_ARGS__);fputc('\n', stderr); })
+#endif
+
+static inline int spa_debug_mem(int indent, const void *data, size_t size)
+{
+	const uint8_t *t = (const uint8_t*)data;
+	char buffer[512];
+	size_t i;
+	int pos = 0;
+
+	for (i = 0; i < size; i++) {
+		if (i % 16 == 0)
+			pos = sprintf(buffer, "%p: ", &t[i]);
+		pos += sprintf(buffer + pos, "%02x ", t[i]);
+		if (i % 16 == 15 || i == size - 1) {
+			spa_debug("%*s" "%s", indent, "", buffer);
+		}
+	}
+	return 0;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DEBUG_MEM_H */
diff --git a/third_party/pipewire/spa/debug/node.h b/third_party/pipewire/spa/debug/node.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/debug/node.h
@@ -0,0 +1,57 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DEBUG_NODE_H
+#define SPA_DEBUG_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/node/node.h>
+#include <spa/debug/dict.h>
+
+#ifndef spa_debug
+#define spa_debug(...)	({ fprintf(stderr, __VA_ARGS__);fputc('\n', stderr); })
+#endif
+
+static inline int spa_debug_port_info(int indent, const struct spa_port_info *info)
+{
+        spa_debug("%*s" "struct spa_port_info %p:", indent, "", info);
+        spa_debug("%*s" " flags: \t%08" PRIx64, indent, "", info->flags);
+        spa_debug("%*s" " rate: \t%d/%d", indent, "", info->rate.num, info->rate.denom);
+        spa_debug("%*s" " props:", indent, "");
+        if (info->props)
+                spa_debug_dict(indent + 2, info->props);
+        else
+                spa_debug("%*s" "  none", indent, "");
+        return 0;
+}
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DEBUG_NODE_H */
diff --git a/third_party/pipewire/spa/debug/pod.h b/third_party/pipewire/spa/debug/pod.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/debug/pod.h
@@ -0,0 +1,207 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DEBUG_POD_H
+#define SPA_DEBUG_POD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/debug/mem.h>
+#include <spa/debug/types.h>
+#include <spa/pod/pod.h>
+#include <spa/pod/iter.h>
+
+#ifndef spa_debug
+#define spa_debug(...)	({ fprintf(stderr, __VA_ARGS__);fputc('\n', stderr); })
+#endif
+
+static inline int
+spa_debug_pod_value(int indent, const struct spa_type_info *info,
+		uint32_t type, void *body, uint32_t size)
+{
+	switch (type) {
+	case SPA_TYPE_Bool:
+		spa_debug("%*s" "Bool %s", indent, "", (*(int32_t *) body) ? "true" : "false");
+		break;
+	case SPA_TYPE_Id:
+		spa_debug("%*s" "Id %-8d (%s)", indent, "", *(int32_t *) body,
+		       spa_debug_type_find_name(info, *(int32_t *) body));
+		break;
+	case SPA_TYPE_Int:
+		spa_debug("%*s" "Int %d", indent, "", *(int32_t *) body);
+		break;
+	case SPA_TYPE_Long:
+		spa_debug("%*s" "Long %" PRIi64 "", indent, "", *(int64_t *) body);
+		break;
+	case SPA_TYPE_Float:
+		spa_debug("%*s" "Float %f", indent, "", *(float *) body);
+		break;
+	case SPA_TYPE_Double:
+		spa_debug("%*s" "Double %f", indent, "", *(double *) body);
+		break;
+	case SPA_TYPE_String:
+		spa_debug("%*s" "String \"%s\"", indent, "", (char *) body);
+		break;
+	case SPA_TYPE_Fd:
+		spa_debug("%*s" "Fd %d", indent, "", *(int *) body);
+		break;
+	case SPA_TYPE_Pointer:
+	{
+		struct spa_pod_pointer_body *b = (struct spa_pod_pointer_body *)body;
+		spa_debug("%*s" "Pointer %s %p", indent, "",
+		       spa_debug_type_find_name(SPA_TYPE_ROOT, b->type), b->value);
+		break;
+	}
+	case SPA_TYPE_Rectangle:
+	{
+		struct spa_rectangle *r = (struct spa_rectangle *)body;
+		spa_debug("%*s" "Rectangle %dx%d", indent, "", r->width, r->height);
+		break;
+	}
+	case SPA_TYPE_Fraction:
+	{
+		struct spa_fraction *f = (struct spa_fraction *)body;
+		spa_debug("%*s" "Fraction %d/%d", indent, "", f->num, f->denom);
+		break;
+	}
+	case SPA_TYPE_Bitmap:
+		spa_debug("%*s" "Bitmap", indent, "");
+		break;
+	case SPA_TYPE_Array:
+	{
+		struct spa_pod_array_body *b = (struct spa_pod_array_body *)body;
+		void *p;
+		const struct spa_type_info *ti = spa_debug_type_find(SPA_TYPE_ROOT, b->child.type);
+
+		spa_debug("%*s" "Array: child.size %d, child.type %s", indent, "",
+		       b->child.size, ti ? ti->name : "unknown");
+
+		SPA_POD_ARRAY_BODY_FOREACH(b, size, p)
+			spa_debug_pod_value(indent + 2, info, b->child.type, p, b->child.size);
+		break;
+	}
+	case SPA_TYPE_Choice:
+	{
+		struct spa_pod_choice_body *b = (struct spa_pod_choice_body *)body;
+		void *p;
+		const struct spa_type_info *ti = spa_debug_type_find(spa_type_choice, b->type);
+
+		spa_debug("%*s" "Choice: type %s, flags %08x %d %d", indent, "",
+		       ti ? ti->name : "unknown", b->flags, size, b->child.size);
+
+		SPA_POD_CHOICE_BODY_FOREACH(b, size, p)
+			spa_debug_pod_value(indent + 2, info, b->child.type, p, b->child.size);
+		break;
+	}
+	case SPA_TYPE_Struct:
+	{
+		struct spa_pod *b = (struct spa_pod *)body, *p;
+		spa_debug("%*s" "Struct: size %d", indent, "", size);
+		SPA_POD_FOREACH(b, size, p)
+			spa_debug_pod_value(indent + 2, info, p->type, SPA_POD_BODY(p), p->size);
+		break;
+	}
+	case SPA_TYPE_Object:
+	{
+		struct spa_pod_object_body *b = (struct spa_pod_object_body *)body;
+		struct spa_pod_prop *p;
+		const struct spa_type_info *ti, *ii;
+
+		ti = spa_debug_type_find(info, b->type);
+		ii = ti ? spa_debug_type_find(ti->values, 0) : NULL;
+		ii = ii ? spa_debug_type_find(ii->values, b->id) : NULL;
+
+		spa_debug("%*s" "Object: size %d, type %s (%d), id %s (%d)", indent, "", size,
+		       ti ? ti->name : "unknown", b->type, ii ? ii->name : "unknown", b->id);
+
+		info = ti ? ti->values : info;
+
+		SPA_POD_OBJECT_BODY_FOREACH(b, size, p) {
+			ii = spa_debug_type_find(info, p->key);
+
+			spa_debug("%*s" "Prop: key %s (%d), flags %08x", indent+2, "",
+					ii ? ii->name : "unknown", p->key, p->flags);
+
+			spa_debug_pod_value(indent + 4, ii ? ii->values : NULL,
+					p->value.type,
+					SPA_POD_CONTENTS(struct spa_pod_prop, p),
+					p->value.size);
+		}
+		break;
+	}
+	case SPA_TYPE_Sequence:
+	{
+		struct spa_pod_sequence_body *b = (struct spa_pod_sequence_body *)body;
+		const struct spa_type_info *ti, *ii;
+		struct spa_pod_control *c;
+
+		ti = spa_debug_type_find(info, b->unit);
+
+		spa_debug("%*s" "Sequence: size %d, unit %s", indent, "", size,
+		       ti ? ti->name : "unknown");
+
+		SPA_POD_SEQUENCE_BODY_FOREACH(b, size, c) {
+			ii = spa_debug_type_find(spa_type_control, c->type);
+
+			spa_debug("%*s" "Control: offset %d, type %s", indent+2, "",
+					c->offset, ii ? ii->name : "unknown");
+
+			spa_debug_pod_value(indent + 4, ii ? ii->values : NULL,
+					c->value.type,
+					SPA_POD_CONTENTS(struct spa_pod_control, c),
+					c->value.size);
+		}
+		break;
+	}
+	case SPA_TYPE_Bytes:
+		spa_debug("%*s" "Bytes", indent, "");
+		spa_debug_mem(indent + 2, body, size);
+		break;
+	case SPA_TYPE_None:
+		spa_debug("%*s" "None", indent, "");
+		spa_debug_mem(indent + 2, body, size);
+		break;
+	default:
+		spa_debug("%*s" "unhandled POD type %d", indent, "", type);
+		break;
+	}
+	return 0;
+}
+
+static inline int spa_debug_pod(int indent,
+		const struct spa_type_info *info, const struct spa_pod *pod)
+{
+	return spa_debug_pod_value(indent, info ? info : SPA_TYPE_ROOT,
+			SPA_POD_TYPE(pod),
+			SPA_POD_BODY(pod),
+			SPA_POD_BODY_SIZE(pod));
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DEBUG_POD_H */
diff --git a/third_party/pipewire/spa/debug/types.h b/third_party/pipewire/spa/debug/types.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/debug/types.h
@@ -0,0 +1,98 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DEBUG_TYPES_H
+#define SPA_DEBUG_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/type-info.h>
+
+#include <string.h>
+
+static inline const struct spa_type_info *spa_debug_type_find(const struct spa_type_info *info, uint32_t type)
+{
+	const struct spa_type_info *res;
+
+	if (info == NULL)
+		info = SPA_TYPE_ROOT;
+
+	while (info && info->name) {
+		if (info->type == SPA_ID_INVALID) {
+			if (info->values && (res = spa_debug_type_find(info->values, type)))
+				return res;
+		}
+		else if (info->type == type)
+			return info;
+		info++;
+	}
+	return NULL;
+}
+
+static inline const char *spa_debug_type_short_name(const char *name)
+{
+	const char *h;
+	if ((h = strrchr(name, ':')) != NULL)
+		name = h + 1;
+	return name;
+}
+
+static inline const char *spa_debug_type_find_name(const struct spa_type_info *info, uint32_t type)
+{
+	if ((info = spa_debug_type_find(info, type)) == NULL)
+		return NULL;
+	return info->name;
+}
+
+static inline const char *spa_debug_type_find_short_name(const struct spa_type_info *info, uint32_t type)
+{
+	const char *str;
+	if ((str = spa_debug_type_find_name(info, type)) == NULL)
+		return NULL;
+	return spa_debug_type_short_name(str);
+}
+
+static inline uint32_t spa_debug_type_find_type(const struct spa_type_info *info, const char *name)
+{
+	if (info == NULL)
+		info = SPA_TYPE_ROOT;
+
+	while (info && info->name) {
+		uint32_t res;
+		if (strcmp(info->name, name) == 0)
+			return info->type;
+		if (info->values && (res = spa_debug_type_find_type(info->values, name)) != SPA_ID_INVALID)
+			return res;
+		info++;
+	}
+	return SPA_ID_INVALID;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DEBUG_NODE_H */
diff --git a/third_party/pipewire/spa/graph/graph.h b/third_party/pipewire/spa/graph/graph.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/graph/graph.h
@@ -0,0 +1,352 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_GRAPH_H
+#define SPA_GRAPH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/list.h>
+#include <spa/utils/hook.h>
+#include <spa/node/node.h>
+#include <spa/node/io.h>
+
+#ifndef spa_debug
+#define spa_debug(...)
+#endif
+
+struct spa_graph;
+struct spa_graph_node;
+struct spa_graph_link;
+struct spa_graph_port;
+
+struct spa_graph_state {
+	int status;			/**< current status */
+	int32_t required;		/**< required number of signals */
+	int32_t pending;		/**< number of pending signals */
+};
+
+static inline void spa_graph_state_reset(struct spa_graph_state *state)
+{
+	state->pending = state->required;
+}
+
+struct spa_graph_link {
+	struct spa_list link;
+	struct spa_graph_state *state;
+	int (*signal) (void *data);
+	void *signal_data;
+};
+
+#define spa_graph_link_signal(l)	((l)->signal((l)->signal_data))
+
+#define spa_graph_state_dec(s,c) (__atomic_sub_fetch(&(s)->pending, c, __ATOMIC_SEQ_CST) == 0)
+
+static inline int spa_graph_link_trigger(struct spa_graph_link *link)
+{
+	struct spa_graph_state *state = link->state;
+
+	spa_debug("link %p: state %p: pending %d/%d", link, state,
+                        state->pending, state->required);
+
+	if (spa_graph_state_dec(state, 1))
+		spa_graph_link_signal(link);
+
+        return state->status;
+}
+struct spa_graph {
+	uint32_t flags;			/* flags */
+	struct spa_graph_node *parent;	/* parent node or NULL when driver */
+	struct spa_graph_state *state;	/* state of graph */
+	struct spa_list nodes;		/* list of nodes of this graph */
+};
+
+struct spa_graph_node_callbacks {
+#define SPA_VERSION_GRAPH_NODE_CALLBACKS	0
+	uint32_t version;
+
+	int (*process) (void *data, struct spa_graph_node *node);
+	int (*reuse_buffer) (void *data, struct spa_graph_node *node,
+			uint32_t port_id, uint32_t buffer_id);
+};
+
+struct spa_graph_node {
+	struct spa_list link;		/**< link in graph nodes list */
+	struct spa_graph *graph;	/**< owner graph */
+	struct spa_list ports[2];	/**< list of input and output ports */
+	struct spa_list links;		/**< list of links to next nodes */
+	uint32_t flags;			/**< node flags */
+	struct spa_graph_state *state;	/**< state of the node */
+	struct spa_graph_link graph_link;	/**< link in graph */
+	struct spa_graph *subgraph;	/**< subgraph or NULL */
+	struct spa_callbacks callbacks;
+	struct spa_list sched_link;	/**< link for scheduler */
+};
+
+#define spa_graph_node_call(n,method,version,...)			\
+({									\
+	int __res = 0;							\
+	spa_callbacks_call_res(&(n)->callbacks,				\
+			struct spa_graph_node_callbacks, __res,		\
+			method, version, ##__VA_ARGS__);		\
+	__res;								\
+})
+
+#define spa_graph_node_process(n)		spa_graph_node_call(n, process, 0, n)
+#define spa_graph_node_reuse_buffer(n,p,i)	spa_graph_node_call(n, reuse_buffer, 0, n, p, i)
+
+struct spa_graph_port {
+	struct spa_list link;		/**< link in node port list */
+	struct spa_graph_node *node;	/**< owner node */
+	enum spa_direction direction;	/**< port direction */
+	uint32_t port_id;		/**< port id */
+	uint32_t flags;			/**< port flags */
+	struct spa_graph_port *peer;	/**< peer */
+};
+
+static inline int spa_graph_node_trigger(struct spa_graph_node *node)
+{
+	struct spa_graph_link *l;
+	spa_debug("node %p trigger", node);
+	spa_list_for_each(l, &node->links, link)
+		spa_graph_link_trigger(l);
+	return 0;
+}
+
+static inline int spa_graph_run(struct spa_graph *graph)
+{
+	struct spa_graph_node *n, *t;
+	struct spa_list pending;
+
+	spa_graph_state_reset(graph->state);
+	spa_debug("graph %p run with state %p pending %d/%d", graph, graph->state,
+			graph->state->pending, graph->state->required);
+
+	spa_list_init(&pending);
+
+	spa_list_for_each(n, &graph->nodes, link) {
+		struct spa_graph_state *s = n->state;
+		spa_graph_state_reset(s);
+		spa_debug("graph %p node %p: state %p pending %d/%d status %d", graph, n,
+				s, s->pending, s->required, s->status);
+		if (--s->pending == 0)
+			spa_list_append(&pending, &n->sched_link);
+	}
+	spa_list_for_each_safe(n, t, &pending, sched_link)
+		spa_graph_node_process(n);
+
+	return 0;
+}
+
+static inline int spa_graph_finish(struct spa_graph *graph)
+{
+	spa_debug("graph %p finish", graph);
+	if (graph->parent)
+		return spa_graph_node_trigger(graph->parent);
+	return 0;
+}
+static inline int spa_graph_link_signal_node(void *data)
+{
+	struct spa_graph_node *node = (struct spa_graph_node *)data;
+	spa_debug("node %p call process", node);
+	return spa_graph_node_process(node);
+}
+
+static inline int spa_graph_link_signal_graph(void *data)
+{
+	struct spa_graph_node *node = (struct spa_graph_node *)data;
+	return spa_graph_finish(node->graph);
+}
+
+static inline void spa_graph_init(struct spa_graph *graph, struct spa_graph_state *state)
+{
+	spa_list_init(&graph->nodes);
+	graph->flags = 0;
+	graph->state = state;
+	spa_debug("graph %p init state %p", graph, state);
+}
+
+static inline void
+spa_graph_link_add(struct spa_graph_node *out,
+		   struct spa_graph_state *state,
+		   struct spa_graph_link *link)
+{
+	link->state = state;
+	state->required++;
+	spa_debug("node %p add link %p to state %p %d", out, link, state, state->required);
+	spa_list_append(&out->links, &link->link);
+}
+
+static inline void spa_graph_link_remove(struct spa_graph_link *link)
+{
+	link->state->required--;
+	spa_debug("link %p state %p remove %d", link, link->state, link->state->required);
+	spa_list_remove(&link->link);
+}
+
+static inline void
+spa_graph_node_init(struct spa_graph_node *node, struct spa_graph_state *state)
+{
+	spa_list_init(&node->ports[SPA_DIRECTION_INPUT]);
+	spa_list_init(&node->ports[SPA_DIRECTION_OUTPUT]);
+	spa_list_init(&node->links);
+	node->flags = 0;
+	node->subgraph = NULL;
+	node->state = state;
+	node->state->required = node->state->pending = 0;
+	node->state->status = SPA_STATUS_OK;
+	node->graph_link.signal = spa_graph_link_signal_graph;
+	node->graph_link.signal_data = node;
+	spa_debug("node %p init state %p", node, state);
+}
+
+
+static inline int spa_graph_node_impl_sub_process(void *data, struct spa_graph_node *node)
+{
+	struct spa_graph *graph = node->subgraph;
+	spa_debug("node %p: sub process %p", node, graph);
+	return spa_graph_run(graph);
+}
+
+static const struct spa_graph_node_callbacks spa_graph_node_sub_impl_default = {
+	SPA_VERSION_GRAPH_NODE_CALLBACKS,
+	.process = spa_graph_node_impl_sub_process,
+};
+
+static inline void spa_graph_node_set_subgraph(struct spa_graph_node *node,
+		struct spa_graph *subgraph)
+{
+	node->subgraph = subgraph;
+	subgraph->parent = node;
+	spa_debug("node %p set subgraph %p", node, subgraph);
+}
+
+static inline void
+spa_graph_node_set_callbacks(struct spa_graph_node *node,
+		const struct spa_graph_node_callbacks *callbacks,
+		void *data)
+{
+	node->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
+}
+
+static inline void
+spa_graph_node_add(struct spa_graph *graph,
+		   struct spa_graph_node *node)
+{
+	node->graph = graph;
+	spa_list_append(&graph->nodes, &node->link);
+	node->state->required++;
+	spa_debug("node %p add to graph %p, state %p required %d",
+			node, graph, node->state, node->state->required);
+	spa_graph_link_add(node, graph->state, &node->graph_link);
+}
+
+static inline void spa_graph_node_remove(struct spa_graph_node *node)
+{
+	spa_debug("node %p remove from graph %p, state %p required %d",
+			node, node->graph, node->state, node->state->required);
+	spa_graph_link_remove(&node->graph_link);
+	node->state->required--;
+	spa_list_remove(&node->link);
+}
+
+
+static inline void
+spa_graph_port_init(struct spa_graph_port *port,
+		    enum spa_direction direction,
+		    uint32_t port_id,
+		    uint32_t flags)
+{
+	spa_debug("port %p init type %d id %d", port, direction, port_id);
+	port->direction = direction;
+	port->port_id = port_id;
+	port->flags = flags;
+}
+
+static inline void
+spa_graph_port_add(struct spa_graph_node *node,
+		   struct spa_graph_port *port)
+{
+	spa_debug("port %p add to node %p", port, node);
+	port->node = node;
+	spa_list_append(&node->ports[port->direction], &port->link);
+}
+
+static inline void spa_graph_port_remove(struct spa_graph_port *port)
+{
+	spa_debug("port %p remove", port);
+	spa_list_remove(&port->link);
+}
+
+static inline void
+spa_graph_port_link(struct spa_graph_port *out, struct spa_graph_port *in)
+{
+	spa_debug("port %p link to %p %p %p", out, in, in->node, in->node->state);
+	out->peer = in;
+	in->peer = out;
+}
+
+static inline void
+spa_graph_port_unlink(struct spa_graph_port *port)
+{
+	spa_debug("port %p unlink from %p", port, port->peer);
+	if (port->peer) {
+		port->peer->peer = NULL;
+		port->peer = NULL;
+	}
+}
+
+static inline int spa_graph_node_impl_process(void *data, struct spa_graph_node *node)
+{
+	struct spa_node *n = (struct spa_node *)data;
+	struct spa_graph_state *state = node->state;
+
+	spa_debug("node %p: process state %p: %d, node %p", node, state, state->status, n);
+	if ((state->status = spa_node_process(n)) != SPA_STATUS_OK)
+		spa_graph_node_trigger(node);
+
+        return state->status;
+}
+
+static inline int spa_graph_node_impl_reuse_buffer(void *data, struct spa_graph_node *node,
+		uint32_t port_id, uint32_t buffer_id)
+{
+	struct spa_node *n = (struct spa_node *)data;
+	return spa_node_port_reuse_buffer(n, port_id, buffer_id);
+}
+
+static const struct spa_graph_node_callbacks spa_graph_node_impl_default = {
+	SPA_VERSION_GRAPH_NODE_CALLBACKS,
+	.process = spa_graph_node_impl_process,
+	.reuse_buffer = spa_graph_node_impl_reuse_buffer,
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_GRAPH_H */
diff --git a/third_party/pipewire/spa/monitor/device.h b/third_party/pipewire/spa/monitor/device.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/monitor/device.h
@@ -0,0 +1,297 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DEVICE_H
+#define SPA_DEVICE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/dict.h>
+#include <spa/pod/event.h>
+
+/**
+ * spa_device:
+ *
+ * The device interface can be used to monitor all kinds of devices
+ * and create objects as a result. Objects a typically other
+ * Devices or Nodes.
+ *
+ */
+#define SPA_TYPE_INTERFACE_Device	SPA_TYPE_INFO_INTERFACE_BASE "Device"
+
+#define SPA_VERSION_DEVICE		0
+struct spa_device { struct spa_interface iface; };
+
+/**
+ * Information about the device and parameters it supports
+ *
+ * This information is part of the info event on a device.
+ */
+struct spa_device_info {
+#define SPA_VERSION_DEVICE_INFO 0
+	uint32_t version;
+
+#define SPA_DEVICE_CHANGE_MASK_FLAGS		(1u<<0)
+#define SPA_DEVICE_CHANGE_MASK_PROPS		(1u<<1)
+#define SPA_DEVICE_CHANGE_MASK_PARAMS		(1u<<2)
+	uint64_t change_mask;
+	uint64_t flags;
+	const struct spa_dict *props;		/**< device properties */
+	struct spa_param_info *params;		/**< supported parameters */
+	uint32_t n_params;			/**< number of elements in params */
+};
+
+#define SPA_DEVICE_INFO_INIT()	(struct spa_device_info){ SPA_VERSION_DEVICE_INFO, }
+
+/**
+ * Information about a device object
+ *
+ * This information is part of the object_info event on the device.
+ */
+struct spa_device_object_info {
+#define SPA_VERSION_DEVICE_OBJECT_INFO 0
+	uint32_t version;
+
+	const char *type;			/**< the object type managed by this device */
+	const char *factory_name;		/**< a factory name that implements the object */
+
+#define SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS	(1u<<0)
+#define SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS	(1u<<1)
+	uint64_t change_mask;
+	uint64_t flags;
+	const struct spa_dict *props;		/**< extra object properties */
+};
+
+#define SPA_DEVICE_OBJECT_INFO_INIT()	(struct spa_device_object_info){ SPA_VERSION_DEVICE_OBJECT_INFO, }
+
+/** the result of spa_device_enum_params() */
+#define SPA_RESULT_TYPE_DEVICE_PARAMS	1
+struct spa_result_device_params {
+	uint32_t id;
+	uint32_t index;
+	uint32_t next;
+	struct spa_pod *param;
+};
+
+#define SPA_DEVICE_EVENT_INFO		0
+#define SPA_DEVICE_EVENT_RESULT		1
+#define SPA_DEVICE_EVENT_EVENT		2
+#define SPA_DEVICE_EVENT_OBJECT_INFO	3
+#define SPA_DEVICE_EVENT_NUM		4
+
+/**
+ * spa_device_events:
+ *
+ * Events are always emitted from the main thread
+ */
+struct spa_device_events {
+	/** version of the structure */
+#define SPA_VERSION_DEVICE_EVENTS	0
+	uint32_t version;
+
+	/** notify extra information about the device */
+	void (*info) (void *data, const struct spa_device_info *info);
+
+	/** notify a result */
+	void (*result) (void *data, int seq, int res, uint32_t type, const void *result);
+
+	/** a device event */
+	void (*event) (void *data, const struct spa_event *event);
+
+	/** info changed for an object managed by the device, info is NULL when
+	 * the object is removed */
+	void (*object_info) (void *data, uint32_t id,
+		const struct spa_device_object_info *info);
+};
+
+#define SPA_DEVICE_METHOD_ADD_LISTENER	0
+#define SPA_DEVICE_METHOD_SYNC		1
+#define SPA_DEVICE_METHOD_ENUM_PARAMS	2
+#define SPA_DEVICE_METHOD_SET_PARAM	3
+#define SPA_DEVICE_METHOD_NUM		4
+
+/**
+ * spa_device_methods:
+ */
+struct spa_device_methods {
+	/* the version of the methods. This can be used to expand this
+	 * structure in the future */
+#define SPA_VERSION_DEVICE_METHODS	0
+	uint32_t version;
+
+	/**
+	 * Set events to receive asynchronous notifications from
+	 * the device.
+	 *
+	 * Setting the events will trigger the info event and an
+	 * object_info event for each managed object on the new
+	 * listener.
+	 *
+	 * \param device a #spa_device
+	 * \param listener a listener
+	 * \param events a #struct spa_device_events
+	 * \param data data passed as first argument in functions of \a events
+	 * \return 0 on success
+	 *	   < 0 errno on error
+	 */
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct spa_device_events *events,
+			void *data);
+	/**
+	 * Perform a sync operation.
+	 *
+	 * This method will emit the result event with the given sequence
+	 * number synchronously or with the returned async return value
+	 * asynchronously.
+	 *
+	 * Because all methods are serialized in the device, this can be used
+	 * to wait for completion of all previous method calls.
+	 *
+	 * \param seq a sequence number
+	 * \return 0 on success
+	 *         -EINVAL when node is NULL
+	 *         an async result
+	 */
+        int (*sync) (void *object, int seq);
+
+	/**
+	 * Enumerate the parameters of a device.
+	 *
+	 * Parameters are identified with an \a id. Some parameters can have
+	 * multiple values, see the documentation of the parameter id.
+	 *
+	 * Parameters can be filtered by passing a non-NULL \a filter.
+	 *
+	 * The result callback will be called at most \max times with a
+	 * struct spa_result_device_params as the result.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param device a \ref spa_device
+	 * \param seq a sequence number to pass to the result function
+	 * \param id the param id to enumerate
+	 * \param index the index of enumeration, pass 0 for the first item.
+	 * \param max the maximum number of items to iterate
+	 * \param filter and optional filter to use
+	 * \return 0 when there are no more parameters to enumerate
+	 *         -EINVAL when invalid arguments are given
+	 *         -ENOENT the parameter \a id is unknown
+	 *         -ENOTSUP when there are no parameters
+	 *                 implemented on \a device
+	 */
+	int (*enum_params) (void *object, int seq,
+			    uint32_t id, uint32_t index, uint32_t max,
+			    const struct spa_pod *filter);
+
+	/**
+	 * Set the configurable parameter in \a device.
+	 *
+	 * Usually, \a param will be obtained from enum_params and then
+	 * modified but it is also possible to set another spa_pod
+	 * as long as its keys and types match a supported object.
+	 *
+	 * Objects with property keys that are not known are ignored.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param device a \ref spa_device
+	 * \param id the parameter id to configure
+	 * \param flags additional flags
+	 * \param param the parameter to configure
+	 *
+	 * \return 0 on success
+	 *         -EINVAL when invalid arguments are given
+	 *         -ENOTSUP when there are no parameters implemented on \a device
+	 *         -ENOENT the parameter is unknown
+	 */
+	int (*set_param) (void *object,
+			  uint32_t id, uint32_t flags,
+			  const struct spa_pod *param);
+};
+
+#define spa_device_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	struct spa_device *_o = o;					\
+	spa_interface_call_res(&_o->iface,				\
+			struct spa_device_methods, _res,		\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define spa_device_add_listener(d,...)	spa_device_method(d, add_listener, 0, __VA_ARGS__)
+#define spa_device_sync(d,...)		spa_device_method(d, sync, 0, __VA_ARGS__)
+#define spa_device_enum_params(d,...)	spa_device_method(d, enum_params, 0, __VA_ARGS__)
+#define spa_device_set_param(d,...)	spa_device_method(d, set_param, 0, __VA_ARGS__)
+
+#define SPA_KEY_DEVICE_ENUM_API		"device.enum.api"	/**< the api used to discover this
+								  *  device */
+#define SPA_KEY_DEVICE_API		"device.api"		/**< the api used by the device
+								  *  Ex. "udev", "alsa", "v4l2". */
+#define SPA_KEY_DEVICE_NAME		"device.name"		/**< the name of the device */
+#define SPA_KEY_DEVICE_ALIAS		"device.alias"		/**< alternative name of the device */
+#define SPA_KEY_DEVICE_NICK		"device.nick"		/**< the device short name */
+#define SPA_KEY_DEVICE_DESCRIPTION	"device.description"	/**< a device description */
+#define SPA_KEY_DEVICE_ICON		"device.icon"		/**< icon for the device. A base64 blob
+								  *  containing PNG image data */
+#define SPA_KEY_DEVICE_ICON_NAME	"device.icon-name"	/**< an XDG icon name for the device.
+								  *  Ex. "sound-card-speakers-usb" */
+#define SPA_KEY_DEVICE_PLUGGED_USEC	"device.plugged.usec"	/**< when the device was plugged */
+
+#define SPA_KEY_DEVICE_BUS_ID		"device.bus-id"		/**< the device bus-id */
+#define SPA_KEY_DEVICE_BUS_PATH		"device.bus-path"	/**< bus path to the device in the OS'
+								  *  format.
+								  *  Ex. "pci-0000:00:14.0-usb-0:3.2:1.0" */
+#define SPA_KEY_DEVICE_BUS		"device.bus"		/**< bus of the device if applicable. One of
+								   *  "isa", "pci", "usb", "firewire",
+								   *  "bluetooth" */
+#define SPA_KEY_DEVICE_SUBSYSTEM	"device.subsystem"	/**< device subsystem */
+#define SPA_KEY_DEVICE_SYSFS_PATH	"device.sysfs.path"	/**< device sysfs path */
+
+#define SPA_KEY_DEVICE_VENDOR_ID	"device.vendor.id"	/**< vendor ID if applicable */
+#define SPA_KEY_DEVICE_VENDOR_NAME	"device.vendor.name"	/**< vendor name if applicable */
+#define SPA_KEY_DEVICE_PRODUCT_ID	"device.product.id"	/**< product ID if applicable */
+#define SPA_KEY_DEVICE_PRODUCT_NAME	"device.product.name"	/**< product name if applicable */
+#define SPA_KEY_DEVICE_SERIAL		"device.serial"		/**< Serial number if applicable */
+#define SPA_KEY_DEVICE_CLASS		"device.class"		/**< device class */
+#define SPA_KEY_DEVICE_CAPABILITIES	"device.capabilities"	/**< api specific device capabilities */
+#define SPA_KEY_DEVICE_FORM_FACTOR	"device.form-factor"	/**< form factor if applicable. One of
+								  *  "internal", "speaker", "handset", "tv",
+								  *  "webcam", "microphone", "headset",
+								  *  "headphone", "hands-free", "car", "hifi",
+								  *  "computer", "portable" */
+#define SPA_KEY_DEVICE_PROFILE		"device.profile	"	/**< profile for the device */
+#define SPA_KEY_DEVICE_PROFILE_SET	"device.profile-set"	/**< profile set for the device */
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DEVICE_H */
diff --git a/third_party/pipewire/spa/monitor/event.h b/third_party/pipewire/spa/monitor/event.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/monitor/event.h
@@ -0,0 +1,54 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_EVENT_DEVICE_H
+#define SPA_EVENT_DEVICE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/pod/event.h>
+
+/* object id of SPA_TYPE_EVENT_Device */
+enum spa_device_event {
+	SPA_DEVICE_EVENT_ObjectConfig,
+};
+
+#define SPA_DEVICE_EVENT_ID(ev)	SPA_EVENT_ID(ev, SPA_TYPE_EVENT_Device)
+#define SPA_DEVICE_EVENT_INIT(id) SPA_EVENT_INIT(SPA_TYPE_EVENT_Device, id)
+
+/* properties for SPA_TYPE_EVENT_Device */
+enum spa_event_device {
+	SPA_EVENT_DEVICE_START,
+
+	SPA_EVENT_DEVICE_Object,	/* an object id (Int) */
+	SPA_EVENT_DEVICE_Props,		/* properties for an object (SPA_TYPE_OBJECT_Props) */
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_EVENT_DEVICE */
diff --git a/third_party/pipewire/spa/monitor/utils.h b/third_party/pipewire/spa/monitor/utils.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/monitor/utils.h
@@ -0,0 +1,95 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DEVICE_UTILS_H
+#define SPA_DEVICE_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/pod/builder.h>
+#include <spa/monitor/device.h>
+
+struct spa_result_device_params_data {
+	struct spa_pod_builder *builder;
+	struct spa_result_device_params data;
+};
+
+static inline void spa_result_func_device_params(void *data, int seq, int res,
+		uint32_t type, const void *result)
+{
+	struct spa_result_device_params_data *d =
+		(struct spa_result_device_params_data *)data;
+	const struct spa_result_device_params *r =
+		(const struct spa_result_device_params *)result;
+	uint32_t offset = d->builder->state.offset;
+	spa_pod_builder_raw_padded(d->builder, r->param, SPA_POD_SIZE(r->param));
+	d->data.next = r->next;
+	d->data.param = SPA_MEMBER(d->builder->data, offset, struct spa_pod);
+}
+
+static inline int spa_device_enum_params_sync(struct spa_device *device,
+			uint32_t id, uint32_t *index,
+			const struct spa_pod *filter,
+			struct spa_pod **param,
+			struct spa_pod_builder *builder)
+{
+	struct spa_result_device_params_data data = { builder, };
+	struct spa_hook listener = { 0 };
+	static const struct spa_device_events device_events = {
+		SPA_VERSION_DEVICE_EVENTS,
+		.result = spa_result_func_device_params,
+	};
+	int res;
+
+	spa_device_add_listener(device, &listener, &device_events, &data);
+	res = spa_device_enum_params(device, 0, id, *index, 1, filter);
+	spa_hook_remove(&listener);
+
+	if (data.data.param == NULL) {
+		if (res > 0)
+			res = 0;
+	} else {
+		*index = data.data.next;
+		*param = data.data.param;
+		res = 1;
+	}
+	return res;
+}
+
+#define spa_device_emit(hooks,method,version,...)				\
+		spa_hook_list_call_simple(hooks, struct spa_device_events,	\
+				method, version, ##__VA_ARGS__)
+
+#define spa_device_emit_info(hooks,i)		spa_device_emit(hooks,info, 0, i)
+#define spa_device_emit_result(hooks,s,r,t,res)	spa_device_emit(hooks,result, 0, s, r, t, res)
+#define spa_device_emit_event(hooks,e)		spa_device_emit(hooks,event, 0, e)
+#define spa_device_emit_object_info(hooks,id,i)	spa_device_emit(hooks,object_info, 0, id, i)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DEVICE_UTILS_H */
diff --git a/third_party/pipewire/spa/node/command.h b/third_party/pipewire/spa/node/command.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/node/command.h
@@ -0,0 +1,54 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_COMMAND_NODE_H
+#define SPA_COMMAND_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/pod/command.h>
+
+/* object id of SPA_TYPE_COMMAND_Node */
+enum spa_node_command {
+	SPA_NODE_COMMAND_Suspend,
+	SPA_NODE_COMMAND_Pause,
+	SPA_NODE_COMMAND_Start,
+	SPA_NODE_COMMAND_Enable,
+	SPA_NODE_COMMAND_Disable,
+	SPA_NODE_COMMAND_Flush,
+	SPA_NODE_COMMAND_Drain,
+	SPA_NODE_COMMAND_Marker,
+};
+
+#define SPA_NODE_COMMAND_ID(cmd)	SPA_COMMAND_ID(cmd, SPA_TYPE_COMMAND_Node)
+#define SPA_NODE_COMMAND_INIT(id)	SPA_COMMAND_INIT(SPA_TYPE_COMMAND_Node, id)
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_COMMAND_NODE_H */
diff --git a/third_party/pipewire/spa/node/event.h b/third_party/pipewire/spa/node/event.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/node/event.h
@@ -0,0 +1,48 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_EVENT_NODE_H
+#define SPA_EVENT_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/pod/event.h>
+
+/* object id of SPA_TYPE_EVENT_Node */
+enum spa_node_event {
+	SPA_NODE_EVENT_Error,
+	SPA_NODE_EVENT_Buffering,
+	SPA_NODE_EVENT_RequestRefresh,
+};
+
+#define SPA_NODE_EVENT_ID(ev)	SPA_EVENT_ID(ev, SPA_TYPE_EVENT_Node)
+#define SPA_NODE_EVENT_INIT(id) SPA_EVENT_INIT(SPA_TYPE_EVENT_Node, id)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_EVENT_NODE_H */
diff --git a/third_party/pipewire/spa/node/io.h b/third_party/pipewire/spa/node/io.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/node/io.h
@@ -0,0 +1,294 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_IO_H
+#define SPA_IO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/pod/pod.h>
+
+/** IO areas
+ *
+ * IO information for a port on a node. This is allocated
+ * by the host and configured on a node or all ports for which
+ * IO is requested.
+ *
+ * The plugin will communicate with the host through the IO
+ * areas.
+ */
+
+/** Different IO area types */
+enum spa_io_type {
+	SPA_IO_Invalid,
+	SPA_IO_Buffers,		/**< area to exchange buffers, struct spa_io_buffers */
+	SPA_IO_Range,		/**< expected byte range, struct spa_io_range */
+	SPA_IO_Clock,		/**< area to update clock information, struct spa_io_clock */
+	SPA_IO_Latency,		/**< latency reporting, struct spa_io_latency */
+	SPA_IO_Control,		/**< area for control messages, struct spa_io_sequence */
+	SPA_IO_Notify,		/**< area for notify messages, struct spa_io_sequence */
+	SPA_IO_Position,	/**< position information in the graph, struct spa_io_position */
+	SPA_IO_RateMatch,	/**< rate matching between nodes, struct spa_io_rate_match */
+	SPA_IO_Memory,		/**< memory pointer, struct spa_io_memory */
+};
+
+/**
+ * IO area to exchange buffers.
+ *
+ * A set of buffers should first be configured on the node/port.
+ * Further references to those buffers will be made by using the
+ * id of the buffer.
+ *
+ * If status is SPA_STATUS_OK, the host should ignore
+ * the io area.
+ *
+ * If status is SPA_STATUS_NEED_DATA, the host should:
+ * 1) recycle the buffer in buffer_id, if possible
+ * 2) prepare a new buffer and place the id in buffer_id.
+ *
+ * If status is SPA_STATUS_HAVE_DATA, the host should consume
+ * the buffer in buffer_id and set the state to
+ * SPA_STATUS_NEED_DATA when new data is requested.
+ *
+ * If status is SPA_STATUS_STOPPED, some error occurred on the
+ * port.
+ *
+ * If status is SPA_STATUS_DRAINED, data from the io area was
+ * used to drain.
+ *
+ * Status can also be a negative errno value to indicate errors.
+ * such as:
+ * -EINVAL: buffer_id is invalid
+ * -EPIPE: no more buffers available
+ */
+struct spa_io_buffers {
+#define SPA_STATUS_OK			0
+#define SPA_STATUS_NEED_DATA		(1<<0)
+#define SPA_STATUS_HAVE_DATA		(1<<1)
+#define SPA_STATUS_STOPPED		(1<<2)
+#define SPA_STATUS_DRAINED		(1<<3)
+	int32_t status;			/**< the status code */
+	uint32_t buffer_id;		/**< a buffer id */
+};
+
+#define SPA_IO_BUFFERS_INIT  (struct spa_io_buffers) { SPA_STATUS_OK, SPA_ID_INVALID, }
+
+/**
+ * IO area to exchange a memory region
+ */
+struct spa_io_memory {
+	int32_t status;			/**< the status code */
+	uint32_t size;			/**< the size of \a data */
+	void *data;			/**< a memory pointer */
+};
+#define SPA_IO_MEMORY_INIT  (struct spa_io_memory) { SPA_STATUS_OK, 0, NULL, }
+
+/** A range, suitable for input ports that can suggest a range to output ports */
+struct spa_io_range {
+	uint64_t offset;	/**< offset in range */
+	uint32_t min_size;	/**< minimum size of data */
+	uint32_t max_size;	/**< maximum size of data */
+};
+
+/**
+ * Absolute time reporting.
+ *
+ * Nodes that can report clocking information will receive this io block.
+ * The application sets the id. This is usually set as part of the
+ * position information but can also be set separately.
+ *
+ * The clock counts the elapsed time according to the clock provider
+ * since the provider was last started.
+ */
+struct spa_io_clock {
+	uint32_t flags;			/**< clock flags */
+	uint32_t id;			/**< unique clock id, set by application */
+	char name[64];			/**< clock name prefixed with API, set by node. The clock name
+					  *  is unique per clock and can be used to check if nodes
+					  *  share the same clock. */
+	uint64_t nsec;			/**< time in nanoseconds against monotonic clock */
+	struct spa_fraction rate;	/**< rate for position/duration/delay */
+	uint64_t position;		/**< current position */
+	uint64_t duration;		/**< duration of current cycle */
+	int64_t delay;			/**< delay between position and hardware,
+					  *  positive for capture, negative for playback */
+	double rate_diff;		/**< rate difference between clock and monotonic time */
+	uint64_t next_nsec;		/**< extimated next wakeup time in nanoseconds */
+	uint32_t padding[8];
+};
+
+/* the size of the video in this cycle */
+struct spa_io_video_size {
+#define SPA_IO_VIDEO_SIZE_VALID		(1<<0)
+	uint32_t flags;			/**< optional flags */
+	uint32_t stride;		/**< video stride in bytes */
+	struct spa_rectangle size;	/**< the video size */
+	struct spa_fraction framerate;  /**< the minimum framerate, the cycle duration is
+					  *  always smaller to ensure there is only one
+					  *  video frame per cycle. */
+	uint32_t padding[4];
+};
+
+/** latency reporting */
+struct spa_io_latency {
+	struct spa_fraction rate;	/**< rate for min/max */
+	uint64_t min;			/**< min latency */
+	uint64_t max;			/**< max latency */
+};
+
+/** control stream, io area for SPA_IO_Control and SPA_IO_Notify */
+struct spa_io_sequence {
+	struct spa_pod_sequence sequence;	/**< sequence of timed events */
+};
+
+/** bar and beat segment */
+struct spa_io_segment_bar {
+#define SPA_IO_SEGMENT_BAR_FLAG_VALID		(1<<0)
+	uint32_t flags;			/**< extra flags */
+	uint32_t offset;		/**< offset in segment of this beat */
+	float signature_num;		/**< time signature numerator */
+	float signature_denom;		/**< time signature denominator */
+	double bpm;			/**< beats per minute */
+	double beat;			/**< current beat in segment */
+	uint32_t padding[8];
+};
+
+/** video frame segment */
+struct spa_io_segment_video {
+#define SPA_IO_SEGMENT_VIDEO_FLAG_VALID		(1<<0)
+#define SPA_IO_SEGMENT_VIDEO_FLAG_DROP_FRAME	(1<<1)
+#define SPA_IO_SEGMENT_VIDEO_FLAG_PULL_DOWN	(1<<2)
+#define SPA_IO_SEGMENT_VIDEO_FLAG_INTERLACED	(1<<3)
+	uint32_t flags;			/**< flags */
+	uint32_t offset;		/**< offset in segment */
+	struct spa_fraction framerate;
+	uint32_t hours;
+	uint32_t minutes;
+	uint32_t seconds;
+	uint32_t frames;
+	uint32_t field_count;		/**< 0 for progressive, 1 and 2 for interlaced */
+	uint32_t padding[11];
+};
+
+/**
+ * A segment converts a running time to a segment (stream) position.
+ *
+ * The segment position is valid when the current running time is between
+ * start and start + duration. The position is then
+ * calculated as:
+ *
+ *   (running time - start) * rate + position;
+ *
+ * Support for looping is done by specifying the LOOPING flags with a
+ * non-zero duration. When the running time reaches start + duration,
+ * duration is added to start and the loop repeats.
+ *
+ * Care has to be taken when the running time + clock.duration extends
+ * past the start + duration from the segment; the user should correctly
+ * wrap around and partially repeat the loop in the current cycle.
+ *
+ * Extra information can be placed in the segment by setting the valid flags
+ * and filling up the corresponding structures.
+ */
+struct spa_io_segment {
+	uint32_t version;
+#define SPA_IO_SEGMENT_FLAG_LOOPING	(1<<0)	/**< after the duration, the segment repeats */
+#define SPA_IO_SEGMENT_FLAG_NO_POSITION	(1<<1)	/**< position is invalid. The position can be invalid
+						  *  after a seek, for example, when the exact mapping
+						  *  of the extra segment info (bar, video, ...) to
+						  *  position has not been determined yet */
+	uint32_t flags;				/**< extra flags */
+	uint64_t start;				/**< value of running time when this
+						  *  info is active. Can be in the future for
+						  *  pending changes. It does not have to be in
+						  *  exact multiples of the clock duration. */
+	uint64_t duration;			/**< duration when this info becomes invalid expressed
+						  *  in running time. If the duration is 0, this
+						  *  segment extends to the next segment. If the
+						  *  segment becomes invalid and the looping flag is
+						  *  set, the segment repeats. */
+	double rate;				/**< overal rate of the segment, can be negative for
+						  *  backwards time reporting. */
+	uint64_t position;			/**< The position when the running time == start.
+						  *  can be invalid when the owner of the extra segment
+						  *  information has not yet made the mapping. */
+
+	struct spa_io_segment_bar bar;
+	struct spa_io_segment_video video;
+};
+
+enum spa_io_position_state {
+	SPA_IO_POSITION_STATE_STOPPED,
+	SPA_IO_POSITION_STATE_STARTING,
+	SPA_IO_POSITION_STATE_RUNNING,
+};
+
+/** the maximum number of segments visible in the future */
+#define SPA_IO_POSITION_MAX_SEGMENTS	8
+
+/**
+ * The position information adds extra meaning to the raw clock times.
+ *
+ * It is set on all nodes and the clock id will contain the clock of the
+ * driving node in the graph.
+ *
+ * The position information contains 1 or more segments that convert the
+ * raw clock times to a stream time. They are sorted based on their
+ * start times, and thus the order in which they will activate in
+ * the future. This makes it possible to look ahead in the scheduled
+ * segments and anticipate the changes in the timeline.
+ */
+struct spa_io_position {
+	struct spa_io_clock clock;		/**< clock position of driver, always valid and
+						  *  read only */
+	struct spa_io_video_size video;		/**< size of the video in the current cycle */
+	int64_t offset;				/**< an offset to subtract from the clock position
+						  *  to get a running time. This is the time that
+						  *  the state has been in the RUNNING state and the
+						  *  time that should be used to compare the segment
+						  *  start values against. */
+	uint32_t state;				/**< one of enum spa_io_position_state */
+
+	uint32_t n_segments;			/**< number of segments */
+	struct spa_io_segment segments[SPA_IO_POSITION_MAX_SEGMENTS];	/**< segments */
+};
+
+/** rate matching */
+struct spa_io_rate_match {
+	uint32_t delay;			/**< extra delay in samples for resampler */
+	uint32_t size;			/**< requested input size for resampler */
+	double rate;			/**< rate for resampler */
+#define SPA_IO_RATE_MATCH_FLAG_ACTIVE	(1 << 0)
+	uint32_t flags;			/**< extra flags */
+	uint32_t padding[7];
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_IO_H */
diff --git a/third_party/pipewire/spa/node/keys.h b/third_party/pipewire/spa/node/keys.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/node/keys.h
@@ -0,0 +1,54 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_NODE_KEYS_H
+#define SPA_NODE_KEYS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** node keys */
+#define SPA_KEY_NODE_NAME		"node.name"		/**< a node name */
+#define SPA_KEY_NODE_LATENCY		"node.latency"		/**< the requested node latency */
+
+#define SPA_KEY_NODE_DRIVER		"node.driver"		/**< the node can be a driver */
+#define SPA_KEY_NODE_ALWAYS_PROCESS	"node.always-process"	/**< call the process function even if
+								  *  not linked. */
+#define SPA_KEY_NODE_PAUSE_ON_IDLE	"node.pause-on-idle"	/**< if the node should be paused
+								  *  immediately when idle. */
+#define SPA_KEY_NODE_MONITOR		"node.monitor"		/**< the node has monitor ports */
+
+
+/** port keys */
+#define SPA_KEY_PORT_NAME		"port.name"		/**< a port name */
+#define SPA_KEY_PORT_ALIAS		"port.alias"		/**< a port alias */
+#define SPA_KEY_PORT_MONITOR		"port.monitor"		/**< this port is a monitor port */
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_NODE_KEYS_H */
diff --git a/third_party/pipewire/spa/node/node.h b/third_party/pipewire/spa/node/node.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/node/node.h
@@ -0,0 +1,662 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_NODE_H
+#define SPA_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/type.h>
+#include <spa/utils/hook.h>
+#include <spa/buffer/buffer.h>
+#include <spa/node/event.h>
+#include <spa/node/command.h>
+
+
+/**
+ * A spa_node is a component that can consume and produce buffers.
+ */
+#define SPA_TYPE_INTERFACE_Node		SPA_TYPE_INFO_INTERFACE_BASE "Node"
+
+#define SPA_VERSION_NODE		0
+struct spa_node { struct spa_interface iface; };
+
+/**
+ * Node information structure
+ *
+ * Contains the basic node information.
+ */
+struct spa_node_info {
+	uint32_t max_input_ports;
+	uint32_t max_output_ports;
+#define SPA_NODE_CHANGE_MASK_FLAGS		(1u<<0)
+#define SPA_NODE_CHANGE_MASK_PROPS		(1u<<1)
+#define SPA_NODE_CHANGE_MASK_PARAMS		(1u<<2)
+	uint64_t change_mask;
+
+#define SPA_NODE_FLAG_RT			(1u<<0)	/**< node can do real-time processing */
+#define SPA_NODE_FLAG_IN_DYNAMIC_PORTS		(1u<<1)	/**< input ports can be added/removed */
+#define SPA_NODE_FLAG_OUT_DYNAMIC_PORTS		(1u<<2)	/**< output ports can be added/removed */
+#define SPA_NODE_FLAG_IN_PORT_CONFIG		(1u<<3)	/**< input ports can be reconfigured with
+							  *  PortConfig parameter */
+#define SPA_NODE_FLAG_OUT_PORT_CONFIG		(1u<<4)	/**< output ports can be reconfigured with
+							  *  PortConfig parameter */
+#define SPA_NODE_FLAG_NEED_CONFIGURE		(1u<<5)	/**< node needs configuration before it can
+							  *  be started. */
+#define SPA_NODE_FLAG_ASYNC			(1u<<6)	/**< the process function might not
+							  *  immediateley produce or consume data
+							  *  but might offload the work to a worker
+							  *  thread. */
+	uint64_t flags;
+	struct spa_dict *props;			/**< extra node properties */
+	struct spa_param_info *params;		/**< parameter information */
+	uint32_t n_params;			/**< number of items in \a params */
+};
+
+#define SPA_NODE_INFO_INIT()	(struct spa_node_info) { 0, }
+
+/**
+ * Port information structure
+ *
+ * Contains the basic port information.
+ */
+struct spa_port_info {
+#define SPA_PORT_CHANGE_MASK_FLAGS		(1u<<0)
+#define SPA_PORT_CHANGE_MASK_RATE		(1u<<1)
+#define SPA_PORT_CHANGE_MASK_PROPS		(1u<<2)
+#define SPA_PORT_CHANGE_MASK_PARAMS		(1u<<3)
+	uint64_t change_mask;
+
+#define SPA_PORT_FLAG_REMOVABLE			(1u<<0)	/**< port can be removed */
+#define SPA_PORT_FLAG_OPTIONAL			(1u<<1)	/**< processing on port is optional */
+#define SPA_PORT_FLAG_CAN_ALLOC_BUFFERS		(1u<<2)	/**< the port can allocate buffer data */
+#define SPA_PORT_FLAG_IN_PLACE			(1u<<3)	/**< the port can process data in-place and
+							 *   will need a writable input buffer */
+#define SPA_PORT_FLAG_NO_REF			(1u<<4)	/**< the port does not keep a ref on the buffer.
+							 *   This means the node will always completely
+							 *   consume the input buffer and it will be
+							 *   recycled after process. */
+#define SPA_PORT_FLAG_LIVE			(1u<<5)	/**< output buffers from this port are
+							 *   timestamped against a live clock. */
+#define SPA_PORT_FLAG_PHYSICAL			(1u<<6)	/**< connects to some device */
+#define SPA_PORT_FLAG_TERMINAL			(1u<<7)	/**< data was not created from this port
+							 *   or will not be made available on another
+							 *   port */
+#define SPA_PORT_FLAG_DYNAMIC_DATA		(1u<<8)	/**< data pointer on buffers can be changed.
+							 *   Only the buffer data marked as DYNAMIC
+							 *   can be changed. */
+	uint64_t flags;				/**< port flags */
+	struct spa_fraction rate;		/**< rate of sequence numbers on port */
+	const struct spa_dict *props;		/**< extra port properties */
+	struct spa_param_info *params;		/**< parameter information */
+	uint32_t n_params;			/**< number of items in \a params */
+};
+
+#define SPA_PORT_INFO_INIT()	(struct spa_port_info) { 0, }
+
+#define SPA_RESULT_TYPE_NODE_ERROR	1
+#define SPA_RESULT_TYPE_NODE_PARAMS	2
+
+/** an error result */
+struct spa_result_node_error {
+	const char *message;
+};
+
+/** the result of enum_params or port_enum_params. */
+struct spa_result_node_params {
+	uint32_t id;		/**< id of parameter */
+	uint32_t index;		/**< index of parameter */
+	uint32_t next;		/**< next index of iteration */
+	struct spa_pod *param;	/**< the result param */
+};
+
+#define SPA_NODE_EVENT_INFO		0
+#define SPA_NODE_EVENT_PORT_INFO	1
+#define SPA_NODE_EVENT_RESULT		2
+#define SPA_NODE_EVENT_EVENT		3
+#define SPA_NODE_EVENT_NUM		4
+
+/** events from the spa_node.
+ *
+ * All event are called from the main thread and multiple
+ * listeners can be registered for the events with
+ * spa_node_add_listener().
+ */
+struct spa_node_events {
+#define SPA_VERSION_NODE_EVENTS	0
+	uint32_t version;	/**< version of this structure */
+
+	/** Emitted when info changes */
+	void (*info) (void *data, const struct spa_node_info *info);
+
+	/** Emitted when port info changes, NULL when port is removed */
+	void (*port_info) (void *data,
+			enum spa_direction direction, uint32_t port,
+			const struct spa_port_info *info);
+
+	/** notify a result.
+	 *
+	 * Some methods will trigger a result event with an optional
+	 * result of the given type. Look at the documentation of the
+	 * method to know when to expect a result event.
+	 *
+	 * The result event can be called synchronously, as an event
+	 * called from inside the method itself, in which case the seq
+	 * number passed to the method will be passed unchanged.
+	 *
+	 * The result event will be called asynchronously when the
+	 * method returned an async return value. In this case, the seq
+	 * number in the result will match the async return value of
+	 * the method call. Users should match the seq number from
+	 * request to the reply.
+	 */
+	void (*result) (void *data, int seq, int res,
+			uint32_t type, const void *result);
+
+	/**
+	 * \param node a spa_node
+	 * \param event the event that was emitted
+	 *
+	 * This will be called when an out-of-bound event is notified
+	 * on \a node.
+	 */
+	void (*event) (void *data, const struct spa_event *event);
+};
+
+#define SPA_NODE_CALLBACK_READY		0
+#define SPA_NODE_CALLBACK_REUSE_BUFFER	1
+#define SPA_NODE_CALLBACK_XRUN		2
+#define SPA_NODE_CALLBACK_NUM		3
+
+/** Node callbacks
+ *
+ * Callbacks are called from the real-time data thread. Only
+ * one callback structure can be set on an spa_node.
+ */
+struct spa_node_callbacks {
+#define SPA_VERSION_NODE_CALLBACKS	0
+	uint32_t version;
+	/**
+	 * \param node a spa_node
+	 *
+	 * The node is ready for processing.
+	 *
+	 * When this function is NULL, synchronous operation is requested
+	 * on the ports.
+	 */
+	int (*ready) (void *data, int state);
+
+	/**
+	 * \param node a spa_node
+	 * \param port_id an input port_id
+	 * \param buffer_id the buffer id to be reused
+	 *
+	 * The node has a buffer that can be reused.
+	 *
+	 * When this function is NULL, the buffers to reuse will be set in
+	 * the io area of the input ports.
+	 */
+	int (*reuse_buffer) (void *data,
+			     uint32_t port_id,
+			     uint32_t buffer_id);
+
+	/**
+	 * \param data user data
+	 * \param trigger the timestamp in microseconds when the xrun happened
+	 * \param delay the amount of microseconds of xrun.
+	 * \param info an object with extra info (NULL for now)
+	 *
+	 * The node has encountered an over or underrun
+	 *
+	 * The info contains an object with more information
+	 */
+	int (*xrun) (void *data, uint64_t trigger, uint64_t delay,
+			struct spa_pod *info);
+};
+
+
+/** flags that can be passed to set_param and port_set_param functions */
+#define SPA_NODE_PARAM_FLAG_TEST_ONLY	(1 << 0)	/**< Just check if the param is accepted */
+#define SPA_NODE_PARAM_FLAG_FIXATE	(1 << 1)	/**< Fixate the non-optional unset fields */
+#define SPA_NODE_PARAM_FLAG_NEAREST	(1 << 2)	/**< Allow set fields to be rounded to the
+							  *  nearest allowed field value. */
+
+/** flags to pass to the use_buffers functions */
+#define SPA_NODE_BUFFERS_FLAG_ALLOC	(1 << 0)	/**< Allocate memory for the buffers. This flag
+							  *  is ignored when the port does not have the
+							  *  SPA_PORT_FLAG_CAN_ALLOC_BUFFERS set. */
+
+
+#define SPA_NODE_METHOD_ADD_LISTENER		0
+#define SPA_NODE_METHOD_SET_CALLBACKS		1
+#define SPA_NODE_METHOD_SYNC			2
+#define SPA_NODE_METHOD_ENUM_PARAMS		3
+#define SPA_NODE_METHOD_SET_PARAM		4
+#define SPA_NODE_METHOD_SET_IO			5
+#define SPA_NODE_METHOD_SEND_COMMAND		6
+#define SPA_NODE_METHOD_ADD_PORT		7
+#define SPA_NODE_METHOD_REMOVE_PORT		8
+#define SPA_NODE_METHOD_PORT_ENUM_PARAMS	9
+#define SPA_NODE_METHOD_PORT_SET_PARAM		10
+#define SPA_NODE_METHOD_PORT_USE_BUFFERS	11
+#define SPA_NODE_METHOD_PORT_SET_IO		12
+#define SPA_NODE_METHOD_PORT_REUSE_BUFFER	13
+#define SPA_NODE_METHOD_PROCESS			14
+#define SPA_NODE_METHOD_NUM			15
+
+/**
+ * Node methods
+ */
+struct spa_node_methods {
+	/* the version of the node methods. This can be used to expand this
+	 * structure in the future */
+#define SPA_VERSION_NODE_METHODS	0
+	uint32_t version;
+
+	/**
+	 * Adds an event listener on \a node.
+	 *
+	 * Setting the events will trigger the info event and a
+	 * port_info event for each managed port on the new
+	 * listener.
+	 *
+	 * \param node a #spa_node
+	 * \param listener a listener
+	 * \param events a #struct spa_node_events
+	 * \param data data passed as first argument in functions of \a events
+	 * \return 0 on success
+	 *	   < 0 errno on error
+	 */
+	int (*add_listener) (void *object,
+			struct spa_hook *listener,
+			const struct spa_node_events *events,
+			void *data);
+	/**
+	 * Set callbacks to on \a node.
+	 * if \a callbacks is NULL, the current callbacks are removed.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * All callbacks are called from the data thread.
+	 *
+	 * \param node a spa_node
+	 * \param callbacks callbacks to set
+	 * \return 0 on success
+	 *         -EINVAL when node is NULL
+	 */
+	int (*set_callbacks) (void *object,
+			      const struct spa_node_callbacks *callbacks,
+			      void *data);
+	/**
+	 * Perform a sync operation.
+	 *
+	 * This method will emit the result event with the given sequence
+	 * number synchronously or with the returned async return value
+	 * asynchronously.
+	 *
+	 * Because all methods are serialized in the node, this can be used
+	 * to wait for completion of all previous method calls.
+	 *
+	 * \param seq a sequence number
+	 * \return 0 on success
+	 *         -EINVAL when node is NULL
+	 *         an async result
+	 */
+	int (*sync) (void *object, int seq);
+
+	/**
+	 * Enumerate the parameters of a node.
+	 *
+	 * Parameters are identified with an \a id. Some parameters can have
+	 * multiple values, see the documentation of the parameter id.
+	 *
+	 * Parameters can be filtered by passing a non-NULL \a filter.
+	 *
+	 * The function will emit the result event up to \a max times with
+	 * the result value. The seq in the result will either be the \a seq
+	 * number when executed synchronously or the async return value of
+	 * this function when executed asynchronously.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param node a \ref spa_node
+	 * \param seq a sequence number to pass to the result event when
+	 *	this method is executed synchronously.
+	 * \param id the param id to enumerate
+	 * \param start the index of enumeration, pass 0 for the first item
+	 * \param max the maximum number of parameters to enumerate
+	 * \param filter and optional filter to use
+	 *
+	 * \return 0 when no more items can be iterated.
+	 *         -EINVAL when invalid arguments are given
+	 *         -ENOENT the parameter \a id is unknown
+	 *         -ENOTSUP when there are no parameters
+	 *                 implemented on \a node
+	 *         an async return value when the result event will be
+	 *             emitted later.
+	 */
+	int (*enum_params) (void *object, int seq,
+			    uint32_t id, uint32_t start, uint32_t max,
+			    const struct spa_pod *filter);
+
+	/**
+	 * Set the configurable parameter in \a node.
+	 *
+	 * Usually, \a param will be obtained from enum_params and then
+	 * modified but it is also possible to set another spa_pod
+	 * as long as its keys and types match a supported object.
+	 *
+	 * Objects with property keys that are not known are ignored.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param node a \ref spa_node
+	 * \param id the parameter id to configure
+	 * \param flags additional flags
+	 * \param param the parameter to configure
+	 *
+	 * \return 0 on success
+	 *         -EINVAL when node is NULL
+	 *         -ENOTSUP when there are no parameters implemented on \a node
+	 *         -ENOENT the parameter is unknown
+	 */
+	int (*set_param) (void *object,
+			  uint32_t id, uint32_t flags,
+			  const struct spa_pod *param);
+
+	/**
+	 * Configure the given memory area with \a id on \a node. This
+	 * structure is allocated by the host and is used to exchange
+	 * data and parameters with the node.
+	 *
+	 * Setting an \a io of NULL will disable the node io.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param id the id of the io area, the available ids can be
+	 *        enumerated with the node parameters.
+	 * \param data a io area memory
+	 * \param size the size of \a data
+	 * \return 0 on success
+	 *         -EINVAL when invalid input is given
+	 *         -ENOENT when \a id is unknown
+	 *         -ENOSPC when \a size is too small
+	 */
+	int (*set_io) (void *object,
+		       uint32_t id, void *data, size_t size);
+
+	/**
+	 * Send a command to a node.
+	 *
+	 * Upon completion, a command might change the state of a node.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param node a  spa_node
+	 * \param command a spa_command
+	 * \return 0 on success
+	 *         -EINVAL when node or command is NULL
+	 *         -ENOTSUP when this node can't process commands
+	 *         -EINVAL \a command is an invalid command
+	 */
+	int (*send_command) (void *object, const struct spa_command *command);
+
+	/**
+	 * Make a new port with \a port_id. The caller should use the lowest unused
+	 * port id for the given \a direction.
+	 *
+	 * Port ids should be between 0 and max_ports as obtained from the info
+	 * event.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param node a  spa_node
+	 * \param direction a #enum spa_direction
+	 * \param port_id an unused port id
+	 * \param props extra properties
+	 * \return 0 on success
+	 *         -EINVAL when node is NULL
+	 */
+	int (*add_port) (void *object,
+			enum spa_direction direction, uint32_t port_id,
+			const struct spa_dict *props);
+
+	/**
+	 * Remove a port with \a port_id.
+	 *
+	 * \param node a  spa_node
+	 * \param direction a #enum spa_direction
+	 * \param port_id a port id
+	 * \return 0 on success
+	 *         -EINVAL when node is NULL or when port_id is unknown or
+	 *		when the port can't be removed.
+	 */
+	int (*remove_port) (void *object,
+			enum spa_direction direction, uint32_t port_id);
+
+	/**
+	 * Enumerate all possible parameters of \a id on \a port_id of \a node
+	 * that are compatible with \a filter.
+	 *
+	 * The result parameters can be queried and modified and ultimately be used
+	 * to call port_set_param.
+	 *
+	 * The function will emit the result event up to \a max times with
+	 * the result value. The seq in the result event will either be the
+	 * \a seq number when executed synchronously or the async return
+	 * value of this function when executed asynchronously.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param node a spa_node
+	 * \param seq a sequence number to pass to the result event when
+	 *	this method is executed synchronously.
+	 * \param direction an spa_direction
+	 * \param port_id the port to query
+	 * \param id the parameter id to query
+	 * \param start the first index to query, 0 to get the first item
+	 * \param max the maximum number of params to query
+	 * \param filter a parameter filter or NULL for no filter
+	 *
+	 * \return 0 when no more items can be iterated.
+	 *         -EINVAL when invalid parameters are given
+	 *         -ENOENT when \a id is unknown
+	 *         an async return value when the result event will be
+	 *             emitted later.
+	 */
+	int (*port_enum_params) (void *object, int seq,
+				 enum spa_direction direction, uint32_t port_id,
+				 uint32_t id, uint32_t start, uint32_t max,
+				 const struct spa_pod *filter);
+	/**
+	 * Set a parameter on \a port_id of \a node.
+	 *
+	 * When \a param is NULL, the parameter will be unset.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param node a #struct spa_node
+	 * \param direction a #enum spa_direction
+	 * \param port_id the port to configure
+	 * \param id the parameter id to set
+	 * \param flags optional flags
+	 * \param param a #struct spa_pod with the parameter to set
+	 * \return 0 on success
+	 *         1 on success, the value of \a param might have been
+	 *                changed depending on \a flags and the final value can be found by
+	 *                doing port_enum_params.
+	 *         -EINVAL when node is NULL or invalid arguments are given
+	 *         -ESRCH when one of the mandatory param
+	 *                 properties is not specified and SPA_NODE_PARAM_FLAG_FIXATE was
+	 *                 not set in \a flags.
+	 *         -ESRCH when the type or size of a property is not correct.
+	 *         -ENOENT when the param id is not found
+	 */
+	int (*port_set_param) (void *object,
+			       enum spa_direction direction,
+			       uint32_t port_id,
+			       uint32_t id, uint32_t flags,
+			       const struct spa_pod *param);
+
+	/**
+	 * Tell the port to use the given buffers
+	 *
+	 * When \a flags contains SPA_NODE_BUFFERS_FLAG_ALLOC, the data
+	 * in the buffers should point to an array of at least 1 data entry
+	 * with the desired supported type that will be filled by this function.
+	 *
+	 * The port should also have a spa_io_buffers io area configured to exchange
+	 * the buffers with the port.
+	 *
+	 * For an input port, all the buffers will remain dequeued.
+	 * Once a buffer has been queued on a port in the spa_io_buffers,
+	 * it should not be reused until the reuse_buffer callback is notified
+	 * or when the buffer has been returned in the spa_io_buffers of
+	 * the port.
+	 *
+	 * For output ports, all buffers will be queued in the port. When process
+	 * returns SPA_STATUS_HAVE_DATA, buffers are available in one or more
+	 * of the spa_io_buffers areas.
+	 *
+	 * When a buffer can be reused, port_reuse_buffer() should be called or the
+	 * buffer_id should be placed in the spa_io_buffers area before calling
+	 * process.
+	 *
+	 * Passing NULL as \a buffers will remove the reference that the port has
+	 * on the buffers.
+	 *
+	 * When this function returns async, use the spa_node_sync operation to
+	 * wait for completion.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param object an object implementing the interface
+	 * \param direction a port direction
+	 * \param port_id a port id
+	 * \param flags extra flags
+	 * \param buffers an array of buffer pointers
+	 * \param n_buffers number of elements in \a buffers
+	 * \return 0 on success
+	 */
+	int (*port_use_buffers) (void *object,
+				 enum spa_direction direction,
+				 uint32_t port_id,
+				 uint32_t flags,
+				 struct spa_buffer **buffers,
+				 uint32_t n_buffers);
+
+	/**
+	 * Configure the given memory area with \a id on \a port_id. This
+	 * structure is allocated by the host and is used to exchange
+	 * data and parameters with the port.
+	 *
+	 * Setting an \a io of NULL will disable the port io.
+	 *
+	 * This function must be called from the main thread.
+	 *
+	 * \param direction a spa_direction
+	 * \param port_id a port id
+	 * \param id the id of the io area, the available ids can be
+	 *        enumerated with the port parameters.
+	 * \param data a io area memory
+	 * \param size the size of \a data
+	 * \return 0 on success
+	 *         -EINVAL when invalid input is given
+	 *         -ENOENT when \a id is unknown
+	 *         -ENOSPC when \a size is too small
+	 */
+	int (*port_set_io) (void *object,
+			    enum spa_direction direction,
+			    uint32_t port_id,
+			    uint32_t id,
+			    void *data, size_t size);
+
+	/**
+	 * Tell an output port to reuse a buffer.
+	 *
+	 * This function must be called from the data thread.
+	 *
+	 * \param node a spa_node
+	 * \param port_id a port id
+	 * \param buffer_id a buffer id to reuse
+	 * \return 0 on success
+	 *         -EINVAL when node is NULL
+	 */
+	int (*port_reuse_buffer) (void *object, uint32_t port_id, uint32_t buffer_id);
+
+	/**
+	 * Process the node
+	 *
+	 * This function must be called from the data thread.
+	 *
+	 * Output io areas with SPA_STATUS_NEED_DATA will recycle the
+	 * buffers if any.
+	 *
+	 * Input areas with SPA_STATUS_HAVE_DATA are consumed if possible
+	 * and the status is set to SPA_STATUS_NEED_DATA or SPA_STATUS_OK.
+	 *
+	 * When the node has new output buffers, the SPA_STATUS_HAVE_DATA
+	 * bit will be set.
+	 *
+	 * When the node can accept new input in the next cycle, the
+	 * SPA_STATUS_NEED_DATA bit will be set.
+	 */
+	int (*process) (void *object);
+};
+
+#define spa_node_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	struct spa_node *_n = o;					\
+	spa_interface_call_res(&_n->iface,				\
+			struct spa_node_methods, _res,			\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define spa_node_add_listener(n,...)		spa_node_method(n, add_listener, 0, __VA_ARGS__)
+#define spa_node_set_callbacks(n,...)		spa_node_method(n, set_callbacks, 0, __VA_ARGS__)
+#define spa_node_sync(n,...)			spa_node_method(n, sync, 0, __VA_ARGS__)
+#define spa_node_enum_params(n,...)		spa_node_method(n, enum_params, 0, __VA_ARGS__)
+#define spa_node_set_param(n,...)		spa_node_method(n, set_param, 0, __VA_ARGS__)
+#define spa_node_set_io(n,...)			spa_node_method(n, set_io, 0, __VA_ARGS__)
+#define spa_node_send_command(n,...)		spa_node_method(n, send_command, 0, __VA_ARGS__)
+#define spa_node_add_port(n,...)		spa_node_method(n, add_port, 0, __VA_ARGS__)
+#define spa_node_remove_port(n,...)		spa_node_method(n, remove_port, 0, __VA_ARGS__)
+#define spa_node_port_enum_params(n,...)	spa_node_method(n, port_enum_params, 0, __VA_ARGS__)
+#define spa_node_port_set_param(n,...)		spa_node_method(n, port_set_param, 0, __VA_ARGS__)
+#define spa_node_port_use_buffers(n,...)	spa_node_method(n, port_use_buffers, 0, __VA_ARGS__)
+#define spa_node_port_set_io(n,...)		spa_node_method(n, port_set_io, 0, __VA_ARGS__)
+
+#define spa_node_port_reuse_buffer(n,...)	spa_node_method(n, port_reuse_buffer, 0, __VA_ARGS__)
+#define spa_node_process(n)			spa_node_method(n, process, 0)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_NODE_H */
diff --git a/third_party/pipewire/spa/node/type-info.h b/third_party/pipewire/spa/node/type-info.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/node/type-info.h
@@ -0,0 +1,94 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_NODE_TYPES_H
+#define SPA_NODE_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/type-info.h>
+
+#include <spa/node/command.h>
+#include <spa/node/event.h>
+#include <spa/node/io.h>
+
+#define SPA_TYPE_INFO_IO			SPA_TYPE_INFO_ENUM_BASE "IO"
+#define SPA_TYPE_INFO_IO_BASE		SPA_TYPE_INFO_IO ":"
+
+static const struct spa_type_info spa_type_io[] = {
+	{ SPA_IO_Invalid, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Invalid", NULL },
+	{ SPA_IO_Buffers, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Buffers", NULL },
+	{ SPA_IO_Range, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Range", NULL },
+	{ SPA_IO_Clock, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Clock", NULL },
+	{ SPA_IO_Latency, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Latency", NULL },
+	{ SPA_IO_Control, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Control", NULL },
+	{ SPA_IO_Notify, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Notify", NULL },
+	{ SPA_IO_Position, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Position", NULL },
+	{ SPA_IO_RateMatch, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "RateMatch", NULL },
+	{ SPA_IO_Memory, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Memory", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_NodeEvent			SPA_TYPE_INFO_EVENT_BASE "Node"
+#define SPA_TYPE_INFO_NODE_EVENT_BASE		SPA_TYPE_INFO_NodeEvent ":"
+
+static const struct spa_type_info spa_type_node_event_id[] = {
+	{ SPA_NODE_EVENT_Error,		 SPA_TYPE_Int, SPA_TYPE_INFO_NODE_EVENT_BASE "Error",   NULL },
+	{ SPA_NODE_EVENT_Buffering,	 SPA_TYPE_Int, SPA_TYPE_INFO_NODE_EVENT_BASE "Buffering", NULL },
+	{ SPA_NODE_EVENT_RequestRefresh, SPA_TYPE_Int, SPA_TYPE_INFO_NODE_EVENT_BASE "RequestRefresh", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+static const struct spa_type_info spa_type_node_event[] = {
+	{ 0, SPA_TYPE_Id, SPA_TYPE_INFO_NODE_EVENT_BASE, spa_type_node_event_id },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_NodeCommand			SPA_TYPE_INFO_COMMAND_BASE "Node"
+#define SPA_TYPE_INFO_NODE_COMMAND_BASE		SPA_TYPE_INFO_NodeCommand ":"
+
+static const struct spa_type_info spa_type_node_command_id[] = {
+	{ SPA_NODE_COMMAND_Suspend,	SPA_TYPE_Int, SPA_TYPE_INFO_NODE_COMMAND_BASE "Suspend", NULL },
+	{ SPA_NODE_COMMAND_Pause,	SPA_TYPE_Int, SPA_TYPE_INFO_NODE_COMMAND_BASE "Pause",   NULL },
+	{ SPA_NODE_COMMAND_Start,	SPA_TYPE_Int, SPA_TYPE_INFO_NODE_COMMAND_BASE "Start",   NULL },
+	{ SPA_NODE_COMMAND_Enable,	SPA_TYPE_Int, SPA_TYPE_INFO_NODE_COMMAND_BASE "Enable",  NULL },
+	{ SPA_NODE_COMMAND_Disable,	SPA_TYPE_Int, SPA_TYPE_INFO_NODE_COMMAND_BASE "Disable", NULL },
+	{ SPA_NODE_COMMAND_Flush,	SPA_TYPE_Int, SPA_TYPE_INFO_NODE_COMMAND_BASE "Flush",   NULL },
+	{ SPA_NODE_COMMAND_Drain,	SPA_TYPE_Int, SPA_TYPE_INFO_NODE_COMMAND_BASE "Drain",   NULL },
+	{ SPA_NODE_COMMAND_Marker,	SPA_TYPE_Int, SPA_TYPE_INFO_NODE_COMMAND_BASE "Marker",  NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+static const struct spa_type_info spa_type_node_command[] = {
+	{ 0, SPA_TYPE_Id, SPA_TYPE_INFO_NODE_COMMAND_BASE, spa_type_node_command_id },
+	{ 0, 0, NULL, NULL },
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_NODE_TYPES_H */
diff --git a/third_party/pipewire/spa/node/utils.h b/third_party/pipewire/spa/node/utils.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/node/utils.h
@@ -0,0 +1,144 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_NODE_UTILS_H
+#define SPA_NODE_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/pod/builder.h>
+
+#include <spa/node/node.h>
+
+struct spa_result_node_params_data {
+	struct spa_pod_builder *builder;
+	struct spa_result_node_params data;
+};
+
+static inline void spa_result_func_node_params(void *data,
+		int seq, int res, uint32_t type, const void *result)
+{
+	struct spa_result_node_params_data *d =
+		(struct spa_result_node_params_data *) data;
+	const struct spa_result_node_params *r =
+		(const struct spa_result_node_params *) result;
+	uint32_t offset = d->builder->state.offset;
+	spa_pod_builder_raw_padded(d->builder, r->param, SPA_POD_SIZE(r->param));
+	d->data.next = r->next;
+	d->data.param = SPA_MEMBER(d->builder->data, offset, struct spa_pod);
+}
+
+static inline int spa_node_enum_params_sync(struct spa_node *node,
+			uint32_t id, uint32_t *index,
+			const struct spa_pod *filter,
+			struct spa_pod **param,
+			struct spa_pod_builder *builder)
+{
+	struct spa_result_node_params_data data = { builder, };
+	struct spa_hook listener = { 0 };
+	static const struct spa_node_events node_events = {
+		SPA_VERSION_NODE_EVENTS,
+		.result = spa_result_func_node_params,
+	};
+	int res;
+
+	res = spa_node_add_listener(node, &listener, &node_events, &data);
+	if (res >= 0) {
+		res = spa_node_enum_params(node, 0, id, *index, 1, filter);
+		spa_hook_remove(&listener);
+	}
+
+	if (data.data.param == NULL) {
+		if (res > 0)
+			res = 0;
+	} else {
+		*index = data.data.next;
+		*param = data.data.param;
+		res = 1;
+	}
+	return res;
+}
+
+static inline int spa_node_port_enum_params_sync(struct spa_node *node,
+			enum spa_direction direction, uint32_t port_id,
+			uint32_t id, uint32_t *index,
+			const struct spa_pod *filter,
+			struct spa_pod **param,
+			struct spa_pod_builder *builder)
+{
+	struct spa_result_node_params_data data = { builder, };
+	struct spa_hook listener = { 0 };
+	static const struct spa_node_events node_events = {
+		SPA_VERSION_NODE_EVENTS,
+		.result = spa_result_func_node_params,
+	};
+	int res;
+
+	res = spa_node_add_listener(node, &listener, &node_events, &data);
+	if (res >= 0) {
+		res = spa_node_port_enum_params(node, 0, direction, port_id,
+				id, *index, 1, filter);
+		spa_hook_remove(&listener);
+	}
+
+	if (data.data.param == NULL) {
+		if (res > 0)
+			res = 0;
+	} else {
+		*index = data.data.next;
+		*param = data.data.param;
+		res = 1;
+	}
+	return res;
+}
+
+#define spa_node_emit(hooks,method,version,...)					\
+		spa_hook_list_call_simple(hooks, struct spa_node_events,	\
+				method, version, ##__VA_ARGS__)
+
+#define spa_node_emit_info(hooks,...)		spa_node_emit(hooks,info, 0, __VA_ARGS__)
+#define spa_node_emit_port_info(hooks,...)	spa_node_emit(hooks,port_info, 0, __VA_ARGS__)
+#define spa_node_emit_result(hooks,...)		spa_node_emit(hooks,result, 0, __VA_ARGS__)
+#define spa_node_emit_event(hooks,...)		spa_node_emit(hooks,event, 0, __VA_ARGS__)
+
+
+#define spa_node_call(callbacks,method,version,...)			\
+({									\
+	int _res = -ENOTSUP;							\
+	spa_callbacks_call_res(callbacks, struct spa_node_callbacks,	\
+			_res, method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define spa_node_call_ready(hook,...)		spa_node_call(hook, ready, 0, __VA_ARGS__)
+#define spa_node_call_reuse_buffer(hook,...)	spa_node_call(hook, reuse_buffer, 0, __VA_ARGS__)
+#define spa_node_call_xrun(hook,...)		spa_node_call(hook, xrun, 0, __VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_NODE_UTILS_H */
diff --git a/third_party/pipewire/spa/param/audio/format-utils.h b/third_party/pipewire/spa/param/audio/format-utils.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/audio/format-utils.h
@@ -0,0 +1,105 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_AUDIO_FORMAT_UTILS_H
+#define SPA_PARAM_AUDIO_FORMAT_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#include <spa/pod/parser.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format.h>
+#include <spa/param/format-utils.h>
+
+static inline int
+spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info)
+{
+	struct spa_pod *position = NULL;
+	int res;
+	info->flags = 0;
+	res = spa_pod_parse_object(format,
+			SPA_TYPE_OBJECT_Format, NULL,
+			SPA_FORMAT_AUDIO_format,	SPA_POD_Id(&info->format),
+			SPA_FORMAT_AUDIO_rate,		SPA_POD_Int(&info->rate),
+			SPA_FORMAT_AUDIO_channels,	SPA_POD_Int(&info->channels),
+			SPA_FORMAT_AUDIO_position,	SPA_POD_OPT_Pod(&position));
+	if (position == NULL ||
+	    !spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_AUDIO_MAX_CHANNELS))
+		SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
+
+	return res;
+}
+
+static inline int
+spa_format_audio_dsp_parse(const struct spa_pod *format, struct spa_audio_info_dsp *info)
+{
+	int res;
+	res = spa_pod_parse_object(format,
+			SPA_TYPE_OBJECT_Format, NULL,
+			SPA_FORMAT_AUDIO_format,	SPA_POD_Id(&info->format));
+	return res;
+}
+
+static inline struct spa_pod *
+spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, struct spa_audio_info_raw *info)
+{
+	struct spa_pod_frame f;
+	spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id);
+	spa_pod_builder_add(builder,
+			SPA_FORMAT_mediaType,		SPA_POD_Id(SPA_MEDIA_TYPE_audio),
+			SPA_FORMAT_mediaSubtype,	SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
+			SPA_FORMAT_AUDIO_format,	SPA_POD_Id(info->format),
+			SPA_FORMAT_AUDIO_rate,		SPA_POD_Int(info->rate),
+			SPA_FORMAT_AUDIO_channels,	SPA_POD_Int(info->channels),
+			0);
+
+	if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) {
+		spa_pod_builder_prop(builder, SPA_FORMAT_AUDIO_position, 0);
+		spa_pod_builder_array(builder, sizeof(uint32_t), SPA_TYPE_Id,
+				info->channels, info->position);
+	}
+	return (struct spa_pod*)spa_pod_builder_pop(builder, &f);
+}
+
+static inline struct spa_pod *
+spa_format_audio_dsp_build(struct spa_pod_builder *builder, uint32_t id, struct spa_audio_info_dsp *info)
+{
+	struct spa_pod_frame f;
+	spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id);
+	spa_pod_builder_add(builder,
+			SPA_FORMAT_mediaType,		SPA_POD_Id(SPA_MEDIA_TYPE_audio),
+			SPA_FORMAT_mediaSubtype,	SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp),
+			SPA_FORMAT_AUDIO_format,	SPA_POD_Id(info->format),
+			0);
+	return (struct spa_pod*)spa_pod_builder_pop(builder, &f);
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_AUDIO_FORMAT_UTILS_H */
diff --git a/third_party/pipewire/spa/param/audio/format.h b/third_party/pipewire/spa/param/audio/format.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/audio/format.h
@@ -0,0 +1,48 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_AUDIO_FORMAT_H
+#define SPA_PARAM_AUDIO_FORMAT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/param/format.h>
+#include <spa/param/audio/raw.h>
+
+struct spa_audio_info {
+	uint32_t media_type;
+	uint32_t media_subtype;
+	union {
+		struct spa_audio_info_raw raw;
+		struct spa_audio_info_dsp dsp;
+	} info;
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_AUDIO_FORMAT_H */
diff --git a/third_party/pipewire/spa/param/audio/layout.h b/third_party/pipewire/spa/param/audio/layout.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/audio/layout.h
@@ -0,0 +1,184 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_AUDIO_LAYOUT_H
+#define SPA_AUDIO_LAYOUT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef __FreeBSD__
+#include <endian.h>
+#endif
+
+#include <spa/param/audio/raw.h>
+
+struct spa_audio_layout_info {
+	uint32_t n_channels;
+	uint32_t position[SPA_AUDIO_MAX_CHANNELS];
+};
+
+#define SPA_AUDIO_LAYOUT_Mono		1, { SPA_AUDIO_CHANNEL_MONO, }
+#define SPA_AUDIO_LAYOUT_Stereo		2, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, }
+#define SPA_AUDIO_LAYOUT_Quad		4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }
+#define SPA_AUDIO_LAYOUT_Pentagonal	5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
+					     SPA_AUDIO_CHANNEL_FC, }
+#define SPA_AUDIO_LAYOUT_Hexagonal	6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, }
+#define SPA_AUDIO_LAYOUT_Octagonal	8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_Cube		8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR }, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
+					     SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, \
+					     SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR, }
+
+
+#define SPA_AUDIO_LAYOUT_MPEG_1_0	SPA_AUDIO_LAYOUT_MONO
+#define SPA_AUDIO_LAYOUT_MPEG_2_0	SPA_AUDIO_LAYOUT_STEREO
+#define SPA_AUDIO_LAYOUT_MPEG_3_0A	3, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, }
+#define SPA_AUDIO_LAYOUT_MPEG_3_0B	3, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FL, \
+					     SPA_AUDIO_CHANNEL_FR, }
+#define SPA_AUDIO_LAYOUT_MPEG_4_0A	4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, }
+#define SPA_AUDIO_LAYOUT_MPEG_4_0B	4, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FL, \
+					     SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RC, }
+#define SPA_AUDIO_LAYOUT_MPEG_5_0A	5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_MPEG_5_0B	5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, \
+					     SPA_AUDIO_CHANNEL_FC, }
+#define SPA_AUDIO_LAYOUT_MPEG_5_0C	5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FC, \
+					     SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_MPEG_5_0D	5, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FL, \
+					     SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_MPEG_5_1A	6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_MPEG_5_1B	6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, }
+#define SPA_AUDIO_LAYOUT_MPEG_5_1C	6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FC, \
+					     SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_LFE, }
+#define SPA_AUDIO_LAYOUT_MPEG_5_1D	6, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FL, \
+					     SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_LFE, }
+#define SPA_AUDIO_LAYOUT_MPEG_6_1A	7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, \
+					     SPA_AUDIO_CHANNEL_RC, }
+#define SPA_AUDIO_LAYOUT_MPEG_7_1A	8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_MPEG_7_1B	8, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_FL, \
+					     SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, \
+					     SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_LFE, }
+#define SPA_AUDIO_LAYOUT_MPEG_7_1C	8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }
+
+
+#define SPA_AUDIO_LAYOUT_2_1		3, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_LFE, }
+
+#define SPA_AUDIO_LAYOUT_2RC		3, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_RC, }
+#define SPA_AUDIO_LAYOUT_2FC		3, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, }
+
+#define SPA_AUDIO_LAYOUT_3_1		4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, }
+#define SPA_AUDIO_LAYOUT_4_0		4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, }
+#define SPA_AUDIO_LAYOUT_2_2		4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
+
+#define SPA_AUDIO_LAYOUT_4_1		5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_RC, }
+#define SPA_AUDIO_LAYOUT_5_0		5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_5_0R		5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RL, \
+					     SPA_AUDIO_CHANNEL_RR, }
+#define SPA_AUDIO_LAYOUT_5_1		6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_5_1R		6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }
+#define SPA_AUDIO_LAYOUT_6_0		6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_6_0F		6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FRC, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_6_1		7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_6_1F		7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
+					     SPA_AUDIO_CHANNEL_RC, }
+#define SPA_AUDIO_LAYOUT_7_0		7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RL, \
+					     SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_7_0F		7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FLC, \
+					     SPA_AUDIO_CHANNEL_FRC, SPA_AUDIO_CHANNEL_SL, \
+					     SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_7_1		8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_7_1W		8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FRC, \
+					     SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
+#define SPA_AUDIO_LAYOUT_7_1WR		8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
+					     SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \
+					     SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
+					     SPA_AUDIO_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FRC, }
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_AUDIO_LAYOUT_H */
diff --git a/third_party/pipewire/spa/param/audio/raw.h b/third_party/pipewire/spa/param/audio/raw.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/audio/raw.h
@@ -0,0 +1,237 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_AUDIO_RAW_H
+#define SPA_AUDIO_RAW_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+#ifndef __FreeBSD__
+#include <endian.h>
+#endif
+
+#define SPA_AUDIO_MAX_CHANNELS	64u
+
+enum spa_audio_format {
+	SPA_AUDIO_FORMAT_UNKNOWN,
+	SPA_AUDIO_FORMAT_ENCODED,
+
+	/* interleaved formats */
+	SPA_AUDIO_FORMAT_START_Interleaved	= 0x100,
+	SPA_AUDIO_FORMAT_S8,
+	SPA_AUDIO_FORMAT_U8,
+	SPA_AUDIO_FORMAT_S16_LE,
+	SPA_AUDIO_FORMAT_S16_BE,
+	SPA_AUDIO_FORMAT_U16_LE,
+	SPA_AUDIO_FORMAT_U16_BE,
+	SPA_AUDIO_FORMAT_S24_32_LE,
+	SPA_AUDIO_FORMAT_S24_32_BE,
+	SPA_AUDIO_FORMAT_U24_32_LE,
+	SPA_AUDIO_FORMAT_U24_32_BE,
+	SPA_AUDIO_FORMAT_S32_LE,
+	SPA_AUDIO_FORMAT_S32_BE,
+	SPA_AUDIO_FORMAT_U32_LE,
+	SPA_AUDIO_FORMAT_U32_BE,
+	SPA_AUDIO_FORMAT_S24_LE,
+	SPA_AUDIO_FORMAT_S24_BE,
+	SPA_AUDIO_FORMAT_U24_LE,
+	SPA_AUDIO_FORMAT_U24_BE,
+	SPA_AUDIO_FORMAT_S20_LE,
+	SPA_AUDIO_FORMAT_S20_BE,
+	SPA_AUDIO_FORMAT_U20_LE,
+	SPA_AUDIO_FORMAT_U20_BE,
+	SPA_AUDIO_FORMAT_S18_LE,
+	SPA_AUDIO_FORMAT_S18_BE,
+	SPA_AUDIO_FORMAT_U18_LE,
+	SPA_AUDIO_FORMAT_U18_BE,
+	SPA_AUDIO_FORMAT_F32_LE,
+	SPA_AUDIO_FORMAT_F32_BE,
+	SPA_AUDIO_FORMAT_F64_LE,
+	SPA_AUDIO_FORMAT_F64_BE,
+
+	/* planar formats */
+	SPA_AUDIO_FORMAT_START_Planar		= 0x200,
+	SPA_AUDIO_FORMAT_U8P,
+	SPA_AUDIO_FORMAT_S16P,
+	SPA_AUDIO_FORMAT_S24_32P,
+	SPA_AUDIO_FORMAT_S32P,
+	SPA_AUDIO_FORMAT_S24P,
+	SPA_AUDIO_FORMAT_F32P,
+	SPA_AUDIO_FORMAT_F64P,
+
+	/* other formats start here */
+	SPA_AUDIO_FORMAT_START_Other		= 0x400,
+
+	/* Aliases */
+
+	/* DSP formats */
+	SPA_AUDIO_FORMAT_DSP_S32 = SPA_AUDIO_FORMAT_S24_32P,
+	SPA_AUDIO_FORMAT_DSP_F32 = SPA_AUDIO_FORMAT_F32P,
+	SPA_AUDIO_FORMAT_DSP_F64 = SPA_AUDIO_FORMAT_F64P,
+
+	/* native endian */
+#if __BYTE_ORDER == __BIG_ENDIAN
+	SPA_AUDIO_FORMAT_S16 = SPA_AUDIO_FORMAT_S16_BE,
+	SPA_AUDIO_FORMAT_U16 = SPA_AUDIO_FORMAT_U16_BE,
+	SPA_AUDIO_FORMAT_S24_32 = SPA_AUDIO_FORMAT_S24_32_BE,
+	SPA_AUDIO_FORMAT_U24_32 = SPA_AUDIO_FORMAT_U24_32_BE,
+	SPA_AUDIO_FORMAT_S32 = SPA_AUDIO_FORMAT_S32_BE,
+	SPA_AUDIO_FORMAT_U32 = SPA_AUDIO_FORMAT_U32_BE,
+	SPA_AUDIO_FORMAT_S24 = SPA_AUDIO_FORMAT_S24_BE,
+	SPA_AUDIO_FORMAT_U24 = SPA_AUDIO_FORMAT_U24_BE,
+	SPA_AUDIO_FORMAT_S20 = SPA_AUDIO_FORMAT_S20_BE,
+	SPA_AUDIO_FORMAT_U20 = SPA_AUDIO_FORMAT_U20_BE,
+	SPA_AUDIO_FORMAT_S18 = SPA_AUDIO_FORMAT_S18_BE,
+	SPA_AUDIO_FORMAT_U18 = SPA_AUDIO_FORMAT_U18_BE,
+	SPA_AUDIO_FORMAT_F32 = SPA_AUDIO_FORMAT_F32_BE,
+	SPA_AUDIO_FORMAT_F64 = SPA_AUDIO_FORMAT_F64_BE,
+	SPA_AUDIO_FORMAT_S16_OE = SPA_AUDIO_FORMAT_S16_LE,
+	SPA_AUDIO_FORMAT_U16_OE = SPA_AUDIO_FORMAT_U16_LE,
+	SPA_AUDIO_FORMAT_S24_32_OE = SPA_AUDIO_FORMAT_S24_32_LE,
+	SPA_AUDIO_FORMAT_U24_32_OE = SPA_AUDIO_FORMAT_U24_32_LE,
+	SPA_AUDIO_FORMAT_S32_OE = SPA_AUDIO_FORMAT_S32_LE,
+	SPA_AUDIO_FORMAT_U32_OE = SPA_AUDIO_FORMAT_U32_LE,
+	SPA_AUDIO_FORMAT_S24_OE = SPA_AUDIO_FORMAT_S24_LE,
+	SPA_AUDIO_FORMAT_U24_OE = SPA_AUDIO_FORMAT_U24_LE,
+	SPA_AUDIO_FORMAT_S20_OE = SPA_AUDIO_FORMAT_S20_LE,
+	SPA_AUDIO_FORMAT_U20_OE = SPA_AUDIO_FORMAT_U20_LE,
+	SPA_AUDIO_FORMAT_S18_OE = SPA_AUDIO_FORMAT_S18_LE,
+	SPA_AUDIO_FORMAT_U18_OE = SPA_AUDIO_FORMAT_U18_LE,
+	SPA_AUDIO_FORMAT_F32_OE = SPA_AUDIO_FORMAT_F32_LE,
+	SPA_AUDIO_FORMAT_F64_OE = SPA_AUDIO_FORMAT_F64_LE,
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+	SPA_AUDIO_FORMAT_S16 = SPA_AUDIO_FORMAT_S16_LE,
+	SPA_AUDIO_FORMAT_U16 = SPA_AUDIO_FORMAT_U16_LE,
+	SPA_AUDIO_FORMAT_S24_32 = SPA_AUDIO_FORMAT_S24_32_LE,
+	SPA_AUDIO_FORMAT_U24_32 = SPA_AUDIO_FORMAT_U24_32_LE,
+	SPA_AUDIO_FORMAT_S32 = SPA_AUDIO_FORMAT_S32_LE,
+	SPA_AUDIO_FORMAT_U32 = SPA_AUDIO_FORMAT_U32_LE,
+	SPA_AUDIO_FORMAT_S24 = SPA_AUDIO_FORMAT_S24_LE,
+	SPA_AUDIO_FORMAT_U24 = SPA_AUDIO_FORMAT_U24_LE,
+	SPA_AUDIO_FORMAT_S20 = SPA_AUDIO_FORMAT_S20_LE,
+	SPA_AUDIO_FORMAT_U20 = SPA_AUDIO_FORMAT_U20_LE,
+	SPA_AUDIO_FORMAT_S18 = SPA_AUDIO_FORMAT_S18_LE,
+	SPA_AUDIO_FORMAT_U18 = SPA_AUDIO_FORMAT_U18_LE,
+	SPA_AUDIO_FORMAT_F32 = SPA_AUDIO_FORMAT_F32_LE,
+	SPA_AUDIO_FORMAT_F64 = SPA_AUDIO_FORMAT_F64_LE,
+	SPA_AUDIO_FORMAT_S16_OE = SPA_AUDIO_FORMAT_S16_BE,
+	SPA_AUDIO_FORMAT_U16_OE = SPA_AUDIO_FORMAT_U16_BE,
+	SPA_AUDIO_FORMAT_S24_32_OE = SPA_AUDIO_FORMAT_S24_32_BE,
+	SPA_AUDIO_FORMAT_U24_32_OE = SPA_AUDIO_FORMAT_U24_32_BE,
+	SPA_AUDIO_FORMAT_S32_OE = SPA_AUDIO_FORMAT_S32_BE,
+	SPA_AUDIO_FORMAT_U32_OE = SPA_AUDIO_FORMAT_U32_BE,
+	SPA_AUDIO_FORMAT_S24_OE = SPA_AUDIO_FORMAT_S24_BE,
+	SPA_AUDIO_FORMAT_U24_OE = SPA_AUDIO_FORMAT_U24_BE,
+	SPA_AUDIO_FORMAT_S20_OE = SPA_AUDIO_FORMAT_S20_BE,
+	SPA_AUDIO_FORMAT_U20_OE = SPA_AUDIO_FORMAT_U20_BE,
+	SPA_AUDIO_FORMAT_S18_OE = SPA_AUDIO_FORMAT_S18_BE,
+	SPA_AUDIO_FORMAT_U18_OE = SPA_AUDIO_FORMAT_U18_BE,
+	SPA_AUDIO_FORMAT_F32_OE = SPA_AUDIO_FORMAT_F32_BE,
+	SPA_AUDIO_FORMAT_F64_OE = SPA_AUDIO_FORMAT_F64_BE,
+#endif
+};
+
+#define SPA_AUDIO_FORMAT_IS_INTERLEAVED(fmt)	((fmt) > SPA_AUDIO_FORMAT_START_Interleaved && (fmt) < SPA_AUDIO_FORMAT_START_Planar)
+#define SPA_AUDIO_FORMAT_IS_PLANAR(fmt)		((fmt) > SPA_AUDIO_FORMAT_START_Planar && (fmt) < SPA_AUDIO_FORMAT_START_Other)
+
+enum spa_audio_channel {
+	SPA_AUDIO_CHANNEL_UNKNOWN,	/**< unspecified */
+	SPA_AUDIO_CHANNEL_NA,		/**< N/A, silent */
+
+	SPA_AUDIO_CHANNEL_MONO,         /**< mono stream */
+
+	SPA_AUDIO_CHANNEL_FL,           /**< front left */
+	SPA_AUDIO_CHANNEL_FR,           /**< front right */
+	SPA_AUDIO_CHANNEL_FC,           /**< front center */
+	SPA_AUDIO_CHANNEL_LFE,          /**< LFE */
+	SPA_AUDIO_CHANNEL_SL,           /**< side left */
+	SPA_AUDIO_CHANNEL_SR,           /**< side right */
+	SPA_AUDIO_CHANNEL_FLC,          /**< front left center */
+	SPA_AUDIO_CHANNEL_FRC,          /**< front right center */
+	SPA_AUDIO_CHANNEL_RC,           /**< rear center */
+	SPA_AUDIO_CHANNEL_RL,           /**< rear left */
+	SPA_AUDIO_CHANNEL_RR,           /**< rear right */
+	SPA_AUDIO_CHANNEL_TC,           /**< top center */
+	SPA_AUDIO_CHANNEL_TFL,          /**< top front left */
+	SPA_AUDIO_CHANNEL_TFC,          /**< top front center */
+	SPA_AUDIO_CHANNEL_TFR,          /**< top front right */
+	SPA_AUDIO_CHANNEL_TRL,          /**< top rear left */
+	SPA_AUDIO_CHANNEL_TRC,          /**< top rear center */
+	SPA_AUDIO_CHANNEL_TRR,          /**< top rear right */
+	SPA_AUDIO_CHANNEL_RLC,          /**< rear left center */
+	SPA_AUDIO_CHANNEL_RRC,          /**< rear right center */
+	SPA_AUDIO_CHANNEL_FLW,          /**< front left wide */
+	SPA_AUDIO_CHANNEL_FRW,          /**< front right wide */
+	SPA_AUDIO_CHANNEL_LFE2,		/**< LFE 2 */
+	SPA_AUDIO_CHANNEL_FLH,          /**< front left high */
+	SPA_AUDIO_CHANNEL_FCH,          /**< front center high */
+	SPA_AUDIO_CHANNEL_FRH,          /**< front right high */
+	SPA_AUDIO_CHANNEL_TFLC,         /**< top front left center */
+	SPA_AUDIO_CHANNEL_TFRC,         /**< top front right center */
+	SPA_AUDIO_CHANNEL_TSL,          /**< top side left */
+	SPA_AUDIO_CHANNEL_TSR,          /**< top side right */
+	SPA_AUDIO_CHANNEL_LLFE,         /**< left LFE */
+	SPA_AUDIO_CHANNEL_RLFE,         /**< right LFE */
+	SPA_AUDIO_CHANNEL_BC,           /**< bottom center */
+	SPA_AUDIO_CHANNEL_BLC,          /**< bottom left center */
+	SPA_AUDIO_CHANNEL_BRC,          /**< bottom right center */
+
+	SPA_AUDIO_CHANNEL_CUSTOM_START	= 0x10000,
+};
+
+/** Extra audio flags */
+#define SPA_AUDIO_FLAG_NONE		(0)		/*< no valid flag */
+#define SPA_AUDIO_FLAG_UNPOSITIONED	(1 << 0)	/*< the position array explicitly
+							 *  contains unpositioned channels. */
+/** Audio information description */
+struct spa_audio_info_raw {
+	enum spa_audio_format format;		/*< format, one of enum spa_audio_format */
+	uint32_t flags;				/*< extra flags */
+	uint32_t rate;				/*< sample rate */
+	uint32_t channels;			/*< number of channels */
+	uint32_t position[SPA_AUDIO_MAX_CHANNELS];	/*< channel position from enum spa_audio_channel */
+};
+
+#define SPA_AUDIO_INFO_RAW_INIT(...)		(struct spa_audio_info_raw) { __VA_ARGS__ }
+
+#define SPA_KEY_AUDIO_CHANNEL		"audio.channel"		/**< an audio channel as string,
+								  *  Ex. "FL" */
+#define SPA_KEY_AUDIO_CHANNELS		"audio.channels"	/**< an audio channel count as int */
+#define SPA_KEY_AUDIO_RATE		"audio.rate"		/**< an audio sample rate as int */
+
+struct spa_audio_info_dsp {
+	enum spa_audio_format format;		/*< format, one of the DSP formats in enum spa_audio_format_dsp */
+};
+
+#define SPA_AUDIO_INFO_DSP_INIT(...)		(struct spa_audio_info_dsp) { __VA_ARGS__ }
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_AUDIO_RAW_H */
diff --git a/third_party/pipewire/spa/param/audio/type-info.h b/third_party/pipewire/spa/param/audio/type-info.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/audio/type-info.h
@@ -0,0 +1,140 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_AUDIO_TYPES_H
+#define SPA_AUDIO_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/param/audio/raw.h>
+
+#define SPA_TYPE_INFO_AudioFormat		SPA_TYPE_INFO_ENUM_BASE "AudioFormat"
+#define SPA_TYPE_INFO_AUDIO_FORMAT_BASE		SPA_TYPE_INFO_AudioFormat ":"
+
+static const struct spa_type_info spa_type_audio_format[] = {
+	{ SPA_AUDIO_FORMAT_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "UNKNOWN", NULL },
+	{ SPA_AUDIO_FORMAT_ENCODED, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "ENCODED", NULL },
+	{ SPA_AUDIO_FORMAT_S8, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S8", NULL },
+	{ SPA_AUDIO_FORMAT_U8, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U8", NULL },
+	{ SPA_AUDIO_FORMAT_S16_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16LE", NULL },
+	{ SPA_AUDIO_FORMAT_S16_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16BE", NULL },
+	{ SPA_AUDIO_FORMAT_U16_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16LE", NULL },
+	{ SPA_AUDIO_FORMAT_U16_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16BE", NULL },
+	{ SPA_AUDIO_FORMAT_S24_32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32LE", NULL },
+	{ SPA_AUDIO_FORMAT_S24_32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32BE", NULL },
+	{ SPA_AUDIO_FORMAT_U24_32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32LE", NULL },
+	{ SPA_AUDIO_FORMAT_U24_32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32BE", NULL },
+	{ SPA_AUDIO_FORMAT_S32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32LE", NULL },
+	{ SPA_AUDIO_FORMAT_S32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32BE", NULL },
+	{ SPA_AUDIO_FORMAT_U32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32LE", NULL },
+	{ SPA_AUDIO_FORMAT_U32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32BE", NULL },
+	{ SPA_AUDIO_FORMAT_S24_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24LE", NULL },
+	{ SPA_AUDIO_FORMAT_S24_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24BE", NULL },
+	{ SPA_AUDIO_FORMAT_U24_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24LE", NULL },
+	{ SPA_AUDIO_FORMAT_U24_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24BE", NULL },
+	{ SPA_AUDIO_FORMAT_S20_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20LE", NULL },
+	{ SPA_AUDIO_FORMAT_S20_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20BE", NULL },
+	{ SPA_AUDIO_FORMAT_U20_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20LE", NULL },
+	{ SPA_AUDIO_FORMAT_U20_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20BE", NULL },
+	{ SPA_AUDIO_FORMAT_S18_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18LE", NULL },
+	{ SPA_AUDIO_FORMAT_S18_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18BE", NULL },
+	{ SPA_AUDIO_FORMAT_U18_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18LE", NULL },
+	{ SPA_AUDIO_FORMAT_U18_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18BE", NULL },
+	{ SPA_AUDIO_FORMAT_F32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32LE", NULL },
+	{ SPA_AUDIO_FORMAT_F32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32BE", NULL },
+	{ SPA_AUDIO_FORMAT_F64_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64LE", NULL },
+	{ SPA_AUDIO_FORMAT_F64_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64BE", NULL },
+
+	{ SPA_AUDIO_FORMAT_U8P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U8P", NULL },
+	{ SPA_AUDIO_FORMAT_S16P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16P", NULL },
+	{ SPA_AUDIO_FORMAT_S24_32P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32P", NULL },
+	{ SPA_AUDIO_FORMAT_S32P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32P", NULL },
+	{ SPA_AUDIO_FORMAT_S24P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24P", NULL },
+	{ SPA_AUDIO_FORMAT_F32P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32P", NULL },
+	{ SPA_AUDIO_FORMAT_F64P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64P", NULL },
+
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_AudioFlags		SPA_TYPE_INFO_FLAGS_BASE "AudioFlags"
+#define SPA_TYPE_INFO_AUDIO_FLAGS_BASE	SPA_TYPE_INFO_AudioFlags ":"
+
+static const struct spa_type_info spa_type_audio_flags[] = {
+	{ SPA_AUDIO_FLAG_NONE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FLAGS_BASE "none", NULL },
+	{ SPA_AUDIO_FLAG_UNPOSITIONED, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FLAGS_BASE "unpositioned", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_AudioChannel		SPA_TYPE_INFO_ENUM_BASE "AudioChannel"
+#define SPA_TYPE_INFO_AUDIO_CHANNEL_BASE	SPA_TYPE_INFO_AudioChannel ":"
+
+static const struct spa_type_info spa_type_audio_channel[] = {
+	{ SPA_AUDIO_CHANNEL_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "UNK", NULL },
+	{ SPA_AUDIO_CHANNEL_NA,	SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "NA", NULL },
+	{ SPA_AUDIO_CHANNEL_MONO, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "MONO", NULL },
+	{ SPA_AUDIO_CHANNEL_FL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FL", NULL },
+	{ SPA_AUDIO_CHANNEL_FR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FR", NULL },
+	{ SPA_AUDIO_CHANNEL_FC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FC", NULL },
+	{ SPA_AUDIO_CHANNEL_LFE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "LFE", NULL },
+	{ SPA_AUDIO_CHANNEL_SL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "SL", NULL },
+	{ SPA_AUDIO_CHANNEL_SR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "SR", NULL },
+	{ SPA_AUDIO_CHANNEL_FLC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FLC", NULL },
+	{ SPA_AUDIO_CHANNEL_FRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FRC", NULL },
+	{ SPA_AUDIO_CHANNEL_RC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RC", NULL },
+	{ SPA_AUDIO_CHANNEL_RL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RL", NULL },
+	{ SPA_AUDIO_CHANNEL_RR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RR", NULL },
+	{ SPA_AUDIO_CHANNEL_TC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TC", NULL },
+	{ SPA_AUDIO_CHANNEL_TFL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFL", NULL },
+	{ SPA_AUDIO_CHANNEL_TFC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFC", NULL },
+	{ SPA_AUDIO_CHANNEL_TFR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFR", NULL },
+	{ SPA_AUDIO_CHANNEL_TRL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TRL", NULL },
+	{ SPA_AUDIO_CHANNEL_TRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TRC", NULL },
+	{ SPA_AUDIO_CHANNEL_TRR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TRR", NULL },
+	{ SPA_AUDIO_CHANNEL_RLC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RLC", NULL },
+	{ SPA_AUDIO_CHANNEL_RRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RRC", NULL },
+	{ SPA_AUDIO_CHANNEL_FLW, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FLW", NULL },
+	{ SPA_AUDIO_CHANNEL_FRW, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FRW", NULL },
+	{ SPA_AUDIO_CHANNEL_LFE2, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "LFE2", NULL },
+	{ SPA_AUDIO_CHANNEL_FLH, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FLH", NULL },
+	{ SPA_AUDIO_CHANNEL_FCH, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FCH", NULL },
+	{ SPA_AUDIO_CHANNEL_FRH, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FRH", NULL },
+	{ SPA_AUDIO_CHANNEL_TFLC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFLC", NULL },
+	{ SPA_AUDIO_CHANNEL_TFRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFRC", NULL },
+	{ SPA_AUDIO_CHANNEL_TSL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TSL", NULL },
+	{ SPA_AUDIO_CHANNEL_TSR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TSR", NULL },
+	{ SPA_AUDIO_CHANNEL_LLFE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "LLFR", NULL },
+	{ SPA_AUDIO_CHANNEL_RLFE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RLFE", NULL },
+	{ SPA_AUDIO_CHANNEL_BC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "BC", NULL },
+	{ SPA_AUDIO_CHANNEL_BLC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "BLC", NULL },
+	{ SPA_AUDIO_CHANNEL_BRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "BRC", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_AUDIO_RAW_TYPES_H */
diff --git a/third_party/pipewire/spa/param/format-utils.h b/third_party/pipewire/spa/param/format-utils.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/format-utils.h
@@ -0,0 +1,49 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_FORMAT_UTILS_H
+#define SPA_PARAM_FORMAT_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#include <spa/pod/parser.h>
+#include <spa/param/format.h>
+
+static inline int
+spa_format_parse(const struct spa_pod *format, uint32_t *media_type, uint32_t *media_subtype)
+{
+	return spa_pod_parse_object(format,
+		SPA_TYPE_OBJECT_Format, NULL,
+		SPA_FORMAT_mediaType,    SPA_POD_Id(media_type),
+		SPA_FORMAT_mediaSubtype, SPA_POD_Id(media_subtype));
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_FORMAT_UTILS_H */
diff --git a/third_party/pipewire/spa/param/format.h b/third_party/pipewire/spa/param/format.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/format.h
@@ -0,0 +1,147 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_FORMAT_H
+#define SPA_PARAM_FORMAT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/param/param.h>
+
+/** media type for SPA_TYPE_OBJECT_Format */
+enum spa_media_type {
+	SPA_MEDIA_TYPE_unknown,
+	SPA_MEDIA_TYPE_audio,
+	SPA_MEDIA_TYPE_video,
+	SPA_MEDIA_TYPE_image,
+	SPA_MEDIA_TYPE_binary,
+	SPA_MEDIA_TYPE_stream,
+	SPA_MEDIA_TYPE_application,
+};
+
+/** media subtype for SPA_TYPE_OBJECT_Format */
+enum spa_media_subtype {
+	SPA_MEDIA_SUBTYPE_unknown,
+	SPA_MEDIA_SUBTYPE_raw,
+	SPA_MEDIA_SUBTYPE_dsp,
+
+	SPA_MEDIA_SUBTYPE_START_Audio	= 0x10000,
+	SPA_MEDIA_SUBTYPE_mp3,
+	SPA_MEDIA_SUBTYPE_aac,
+	SPA_MEDIA_SUBTYPE_vorbis,
+	SPA_MEDIA_SUBTYPE_wma,
+	SPA_MEDIA_SUBTYPE_ra,
+	SPA_MEDIA_SUBTYPE_sbc,
+	SPA_MEDIA_SUBTYPE_adpcm,
+	SPA_MEDIA_SUBTYPE_g723,
+	SPA_MEDIA_SUBTYPE_g726,
+	SPA_MEDIA_SUBTYPE_g729,
+	SPA_MEDIA_SUBTYPE_amr,
+	SPA_MEDIA_SUBTYPE_gsm,
+
+	SPA_MEDIA_SUBTYPE_START_Video	= 0x20000,
+	SPA_MEDIA_SUBTYPE_h264,
+	SPA_MEDIA_SUBTYPE_mjpg,
+	SPA_MEDIA_SUBTYPE_dv,
+	SPA_MEDIA_SUBTYPE_mpegts,
+	SPA_MEDIA_SUBTYPE_h263,
+	SPA_MEDIA_SUBTYPE_mpeg1,
+	SPA_MEDIA_SUBTYPE_mpeg2,
+	SPA_MEDIA_SUBTYPE_mpeg4,
+	SPA_MEDIA_SUBTYPE_xvid,
+	SPA_MEDIA_SUBTYPE_vc1,
+	SPA_MEDIA_SUBTYPE_vp8,
+	SPA_MEDIA_SUBTYPE_vp9,
+	SPA_MEDIA_SUBTYPE_bayer,
+
+	SPA_MEDIA_SUBTYPE_START_Image	= 0x30000,
+	SPA_MEDIA_SUBTYPE_jpeg,
+
+	SPA_MEDIA_SUBTYPE_START_Binary	= 0x40000,
+
+	SPA_MEDIA_SUBTYPE_START_Stream	= 0x50000,
+	SPA_MEDIA_SUBTYPE_midi,
+
+	SPA_MEDIA_SUBTYPE_START_Application	= 0x60000,
+	SPA_MEDIA_SUBTYPE_control,		/**< control stream, data contains
+						  *  spa_pod_sequence with control info. */
+};
+
+/** properties for audio SPA_TYPE_OBJECT_Format */
+enum spa_format {
+	SPA_FORMAT_START,
+
+	SPA_FORMAT_mediaType,		/**< media type (Id enum spa_media_type) */
+	SPA_FORMAT_mediaSubtype,	/**< media subtype (Id enum spa_media_subtype) */
+
+	/* Audio format keys */
+	SPA_FORMAT_START_Audio = 0x10000,
+	SPA_FORMAT_AUDIO_format,	/**< audio format, (Id enum spa_audio_format) */
+	SPA_FORMAT_AUDIO_flags,		/**< optional flags (Int) */
+	SPA_FORMAT_AUDIO_rate,		/**< sample rate (Int) */
+	SPA_FORMAT_AUDIO_channels,	/**< number of audio channels (Int) */
+	SPA_FORMAT_AUDIO_position,	/**< channel positions (Id enum spa_audio_position) */
+
+	/* Video Format keys */
+	SPA_FORMAT_START_Video = 0x20000,
+	SPA_FORMAT_VIDEO_format,		/**< video format (Id enum spa_video_format) */
+	SPA_FORMAT_VIDEO_modifier,		/**< format modifier (Long) */
+	SPA_FORMAT_VIDEO_size,			/**< size (Rectangle) */
+	SPA_FORMAT_VIDEO_framerate,		/**< frame rate (Fraction) */
+	SPA_FORMAT_VIDEO_maxFramerate,		/**< maximum frame rate (Fraction) */
+	SPA_FORMAT_VIDEO_views,			/**< number of views (Int) */
+	SPA_FORMAT_VIDEO_interlaceMode,		/**< (Id enum spa_video_interlace_mode) */
+	SPA_FORMAT_VIDEO_pixelAspectRatio,	/**< (Rectangle) */
+	SPA_FORMAT_VIDEO_multiviewMode,		/**< (Id enum spa_video_multiview_mode) */
+	SPA_FORMAT_VIDEO_multiviewFlags,	/**< (Id enum spa_video_multiview_flags) */
+	SPA_FORMAT_VIDEO_chromaSite,		/**< /Id enum spa_video_chroma_site) */
+	SPA_FORMAT_VIDEO_colorRange,		/**< /Id enum spa_video_color_range) */
+	SPA_FORMAT_VIDEO_colorMatrix,		/**< /Id enum spa_video_color_matrix) */
+	SPA_FORMAT_VIDEO_transferFunction,	/**< /Id enum spa_video_transfer_function) */
+	SPA_FORMAT_VIDEO_colorPrimaries,	/**< /Id enum spa_video_color_primaries) */
+	SPA_FORMAT_VIDEO_profile,		/**< (Int) */
+	SPA_FORMAT_VIDEO_level,			/**< (Int) */
+	SPA_FORMAT_VIDEO_H264_streamFormat,	/**< (Id enum spa_h264_stream_format) */
+	SPA_FORMAT_VIDEO_H264_alignment,	/**< (Id enum spa_h264_alignment) */
+
+	/* Image Format keys */
+	SPA_FORMAT_START_Image = 0x30000,
+	/* Binary Format keys */
+	SPA_FORMAT_START_Binary = 0x40000,
+	/* Stream Format keys */
+	SPA_FORMAT_START_Stream = 0x50000,
+	/* Application Format keys */
+	SPA_FORMAT_START_Application = 0x60000,
+};
+
+#define SPA_KEY_FORMAT_DSP		"format.dsp"		/**< a predefined DSP format,
+								  *  Ex. "32 bit float mono audio" */
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_FORMAT_H */
diff --git a/third_party/pipewire/spa/param/param.h b/third_party/pipewire/spa/param/param.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/param.h
@@ -0,0 +1,166 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_H
+#define SPA_PARAM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+
+/** different parameter types that can be queried */
+enum spa_param_type {
+	SPA_PARAM_Invalid,		/**< invalid */
+	SPA_PARAM_PropInfo,		/**< property information as SPA_TYPE_OBJECT_PropInfo */
+	SPA_PARAM_Props,		/**< properties as SPA_TYPE_OBJECT_Props */
+	SPA_PARAM_EnumFormat,		/**< available formats as SPA_TYPE_OBJECT_Format */
+	SPA_PARAM_Format,		/**< configured format as SPA_TYPE_OBJECT_Format */
+	SPA_PARAM_Buffers,		/**< buffer configurations as SPA_TYPE_OBJECT_ParamBuffers*/
+	SPA_PARAM_Meta,			/**< allowed metadata for buffers as SPA_TYPE_OBJECT_ParamMeta*/
+	SPA_PARAM_IO,			/**< configurable IO areas as SPA_TYPE_OBJECT_ParamIO */
+	SPA_PARAM_EnumProfile,		/**< profile enumeration as SPA_TYPE_OBJECT_ParamProfile */
+	SPA_PARAM_Profile,		/**< profile configuration as SPA_TYPE_OBJECT_ParamProfile */
+	SPA_PARAM_EnumPortConfig,	/**< port configuration enumeration as SPA_TYPE_OBJECT_ParamPortConfig */
+	SPA_PARAM_PortConfig,		/**< port configuration as SPA_TYPE_OBJECT_ParamPortConfig */
+	SPA_PARAM_EnumRoute,		/**< routing enumeration as SPA_TYPE_OBJECT_ParamRoute */
+	SPA_PARAM_Route,		/**< routing configuration as SPA_TYPE_OBJECT_ParamRoute */
+	SPA_PARAM_Control,		/**< Control parameter, a SPA_TYPE_Sequence */
+};
+
+/** information about a parameter */
+struct spa_param_info {
+	uint32_t id;			/**< enum spa_param_type */
+#define SPA_PARAM_INFO_SERIAL		(1<<0)	/**< bit to signal update even when the
+						 *   read/write flags don't change */
+#define SPA_PARAM_INFO_READ		(1<<1)
+#define SPA_PARAM_INFO_WRITE		(1<<2)
+#define SPA_PARAM_INFO_READWRITE	(SPA_PARAM_INFO_WRITE|SPA_PARAM_INFO_READ)
+	uint32_t flags;
+	uint32_t user;			/**< private user field. You can use this to keep
+					  *  state. */
+	uint32_t padding[5];
+};
+
+#define SPA_PARAM_INFO(id,flags) (struct spa_param_info){ (id), (flags) }
+
+/** properties for SPA_TYPE_OBJECT_ParamBuffers */
+enum spa_param_buffers {
+	SPA_PARAM_BUFFERS_START,
+	SPA_PARAM_BUFFERS_buffers,	/**< number of buffers (Int) */
+	SPA_PARAM_BUFFERS_blocks,	/**< number of data blocks per buffer (Int) */
+	SPA_PARAM_BUFFERS_size,		/**< size of a data block memory (Int)*/
+	SPA_PARAM_BUFFERS_stride,	/**< stride of data block memory (Int) */
+	SPA_PARAM_BUFFERS_align,	/**< alignment of data block memory (Int) */
+	SPA_PARAM_BUFFERS_dataType,	/**< possible memory types (Int, mask of enum spa_data_type) */
+};
+
+/** properties for SPA_TYPE_OBJECT_ParamMeta */
+enum spa_param_meta {
+	SPA_PARAM_META_START,
+	SPA_PARAM_META_type,	/**< the metadata, one of enum spa_meta_type (Id enum spa_meta_type) */
+	SPA_PARAM_META_size,	/**< the expected maximum size the meta (Int) */
+};
+
+/** properties for SPA_TYPE_OBJECT_ParamIO */
+enum spa_param_io {
+	SPA_PARAM_IO_START,
+	SPA_PARAM_IO_id,	/**< type ID, uniquely identifies the io area (Id enum spa_io_type) */
+	SPA_PARAM_IO_size,	/**< size of the io area (Int) */
+};
+
+enum spa_param_availability {
+	SPA_PARAM_AVAILABILITY_unknown,	/**< unknown availability */
+	SPA_PARAM_AVAILABILITY_no,	/**< not available */
+	SPA_PARAM_AVAILABILITY_yes,	/**< available */
+};
+
+/** properties for SPA_TYPE_OBJECT_ParamProfile */
+enum spa_param_profile {
+	SPA_PARAM_PROFILE_START,
+	SPA_PARAM_PROFILE_index,	/**< profile index (Int) */
+	SPA_PARAM_PROFILE_name,		/**< profile name (String) */
+	SPA_PARAM_PROFILE_description,	/**< profile description (String) */
+	SPA_PARAM_PROFILE_priority,	/**< profile priority (Int) */
+	SPA_PARAM_PROFILE_available,	/**< availability of the profile
+					  *  (Id enum spa_param_availability) */
+	SPA_PARAM_PROFILE_info,		/**< info (Struct(
+					  *		  Int : n_items,
+					  *		  (String : key,
+					  *		   String : value)*)) */
+	SPA_PARAM_PROFILE_classes,	/**< node classes provided by this profile
+					  *  (Struct(
+					  *	   Int : number of items following
+					  *        Struct(
+					  *           String : class name (eg. "Audio/Source"),
+					  *           Int : number of nodes)*)) */
+};
+
+enum spa_param_port_config_mode {
+	SPA_PARAM_PORT_CONFIG_MODE_none,	/**< no configuration */
+	SPA_PARAM_PORT_CONFIG_MODE_passthrough,	/**< passthrough configuration */
+	SPA_PARAM_PORT_CONFIG_MODE_convert,	/**< convert configuration */
+	SPA_PARAM_PORT_CONFIG_MODE_dsp,		/**< dsp configuration, depending on the external
+						  *  format. For audio, ports will be configured for
+						  *  the given number of channels with F32 format. */
+};
+
+/** properties for SPA_TYPE_OBJECT_ParamPortConfig */
+enum spa_param_port_config {
+	SPA_PARAM_PORT_CONFIG_START,
+	SPA_PARAM_PORT_CONFIG_direction,	/**< direction, input/output (Id enum spa_direction) */
+	SPA_PARAM_PORT_CONFIG_mode,		/**< (Id enum spa_param_port_config_mode) mode */
+	SPA_PARAM_PORT_CONFIG_monitor,		/**< (Bool) enable monitor output ports on input ports */
+	SPA_PARAM_PORT_CONFIG_control,		/**< (Bool) enable control ports */
+	SPA_PARAM_PORT_CONFIG_format,		/**< (Object) format filter */
+};
+
+/** properties for SPA_TYPE_OBJECT_ParamRoute */
+enum spa_param_route {
+	SPA_PARAM_ROUTE_START,
+	SPA_PARAM_ROUTE_index,			/**< index of the routing destination (Int) */
+	SPA_PARAM_ROUTE_direction,		/**< direction, input/output (Id enum spa_direction) */
+	SPA_PARAM_ROUTE_device,			/**< device id (Int) */
+	SPA_PARAM_ROUTE_name,			/**< name of the routing destination (String) */
+	SPA_PARAM_ROUTE_description,		/**< description of the destination (String) */
+	SPA_PARAM_ROUTE_priority,		/**< priority of the destination (Int) */
+	SPA_PARAM_ROUTE_available,		/**< availability of the destination
+						  *  (Id enum spa_param_availability) */
+	SPA_PARAM_ROUTE_info,			/**< info (Struct(
+						  *		  Int : n_items,
+						  *		  (String : key,
+						  *		   String : value)*)) */
+	SPA_PARAM_ROUTE_profiles,		/**< associated profile indexes (Array of Int) */
+	SPA_PARAM_ROUTE_props,			/**< properties SPA_TYPE_OBJECT_Props */
+	SPA_PARAM_ROUTE_devices,		/**< associated device indexes (Array of Int) */
+	SPA_PARAM_ROUTE_profile,		/**< profile id (Int) */
+};
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_H */
diff --git a/third_party/pipewire/spa/param/profiler.h b/third_party/pipewire/spa/param/profiler.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/profiler.h
@@ -0,0 +1,84 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_PROFILER_H
+#define SPA_PARAM_PROFILER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/param/param.h>
+
+/** properties for SPA_TYPE_OBJECT_Profiler */
+enum spa_profiler {
+	SPA_PROFILER_START,
+
+	SPA_PROFILER_START_Driver	= 0x10000,	/**< driver related profiler properties */
+	SPA_PROFILER_info,				/**< Generic info, counter and CPU load,
+							  * (Struct(
+							  *      Long : counter,
+							  *      Float : cpu_load fast,
+							  *      Float : cpu_load medium,
+							  *      Float : cpu_load slow))  */
+	SPA_PROFILER_clock,				/**< clock information
+							  *  (Struct(
+							  *      Int : clock flags,
+							  *      Int : clock id,
+							  *      String: clock name,
+							  *      Long : clock nsec,
+							  *      Fraction : clock rate,
+							  *      Long : clock position,
+							  *      Long : clock duration,
+							  *      Long : clock delay,
+							  *      Double : clock rate_diff,
+							  *      Long : clock next_nsec)) */
+	SPA_PROFILER_driverBlock,			/**< generic driver info block
+							  *  (Struct(
+							  *      Int : driver_id,
+							  *      String : name,
+							  *      Long : driver prev_signal,
+							  *      Long : driver signal,
+							  *      Long : driver awake,
+							  *      Long : driver finish,
+							  *      Int : driver status)) */
+
+	SPA_PROFILER_START_Follower	= 0x20000,	/**< follower related profiler properties */
+	SPA_PROFILER_followerBlock,			/**< generic follower info block
+							  *  (Struct(
+							  *      Int : id,
+							  *      String : name,
+							  *      Long : prev_signal,
+							  *      Long : signal,
+							  *      Long : awake,
+							  *      Long : finish,
+							  *      Int : status)) */
+	SPA_PROFILER_START_CUSTOM	= 0x1000000,
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_PROFILER_H */
diff --git a/third_party/pipewire/spa/param/props.h b/third_party/pipewire/spa/param/props.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/props.h
@@ -0,0 +1,99 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_PROPS_H
+#define SPA_PARAM_PROPS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/param/param.h>
+
+/** properties of SPA_TYPE_OBJECT_PropInfo */
+enum spa_prop_info {
+	SPA_PROP_INFO_START,
+	SPA_PROP_INFO_id,		/**< associated id of the property */
+	SPA_PROP_INFO_name,		/**< name of the property */
+	SPA_PROP_INFO_type,		/**< type and range/enums of property */
+	SPA_PROP_INFO_labels,		/**< labels of property if any, this is a
+					  *  struct with pairs of values, the first one
+					  *  is of the type of the property, the second
+					  *  one is a string with a user readable label
+					  *  for the value. */
+};
+
+/** predefined properties for SPA_TYPE_OBJECT_Props */
+enum spa_prop {
+	SPA_PROP_START,
+
+	SPA_PROP_unknown,		/**< an unknown property */
+
+	SPA_PROP_START_Device	= 0x100,	/**< device related properties */
+	SPA_PROP_device,
+	SPA_PROP_deviceName,
+	SPA_PROP_deviceFd,
+	SPA_PROP_card,
+	SPA_PROP_cardName,
+
+	SPA_PROP_minLatency,
+	SPA_PROP_maxLatency,
+	SPA_PROP_periods,
+	SPA_PROP_periodSize,
+	SPA_PROP_periodEvent,
+	SPA_PROP_live,
+	SPA_PROP_rate,
+	SPA_PROP_quality,
+
+	SPA_PROP_START_Audio	= 0x10000,	/**< audio related properties */
+	SPA_PROP_waveType,
+	SPA_PROP_frequency,
+	SPA_PROP_volume,			/**< a volume (Float), 0.0 silence, 1.0 normal */
+	SPA_PROP_mute,				/**< mute (Bool) */
+	SPA_PROP_patternType,
+	SPA_PROP_ditherType,
+	SPA_PROP_truncate,
+	SPA_PROP_channelVolumes,		/**< a volume array, one volume per
+						  *  channel (Array of Float) */
+	SPA_PROP_volumeBase,			/**< a volume base (Float) */
+	SPA_PROP_volumeStep,			/**< a volume step (Float) */
+
+	SPA_PROP_START_Video	= 0x20000,	/**< video related properties */
+	SPA_PROP_brightness,
+	SPA_PROP_contrast,
+	SPA_PROP_saturation,
+	SPA_PROP_hue,
+	SPA_PROP_gamma,
+	SPA_PROP_exposure,
+	SPA_PROP_gain,
+	SPA_PROP_sharpness,
+
+	SPA_PROP_START_CUSTOM	= 0x1000000,
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_PROPS_H */
diff --git a/third_party/pipewire/spa/param/type-info.h b/third_party/pipewire/spa/param/type-info.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/type-info.h
@@ -0,0 +1,362 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_TYPES_H
+#define SPA_PARAM_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/param/props.h>
+#include <spa/param/format.h>
+#include <spa/buffer/type-info.h>
+
+/* base for parameter object enumerations */
+#define SPA_TYPE_INFO_ParamId		SPA_TYPE_INFO_ENUM_BASE "ParamId"
+#define SPA_TYPE_INFO_PARAM_ID_BASE	SPA_TYPE_INFO_ParamId ":"
+
+static const struct spa_type_info spa_type_param[] = {
+	{ SPA_PARAM_Invalid, SPA_TYPE_None, SPA_TYPE_INFO_PARAM_ID_BASE "Invalid", NULL },
+	{ SPA_PARAM_PropInfo, SPA_TYPE_OBJECT_Props, SPA_TYPE_INFO_PARAM_ID_BASE "PropInfo", NULL },
+	{ SPA_PARAM_Props, SPA_TYPE_OBJECT_Props, SPA_TYPE_INFO_PARAM_ID_BASE "Props", NULL },
+	{ SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format, SPA_TYPE_INFO_PARAM_ID_BASE "EnumFormat", NULL },
+	{ SPA_PARAM_Format, SPA_TYPE_OBJECT_Format, SPA_TYPE_INFO_PARAM_ID_BASE "Format", NULL },
+	{ SPA_PARAM_Buffers, SPA_TYPE_OBJECT_ParamBuffers, SPA_TYPE_INFO_PARAM_ID_BASE "Buffers", NULL },
+	{ SPA_PARAM_Meta, SPA_TYPE_OBJECT_ParamMeta, SPA_TYPE_INFO_PARAM_ID_BASE "Meta", NULL },
+	{ SPA_PARAM_IO, SPA_TYPE_OBJECT_ParamIO, SPA_TYPE_INFO_PARAM_ID_BASE "IO", NULL },
+	{ SPA_PARAM_EnumProfile, SPA_TYPE_OBJECT_ParamProfile, SPA_TYPE_INFO_PARAM_ID_BASE "EnumProfile", NULL },
+	{ SPA_PARAM_Profile, SPA_TYPE_OBJECT_ParamProfile, SPA_TYPE_INFO_PARAM_ID_BASE "Profile", NULL },
+	{ SPA_PARAM_EnumPortConfig, SPA_TYPE_OBJECT_ParamPortConfig, SPA_TYPE_INFO_PARAM_ID_BASE "EnumPortConfig", NULL },
+	{ SPA_PARAM_PortConfig, SPA_TYPE_OBJECT_ParamPortConfig, SPA_TYPE_INFO_PARAM_ID_BASE "PortConfig", NULL },
+	{ SPA_PARAM_EnumRoute, SPA_TYPE_OBJECT_ParamRoute, SPA_TYPE_INFO_PARAM_ID_BASE "EnumRoute", NULL },
+	{ SPA_PARAM_Route, SPA_TYPE_OBJECT_ParamRoute, SPA_TYPE_INFO_PARAM_ID_BASE "Route", NULL },
+	{ SPA_PARAM_Control, SPA_TYPE_Sequence, SPA_TYPE_INFO_PARAM_ID_BASE "Control", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+/* base for parameter objects */
+#define SPA_TYPE_INFO_Param			SPA_TYPE_INFO_OBJECT_BASE "Param"
+#define SPA_TYPE_INFO_PARAM_BASE		SPA_TYPE_INFO_Param ":"
+
+#define SPA_TYPE_INFO_Props			SPA_TYPE_INFO_PARAM_BASE "Props"
+#define SPA_TYPE_INFO_PROPS_BASE		SPA_TYPE_INFO_Props ":"
+
+static const struct spa_type_info spa_type_props[] = {
+	{ SPA_PROP_START, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE, spa_type_param, },
+	{ SPA_PROP_unknown, SPA_TYPE_None, SPA_TYPE_INFO_PROPS_BASE "unknown", NULL },
+	{ SPA_PROP_device, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "device", NULL },
+	{ SPA_PROP_deviceName, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "deviceName", NULL },
+	{ SPA_PROP_deviceFd, SPA_TYPE_Fd, SPA_TYPE_INFO_PROPS_BASE "deviceFd", NULL },
+	{ SPA_PROP_card, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "card", NULL },
+	{ SPA_PROP_cardName, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "cardName", NULL },
+	{ SPA_PROP_minLatency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "minLatency", NULL },
+	{ SPA_PROP_maxLatency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "maxLatency", NULL },
+	{ SPA_PROP_periods, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "periods", NULL },
+	{ SPA_PROP_periodSize, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "periodSize", NULL },
+	{ SPA_PROP_periodEvent, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "periodEvent", NULL },
+	{ SPA_PROP_live, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "live", NULL },
+	{ SPA_PROP_rate, SPA_TYPE_Double, SPA_TYPE_INFO_PROPS_BASE "rate", NULL },
+	{ SPA_PROP_quality, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "quality", NULL },
+
+	{ SPA_PROP_waveType, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "waveType", NULL },
+	{ SPA_PROP_frequency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "frequency", NULL },
+	{ SPA_PROP_volume, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volume", NULL },
+	{ SPA_PROP_mute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "mute", NULL },
+	{ SPA_PROP_patternType, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "patternType", NULL },
+	{ SPA_PROP_ditherType, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "ditherType", NULL },
+	{ SPA_PROP_truncate, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "truncate", NULL },
+	{ SPA_PROP_channelVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "channelVolumes", NULL },
+	{ SPA_PROP_volumeBase, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volumeBase", NULL },
+	{ SPA_PROP_volumeStep, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volumeStep", NULL },
+
+	{ SPA_PROP_brightness, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL },
+	{ SPA_PROP_contrast, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL },
+	{ SPA_PROP_saturation, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "saturation", NULL },
+	{ SPA_PROP_hue, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "hue", NULL },
+	{ SPA_PROP_gamma, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "gamma", NULL },
+	{ SPA_PROP_exposure, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "exposure", NULL },
+	{ SPA_PROP_gain, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "gain", NULL },
+	{ SPA_PROP_sharpness, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "sharpness", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+/** Enum Property info */
+#define SPA_TYPE_INFO_PropInfo			SPA_TYPE_INFO_PARAM_BASE "PropInfo"
+#define SPA_TYPE_INFO_PROP_INFO_BASE		SPA_TYPE_INFO_PropInfo ":"
+
+static const struct spa_type_info spa_type_prop_info[] = {
+	{ SPA_PROP_INFO_START, SPA_TYPE_Id, SPA_TYPE_INFO_PROP_INFO_BASE, spa_type_param, },
+	{ SPA_PROP_INFO_id, SPA_TYPE_Id, SPA_TYPE_INFO_PROP_INFO_BASE "id", spa_type_props },
+	{ SPA_PROP_INFO_name, SPA_TYPE_String, SPA_TYPE_INFO_PROP_INFO_BASE "name", NULL },
+	{ SPA_PROP_INFO_type, SPA_TYPE_Pod, SPA_TYPE_INFO_PROP_INFO_BASE "type", NULL },
+	{ SPA_PROP_INFO_labels, SPA_TYPE_Struct, SPA_TYPE_INFO_PROP_INFO_BASE "labels", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_PARAM_Meta		SPA_TYPE_INFO_PARAM_BASE "Meta"
+#define SPA_TYPE_INFO_PARAM_META_BASE		SPA_TYPE_INFO_PARAM_Meta ":"
+
+static const struct spa_type_info spa_type_param_meta[] = {
+	{ SPA_PARAM_META_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_META_BASE, spa_type_param },
+	{ SPA_PARAM_META_type, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_META_BASE "type", spa_type_meta_type },
+	{ SPA_PARAM_META_size, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_META_BASE "size", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+/** Base for parameters that describe IO areas to exchange data,
+ * control and properties with a node.
+ */
+#define SPA_TYPE_INFO_PARAM_IO		SPA_TYPE_INFO_PARAM_BASE "IO"
+#define SPA_TYPE_INFO_PARAM_IO_BASE		SPA_TYPE_INFO_PARAM_IO ":"
+
+static const struct spa_type_info spa_type_param_io[] = {
+	{ SPA_PARAM_IO_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_IO_BASE, spa_type_param, },
+	{ SPA_PARAM_IO_id, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_IO_BASE "id", spa_type_io },
+	{ SPA_PARAM_IO_size, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_IO_BASE "size", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_Format			SPA_TYPE_INFO_PARAM_BASE "Format"
+#define SPA_TYPE_INFO_FORMAT_BASE		SPA_TYPE_INFO_Format ":"
+
+#define SPA_TYPE_INFO_MediaType		SPA_TYPE_INFO_ENUM_BASE "MediaType"
+#define SPA_TYPE_INFO_MEDIA_TYPE_BASE	SPA_TYPE_INFO_MediaType ":"
+
+#include <spa/param/audio/type-info.h>
+#include <spa/param/video/type-info.h>
+
+static const struct spa_type_info spa_type_media_type[] = {
+	{ SPA_MEDIA_TYPE_unknown, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "unknown", NULL },
+	{ SPA_MEDIA_TYPE_audio,   SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "audio", NULL },
+	{ SPA_MEDIA_TYPE_video,   SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "video", NULL },
+	{ SPA_MEDIA_TYPE_image,   SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "image", NULL },
+	{ SPA_MEDIA_TYPE_binary,  SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "binary", NULL },
+	{ SPA_MEDIA_TYPE_stream,  SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "stream", NULL },
+	{ SPA_MEDIA_TYPE_application,  SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "application", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_MediaSubtype		SPA_TYPE_INFO_ENUM_BASE "MediaSubtype"
+#define SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE	SPA_TYPE_INFO_MediaSubtype ":"
+
+static const struct spa_type_info spa_type_media_subtype[] = {
+	{ SPA_MEDIA_SUBTYPE_unknown, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "unknown", NULL },
+	/* generic subtypes */
+	{ SPA_MEDIA_SUBTYPE_raw, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "raw", NULL },
+	{ SPA_MEDIA_SUBTYPE_dsp, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dsp", NULL },
+	/* audio subtypes */
+	{ SPA_MEDIA_SUBTYPE_mp3, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mp3", NULL },
+	{ SPA_MEDIA_SUBTYPE_aac, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "aac", NULL },
+	{ SPA_MEDIA_SUBTYPE_vorbis, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vorbis", NULL },
+	{ SPA_MEDIA_SUBTYPE_wma, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "wma", NULL },
+	{ SPA_MEDIA_SUBTYPE_ra, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "ra", NULL },
+	{ SPA_MEDIA_SUBTYPE_sbc, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "sbc", NULL },
+	{ SPA_MEDIA_SUBTYPE_adpcm, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "adpcm", NULL },
+	{ SPA_MEDIA_SUBTYPE_g723, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g723", NULL },
+	{ SPA_MEDIA_SUBTYPE_g726, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g726", NULL },
+	{ SPA_MEDIA_SUBTYPE_g729, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g729", NULL },
+	{ SPA_MEDIA_SUBTYPE_amr, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "amr", NULL },
+	{ SPA_MEDIA_SUBTYPE_gsm, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "gsm", NULL },
+	/* video subtypes */
+	{ SPA_MEDIA_SUBTYPE_h264, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h264", NULL },
+	{ SPA_MEDIA_SUBTYPE_mjpg, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mjpg", NULL },
+	{ SPA_MEDIA_SUBTYPE_dv, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dv", NULL },
+	{ SPA_MEDIA_SUBTYPE_mpegts, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpegts", NULL },
+	{ SPA_MEDIA_SUBTYPE_h263, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h263", NULL },
+	{ SPA_MEDIA_SUBTYPE_mpeg1, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg1", NULL },
+	{ SPA_MEDIA_SUBTYPE_mpeg2, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg2", NULL },
+	{ SPA_MEDIA_SUBTYPE_mpeg4, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg4", NULL },
+	{ SPA_MEDIA_SUBTYPE_xvid, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "xvid", NULL },
+	{ SPA_MEDIA_SUBTYPE_vc1, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vc1", NULL },
+	{ SPA_MEDIA_SUBTYPE_vp8, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vp8", NULL },
+	{ SPA_MEDIA_SUBTYPE_vp9, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vp9", NULL },
+	{ SPA_MEDIA_SUBTYPE_bayer, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "bayer", NULL },
+	/* image subtypes */
+	{ SPA_MEDIA_SUBTYPE_jpeg, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "jpeg", NULL },
+	/* stream subtypes */
+	{ SPA_MEDIA_SUBTYPE_midi, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "midi", NULL },
+	/* application subtypes */
+	{ SPA_MEDIA_SUBTYPE_control, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "control", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_FormatAudio		SPA_TYPE_INFO_FORMAT_BASE "Audio"
+#define SPA_TYPE_INFO_FORMAT_AUDIO_BASE		SPA_TYPE_INFO_FormatAudio ":"
+
+#define SPA_TYPE_INFO_FormatVideo		SPA_TYPE_INFO_FORMAT_BASE "Video"
+#define SPA_TYPE_INFO_FORMAT_VIDEO_BASE		SPA_TYPE_INFO_FormatVideo ":"
+
+#define SPA_TYPE_INFO_FORMAT_VIDEO_H264		SPA_TYPE_INFO_FORMAT_VIDEO_BASE "H264"
+#define SPA_TYPE_INFO_FORMAT_VIDEO_H264_BASE	SPA_TYPE_INFO_FORMAT_VIDEO_H264 ":"
+
+static const struct spa_type_info spa_type_format[] = {
+	{ SPA_FORMAT_START, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_BASE, spa_type_param, },
+
+	{ SPA_FORMAT_mediaType, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_BASE "mediaType",
+		spa_type_media_type, },
+	{ SPA_FORMAT_mediaSubtype, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_BASE "mediaSubtype",
+		spa_type_media_subtype, },
+
+	{ SPA_FORMAT_AUDIO_format, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "format",
+		spa_type_audio_format },
+	{ SPA_FORMAT_AUDIO_flags, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "flags",
+		spa_type_audio_flags },
+	{ SPA_FORMAT_AUDIO_rate, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "rate", NULL },
+	{ SPA_FORMAT_AUDIO_channels, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "channels", NULL },
+	{ SPA_FORMAT_AUDIO_position, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "position",
+		spa_type_audio_channel },
+
+	{ SPA_FORMAT_VIDEO_format, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "format",
+		spa_type_video_format, },
+	{ SPA_FORMAT_VIDEO_modifier, SPA_TYPE_Long, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "modifier", NULL },
+	{ SPA_FORMAT_VIDEO_size,  SPA_TYPE_Rectangle, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "size", NULL },
+	{ SPA_FORMAT_VIDEO_framerate, SPA_TYPE_Fraction, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "framerate", NULL },
+	{ SPA_FORMAT_VIDEO_maxFramerate, SPA_TYPE_Fraction, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "maxFramerate", NULL },
+	{ SPA_FORMAT_VIDEO_views, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "views", NULL },
+	{ SPA_FORMAT_VIDEO_interlaceMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "interlaceMode", NULL },
+	{ SPA_FORMAT_VIDEO_pixelAspectRatio, SPA_TYPE_Fraction, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "pixelAspectRatio", NULL },
+	{ SPA_FORMAT_VIDEO_multiviewMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "multiviewMode", NULL },
+	{ SPA_FORMAT_VIDEO_multiviewFlags, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "multiviewFlags", NULL },
+	{ SPA_FORMAT_VIDEO_chromaSite, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "chromaSite", NULL },
+	{ SPA_FORMAT_VIDEO_colorRange, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorRange", NULL },
+	{ SPA_FORMAT_VIDEO_colorMatrix, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorMatrix", NULL },
+	{ SPA_FORMAT_VIDEO_transferFunction, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "transferFunction", NULL },
+	{ SPA_FORMAT_VIDEO_colorPrimaries, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorPrimaries", NULL },
+	{ SPA_FORMAT_VIDEO_profile, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "profile", NULL },
+	{ SPA_FORMAT_VIDEO_level, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "level", NULL },
+
+	{ SPA_FORMAT_VIDEO_H264_streamFormat, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_H264_BASE "streamFormat", NULL },
+	{ SPA_FORMAT_VIDEO_H264_alignment, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_H264_BASE "alignment", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_PARAM_Buffers			SPA_TYPE_INFO_PARAM_BASE "Buffers"
+#define SPA_TYPE_INFO_PARAM_BUFFERS_BASE		SPA_TYPE_INFO_PARAM_Buffers ":"
+
+#define SPA_TYPE_INFO_PARAM_BlockInfo			SPA_TYPE_INFO_PARAM_BUFFERS_BASE "BlockInfo"
+#define SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE		SPA_TYPE_INFO_PARAM_BlockInfo ":"
+
+static const struct spa_type_info spa_type_param_buffers[] = {
+	{ SPA_PARAM_BUFFERS_START,    SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_BUFFERS_BASE, spa_type_param, },
+	{ SPA_PARAM_BUFFERS_buffers,  SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BUFFERS_BASE "buffers", NULL },
+	{ SPA_PARAM_BUFFERS_blocks,   SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BUFFERS_BASE "blocks", NULL },
+	{ SPA_PARAM_BUFFERS_size,     SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "size", NULL },
+	{ SPA_PARAM_BUFFERS_stride,   SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "stride", NULL },
+	{ SPA_PARAM_BUFFERS_align,    SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "align", NULL },
+	{ SPA_PARAM_BUFFERS_dataType, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "dataType", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_ParamAvailability		SPA_TYPE_INFO_ENUM_BASE "ParamAvailability"
+#define SPA_TYPE_INFO_PARAM_AVAILABILITY_BASE	SPA_TYPE_INFO_ParamAvailability ":"
+
+static const struct spa_type_info spa_type_param_availability[] = {
+	{ SPA_PARAM_AVAILABILITY_unknown, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_AVAILABILITY_BASE "unknown", NULL },
+	{ SPA_PARAM_AVAILABILITY_no, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_AVAILABILITY_BASE "no", NULL },
+	{ SPA_PARAM_AVAILABILITY_yes, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_AVAILABILITY_BASE "yes", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_PARAM_Profile		SPA_TYPE_INFO_PARAM_BASE "Profile"
+#define SPA_TYPE_INFO_PARAM_PROFILE_BASE	SPA_TYPE_INFO_PARAM_Profile ":"
+
+static const struct spa_type_info spa_type_param_profile[] = {
+	{ SPA_PARAM_PROFILE_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PROFILE_BASE, spa_type_param, },
+	{ SPA_PARAM_PROFILE_index, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PROFILE_BASE "index", NULL },
+	{ SPA_PARAM_PROFILE_name, SPA_TYPE_String, SPA_TYPE_INFO_PARAM_PROFILE_BASE "name", NULL },
+	{ SPA_PARAM_PROFILE_description, SPA_TYPE_String, SPA_TYPE_INFO_PARAM_PROFILE_BASE "description", NULL },
+	{ SPA_PARAM_PROFILE_priority, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PROFILE_BASE "priority", NULL },
+	{ SPA_PARAM_PROFILE_available, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PROFILE_BASE "available", spa_type_param_availability, },
+	{ SPA_PARAM_PROFILE_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_PROFILE_BASE "info", NULL, },
+	{ SPA_PARAM_PROFILE_classes, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_PROFILE_BASE "classes", NULL, },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_ParamPortConfigMode		SPA_TYPE_INFO_ENUM_BASE "ParamPortConfigMode"
+#define SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE	SPA_TYPE_INFO_ParamPortConfigMode ":"
+
+static const struct spa_type_info spa_type_param_port_config_mode[] = {
+	{ SPA_PARAM_PORT_CONFIG_MODE_none, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE "none", NULL },
+	{ SPA_PARAM_PORT_CONFIG_MODE_passthrough, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE "passthrough", NULL },
+	{ SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE "convert", NULL },
+	{ SPA_PARAM_PORT_CONFIG_MODE_dsp, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE "dsp", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#define SPA_TYPE_INFO_PARAM_PortConfig		SPA_TYPE_INFO_PARAM_BASE "PortConfig"
+#define SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE	SPA_TYPE_INFO_PARAM_PortConfig ":"
+
+static const struct spa_type_info spa_type_param_port_config[] = {
+	{ SPA_PARAM_PORT_CONFIG_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE, spa_type_param, },
+	{ SPA_PARAM_PORT_CONFIG_direction, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "direction", spa_type_direction, },
+	{ SPA_PARAM_PORT_CONFIG_mode, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "mode", spa_type_param_port_config_mode },
+	{ SPA_PARAM_PORT_CONFIG_monitor, SPA_TYPE_Bool, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "monitor", NULL },
+	{ SPA_PARAM_PORT_CONFIG_control, SPA_TYPE_Bool, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "control", NULL },
+	{ SPA_PARAM_PORT_CONFIG_format, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "format", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+
+#define SPA_TYPE_INFO_PARAM_Route		SPA_TYPE_INFO_PARAM_BASE "Route"
+#define SPA_TYPE_INFO_PARAM_ROUTE_BASE		SPA_TYPE_INFO_PARAM_Route ":"
+
+static const struct spa_type_info spa_type_param_route[] = {
+	{ SPA_PARAM_ROUTE_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_ROUTE_BASE, spa_type_param, },
+	{ SPA_PARAM_ROUTE_index, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "index", NULL, },
+	{ SPA_PARAM_ROUTE_direction, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_ROUTE_BASE "direction", spa_type_direction, },
+	{ SPA_PARAM_ROUTE_device, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "device", NULL, },
+	{ SPA_PARAM_ROUTE_name, SPA_TYPE_String, SPA_TYPE_INFO_PARAM_ROUTE_BASE "name", NULL, },
+	{ SPA_PARAM_ROUTE_description, SPA_TYPE_String, SPA_TYPE_INFO_PARAM_ROUTE_BASE "description", NULL, },
+	{ SPA_PARAM_ROUTE_priority, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "priority", NULL, },
+	{ SPA_PARAM_ROUTE_available, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_ROUTE_BASE "available", spa_type_param_availability, },
+	{ SPA_PARAM_ROUTE_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_ROUTE_BASE "info", NULL, },
+	{ SPA_PARAM_ROUTE_profiles, SPA_TYPE_Array, SPA_TYPE_INFO_PARAM_ROUTE_BASE "profiles", NULL, },
+	{ SPA_PARAM_ROUTE_props, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_ROUTE_BASE "props", NULL, },
+	{ SPA_PARAM_ROUTE_devices, SPA_TYPE_Array, SPA_TYPE_INFO_PARAM_ROUTE_BASE "devices", NULL, },
+	{ SPA_PARAM_ROUTE_profile, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "profile", NULL, },
+	{ 0, 0, NULL, NULL },
+};
+
+#include <spa/param/profiler.h>
+
+#define SPA_TYPE_INFO_Profiler		SPA_TYPE_INFO_OBJECT_BASE "Profiler"
+#define SPA_TYPE_INFO_PROFILER_BASE	SPA_TYPE_INFO_Profiler ":"
+
+static const struct spa_type_info spa_type_profiler[] = {
+	{ SPA_PROFILER_START, SPA_TYPE_Id, SPA_TYPE_INFO_PROFILER_BASE, spa_type_param, },
+	{ SPA_PROFILER_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "info", NULL, },
+	{ SPA_PROFILER_clock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "clock", NULL, },
+	{ SPA_PROFILER_driverBlock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "driverBlock", NULL, },
+	{ SPA_PROFILER_followerBlock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "followerBlock", NULL, },
+	{ 0, 0, NULL, NULL },
+};
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_TYPES_H */
diff --git a/third_party/pipewire/spa/param/video/chroma.h b/third_party/pipewire/spa/param/video/chroma.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/video/chroma.h
@@ -0,0 +1,60 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_VIDEO_CHROMA_H
+#define SPA_VIDEO_CHROMA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Various Chroma sitings.
+ * @SPA_VIDEO_CHROMA_SITE_UNKNOWN: unknown cositing
+ * @SPA_VIDEO_CHROMA_SITE_NONE: no cositing
+ * @SPA_VIDEO_CHROMA_SITE_H_COSITED: chroma is horizontally cosited
+ * @SPA_VIDEO_CHROMA_SITE_V_COSITED: chroma is vertically cosited
+ * @SPA_VIDEO_CHROMA_SITE_ALT_LINE: choma samples are sited on alternate lines
+ * @SPA_VIDEO_CHROMA_SITE_COSITED: chroma samples cosited with luma samples
+ * @SPA_VIDEO_CHROMA_SITE_JPEG: jpeg style cositing, also for mpeg1 and mjpeg
+ * @SPA_VIDEO_CHROMA_SITE_MPEG2: mpeg2 style cositing
+ * @SPA_VIDEO_CHROMA_SITE_DV: DV style cositing
+ */
+enum spa_video_chroma_site {
+	SPA_VIDEO_CHROMA_SITE_UNKNOWN = 0,
+	SPA_VIDEO_CHROMA_SITE_NONE = (1 << 0),
+	SPA_VIDEO_CHROMA_SITE_H_COSITED = (1 << 1),
+	SPA_VIDEO_CHROMA_SITE_V_COSITED = (1 << 2),
+	SPA_VIDEO_CHROMA_SITE_ALT_LINE = (1 << 3),
+	/* some common chroma cositing */
+	SPA_VIDEO_CHROMA_SITE_COSITED = (SPA_VIDEO_CHROMA_SITE_H_COSITED | SPA_VIDEO_CHROMA_SITE_V_COSITED),
+	SPA_VIDEO_CHROMA_SITE_JPEG = (SPA_VIDEO_CHROMA_SITE_NONE),
+	SPA_VIDEO_CHROMA_SITE_MPEG2 = (SPA_VIDEO_CHROMA_SITE_H_COSITED),
+	SPA_VIDEO_CHROMA_SITE_DV = (SPA_VIDEO_CHROMA_SITE_COSITED | SPA_VIDEO_CHROMA_SITE_ALT_LINE),
+};
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_VIDEO_CHROMA_H */
diff --git a/third_party/pipewire/spa/param/video/color.h b/third_party/pipewire/spa/param/video/color.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/video/color.h
@@ -0,0 +1,162 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_VIDEO_COLOR_H
+#define SPA_VIDEO_COLOR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * spa_video_color_range:
+ * @SPA_VIDEO_COLOR_RANGE_UNKNOWN: unknown range
+ * @SPA_VIDEO_COLOR_RANGE_0_255: [0..255] for 8 bit components
+ * @SPA_VIDEO_COLOR_RANGE_16_235: [16..235] for 8 bit components. Chroma has
+ *                 [16..240] range.
+ *
+ * Possible color range values. These constants are defined for 8 bit color
+ * values and can be scaled for other bit depths.
+ */
+enum spa_video_color_range {
+	SPA_VIDEO_COLOR_RANGE_UNKNOWN = 0,
+	SPA_VIDEO_COLOR_RANGE_0_255,
+	SPA_VIDEO_COLOR_RANGE_16_235
+};
+
+/**
+ * spa_video_color_matrix:
+ * @SPA_VIDEO_COLOR_MATRIX_UNKNOWN: unknown matrix
+ * @SPA_VIDEO_COLOR_MATRIX_RGB: identity matrix
+ * @SPA_VIDEO_COLOR_MATRIX_FCC: FCC color matrix
+ * @SPA_VIDEO_COLOR_MATRIX_BT709: ITU-R BT.709 color matrix
+ * @SPA_VIDEO_COLOR_MATRIX_BT601: ITU-R BT.601 color matrix
+ * @SPA_VIDEO_COLOR_MATRIX_SMPTE240M: SMPTE 240M color matrix
+ * @SPA_VIDEO_COLOR_MATRIX_BT2020: ITU-R BT.2020 color matrix. Since: 1.6.
+ *
+ * The color matrix is used to convert between Y'PbPr and
+ * non-linear RGB (R'G'B')
+ */
+enum spa_video_color_matrix {
+	SPA_VIDEO_COLOR_MATRIX_UNKNOWN = 0,
+	SPA_VIDEO_COLOR_MATRIX_RGB,
+	SPA_VIDEO_COLOR_MATRIX_FCC,
+	SPA_VIDEO_COLOR_MATRIX_BT709,
+	SPA_VIDEO_COLOR_MATRIX_BT601,
+	SPA_VIDEO_COLOR_MATRIX_SMPTE240M,
+	SPA_VIDEO_COLOR_MATRIX_BT2020
+};
+
+/**
+ * spa_video_transfer_function:
+ * @SPA_VIDEO_TRANSFER_UNKNOWN: unknown transfer function
+ * @SPA_VIDEO_TRANSFER_GAMMA10: linear RGB, gamma 1.0 curve
+ * @SPA_VIDEO_TRANSFER_GAMMA18: Gamma 1.8 curve
+ * @SPA_VIDEO_TRANSFER_GAMMA20: Gamma 2.0 curve
+ * @SPA_VIDEO_TRANSFER_GAMMA22: Gamma 2.2 curve
+ * @SPA_VIDEO_TRANSFER_BT709: Gamma 2.2 curve with a linear segment in the lower
+ *                           range
+ * @SPA_VIDEO_TRANSFER_SMPTE240M: Gamma 2.2 curve with a linear segment in the
+ *                               lower range
+ * @SPA_VIDEO_TRANSFER_SRGB: Gamma 2.4 curve with a linear segment in the lower
+ *                          range
+ * @SPA_VIDEO_TRANSFER_GAMMA28: Gamma 2.8 curve
+ * @SPA_VIDEO_TRANSFER_LOG100: Logarithmic transfer characteristic
+ *                             100:1 range
+ * @SPA_VIDEO_TRANSFER_LOG316: Logarithmic transfer characteristic
+ *                             316.22777:1 range
+ * @SPA_VIDEO_TRANSFER_BT2020_12: Gamma 2.2 curve with a linear segment in the lower
+ *                                range. Used for BT.2020 with 12 bits per
+ *                                component. Since: 1.6.
+ * @SPA_VIDEO_TRANSFER_ADOBERGB: Gamma 2.19921875. Since: 1.8
+ *
+ * The video transfer function defines the formula for converting between
+ * non-linear RGB (R'G'B') and linear RGB
+ */
+enum spa_video_transfer_function {
+	SPA_VIDEO_TRANSFER_UNKNOWN = 0,
+	SPA_VIDEO_TRANSFER_GAMMA10,
+	SPA_VIDEO_TRANSFER_GAMMA18,
+	SPA_VIDEO_TRANSFER_GAMMA20,
+	SPA_VIDEO_TRANSFER_GAMMA22,
+	SPA_VIDEO_TRANSFER_BT709,
+	SPA_VIDEO_TRANSFER_SMPTE240M,
+	SPA_VIDEO_TRANSFER_SRGB,
+	SPA_VIDEO_TRANSFER_GAMMA28,
+	SPA_VIDEO_TRANSFER_LOG100,
+	SPA_VIDEO_TRANSFER_LOG316,
+	SPA_VIDEO_TRANSFER_BT2020_12,
+	SPA_VIDEO_TRANSFER_ADOBERGB
+};
+
+/**
+ * spa_video_color_primaries:
+ * @SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN: unknown color primaries
+ * @SPA_VIDEO_COLOR_PRIMARIES_BT709: BT709 primaries
+ * @SPA_VIDEO_COLOR_PRIMARIES_BT470M: BT470M primaries
+ * @SPA_VIDEO_COLOR_PRIMARIES_BT470BG: BT470BG primaries
+ * @SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M: SMPTE170M primaries
+ * @SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M: SMPTE240M primaries
+ * @SPA_VIDEO_COLOR_PRIMARIES_FILM: Generic film
+ * @SPA_VIDEO_COLOR_PRIMARIES_BT2020: BT2020 primaries. Since: 1.6.
+ * @SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB: Adobe RGB primaries. Since: 1.8
+ *
+ * The color primaries define the how to transform linear RGB values to and from
+ * the CIE XYZ colorspace.
+ */
+enum spa_video_color_primaries {
+	SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN = 0,
+	SPA_VIDEO_COLOR_PRIMARIES_BT709,
+	SPA_VIDEO_COLOR_PRIMARIES_BT470M,
+	SPA_VIDEO_COLOR_PRIMARIES_BT470BG,
+	SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M,
+	SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M,
+	SPA_VIDEO_COLOR_PRIMARIES_FILM,
+	SPA_VIDEO_COLOR_PRIMARIES_BT2020,
+	SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB
+};
+
+/**
+ * spa_video_colorimetry:
+ * @range: the color range. This is the valid range for the samples.
+ *         It is used to convert the samples to Y'PbPr values.
+ * @matrix: the color matrix. Used to convert between Y'PbPr and
+ *          non-linear RGB (R'G'B')
+ * @transfer: the transfer function. used to convert between R'G'B' and RGB
+ * @primaries: color primaries. used to convert between R'G'B' and CIE XYZ
+ *
+ * Structure describing the color info.
+ */
+struct spa_video_colorimetry {
+	enum spa_video_color_range range;
+	enum spa_video_color_matrix matrix;
+	enum spa_video_transfer_function transfer;
+	enum spa_video_color_primaries primaries;
+};
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_VIDEO_COLOR_H */
diff --git a/third_party/pipewire/spa/param/video/encoded.h b/third_party/pipewire/spa/param/video/encoded.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/video/encoded.h
@@ -0,0 +1,65 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_VIDEO_ENCODED_H
+#define SPA_VIDEO_ENCODED_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/param/format.h>
+
+enum spa_h264_stream_format {
+	SPA_H264_STREAM_FORMAT_UNKNOWN = 0,
+	SPA_H264_STREAM_FORMAT_AVC,
+	SPA_H264_STREAM_FORMAT_AVC3,
+	SPA_H264_STREAM_FORMAT_BYTESTREAM
+};
+
+enum spa_h264_alignment {
+	SPA_H264_ALIGNMENT_UNKNOWN = 0,
+	SPA_H264_ALIGNMENT_AU,
+	SPA_H264_ALIGNMENT_NAL
+};
+
+struct spa_video_info_h264 {
+	struct spa_rectangle size;
+	struct spa_fraction framerate;
+	struct spa_fraction max_framerate;
+	enum spa_h264_stream_format stream_format;
+	enum spa_h264_alignment alignment;
+};
+
+struct spa_video_info_mjpg {
+	struct spa_rectangle size;
+	struct spa_fraction framerate;
+	struct spa_fraction max_framerate;
+};
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_VIDEO_ENCODED_H */
diff --git a/third_party/pipewire/spa/param/video/format-utils.h b/third_party/pipewire/spa/param/video/format-utils.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/video/format-utils.h
@@ -0,0 +1,167 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_VIDEO_FORMAT_UTILS_H
+#define SPA_PARAM_VIDEO_FORMAT_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/pod/parser.h>
+#include <spa/pod/builder.h>
+#include <spa/param/video/format.h>
+#include <spa/param/format-utils.h>
+
+static inline int
+spa_format_video_raw_parse(const struct spa_pod *format,
+			   struct spa_video_info_raw *info)
+{
+	return spa_pod_parse_object(format,
+		SPA_TYPE_OBJECT_Format, NULL,
+		SPA_FORMAT_VIDEO_format,		SPA_POD_Id(&info->format),
+		SPA_FORMAT_VIDEO_modifier,		SPA_POD_OPT_Long(&info->modifier),
+		SPA_FORMAT_VIDEO_size,			SPA_POD_Rectangle(&info->size),
+		SPA_FORMAT_VIDEO_framerate,		SPA_POD_Fraction(&info->framerate),
+		SPA_FORMAT_VIDEO_maxFramerate,		SPA_POD_OPT_Fraction(&info->max_framerate),
+		SPA_FORMAT_VIDEO_views,			SPA_POD_OPT_Int(&info->views),
+		SPA_FORMAT_VIDEO_interlaceMode,		SPA_POD_OPT_Id(&info->interlace_mode),
+		SPA_FORMAT_VIDEO_pixelAspectRatio,	SPA_POD_OPT_Fraction(&info->pixel_aspect_ratio),
+		SPA_FORMAT_VIDEO_multiviewMode,		SPA_POD_OPT_Id(&info->multiview_mode),
+		SPA_FORMAT_VIDEO_multiviewFlags,	SPA_POD_OPT_Id(&info->multiview_flags),
+		SPA_FORMAT_VIDEO_chromaSite,		SPA_POD_OPT_Id(&info->chroma_site),
+		SPA_FORMAT_VIDEO_colorRange,		SPA_POD_OPT_Id(&info->color_range),
+		SPA_FORMAT_VIDEO_colorMatrix,		SPA_POD_OPT_Id(&info->color_matrix),
+		SPA_FORMAT_VIDEO_transferFunction,	SPA_POD_OPT_Id(&info->transfer_function),
+		SPA_FORMAT_VIDEO_colorPrimaries,	SPA_POD_OPT_Id(&info->color_primaries));
+}
+
+static inline int
+spa_format_video_dsp_parse(const struct spa_pod *format,
+			   struct spa_video_info_dsp *info)
+{
+	return spa_pod_parse_object(format,
+		SPA_TYPE_OBJECT_Format, NULL,
+		SPA_FORMAT_VIDEO_format,		SPA_POD_Id(&info->format),
+		SPA_FORMAT_VIDEO_modifier,		SPA_POD_OPT_Long(&info->modifier));
+}
+
+static inline struct spa_pod *
+spa_format_video_raw_build(struct spa_pod_builder *builder, uint32_t id,
+			   struct spa_video_info_raw *info)
+{
+	struct spa_pod_frame f;
+	spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id);
+	spa_pod_builder_add(builder,
+			SPA_FORMAT_mediaType,		SPA_POD_Id(SPA_MEDIA_TYPE_video),
+			SPA_FORMAT_mediaSubtype,	SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
+			SPA_FORMAT_VIDEO_format,	SPA_POD_Id(info->format),
+			SPA_FORMAT_VIDEO_size,		SPA_POD_Rectangle(&info->size),
+			SPA_FORMAT_VIDEO_framerate,	SPA_POD_Fraction(&info->framerate),
+			0);
+	if (info->modifier != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_modifier,	SPA_POD_Long(info->modifier), 0);
+	if (info->max_framerate.denom != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_maxFramerate,	SPA_POD_Fraction(info->max_framerate), 0);
+	if (info->views != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_views,		SPA_POD_Int(info->views), 0);
+	if (info->interlace_mode != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_interlaceMode,	SPA_POD_Id(info->interlace_mode), 0);
+	if (info->pixel_aspect_ratio.denom != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_pixelAspectRatio,SPA_POD_Fraction(info->pixel_aspect_ratio), 0);
+	if (info->multiview_mode != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_multiviewMode,	SPA_POD_Id(info->multiview_mode), 0);
+	if (info->multiview_flags != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_multiviewFlags,SPA_POD_Id(info->multiview_flags), 0);
+	if (info->chroma_site != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_chromaSite,	SPA_POD_Id(info->chroma_site), 0);
+	if (info->color_range != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_colorRange,	SPA_POD_Id(info->color_range), 0);
+	if (info->color_matrix != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_colorMatrix,	SPA_POD_Id(info->color_matrix), 0);
+	if (info->transfer_function != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_transferFunction,SPA_POD_Id(info->transfer_function), 0);
+	if (info->color_primaries != 0)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_colorPrimaries,SPA_POD_Id(info->color_primaries), 0);
+	return (struct spa_pod*)spa_pod_builder_pop(builder, &f);
+}
+
+static inline struct spa_pod *
+spa_format_video_dsp_build(struct spa_pod_builder *builder, uint32_t id,
+			   struct spa_video_info_dsp *info)
+{
+	struct spa_pod_frame f;
+	spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id);
+	spa_pod_builder_add(builder,
+			SPA_FORMAT_mediaType,		SPA_POD_Id(SPA_MEDIA_TYPE_video),
+			SPA_FORMAT_mediaSubtype,	SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp),
+			SPA_FORMAT_VIDEO_format,	SPA_POD_Id(info->format),
+			0);
+	if (info->modifier)
+		spa_pod_builder_add(builder,
+			SPA_FORMAT_VIDEO_modifier,	SPA_POD_Long(info->modifier), 0);
+	return (struct spa_pod*)spa_pod_builder_pop(builder, &f);
+}
+
+static inline int
+spa_format_video_h264_parse(const struct spa_pod *format,
+			    struct spa_video_info_h264 *info)
+{
+	return spa_pod_parse_object(format,
+			SPA_TYPE_OBJECT_Format, NULL,
+			SPA_FORMAT_VIDEO_size,			SPA_POD_OPT_Rectangle(&info->size),
+			SPA_FORMAT_VIDEO_framerate,		SPA_POD_OPT_Fraction(&info->framerate),
+			SPA_FORMAT_VIDEO_maxFramerate,		SPA_POD_OPT_Fraction(&info->max_framerate),
+			SPA_FORMAT_VIDEO_H264_streamFormat,	SPA_POD_OPT_Id(&info->stream_format),
+			SPA_FORMAT_VIDEO_H264_alignment,	SPA_POD_OPT_Id(&info->alignment));
+}
+
+static inline int
+spa_format_video_mjpg_parse(const struct spa_pod *format,
+			    struct spa_video_info_mjpg *info)
+{
+	return spa_pod_parse_object(format,
+			SPA_TYPE_OBJECT_Format, NULL,
+			SPA_FORMAT_VIDEO_size,		SPA_POD_OPT_Rectangle(&info->size),
+			SPA_FORMAT_VIDEO_framerate,	SPA_POD_OPT_Fraction(&info->framerate),
+			SPA_FORMAT_VIDEO_maxFramerate,	SPA_POD_OPT_Fraction(&info->max_framerate));
+}
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_VIDEO_FORMAT_UTILS_H */
diff --git a/third_party/pipewire/spa/param/video/format.h b/third_party/pipewire/spa/param/video/format.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/video/format.h
@@ -0,0 +1,50 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PARAM_VIDEO_FORMAT_H
+#define SPA_PARAM_VIDEO_FORMAT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/param/video/raw.h>
+#include <spa/param/video/encoded.h>
+
+struct spa_video_info {
+	uint32_t media_type;
+	uint32_t media_subtype;
+	union {
+		struct spa_video_info_raw raw;
+		struct spa_video_info_dsp dsp;
+		struct spa_video_info_h264 h264;
+		struct spa_video_info_mjpg mjpg;
+	} info;
+};
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_PARAM_VIDEO_FORMAT_H */
diff --git a/third_party/pipewire/spa/param/video/multiview.h b/third_party/pipewire/spa/param/video/multiview.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/video/multiview.h
@@ -0,0 +1,140 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_VIDEO_MULTIVIEW_H
+#define SPA_VIDEO_MULTIVIEW_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * spa_video_multiview_mode:
+ * @SPA_VIDEO_MULTIVIEW_MODE_NONE: A special value indicating
+ * no multiview information. Used in spa_video_info and other places to
+ * indicate that no specific multiview handling has been requested or
+ * provided. This value is never carried on caps.
+ * @SPA_VIDEO_MULTIVIEW_MODE_MONO: All frames are monoscopic.
+ * @SPA_VIDEO_MULTIVIEW_MODE_LEFT: All frames represent a left-eye view.
+ * @SPA_VIDEO_MULTIVIEW_MODE_RIGHT: All frames represent a right-eye view.
+ * @SPA_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE: Left and right eye views are
+ * provided in the left and right half of the frame respectively.
+ * @SPA_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX: Left and right eye
+ * views are provided in the left and right half of the frame, but
+ * have been sampled using quincunx method, with half-pixel offset
+ * between the 2 views.
+ * @SPA_VIDEO_MULTIVIEW_MODE_COLUMN_INTERLEAVED: Alternating vertical
+ * columns of pixels represent the left and right eye view respectively.
+ * @SPA_VIDEO_MULTIVIEW_MODE_ROW_INTERLEAVED: Alternating horizontal
+ * rows of pixels represent the left and right eye view respectively.
+ * @SPA_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM: The top half of the frame
+ * contains the left eye, and the bottom half the right eye.
+ * @SPA_VIDEO_MULTIVIEW_MODE_CHECKERBOARD: Pixels are arranged with
+ * alternating pixels representing left and right eye views in a
+ * checkerboard fashion.
+ * @SPA_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME: Left and right eye views
+ * are provided in separate frames alternately.
+ * @SPA_VIDEO_MULTIVIEW_MODE_MULTIVIEW_FRAME_BY_FRAME: Multiple
+ * independent views are provided in separate frames in sequence.
+ * This method only applies to raw video buffers at the moment.
+ * Specific view identification is via the #spa_video_multiview_meta
+ * on raw video buffers.
+ * @SPA_VIDEO_MULTIVIEW_MODE_SEPARATED: Multiple views are
+ * provided as separate #spa_data framebuffers attached to each
+ * #spa_buffer, described by the #spa_video_multiview_meta
+ *
+ * All possible stereoscopic 3D and multiview representations.
+ * In conjunction with #soa_video_multiview_flags, describes how
+ * multiview content is being transported in the stream.
+ */
+enum spa_video_multiview_mode {
+	SPA_VIDEO_MULTIVIEW_MODE_NONE = -1,
+	SPA_VIDEO_MULTIVIEW_MODE_MONO = 0,
+	/* Single view modes */
+	SPA_VIDEO_MULTIVIEW_MODE_LEFT,
+	SPA_VIDEO_MULTIVIEW_MODE_RIGHT,
+	/* Stereo view modes */
+	SPA_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE,
+	SPA_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX,
+	SPA_VIDEO_MULTIVIEW_MODE_COLUMN_INTERLEAVED,
+	SPA_VIDEO_MULTIVIEW_MODE_ROW_INTERLEAVED,
+	SPA_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM,
+	SPA_VIDEO_MULTIVIEW_MODE_CHECKERBOARD,
+	/* Padding for new frame packing modes */
+
+	SPA_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME = 32,
+	/* Multivew mode(s) */
+	SPA_VIDEO_MULTIVIEW_MODE_MULTIVIEW_FRAME_BY_FRAME,
+	SPA_VIDEO_MULTIVIEW_MODE_SEPARATED
+	    /* future expansion for annotated modes */
+};
+
+/**
+ * spa_video_multiview_flags:
+ * @SPA_VIDEO_MULTIVIEW_FLAGS_NONE: No flags
+ * @SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST: For stereo streams, the
+ *     normal arrangement of left and right views is reversed.
+ * @SPA_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED: The left view is vertically
+ *     mirrored.
+ * @SPA_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED: The left view is horizontally
+ *     mirrored.
+ * @SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED: The right view is
+ *     vertically mirrored.
+ * @SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED: The right view is
+ *     horizontally mirrored.
+ * @SPA_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT: For frame-packed
+ *     multiview modes, indicates that the individual
+ *     views have been encoded with half the true width or height
+ *     and should be scaled back up for display. This flag
+ *     is used for overriding input layout interpretation
+ *     by adjusting pixel-aspect-ratio.
+ *     For side-by-side, column interleaved or checkerboard packings, the
+ *     pixel width will be doubled. For row interleaved and top-bottom
+ *     encodings, pixel height will be doubled.
+ * @SPA_VIDEO_MULTIVIEW_FLAGS_MIXED_MONO: The video stream contains both
+ *     mono and multiview portions, signalled on each buffer by the
+ *     absence or presence of the @SPA_VIDEO_BUFFER_FLAG_MULTIPLE_VIEW
+ *     buffer flag.
+ *
+ * spa_video_multiview_flags are used to indicate extra properties of a
+ * stereo/multiview stream beyond the frame layout and buffer mapping
+ * that is conveyed in the #spa_video_multiview_mode.
+ */
+enum spa_video_multiview_flags {
+	SPA_VIDEO_MULTIVIEW_FLAGS_NONE = 0,
+	SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST = (1 << 0),
+	SPA_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED = (1 << 1),
+	SPA_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED = (1 << 2),
+	SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED = (1 << 3),
+	SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED = (1 << 4),
+	SPA_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT = (1 << 14),
+	SPA_VIDEO_MULTIVIEW_FLAGS_MIXED_MONO = (1 << 15)
+};
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_VIDEO_MULTIVIEW_H */
diff --git a/third_party/pipewire/spa/param/video/raw.h b/third_party/pipewire/spa/param/video/raw.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/video/raw.h
@@ -0,0 +1,221 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_VIDEO_RAW_H
+#define SPA_VIDEO_RAW_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/param/video/chroma.h>
+#include <spa/param/video/color.h>
+#include <spa/param/video/multiview.h>
+
+#define SPA_VIDEO_MAX_PLANES 4
+#define SPA_VIDEO_MAX_COMPONENTS 4
+
+enum spa_video_format {
+	SPA_VIDEO_FORMAT_UNKNOWN,
+	SPA_VIDEO_FORMAT_ENCODED,
+
+	SPA_VIDEO_FORMAT_I420,
+	SPA_VIDEO_FORMAT_YV12,
+	SPA_VIDEO_FORMAT_YUY2,
+	SPA_VIDEO_FORMAT_UYVY,
+	SPA_VIDEO_FORMAT_AYUV,
+	SPA_VIDEO_FORMAT_RGBx,
+	SPA_VIDEO_FORMAT_BGRx,
+	SPA_VIDEO_FORMAT_xRGB,
+	SPA_VIDEO_FORMAT_xBGR,
+	SPA_VIDEO_FORMAT_RGBA,
+	SPA_VIDEO_FORMAT_BGRA,
+	SPA_VIDEO_FORMAT_ARGB,
+	SPA_VIDEO_FORMAT_ABGR,
+	SPA_VIDEO_FORMAT_RGB,
+	SPA_VIDEO_FORMAT_BGR,
+	SPA_VIDEO_FORMAT_Y41B,
+	SPA_VIDEO_FORMAT_Y42B,
+	SPA_VIDEO_FORMAT_YVYU,
+	SPA_VIDEO_FORMAT_Y444,
+	SPA_VIDEO_FORMAT_v210,
+	SPA_VIDEO_FORMAT_v216,
+	SPA_VIDEO_FORMAT_NV12,
+	SPA_VIDEO_FORMAT_NV21,
+	SPA_VIDEO_FORMAT_GRAY8,
+	SPA_VIDEO_FORMAT_GRAY16_BE,
+	SPA_VIDEO_FORMAT_GRAY16_LE,
+	SPA_VIDEO_FORMAT_v308,
+	SPA_VIDEO_FORMAT_RGB16,
+	SPA_VIDEO_FORMAT_BGR16,
+	SPA_VIDEO_FORMAT_RGB15,
+	SPA_VIDEO_FORMAT_BGR15,
+	SPA_VIDEO_FORMAT_UYVP,
+	SPA_VIDEO_FORMAT_A420,
+	SPA_VIDEO_FORMAT_RGB8P,
+	SPA_VIDEO_FORMAT_YUV9,
+	SPA_VIDEO_FORMAT_YVU9,
+	SPA_VIDEO_FORMAT_IYU1,
+	SPA_VIDEO_FORMAT_ARGB64,
+	SPA_VIDEO_FORMAT_AYUV64,
+	SPA_VIDEO_FORMAT_r210,
+	SPA_VIDEO_FORMAT_I420_10BE,
+	SPA_VIDEO_FORMAT_I420_10LE,
+	SPA_VIDEO_FORMAT_I422_10BE,
+	SPA_VIDEO_FORMAT_I422_10LE,
+	SPA_VIDEO_FORMAT_Y444_10BE,
+	SPA_VIDEO_FORMAT_Y444_10LE,
+	SPA_VIDEO_FORMAT_GBR,
+	SPA_VIDEO_FORMAT_GBR_10BE,
+	SPA_VIDEO_FORMAT_GBR_10LE,
+	SPA_VIDEO_FORMAT_NV16,
+	SPA_VIDEO_FORMAT_NV24,
+	SPA_VIDEO_FORMAT_NV12_64Z32,
+	SPA_VIDEO_FORMAT_A420_10BE,
+	SPA_VIDEO_FORMAT_A420_10LE,
+	SPA_VIDEO_FORMAT_A422_10BE,
+	SPA_VIDEO_FORMAT_A422_10LE,
+	SPA_VIDEO_FORMAT_A444_10BE,
+	SPA_VIDEO_FORMAT_A444_10LE,
+	SPA_VIDEO_FORMAT_NV61,
+	SPA_VIDEO_FORMAT_P010_10BE,
+	SPA_VIDEO_FORMAT_P010_10LE,
+	SPA_VIDEO_FORMAT_IYU2,
+	SPA_VIDEO_FORMAT_VYUY,
+	SPA_VIDEO_FORMAT_GBRA,
+	SPA_VIDEO_FORMAT_GBRA_10BE,
+	SPA_VIDEO_FORMAT_GBRA_10LE,
+	SPA_VIDEO_FORMAT_GBR_12BE,
+	SPA_VIDEO_FORMAT_GBR_12LE,
+	SPA_VIDEO_FORMAT_GBRA_12BE,
+	SPA_VIDEO_FORMAT_GBRA_12LE,
+	SPA_VIDEO_FORMAT_I420_12BE,
+	SPA_VIDEO_FORMAT_I420_12LE,
+	SPA_VIDEO_FORMAT_I422_12BE,
+	SPA_VIDEO_FORMAT_I422_12LE,
+	SPA_VIDEO_FORMAT_Y444_12BE,
+	SPA_VIDEO_FORMAT_Y444_12LE,
+
+	SPA_VIDEO_FORMAT_RGBA_F16,
+	SPA_VIDEO_FORMAT_RGBA_F32,
+
+	/* Aliases */
+	SPA_VIDEO_FORMAT_DSP_F32 = SPA_VIDEO_FORMAT_RGBA_F32,
+};
+
+/**
+ * spa_video_flags:
+ * @SPA_VIDEO_FLAG_NONE: no flags
+ * @SPA_VIDEO_FLAG_VARIABLE_FPS: a variable fps is selected, fps_n and fps_d
+ *     denote the maximum fps of the video
+ * @SPA_VIDEO_FLAG_PREMULTIPLIED_ALPHA: Each color has been scaled by the alpha
+ *     value.
+ *
+ * Extra video flags
+ */
+enum spa_video_flags {
+	SPA_VIDEO_FLAG_NONE = 0,
+	SPA_VIDEO_FLAG_VARIABLE_FPS = (1 << 0),
+	SPA_VIDEO_FLAG_PREMULTIPLIED_ALPHA = (1 << 1)
+};
+
+/**
+ * spa_video_interlace_mode:
+ * @SPA_VIDEO_INTERLACE_MODE_PROGRESSIVE: all frames are progressive
+ * @SPA_VIDEO_INTERLACE_MODE_INTERLEAVED: 2 fields are interleaved in one video
+ *     frame. Extra buffer flags describe the field order.
+ * @SPA_VIDEO_INTERLACE_MODE_MIXED: frames contains both interlaced and
+ *     progressive video, the buffer flags describe the frame and fields.
+ * @SPA_VIDEO_INTERLACE_MODE_FIELDS: 2 fields are stored in one buffer, use the
+ *     frame ID to get access to the required field. For multiview (the
+ *     'views' property > 1) the fields of view N can be found at frame ID
+ *     (N * 2) and (N * 2) + 1.
+ *     Each field has only half the amount of lines as noted in the
+ *     height property. This mode requires multiple spa_data
+ *     to describe the fields.
+ *
+ * The possible values of the #spa_video_interlace_mode describing the interlace
+ * mode of the stream.
+ */
+enum spa_video_interlace_mode {
+	SPA_VIDEO_INTERLACE_MODE_PROGRESSIVE = 0,
+	SPA_VIDEO_INTERLACE_MODE_INTERLEAVED,
+	SPA_VIDEO_INTERLACE_MODE_MIXED,
+	SPA_VIDEO_INTERLACE_MODE_FIELDS
+};
+
+/**
+ * spa_video_info_raw:
+ * @format: the format
+ * @modifier: format modifier
+ * @size: the frame size of the video
+ * @framerate: the framerate of the video 0/1 means variable rate
+ * @max_framerate: the maximum framerate of the video. This is only valid when
+ *             @framerate is 0/1
+ * @views: the number of views in this video
+ * @interlace_mode: the interlace mode
+ * @pixel_aspect_ratio: The pixel aspect ratio
+ * @multiview_mode: multiview mode
+ * @multiview_flags: multiview flags
+ * @chroma_site: the chroma siting
+ * @color_range: the color range. This is the valid range for the samples.
+ *         It is used to convert the samples to Y'PbPr values.
+ * @color_matrix: the color matrix. Used to convert between Y'PbPr and
+ *         non-linear RGB (R'G'B')
+ * @transfer_function: the transfer function. used to convert between R'G'B' and RGB
+ * @color_primaries: color primaries. used to convert between R'G'B' and CIE XYZ
+ */
+struct spa_video_info_raw {
+	enum spa_video_format format;
+	int64_t modifier;
+	struct spa_rectangle size;
+	struct spa_fraction framerate;
+	struct spa_fraction max_framerate;
+	uint32_t views;
+	enum spa_video_interlace_mode interlace_mode;
+	struct spa_fraction pixel_aspect_ratio;
+	enum spa_video_multiview_mode multiview_mode;
+	enum spa_video_multiview_flags multiview_flags;
+	enum spa_video_chroma_site chroma_site;
+	enum spa_video_color_range color_range;
+	enum spa_video_color_matrix color_matrix;
+	enum spa_video_transfer_function transfer_function;
+	enum spa_video_color_primaries color_primaries;
+};
+
+#define SPA_VIDEO_INFO_RAW_INIT(...)	(struct spa_video_info_raw) { __VA_ARGS__ }
+
+struct spa_video_info_dsp {
+	enum spa_video_format format;
+	int64_t modifier;
+};
+
+#define SPA_VIDEO_INFO_DSP_INIT(...)	(struct spa_video_info_dsp) { __VA_ARGS__ }
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_VIDEO_RAW_H */
diff --git a/third_party/pipewire/spa/param/video/type-info.h b/third_party/pipewire/spa/param/video/type-info.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/param/video/type-info.h
@@ -0,0 +1,124 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_VIDEO_TYPES_H
+#define SPA_VIDEO_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/param/video/raw.h>
+
+#define SPA_TYPE_INFO_VideoFormat		SPA_TYPE_INFO_ENUM_BASE "VideoFormat"
+#define SPA_TYPE_INFO_VIDEO_FORMAT_BASE		SPA_TYPE_INFO_VideoFormat ":"
+
+static const struct spa_type_info spa_type_video_format[] = {
+	{ SPA_VIDEO_FORMAT_ENCODED,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "encoded", NULL },
+	{ SPA_VIDEO_FORMAT_I420,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420", NULL },
+	{ SPA_VIDEO_FORMAT_YV12,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YV12", NULL },
+	{ SPA_VIDEO_FORMAT_YUY2,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YUY2", NULL },
+	{ SPA_VIDEO_FORMAT_UYVY,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UYVY", NULL },
+	{ SPA_VIDEO_FORMAT_AYUV,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "AYUV", NULL },
+	{ SPA_VIDEO_FORMAT_RGBx,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBx", NULL },
+	{ SPA_VIDEO_FORMAT_BGRx,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRx", NULL },
+	{ SPA_VIDEO_FORMAT_xRGB,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xRGB", NULL },
+	{ SPA_VIDEO_FORMAT_xBGR,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xBGR", NULL },
+	{ SPA_VIDEO_FORMAT_RGBA,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA", NULL },
+	{ SPA_VIDEO_FORMAT_BGRA,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRA", NULL },
+	{ SPA_VIDEO_FORMAT_ARGB,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB", NULL },
+	{ SPA_VIDEO_FORMAT_ABGR,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ABGR", NULL },
+	{ SPA_VIDEO_FORMAT_RGB,		SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB", NULL },
+	{ SPA_VIDEO_FORMAT_BGR,		SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR", NULL },
+	{ SPA_VIDEO_FORMAT_Y41B,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y41B", NULL },
+	{ SPA_VIDEO_FORMAT_Y42B,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y42B", NULL },
+	{ SPA_VIDEO_FORMAT_YVYU,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YVYU", NULL },
+	{ SPA_VIDEO_FORMAT_Y444,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444", NULL },
+	{ SPA_VIDEO_FORMAT_v210,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v210", NULL },
+	{ SPA_VIDEO_FORMAT_v216,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v216", NULL },
+	{ SPA_VIDEO_FORMAT_NV12,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV12", NULL },
+	{ SPA_VIDEO_FORMAT_NV21,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV21", NULL },
+	{ SPA_VIDEO_FORMAT_GRAY8,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY8", NULL },
+	{ SPA_VIDEO_FORMAT_GRAY16_BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY16_BE", NULL },
+	{ SPA_VIDEO_FORMAT_GRAY16_LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY16_LE", NULL },
+	{ SPA_VIDEO_FORMAT_v308,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v308", NULL },
+	{ SPA_VIDEO_FORMAT_RGB16,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB16", NULL },
+	{ SPA_VIDEO_FORMAT_BGR16,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR16", NULL },
+	{ SPA_VIDEO_FORMAT_RGB15,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB15", NULL },
+	{ SPA_VIDEO_FORMAT_BGR15,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR15", NULL },
+	{ SPA_VIDEO_FORMAT_UYVP,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UYVP", NULL },
+	{ SPA_VIDEO_FORMAT_A420,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420", NULL },
+	{ SPA_VIDEO_FORMAT_RGB8P,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB8P", NULL },
+	{ SPA_VIDEO_FORMAT_YUV9,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YUV9", NULL },
+	{ SPA_VIDEO_FORMAT_YVU9,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YVU9", NULL },
+	{ SPA_VIDEO_FORMAT_IYU1,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "IYU1", NULL },
+	{ SPA_VIDEO_FORMAT_ARGB64,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB64", NULL },
+	{ SPA_VIDEO_FORMAT_AYUV64,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "AYUV64", NULL },
+	{ SPA_VIDEO_FORMAT_r210,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "r210", NULL },
+	{ SPA_VIDEO_FORMAT_I420_10BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_10BE", NULL },
+	{ SPA_VIDEO_FORMAT_I420_10LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_10LE", NULL },
+	{ SPA_VIDEO_FORMAT_I422_10BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_10BE", NULL },
+	{ SPA_VIDEO_FORMAT_I422_10LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_10LE", NULL },
+	{ SPA_VIDEO_FORMAT_Y444_10BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_10BE", NULL },
+	{ SPA_VIDEO_FORMAT_Y444_10LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_10LE", NULL },
+	{ SPA_VIDEO_FORMAT_GBR,		SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR", NULL },
+	{ SPA_VIDEO_FORMAT_GBR_10BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_10BE", NULL },
+	{ SPA_VIDEO_FORMAT_GBR_10LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_10LE", NULL },
+	{ SPA_VIDEO_FORMAT_NV16,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV16", NULL },
+	{ SPA_VIDEO_FORMAT_NV24,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV24", NULL },
+	{ SPA_VIDEO_FORMAT_NV12_64Z32,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV12_64Z32", NULL },
+	{ SPA_VIDEO_FORMAT_A420_10BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420_10BE", NULL },
+	{ SPA_VIDEO_FORMAT_A420_10LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420_10LE", NULL },
+	{ SPA_VIDEO_FORMAT_A422_10BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A422_10BE", NULL },
+	{ SPA_VIDEO_FORMAT_A422_10LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A422_10LE", NULL },
+	{ SPA_VIDEO_FORMAT_A444_10BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A444_10BE", NULL },
+	{ SPA_VIDEO_FORMAT_A444_10LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A444_10LE", NULL },
+	{ SPA_VIDEO_FORMAT_NV61,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV61", NULL },
+	{ SPA_VIDEO_FORMAT_P010_10BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "P010_10BE", NULL },
+	{ SPA_VIDEO_FORMAT_P010_10LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "P010_10LE", NULL },
+	{ SPA_VIDEO_FORMAT_IYU2,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "IYU2", NULL },
+	{ SPA_VIDEO_FORMAT_VYUY,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "VYUY", NULL },
+	{ SPA_VIDEO_FORMAT_GBRA,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA", NULL },
+	{ SPA_VIDEO_FORMAT_GBRA_10BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_10BE", NULL },
+	{ SPA_VIDEO_FORMAT_GBRA_10LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_10LE", NULL },
+	{ SPA_VIDEO_FORMAT_GBR_12BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_12BE", NULL },
+	{ SPA_VIDEO_FORMAT_GBR_12LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_12LE", NULL },
+	{ SPA_VIDEO_FORMAT_GBRA_12BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_12BE", NULL },
+	{ SPA_VIDEO_FORMAT_GBRA_12LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_12LE", NULL },
+	{ SPA_VIDEO_FORMAT_I420_12BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_12BE", NULL },
+	{ SPA_VIDEO_FORMAT_I420_12LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_12LE", NULL },
+	{ SPA_VIDEO_FORMAT_I422_12BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_12BE", NULL },
+	{ SPA_VIDEO_FORMAT_I422_12LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_12LE", NULL },
+	{ SPA_VIDEO_FORMAT_Y444_12BE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_12BE", NULL },
+	{ SPA_VIDEO_FORMAT_Y444_12LE,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_12LE", NULL },
+	{ SPA_VIDEO_FORMAT_RGBA_F16,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA_F16", NULL },
+	{ SPA_VIDEO_FORMAT_RGBA_F32,	SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA_F32", NULL },
+	{ 0, 0, NULL, NULL },
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_VIDEO_RAW_TYPES_H */
diff --git a/third_party/pipewire/spa/pod/builder.h b/third_party/pipewire/spa/pod/builder.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/pod/builder.h
@@ -0,0 +1,671 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_POD_BUILDER_H
+#define SPA_POD_BUILDER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+#include <spa/utils/hook.h>
+#include <spa/pod/iter.h>
+#include <spa/pod/vararg.h>
+
+struct spa_pod_builder_state {
+	uint32_t offset;
+#define SPA_POD_BUILDER_FLAG_BODY	(1<<0)
+#define SPA_POD_BUILDER_FLAG_FIRST	(1<<1)
+	uint32_t flags;
+	struct spa_pod_frame *frame;
+};
+
+struct spa_pod_builder;
+
+struct spa_pod_builder_callbacks {
+#define SPA_VERSION_POD_BUILDER_CALLBACKS 0
+	uint32_t version;
+
+	int (*overflow) (void *data, uint32_t size);
+};
+
+struct spa_pod_builder {
+	void *data;
+	uint32_t size;
+	uint32_t _padding;
+	struct spa_pod_builder_state state;
+	struct spa_callbacks callbacks;
+};
+
+#define SPA_POD_BUILDER_INIT(buffer,size)  (struct spa_pod_builder){ buffer, size, }
+
+static inline void
+spa_pod_builder_get_state(struct spa_pod_builder *builder, struct spa_pod_builder_state *state)
+{
+	*state = builder->state;
+}
+
+static inline void
+spa_pod_builder_set_callbacks(struct spa_pod_builder *builder,
+		const struct spa_pod_builder_callbacks *callbacks, void *data)
+{
+	builder->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
+}
+
+static inline void
+spa_pod_builder_reset(struct spa_pod_builder *builder, struct spa_pod_builder_state *state)
+{
+	struct spa_pod_frame *f;
+	uint32_t size = builder->state.offset - state->offset;
+	builder->state = *state;
+	for (f = builder->state.frame; f ; f = f->parent)
+		f->pod.size -= size;
+}
+
+static inline void spa_pod_builder_init(struct spa_pod_builder *builder, void *data, uint32_t size)
+{
+	*builder = SPA_POD_BUILDER_INIT(data, size);
+}
+
+static inline struct spa_pod *
+spa_pod_builder_deref(struct spa_pod_builder *builder, uint32_t offset)
+{
+	uint32_t size = builder->size;
+	if (offset + 8 <= size) {
+		struct spa_pod *pod = SPA_MEMBER(builder->data, offset, struct spa_pod);
+		if (offset + SPA_POD_SIZE(pod) <= size)
+			return pod;
+	}
+	return NULL;
+}
+
+static inline struct spa_pod *
+spa_pod_builder_frame(struct spa_pod_builder *builder, struct spa_pod_frame *frame)
+{
+	if (frame->offset + SPA_POD_SIZE(&frame->pod) <= builder->size)
+		return SPA_MEMBER(builder->data, frame->offset, struct spa_pod);
+	return NULL;
+}
+
+static inline void
+spa_pod_builder_push(struct spa_pod_builder *builder,
+		     struct spa_pod_frame *frame,
+		     const struct spa_pod *pod,
+		     uint32_t offset)
+{
+	frame->pod = *pod;
+	frame->offset = offset;
+	frame->parent = builder->state.frame;
+	frame->flags = builder->state.flags;
+	builder->state.frame = frame;
+
+	if (frame->pod.type == SPA_TYPE_Array || frame->pod.type == SPA_TYPE_Choice)
+		builder->state.flags = SPA_POD_BUILDER_FLAG_FIRST | SPA_POD_BUILDER_FLAG_BODY;
+}
+
+static inline int spa_pod_builder_raw(struct spa_pod_builder *builder, const void *data, uint32_t size)
+{
+	int res = 0;
+	struct spa_pod_frame *f;
+	uint32_t offset = builder->state.offset;
+
+	if (offset + size > builder->size) {
+		res = -ENOSPC;
+		spa_callbacks_call_res(&builder->callbacks, struct spa_pod_builder_callbacks, res,
+				overflow, 0, offset + size);
+	}
+	if (res == 0 && data)
+		memcpy(SPA_MEMBER(builder->data, offset, void), data, size);
+
+	builder->state.offset += size;
+
+	for (f = builder->state.frame; f ; f = f->parent)
+		f->pod.size += size;
+
+	return res;
+}
+
+static inline int spa_pod_builder_pad(struct spa_pod_builder *builder, uint32_t size)
+{
+	uint64_t zeroes = 0;
+	size = SPA_ROUND_UP_N(size, 8) - size;
+	return size ? spa_pod_builder_raw(builder, &zeroes, size) : 0;
+}
+
+static inline int
+spa_pod_builder_raw_padded(struct spa_pod_builder *builder, const void *data, uint32_t size)
+{
+	int r, res = spa_pod_builder_raw(builder, data, size);
+	if ((r = spa_pod_builder_pad(builder, size)) < 0)
+		res = r;
+	return res;
+}
+
+static inline void *spa_pod_builder_pop(struct spa_pod_builder *builder, struct spa_pod_frame *frame)
+{
+	struct spa_pod *pod;
+
+	if ((pod = (struct spa_pod*)spa_pod_builder_frame(builder, frame)) != NULL)
+		*pod = frame->pod;
+
+	builder->state.frame = frame->parent;
+	builder->state.flags = frame->flags;
+	spa_pod_builder_pad(builder, builder->state.offset);
+	return pod;
+}
+
+static inline int
+spa_pod_builder_primitive(struct spa_pod_builder *builder, const struct spa_pod *p)
+{
+	const void *data;
+	uint32_t size;
+	int r, res;
+
+	if (builder->state.flags == SPA_POD_BUILDER_FLAG_BODY) {
+		data = SPA_POD_BODY_CONST(p);
+		size = SPA_POD_BODY_SIZE(p);
+	} else {
+		data = p;
+		size = SPA_POD_SIZE(p);
+		SPA_FLAG_CLEAR(builder->state.flags, SPA_POD_BUILDER_FLAG_FIRST);
+	}
+	res = spa_pod_builder_raw(builder, data, size);
+	if (builder->state.flags != SPA_POD_BUILDER_FLAG_BODY)
+		if ((r = spa_pod_builder_pad(builder, size)) < 0)
+			res = r;
+	return res;
+}
+
+#define SPA_POD_INIT(size,type) (struct spa_pod) { size, type }
+
+#define SPA_POD_INIT_None() SPA_POD_INIT(0, SPA_TYPE_None)
+
+static inline int spa_pod_builder_none(struct spa_pod_builder *builder)
+{
+	const struct spa_pod p = SPA_POD_INIT_None();
+	return spa_pod_builder_primitive(builder, &p);
+}
+
+#define SPA_POD_INIT_Bool(val) (struct spa_pod_bool){ { sizeof(uint32_t), SPA_TYPE_Bool }, val ? 1 : 0, 0 }
+
+static inline int spa_pod_builder_bool(struct spa_pod_builder *builder, bool val)
+{
+	const struct spa_pod_bool p = SPA_POD_INIT_Bool(val);
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+#define SPA_POD_INIT_Id(val) (struct spa_pod_id){ { sizeof(uint32_t), SPA_TYPE_Id }, (uint32_t)val, 0 }
+
+static inline int spa_pod_builder_id(struct spa_pod_builder *builder, uint32_t val)
+{
+	const struct spa_pod_id p = SPA_POD_INIT_Id(val);
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+#define SPA_POD_INIT_Int(val) (struct spa_pod_int){ { sizeof(int32_t), SPA_TYPE_Int }, (int32_t)val, 0 }
+
+static inline int spa_pod_builder_int(struct spa_pod_builder *builder, int32_t val)
+{
+	const struct spa_pod_int p = SPA_POD_INIT_Int(val);
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+#define SPA_POD_INIT_Long(val) (struct spa_pod_long){ { sizeof(int64_t), SPA_TYPE_Long }, (int64_t)val }
+
+static inline int spa_pod_builder_long(struct spa_pod_builder *builder, int64_t val)
+{
+	const struct spa_pod_long p = SPA_POD_INIT_Long(val);
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+#define SPA_POD_INIT_Float(val) (struct spa_pod_float){ { sizeof(float), SPA_TYPE_Float }, val }
+
+static inline int spa_pod_builder_float(struct spa_pod_builder *builder, float val)
+{
+	const struct spa_pod_float p = SPA_POD_INIT_Float(val);
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+#define SPA_POD_INIT_Double(val) (struct spa_pod_double){ { sizeof(double), SPA_TYPE_Double }, val }
+
+static inline int spa_pod_builder_double(struct spa_pod_builder *builder, double val)
+{
+	const struct spa_pod_double p = SPA_POD_INIT_Double(val);
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+#define SPA_POD_INIT_String(len) (struct spa_pod_string){ { len, SPA_TYPE_String } }
+
+static inline int
+spa_pod_builder_write_string(struct spa_pod_builder *builder, const char *str, uint32_t len)
+{
+	int r, res;
+	res = spa_pod_builder_raw(builder, str, len);
+	if ((r = spa_pod_builder_raw(builder, "", 1)) < 0)
+		res = r;
+	if ((r = spa_pod_builder_pad(builder, builder->state.offset)) < 0)
+		res = r;
+	return res;
+}
+
+static inline int
+spa_pod_builder_string_len(struct spa_pod_builder *builder, const char *str, uint32_t len)
+{
+	const struct spa_pod_string p = SPA_POD_INIT_String(len+1);
+	int r, res = spa_pod_builder_raw(builder, &p, sizeof(p));
+	if ((r = spa_pod_builder_write_string(builder, str, len)) < 0)
+		res = r;
+	return res;
+}
+
+static inline int spa_pod_builder_string(struct spa_pod_builder *builder, const char *str)
+{
+	uint32_t len = str ? strlen(str) : 0;
+	return spa_pod_builder_string_len(builder, str ? str : "", len);
+}
+
+#define SPA_POD_INIT_Bytes(len) (struct spa_pod_bytes){ { len, SPA_TYPE_Bytes } }
+
+static inline int
+spa_pod_builder_bytes(struct spa_pod_builder *builder, const void *bytes, uint32_t len)
+{
+	const struct spa_pod_bytes p = SPA_POD_INIT_Bytes(len);
+	int r, res = spa_pod_builder_raw(builder, &p, sizeof(p));
+	if ((r = spa_pod_builder_raw_padded(builder, bytes, len)) < 0)
+		res = r;
+	return res;
+}
+static inline void *
+spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len)
+{
+	uint32_t offset = builder->state.offset;
+	if (spa_pod_builder_bytes(builder, NULL, len) < 0)
+		return NULL;
+	return SPA_POD_BODY(spa_pod_builder_deref(builder, offset));
+}
+
+#define SPA_POD_INIT_Pointer(type,value) (struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { type, 0, value } }
+
+static inline int
+spa_pod_builder_pointer(struct spa_pod_builder *builder, uint32_t type, const void *val)
+{
+	const struct spa_pod_pointer p = SPA_POD_INIT_Pointer(type, val);
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+#define SPA_POD_INIT_Fd(fd) (struct spa_pod_fd){ { sizeof(int64_t), SPA_TYPE_Fd }, fd }
+
+static inline int spa_pod_builder_fd(struct spa_pod_builder *builder, int64_t fd)
+{
+	const struct spa_pod_fd p = SPA_POD_INIT_Fd(fd);
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+#define SPA_POD_INIT_Rectangle(val) (struct spa_pod_rectangle){ { sizeof(struct spa_rectangle), SPA_TYPE_Rectangle }, val }
+
+static inline int
+spa_pod_builder_rectangle(struct spa_pod_builder *builder, uint32_t width, uint32_t height)
+{
+	const struct spa_pod_rectangle p = SPA_POD_INIT_Rectangle(SPA_RECTANGLE(width, height));
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+#define SPA_POD_INIT_Fraction(val) (struct spa_pod_fraction){ { sizeof(struct spa_fraction), SPA_TYPE_Fraction }, val }
+
+static inline int
+spa_pod_builder_fraction(struct spa_pod_builder *builder, uint32_t num, uint32_t denom)
+{
+	const struct spa_pod_fraction p = SPA_POD_INIT_Fraction(SPA_FRACTION(num, denom));
+	return spa_pod_builder_primitive(builder, &p.pod);
+}
+
+static inline int
+spa_pod_builder_push_array(struct spa_pod_builder *builder, struct spa_pod_frame *frame)
+{
+	const struct spa_pod_array p =
+	    { {sizeof(struct spa_pod_array_body) - sizeof(struct spa_pod), SPA_TYPE_Array},
+	    {{0, 0}} };
+	uint32_t offset = builder->state.offset;
+	int res = spa_pod_builder_raw(builder, &p, sizeof(p) - sizeof(struct spa_pod));
+	spa_pod_builder_push(builder, frame, &p.pod, offset);
+	return res;
+}
+
+static inline int
+spa_pod_builder_array(struct spa_pod_builder *builder,
+		      uint32_t child_size, uint32_t child_type, uint32_t n_elems, const void *elems)
+{
+	const struct spa_pod_array p = {
+		{(uint32_t)(sizeof(struct spa_pod_array_body) + n_elems * child_size), SPA_TYPE_Array},
+		{{child_size, child_type}}
+	};
+	int r, res = spa_pod_builder_raw(builder, &p, sizeof(p));
+	if ((r = spa_pod_builder_raw_padded(builder, elems, child_size * n_elems)) < 0)
+		res = r;
+	return res;
+}
+
+#define SPA_POD_INIT_CHOICE_BODY(type, flags, child_size, child_type)				\
+	(struct spa_pod_choice_body) { type, flags, { child_size, child_type }}
+
+#define SPA_POD_INIT_Choice(type, ctype, child_type, n_vals, ...)				\
+	(struct { struct spa_pod_choice choice; ctype vals[n_vals];})				\
+	{ { { n_vals * sizeof(ctype) + sizeof(struct spa_pod_choice_body), SPA_TYPE_Choice },	\
+		{ type, 0, { sizeof(ctype), child_type } } }, { __VA_ARGS__ } }
+
+static inline int
+spa_pod_builder_push_choice(struct spa_pod_builder *builder, struct spa_pod_frame *frame,
+		uint32_t type, uint32_t flags)
+{
+	const struct spa_pod_choice p =
+	    { {sizeof(struct spa_pod_choice_body) - sizeof(struct spa_pod), SPA_TYPE_Choice},
+	    { type, flags, {0, 0}} };
+	uint32_t offset = builder->state.offset;
+	int res = spa_pod_builder_raw(builder, &p, sizeof(p) - sizeof(struct spa_pod));
+	spa_pod_builder_push(builder, frame, &p.pod, offset);
+	return res;
+}
+
+#define SPA_POD_INIT_Struct(size) (struct spa_pod_struct){ { size, SPA_TYPE_Struct } }
+
+static inline int
+spa_pod_builder_push_struct(struct spa_pod_builder *builder, struct spa_pod_frame *frame)
+{
+	const struct spa_pod_struct p = SPA_POD_INIT_Struct(0);
+	uint32_t offset = builder->state.offset;
+	int res = spa_pod_builder_raw(builder, &p, sizeof(p));
+	spa_pod_builder_push(builder, frame, &p.pod, offset);
+	return res;
+}
+
+#define SPA_POD_INIT_Object(size,type,id,...)	(struct spa_pod_object){ { size, SPA_TYPE_Object }, { type, id }, ##__VA_ARGS__ }
+
+static inline int
+spa_pod_builder_push_object(struct spa_pod_builder *builder, struct spa_pod_frame *frame,
+		uint32_t type, uint32_t id)
+{
+	const struct spa_pod_object p =
+	    SPA_POD_INIT_Object(sizeof(struct spa_pod_object_body), type, id);
+	uint32_t offset = builder->state.offset;
+	int res = spa_pod_builder_raw(builder, &p, sizeof(p));
+	spa_pod_builder_push(builder, frame, &p.pod, offset);
+	return res;
+}
+
+#define SPA_POD_INIT_Prop(key,flags,size,type)	\
+	(struct spa_pod_prop){ key, flags, { size, type } }
+
+static inline int
+spa_pod_builder_prop(struct spa_pod_builder *builder, uint32_t key, uint32_t flags)
+{
+	const struct { uint32_t key; uint32_t flags; } p = { key, flags };
+	return spa_pod_builder_raw(builder, &p, sizeof(p));
+}
+
+#define SPA_POD_INIT_Sequence(size,unit)	\
+	(struct spa_pod_sequence){ { size, SPA_TYPE_Sequence}, {unit, 0 } }
+
+static inline int
+spa_pod_builder_push_sequence(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t unit)
+{
+	const struct spa_pod_sequence p =
+	    SPA_POD_INIT_Sequence(sizeof(struct spa_pod_sequence_body), unit);
+	uint32_t offset = builder->state.offset;
+	int res = spa_pod_builder_raw(builder, &p, sizeof(p));
+	spa_pod_builder_push(builder, frame, &p.pod, offset);
+	return res;
+}
+
+static inline uint32_t
+spa_pod_builder_control(struct spa_pod_builder *builder, uint32_t offset, uint32_t type)
+{
+	const struct { uint32_t offset; uint32_t type; } p = { offset, type };
+	return spa_pod_builder_raw(builder, &p, sizeof(p));
+}
+
+static inline uint32_t spa_choice_from_id(char id)
+{
+	switch (id) {
+	case 'r':
+		return SPA_CHOICE_Range;
+	case 's':
+		return SPA_CHOICE_Step;
+	case 'e':
+		return SPA_CHOICE_Enum;
+	case 'f':
+		return SPA_CHOICE_Flags;
+	case 'n':
+	default:
+		return SPA_CHOICE_None;
+	}
+}
+
+#define SPA_POD_BUILDER_COLLECT(builder,type,args)				\
+do {										\
+	switch (type) {								\
+	case 'b':								\
+		spa_pod_builder_bool(builder, !!va_arg(args, int));		\
+		break;								\
+	case 'I':								\
+		spa_pod_builder_id(builder, va_arg(args, uint32_t));		\
+		break;								\
+	case 'i':								\
+		spa_pod_builder_int(builder, va_arg(args, int));		\
+		break;								\
+	case 'l':								\
+		spa_pod_builder_long(builder, va_arg(args, int64_t));		\
+		break;								\
+	case 'f':								\
+		spa_pod_builder_float(builder, va_arg(args, double));		\
+		break;								\
+	case 'd':								\
+		spa_pod_builder_double(builder, va_arg(args, double));		\
+		break;								\
+	case 's':								\
+	{									\
+		char *strval = va_arg(args, char *);				\
+		if (strval != NULL) {						\
+			size_t len = strlen(strval);				\
+			spa_pod_builder_string_len(builder, strval, len);	\
+		}								\
+		else								\
+			spa_pod_builder_none(builder);				\
+		break;								\
+	}									\
+	case 'S':								\
+	{									\
+		char *strval = va_arg(args, char *);				\
+		size_t len = va_arg(args, int);					\
+		spa_pod_builder_string_len(builder, strval, len);		\
+		break;								\
+	}									\
+	case 'y':								\
+	{									\
+		void *ptr  = va_arg(args, void *);				\
+		int len = va_arg(args, int);					\
+		spa_pod_builder_bytes(builder, ptr, len);			\
+		break;								\
+	}									\
+	case 'R':								\
+	{									\
+		struct spa_rectangle *rectval =					\
+			va_arg(args, struct spa_rectangle *);			\
+		spa_pod_builder_rectangle(builder,				\
+				rectval->width, rectval->height);		\
+		break;								\
+	}									\
+	case 'F':								\
+	{									\
+		struct spa_fraction *fracval =					\
+			va_arg(args, struct spa_fraction *);			\
+		spa_pod_builder_fraction(builder, fracval->num, fracval->denom);\
+		break;								\
+	}									\
+	case 'a':								\
+	{									\
+		int child_size = va_arg(args, int);				\
+		int child_type = va_arg(args, int);				\
+		int n_elems = va_arg(args, int);				\
+		void *elems = va_arg(args, void *);				\
+		spa_pod_builder_array(builder, child_size,			\
+				child_type, n_elems, elems);			\
+		break;								\
+	}									\
+	case 'p':								\
+	{									\
+		int t = va_arg(args, uint32_t);					\
+		spa_pod_builder_pointer(builder, t, va_arg(args, void *));	\
+		break;								\
+	}									\
+	case 'h':								\
+		spa_pod_builder_fd(builder, va_arg(args, int));			\
+		break;								\
+	case 'P':								\
+	case 'O':								\
+	case 'T':								\
+	case 'V':								\
+	{									\
+		struct spa_pod *pod = va_arg(args, struct spa_pod *);		\
+		if (pod == NULL)						\
+			spa_pod_builder_none(builder);				\
+		else								\
+			spa_pod_builder_primitive(builder, pod);		\
+		break;								\
+	}									\
+	}									\
+} while(false)
+
+static inline int
+spa_pod_builder_addv(struct spa_pod_builder *builder, va_list args)
+{
+	int res = 0;
+	struct spa_pod_frame *f = builder->state.frame;
+	uint32_t ftype = f ? f->pod.type : (uint32_t)SPA_TYPE_None;
+
+	do {
+		const char *format;
+		int n_values = 1;
+		struct spa_pod_frame f;
+		bool choice;
+
+		switch (ftype) {
+		case SPA_TYPE_Object:
+		{
+			uint32_t key = va_arg(args, uint32_t);
+			if (key == 0)
+				goto exit;
+			spa_pod_builder_prop(builder, key, 0);
+			break;
+		}
+		case SPA_TYPE_Sequence:
+		{
+			uint32_t offset = va_arg(args, uint32_t);
+			uint32_t type = va_arg(args, uint32_t);
+			if (type == 0)
+				goto exit;
+			spa_pod_builder_control(builder, offset, type);
+		}
+		default:
+			break;
+		}
+		if ((format = va_arg(args, const char *)) == NULL)
+			break;
+
+		choice = *format == '?';
+		if (choice) {
+			uint32_t type = spa_choice_from_id(*++format);
+			if (*format != '\0')
+				format++;
+
+			spa_pod_builder_push_choice(builder, &f, type, 0);
+
+			n_values = va_arg(args, int);
+		}
+		while (n_values-- > 0)
+			SPA_POD_BUILDER_COLLECT(builder, *format, args);
+
+		if (choice)
+			spa_pod_builder_pop(builder, &f);
+	} while (true);
+
+      exit:
+	return res;
+}
+
+static inline int spa_pod_builder_add(struct spa_pod_builder *builder, ...)
+{
+	int res;
+	va_list args;
+
+	va_start(args, builder);
+	res = spa_pod_builder_addv(builder, args);
+	va_end(args);
+
+	return res;
+}
+
+#define spa_pod_builder_add_object(b,type,id,...)				\
+({										\
+	struct spa_pod_frame _f;						\
+	spa_pod_builder_push_object(b, &_f, type, id);				\
+	spa_pod_builder_add(b, ##__VA_ARGS__, 0);				\
+	spa_pod_builder_pop(b, &_f);						\
+})
+
+#define spa_pod_builder_add_struct(b,...)					\
+({										\
+	struct spa_pod_frame _f;						\
+	spa_pod_builder_push_struct(b, &_f);					\
+	spa_pod_builder_add(b, ##__VA_ARGS__, NULL);				\
+	spa_pod_builder_pop(b, &_f);						\
+})
+
+#define spa_pod_builder_add_sequence(b,unit,...)				\
+({										\
+	struct spa_pod_frame _f;						\
+	spa_pod_builder_push_sequence(b, &_f, unit);				\
+	spa_pod_builder_add(b, ##__VA_ARGS__, 0, 0);				\
+	spa_pod_builder_pop(b, &_f);						\
+})
+
+/** Copy a pod structure */
+static inline struct spa_pod *
+spa_pod_copy(const struct spa_pod *pod)
+{
+	size_t size;
+	struct spa_pod *c;
+
+	size = SPA_POD_SIZE(pod);
+	if ((c = (struct spa_pod *) malloc(size)) == NULL)
+		return NULL;
+	return (struct spa_pod *) memcpy(c, pod, size);
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_POD_BUILDER_H */
diff --git a/third_party/pipewire/spa/pod/command.h b/third_party/pipewire/spa/pod/command.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/pod/command.h
@@ -0,0 +1,61 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_COMMAND_H
+#define SPA_COMMAND_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/pod/pod.h>
+
+struct spa_command_body {
+	struct spa_pod_object_body body;
+};
+
+struct spa_command {
+	struct spa_pod		pod;
+	struct spa_command_body body;
+};
+
+#define SPA_COMMAND_TYPE(cmd)		((cmd)->body.body.type)
+#define SPA_COMMAND_ID(cmd,type)	(SPA_COMMAND_TYPE(cmd) == type ? \
+						(cmd)->body.body.id : SPA_ID_INVALID)
+
+#define SPA_COMMAND_INIT_FULL(t,size,type,id,...) (t)			\
+	{ { size, SPA_TYPE_Object },					\
+	  { { type, id }, ##__VA_ARGS__ } }				\
+
+#define SPA_COMMAND_INIT(type,id)					\
+	SPA_COMMAND_INIT_FULL(struct spa_command,			\
+			sizeof(struct spa_command_body), type, id)
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_COMMAND_H */
diff --git a/third_party/pipewire/spa/pod/compare.h b/third_party/pipewire/spa/pod/compare.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/pod/compare.h
@@ -0,0 +1,181 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_POD_COMPARE_H
+#define SPA_POD_COMPARE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <spa/param/props.h>
+#include <spa/pod/iter.h>
+#include <spa/pod/builder.h>
+
+static inline int spa_pod_compare_value(uint32_t type, const void *r1, const void *r2, uint32_t size)
+{
+	switch (type) {
+	case SPA_TYPE_None:
+		return 0;
+	case SPA_TYPE_Bool:
+	case SPA_TYPE_Id:
+		return *(uint32_t *) r1 == *(uint32_t *) r2 ? 0 : 1;
+	case SPA_TYPE_Int:
+		return *(int32_t *) r1 - *(int32_t *) r2;
+	case SPA_TYPE_Long:
+		return *(int64_t *) r1 - *(int64_t *) r2;
+	case SPA_TYPE_Float:
+		return *(float *) r1 - *(float *) r2;
+	case SPA_TYPE_Double:
+		return *(double *) r1 - *(double *) r2;
+	case SPA_TYPE_String:
+		return strcmp((char *)r1, (char *)r2);
+	case SPA_TYPE_Bytes:
+		return memcmp((char *)r1, (char *)r2, size);
+	case SPA_TYPE_Rectangle:
+	{
+		const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1,
+		    *rec2 = (struct spa_rectangle *) r2;
+		if (rec1->width == rec2->width && rec1->height == rec2->height)
+			return 0;
+		else if (rec1->width < rec2->width || rec1->height < rec2->height)
+			return -1;
+		else
+			return 1;
+	}
+	case SPA_TYPE_Fraction:
+	{
+		const struct spa_fraction *f1 = (struct spa_fraction *) r1,
+		    *f2 = (struct spa_fraction *) r2;
+		int64_t n1, n2;
+		n1 = ((int64_t) f1->num) * f2->denom;
+		n2 = ((int64_t) f2->num) * f1->denom;
+		if (n1 < n2)
+			return -1;
+		else if (n1 > n2)
+			return 1;
+		else
+			return 0;
+	}
+	default:
+		break;
+	}
+	return 0;
+}
+
+static inline int spa_pod_compare(const struct spa_pod *pod1,
+				  const struct spa_pod *pod2)
+{
+	int res = 0;
+	uint32_t n_vals1, n_vals2;
+	uint32_t choice1, choice2;
+
+        spa_return_val_if_fail(pod1 != NULL, -EINVAL);
+        spa_return_val_if_fail(pod2 != NULL, -EINVAL);
+
+	pod1 = spa_pod_get_values(pod1,  &n_vals1, &choice1);
+	pod2 = spa_pod_get_values(pod2,  &n_vals2, &choice2);
+
+	if (n_vals1 != n_vals2)
+		return -EINVAL;
+
+	if (SPA_POD_TYPE(pod1) != SPA_POD_TYPE(pod2))
+		return -EINVAL;
+
+	switch (SPA_POD_TYPE(pod1)) {
+	case SPA_TYPE_Struct:
+	{
+		const struct spa_pod *p1, *p2;
+		size_t p1s, p2s;
+
+		p1 = (const struct spa_pod*)SPA_POD_BODY_CONST(pod1);
+		p1s = SPA_POD_BODY_SIZE(pod1);
+		p2 = (const struct spa_pod*)SPA_POD_BODY_CONST(pod2);
+		p2s = SPA_POD_BODY_SIZE(pod2);
+
+		while (true) {
+			if (!spa_pod_is_inside(pod1, p1s, p1) ||
+			    !spa_pod_is_inside(pod2, p2s, p2))
+				return -EINVAL;
+
+			if ((res = spa_pod_compare(p1, p2)) != 0)
+				return res;
+
+			p1 = (const struct spa_pod*)spa_pod_next(p1);
+			p2 = (const struct spa_pod*)spa_pod_next(p2);
+		}
+		break;
+	}
+	case SPA_TYPE_Object:
+	{
+		const struct spa_pod_prop *p1, *p2;
+		const struct spa_pod_object *o1, *o2;
+
+		o1 = (const struct spa_pod_object*)pod1;
+		o2 = (const struct spa_pod_object*)pod2;
+
+		p2 = NULL;
+		SPA_POD_OBJECT_FOREACH(o1, p1) {
+			if ((p2 = spa_pod_object_find_prop(o2, p2, p1->key)) == NULL)
+				return 1;
+			if ((res = spa_pod_compare(&p1->value, &p2->value)) != 0)
+				return res;
+		}
+		p1 = NULL;
+		SPA_POD_OBJECT_FOREACH(o2, p2) {
+			if ((p1 = spa_pod_object_find_prop(o1, p1, p2->key)) == NULL)
+				return -1;
+		}
+		break;
+	}
+	case SPA_TYPE_Array:
+	{
+		if (SPA_POD_BODY_SIZE(pod1) != SPA_POD_BODY_SIZE(pod2))
+			return -EINVAL;
+		res = memcmp(SPA_POD_BODY(pod1), SPA_POD_BODY(pod2), SPA_POD_BODY_SIZE(pod2));
+		break;
+	}
+	default:
+		if (SPA_POD_BODY_SIZE(pod1) != SPA_POD_BODY_SIZE(pod2))
+			return -EINVAL;
+		res = spa_pod_compare_value(SPA_POD_TYPE(pod1),
+				SPA_POD_BODY(pod1), SPA_POD_BODY(pod2),
+				SPA_POD_BODY_SIZE(pod1));
+		break;
+	}
+	return res;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/third_party/pipewire/spa/pod/event.h b/third_party/pipewire/spa/pod/event.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/pod/event.h
@@ -0,0 +1,59 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_EVENT_H
+#define SPA_EVENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/pod/pod.h>
+
+struct spa_event_body {
+	struct spa_pod_object_body body;
+};
+
+struct spa_event {
+	struct spa_pod pod;
+	struct spa_event_body body;
+};
+
+#define SPA_EVENT_TYPE(ev)	((ev)->body.body.type)
+#define SPA_EVENT_ID(ev,type)	(SPA_EVENT_TYPE(ev) == type ? \
+					(ev)->body.body.id : SPA_ID_INVALID)
+
+#define SPA_EVENT_INIT_FULL(t,size,type,id,...) (t)			\
+	{ { size, SPA_TYPE_OBJECT },					\
+	  { { type, id }, ##__VA_ARGS__ } }				\
+
+#define SPA_EVENT_INIT(type,id)						\
+	SPA_EVENT_INIT_FULL(struct spa_event,				\
+			sizeof(struct spa_event_body), type, id)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_EVENT_H */
diff --git a/third_party/pipewire/spa/pod/filter.h b/third_party/pipewire/spa/pod/filter.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/pod/filter.h
@@ -0,0 +1,395 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <spa/param/props.h>
+#include <spa/pod/iter.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/compare.h>
+
+static inline int spa_pod_choice_fix_default(struct spa_pod_choice *choice)
+{
+	void *val, *alt;
+	int i, nvals;
+	uint32_t type, size;
+
+	nvals = SPA_POD_CHOICE_N_VALUES(choice);
+	type = SPA_POD_CHOICE_VALUE_TYPE(choice);
+	size = SPA_POD_CHOICE_VALUE_SIZE(choice);
+	alt = val = SPA_POD_CHOICE_VALUES(choice);
+
+	switch (choice->body.type) {
+	case SPA_CHOICE_None:
+		break;
+	case SPA_CHOICE_Range:
+	case SPA_CHOICE_Step:
+		if (nvals > 1) {
+			alt = SPA_MEMBER(alt, size, void);
+			if (spa_pod_compare_value(type, val, alt, size) < 0)
+				memcpy(val, alt, size);
+		}
+		if (nvals > 2) {
+			alt = SPA_MEMBER(alt, size, void);
+			if (spa_pod_compare_value(type, val, alt, size) > 0)
+				memcpy(val, alt, size);
+		}
+		break;
+	case SPA_CHOICE_Flags:
+	case SPA_CHOICE_Enum:
+	{
+		void *best = NULL;
+
+		for (i = 1; i < nvals; i++) {
+			alt = SPA_MEMBER(alt, size, void);
+			if (spa_pod_compare_value(type, val, alt, size) == 0) {
+				best = alt;
+				break;
+			}
+			if (best == NULL)
+				best = alt;
+		}
+		if (best)
+			memcpy(val, best, size);
+
+		if (nvals <= 1)
+			choice->body.type = SPA_CHOICE_None;
+		break;
+	}
+	}
+	return 0;
+}
+
+static inline int spa_pod_filter_flags_value(struct spa_pod_builder *b,
+		uint32_t type, const void *r1, const void *r2, uint32_t size)
+{
+	switch (type) {
+	case SPA_TYPE_Int:
+	{
+		int32_t val = (*(int32_t *) r1) & (*(int32_t *) r2);
+		if (val == 0)
+			return 0;
+		spa_pod_builder_int(b, val);
+		break;
+	}
+	case SPA_TYPE_Long:
+	{
+		int64_t val = (*(int64_t *) r1) & (*(int64_t *) r2);
+		if (val == 0)
+			return 0;
+		spa_pod_builder_long(b, val);
+		break;
+	}
+	default:
+		return -ENOTSUP;
+	}
+	return 1;
+}
+
+
+static inline int
+spa_pod_filter_prop(struct spa_pod_builder *b,
+	    const struct spa_pod_prop *p1,
+	    const struct spa_pod_prop *p2)
+{
+	const struct spa_pod *v1, *v2;
+	struct spa_pod_choice *nc;
+	uint32_t j, k, nalt1, nalt2;
+	void *alt1, *alt2, *a1, *a2;
+	uint32_t type, size, p1c, p2c;
+	struct spa_pod_frame f;
+
+	v1 = spa_pod_get_values(&p1->value, &nalt1, &p1c);
+	alt1 = SPA_POD_BODY(v1);
+	v2 = spa_pod_get_values(&p2->value, &nalt2, &p2c);
+	alt2 = SPA_POD_BODY(v2);
+
+	type = v1->type;
+	size = v1->size;
+
+	/* incompatible property types */
+	if (type != v2->type || size != v2->size || p1->key != p2->key)
+		return -EINVAL;
+
+	if (p1c == SPA_CHOICE_None || p1c == SPA_CHOICE_Flags) {
+		nalt1 = 1;
+	} else {
+		alt1 = SPA_MEMBER(alt1, size, void);
+		nalt1--;
+	}
+
+	if (p2c == SPA_CHOICE_None || p2c == SPA_CHOICE_Flags) {
+		nalt2 = 1;
+	} else {
+		alt2 = SPA_MEMBER(alt2, size, void);
+		nalt2--;
+	}
+
+	/* start with copying the property */
+	spa_pod_builder_prop(b, p1->key, 0);
+	spa_pod_builder_push_choice(b, &f, 0, 0);
+	nc = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f);
+
+	/* default value */
+	spa_pod_builder_primitive(b, v1);
+
+	if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_None) ||
+	    (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Enum) ||
+	    (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_None) ||
+	    (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Enum)) {
+		int n_copied = 0;
+		/* copy all equal values but don't copy the default value again */
+		for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_MEMBER(a1, size, void)) {
+			for (k = 0, a2 = alt2; k < nalt2; k++, a2 = SPA_MEMBER(a2,size,void)) {
+				if (spa_pod_compare_value(type, a1, a2, size) == 0) {
+					if (p1c == SPA_CHOICE_Enum || j > 0)
+						spa_pod_builder_raw(b, a1, size);
+					n_copied++;
+				}
+			}
+		}
+		if (n_copied == 0)
+			return -EINVAL;
+		nc->body.type = SPA_CHOICE_Enum;
+	}
+
+	if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Range) ||
+	    (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Range)) {
+		int n_copied = 0;
+		/* copy all values inside the range */
+		for (j = 0, a1 = alt1, a2 = alt2; j < nalt1; j++, a1 = SPA_MEMBER(a1,size,void)) {
+			if (spa_pod_compare_value(type, a1, a2, size) < 0)
+				continue;
+			if (spa_pod_compare_value(type, a1, SPA_MEMBER(a2,size,void), size) > 0)
+				continue;
+			spa_pod_builder_raw(b, a1, size);
+			n_copied++;
+		}
+		if (n_copied == 0)
+			return -EINVAL;
+		nc->body.type = SPA_CHOICE_Enum;
+	}
+
+	if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Step) ||
+	    (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Step)) {
+		return -ENOTSUP;
+	}
+
+	if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_None) ||
+	    (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Enum)) {
+		int n_copied = 0;
+		/* copy all values inside the range */
+		for (k = 0, a1 = alt1, a2 = alt2; k < nalt2; k++, a2 = SPA_MEMBER(a2,size,void)) {
+			if (spa_pod_compare_value(type, a2, a1, size) < 0)
+				continue;
+			if (spa_pod_compare_value(type, a2, SPA_MEMBER(a1,size,void), size) > 0)
+				continue;
+			spa_pod_builder_raw(b, a2, size);
+			n_copied++;
+		}
+		if (n_copied == 0)
+			return -EINVAL;
+		nc->body.type = SPA_CHOICE_Enum;
+	}
+
+	if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Range) ||
+	    (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Step) ||
+	    (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Range) ||
+	    (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Step)) {
+		if (spa_pod_compare_value(type, alt1, alt2, size) < 0)
+			spa_pod_builder_raw(b, alt2, size);
+		else
+			spa_pod_builder_raw(b, alt1, size);
+
+		alt1 = SPA_MEMBER(alt1,size,void);
+		alt2 = SPA_MEMBER(alt2,size,void);
+
+		if (spa_pod_compare_value(type, alt1, alt2, size) < 0)
+			spa_pod_builder_raw(b, alt1, size);
+		else
+			spa_pod_builder_raw(b, alt2, size);
+
+		nc->body.type = SPA_CHOICE_Range;
+	}
+
+	if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Flags) ||
+	    (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_None) ||
+	    (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Flags)) {
+		if (spa_pod_filter_flags_value(b, type, alt1, alt2, size) != 1)
+			return -EINVAL;
+		nc->body.type = SPA_CHOICE_Flags;
+	}
+
+	if (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Flags)
+		return -ENOTSUP;
+
+	if (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Flags)
+		return -ENOTSUP;
+
+	if (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_None)
+		return -ENOTSUP;
+	if (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Enum)
+		return -ENOTSUP;
+	if (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Flags)
+		return -ENOTSUP;
+
+	if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Range)
+		return -ENOTSUP;
+	if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Step)
+		return -ENOTSUP;
+	if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Enum)
+		return -ENOTSUP;
+
+	spa_pod_builder_pop(b, &f);
+	spa_pod_choice_fix_default(nc);
+
+	return 0;
+}
+
+static inline int spa_pod_filter_part(struct spa_pod_builder *b,
+	       const struct spa_pod *pod, uint32_t pod_size,
+	       const struct spa_pod *filter, uint32_t filter_size)
+{
+	const struct spa_pod *pp, *pf;
+	int res = 0;
+
+	pf = filter;
+
+	SPA_POD_FOREACH(pod, pod_size, pp) {
+		bool do_copy = false, do_advance = false;
+		uint32_t filter_offset = 0;
+		struct spa_pod_frame f;
+
+		switch (SPA_POD_TYPE(pp)) {
+		case SPA_TYPE_Object:
+			if (pf != NULL) {
+				struct spa_pod_object *op = (struct spa_pod_object *) pp;
+				struct spa_pod_object *of = (struct spa_pod_object *) pf;
+				const struct spa_pod_prop *p1, *p2;
+
+				if (SPA_POD_TYPE(pf) != SPA_POD_TYPE(pp))
+					return -EINVAL;
+
+				spa_pod_builder_push_object(b, &f, op->body.type, op->body.id);
+				p2 = NULL;
+				SPA_POD_OBJECT_FOREACH(op, p1) {
+					p2 = spa_pod_object_find_prop(of, p2, p1->key);
+					if (p2 != NULL)
+						res = spa_pod_filter_prop(b, p1, p2);
+					else
+						spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1));
+					if (res < 0)
+						break;
+				}
+				if (res >= 0) {
+					p1 = NULL;
+					SPA_POD_OBJECT_FOREACH(of, p2) {
+						p1 = spa_pod_object_find_prop(op, p1, p2->key);
+						if (p1 != NULL)
+							continue;
+
+						spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2));
+					}
+				}
+				spa_pod_builder_pop(b, &f);
+				do_advance = true;
+			}
+			else
+				do_copy = true;
+			break;
+
+		case SPA_TYPE_Struct:
+			if (pf != NULL) {
+				if (SPA_POD_TYPE(pf) != SPA_POD_TYPE(pp))
+					return -EINVAL;
+
+				filter_offset = sizeof(struct spa_pod_struct);
+				spa_pod_builder_push_struct(b, &f);
+				res = spa_pod_filter_part(b,
+					SPA_MEMBER(pp,filter_offset,const struct spa_pod),
+					SPA_POD_SIZE(pp) - filter_offset,
+					SPA_MEMBER(pf,filter_offset,const struct spa_pod),
+					SPA_POD_SIZE(pf) - filter_offset);
+			        spa_pod_builder_pop(b, &f);
+				do_advance = true;
+			}
+			else
+				do_copy = true;
+			break;
+
+		default:
+			if (pf != NULL) {
+				if (SPA_POD_SIZE(pp) != SPA_POD_SIZE(pf))
+					return -EINVAL;
+				if (memcmp(pp, pf, SPA_POD_SIZE(pp)) != 0)
+					return -EINVAL;
+				do_advance = true;
+			}
+			do_copy = true;
+			break;
+		}
+		if (do_copy)
+			spa_pod_builder_raw_padded(b, pp, SPA_POD_SIZE(pp));
+		if (do_advance) {
+			pf = (const struct spa_pod*)spa_pod_next(pf);
+			if (!spa_pod_is_inside(filter, filter_size, pf))
+				pf = NULL;
+		}
+		if (res < 0)
+			break;
+	}
+	return res;
+}
+
+static inline int
+spa_pod_filter(struct spa_pod_builder *b,
+	       struct spa_pod **result,
+	       const struct spa_pod *pod,
+	       const struct spa_pod *filter)
+{
+	int res;
+	struct spa_pod_builder_state state;
+
+        spa_return_val_if_fail(pod != NULL, -EINVAL);
+        spa_return_val_if_fail(b != NULL, -EINVAL);
+
+	spa_pod_builder_get_state(b, &state);
+	if (filter == NULL)
+		res = spa_pod_builder_raw_padded(b, pod, SPA_POD_SIZE(pod));
+	else
+		res = spa_pod_filter_part(b, pod, SPA_POD_SIZE(pod), filter, SPA_POD_SIZE(filter));
+
+	if (res < 0) {
+		spa_pod_builder_reset(b, &state);
+	} else if (result) {
+		*result = (struct spa_pod*)spa_pod_builder_deref(b, state.offset);
+		if (*result == NULL)
+			res = -ENOSPC;
+	}
+	return res;
+}
diff --git a/third_party/pipewire/spa/pod/iter.h b/third_party/pipewire/spa/pod/iter.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/pod/iter.h
@@ -0,0 +1,446 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_POD_ITER_H
+#define SPA_POD_ITER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <errno.h>
+#include <sys/types.h>
+
+#include <spa/pod/pod.h>
+
+struct spa_pod_frame {
+	struct spa_pod pod;
+	struct spa_pod_frame *parent;
+	uint32_t offset;
+	uint32_t flags;
+};
+
+static inline bool spa_pod_is_inside(const void *pod, uint32_t size, const void *iter)
+{
+	return SPA_POD_BODY(iter) <= SPA_MEMBER(pod, size, void) &&
+		SPA_MEMBER(iter, SPA_POD_SIZE(iter), void) <= SPA_MEMBER(pod, size, void);
+}
+
+static inline void *spa_pod_next(const void *iter)
+{
+	return SPA_MEMBER(iter, SPA_ROUND_UP_N(SPA_POD_SIZE(iter), 8), void);
+}
+
+static inline struct spa_pod_prop *spa_pod_prop_first(const struct spa_pod_object_body *body)
+{
+	return SPA_MEMBER(body, sizeof(struct spa_pod_object_body), struct spa_pod_prop);
+}
+
+static inline bool spa_pod_prop_is_inside(const struct spa_pod_object_body *body,
+		uint32_t size, const struct spa_pod_prop *iter)
+{
+	return SPA_POD_CONTENTS(struct spa_pod_prop, iter) <= SPA_MEMBER(body, size, void) &&
+		SPA_MEMBER(iter, SPA_POD_PROP_SIZE(iter), void) <= SPA_MEMBER(body, size, void);
+}
+
+static inline struct spa_pod_prop *spa_pod_prop_next(const struct spa_pod_prop *iter)
+{
+	return SPA_MEMBER(iter, SPA_ROUND_UP_N(SPA_POD_PROP_SIZE(iter), 8), struct spa_pod_prop);
+}
+
+static inline struct spa_pod_control *spa_pod_control_first(const struct spa_pod_sequence_body *body)
+{
+	return SPA_MEMBER(body, sizeof(struct spa_pod_sequence_body), struct spa_pod_control);
+}
+
+static inline bool spa_pod_control_is_inside(const struct spa_pod_sequence_body *body,
+		uint32_t size, const struct spa_pod_control *iter)
+{
+	return SPA_POD_CONTENTS(struct spa_pod_control, iter) <= SPA_MEMBER(body, size, void) &&
+		SPA_MEMBER(iter, SPA_POD_CONTROL_SIZE(iter), void) <= SPA_MEMBER(body, size, void);
+}
+
+static inline struct spa_pod_control *spa_pod_control_next(const struct spa_pod_control *iter)
+{
+	return SPA_MEMBER(iter, SPA_ROUND_UP_N(SPA_POD_CONTROL_SIZE(iter), 8), struct spa_pod_control);
+}
+
+#define SPA_POD_ARRAY_BODY_FOREACH(body, _size, iter)							\
+	for ((iter) = (__typeof__(iter))SPA_MEMBER((body), sizeof(struct spa_pod_array_body), void);	\
+	     (iter) < (__typeof__(iter))SPA_MEMBER((body), (_size), void);				\
+	     (iter) = (__typeof__(iter))SPA_MEMBER((iter), (body)->child.size, void))
+
+#define SPA_POD_ARRAY_FOREACH(obj, iter)							\
+	SPA_POD_ARRAY_BODY_FOREACH(&(obj)->body, SPA_POD_BODY_SIZE(obj), iter)
+
+#define SPA_POD_CHOICE_BODY_FOREACH(body, _size, iter)							\
+	for ((iter) = (__typeof__(iter))SPA_MEMBER((body), sizeof(struct spa_pod_choice_body), void);	\
+	     (iter) < (__typeof__(iter))SPA_MEMBER((body), (_size), void);				\
+	     (iter) = (__typeof__(iter))SPA_MEMBER((iter), (body)->child.size, void))
+
+#define SPA_POD_CHOICE_FOREACH(obj, iter)							\
+	SPA_POD_CHOICE_BODY_FOREACH(&(obj)->body, SPA_POD_BODY_SIZE(obj), iter)
+
+#define SPA_POD_FOREACH(pod, size, iter)					\
+	for ((iter) = (pod);							\
+	     spa_pod_is_inside(pod, size, iter);				\
+	     (iter) = (__typeof__(iter))spa_pod_next(iter))
+
+#define SPA_POD_STRUCT_FOREACH(obj, iter)							\
+	SPA_POD_FOREACH(SPA_POD_BODY(obj), SPA_POD_BODY_SIZE(obj), iter)
+
+#define SPA_POD_OBJECT_BODY_FOREACH(body, size, iter)						\
+	for ((iter) = spa_pod_prop_first(body);				\
+	     spa_pod_prop_is_inside(body, size, iter);			\
+	     (iter) = spa_pod_prop_next(iter))
+
+#define SPA_POD_OBJECT_FOREACH(obj, iter)							\
+	SPA_POD_OBJECT_BODY_FOREACH(&(obj)->body, SPA_POD_BODY_SIZE(obj), iter)
+
+#define SPA_POD_SEQUENCE_BODY_FOREACH(body, size, iter)						\
+	for ((iter) = spa_pod_control_first(body);						\
+	     spa_pod_control_is_inside(body, size, iter);						\
+	     (iter) = spa_pod_control_next(iter))
+
+#define SPA_POD_SEQUENCE_FOREACH(seq, iter)							\
+	SPA_POD_SEQUENCE_BODY_FOREACH(&(seq)->body, SPA_POD_BODY_SIZE(seq), iter)
+
+
+static inline void *spa_pod_from_data(void *data, size_t maxsize, off_t offset, size_t size)
+{
+	void *pod;
+	if (size < sizeof(struct spa_pod) || offset + size > maxsize)
+		return NULL;
+	pod = SPA_MEMBER(data, offset, void);
+	if (SPA_POD_SIZE(pod) > size)
+		return NULL;
+	return pod;
+}
+
+static inline int spa_pod_is_none(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_None);
+}
+
+static inline int spa_pod_is_bool(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Bool && SPA_POD_BODY_SIZE(pod) >= sizeof(int32_t));
+}
+
+static inline int spa_pod_get_bool(const struct spa_pod *pod, bool *value)
+{
+	if (!spa_pod_is_bool(pod))
+		return -EINVAL;
+	*value = !!SPA_POD_VALUE(struct spa_pod_bool, pod);
+	return 0;
+}
+
+static inline int spa_pod_is_id(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Id && SPA_POD_BODY_SIZE(pod) >= sizeof(uint32_t));
+}
+
+static inline int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value)
+{
+	if (!spa_pod_is_id(pod))
+		return -EINVAL;
+	*value = SPA_POD_VALUE(struct spa_pod_id, pod);
+	return 0;
+}
+
+static inline int spa_pod_is_int(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Int && SPA_POD_BODY_SIZE(pod) >= sizeof(int32_t));
+}
+
+static inline int spa_pod_get_int(const struct spa_pod *pod, int32_t *value)
+{
+	if (!spa_pod_is_int(pod))
+		return -EINVAL;
+	*value = SPA_POD_VALUE(struct spa_pod_int, pod);
+	return 0;
+}
+
+static inline int spa_pod_is_long(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Long && SPA_POD_BODY_SIZE(pod) >= sizeof(int64_t));
+}
+
+static inline int spa_pod_get_long(const struct spa_pod *pod, int64_t *value)
+{
+	if (!spa_pod_is_long(pod))
+		return -EINVAL;
+	*value = SPA_POD_VALUE(struct spa_pod_long, pod);
+	return 0;
+}
+
+static inline int spa_pod_is_float(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Float && SPA_POD_BODY_SIZE(pod) >= sizeof(float));
+}
+
+static inline int spa_pod_get_float(const struct spa_pod *pod, float *value)
+{
+	if (!spa_pod_is_float(pod))
+		return -EINVAL;
+	*value = SPA_POD_VALUE(struct spa_pod_float, pod);
+	return 0;
+}
+
+static inline int spa_pod_is_double(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Double && SPA_POD_BODY_SIZE(pod) >= sizeof(double));
+}
+
+static inline int spa_pod_get_double(const struct spa_pod *pod, double *value)
+{
+	if (!spa_pod_is_double(pod))
+		return -EINVAL;
+	*value = SPA_POD_VALUE(struct spa_pod_double, pod);
+	return 0;
+}
+
+static inline int spa_pod_is_string(const struct spa_pod *pod)
+{
+	const char *s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod);
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_String &&
+			SPA_POD_BODY_SIZE(pod) > 0 &&
+			s[SPA_POD_BODY_SIZE(pod)-1] == '\0');
+}
+
+static inline int spa_pod_get_string(const struct spa_pod *pod, const char **value)
+{
+	if (!spa_pod_is_string(pod))
+		return -EINVAL;
+	*value = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod);
+	return 0;
+}
+
+static inline int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, char *dest)
+{
+	const char *s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod);
+	if (!spa_pod_is_string(pod) || maxlen < 1)
+		return -EINVAL;
+	strncpy(dest, s, maxlen-1);
+	dest[maxlen-1]= '\0';
+	return 0;
+}
+
+static inline int spa_pod_is_bytes(const struct spa_pod *pod)
+{
+	return SPA_POD_TYPE(pod) == SPA_TYPE_Bytes;
+}
+
+static inline int spa_pod_get_bytes(const struct spa_pod *pod, const void **value, uint32_t *len)
+{
+	if (!spa_pod_is_bytes(pod))
+		return -EINVAL;
+	*value = (const void *)SPA_POD_CONTENTS(struct spa_pod_bytes, pod);
+	*len = SPA_POD_BODY_SIZE(pod);
+	return 0;
+}
+
+static inline int spa_pod_is_pointer(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Pointer &&
+			SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_pointer_body));
+}
+
+static inline int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *type, const void **value)
+{
+	if (!spa_pod_is_pointer(pod))
+		return -EINVAL;
+	*type = ((struct spa_pod_pointer*)pod)->body.type;
+	*value = ((struct spa_pod_pointer*)pod)->body.value;
+	return 0;
+}
+
+static inline int spa_pod_is_fd(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Fd &&
+			SPA_POD_BODY_SIZE(pod) >= sizeof(int64_t));
+}
+
+static inline int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value)
+{
+	if (!spa_pod_is_fd(pod))
+		return -EINVAL;
+	*value = SPA_POD_VALUE(struct spa_pod_fd, pod);
+	return 0;
+}
+
+static inline int spa_pod_is_rectangle(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Rectangle &&
+			SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_rectangle));
+}
+
+static inline int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa_rectangle *value)
+{
+	if (!spa_pod_is_rectangle(pod))
+		return -EINVAL;
+	*value = SPA_POD_VALUE(struct spa_pod_rectangle, pod);
+	return 0;
+}
+
+static inline int spa_pod_is_fraction(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Fraction &&
+			SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_fraction));
+}
+
+static inline int spa_pod_get_fraction(const struct spa_pod *pod, struct spa_fraction *value)
+{
+	spa_return_val_if_fail(spa_pod_is_fraction(pod), -EINVAL);
+	*value = SPA_POD_VALUE(struct spa_pod_fraction, pod);
+	return 0;
+}
+
+static inline int spa_pod_is_bitmap(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Bitmap &&
+			SPA_POD_BODY_SIZE(pod) >= sizeof(uint8_t));
+}
+
+static inline int spa_pod_is_array(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Array &&
+			SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_array_body));
+}
+
+static inline void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values)
+{
+	spa_return_val_if_fail(spa_pod_is_array(pod), NULL);
+	*n_values = SPA_POD_ARRAY_N_VALUES(pod);
+	return SPA_POD_ARRAY_VALUES(pod);
+}
+
+static inline uint32_t spa_pod_copy_array(const struct spa_pod *pod, uint32_t type,
+		void *values, uint32_t max_values)
+{
+	uint32_t n_values;
+	void *v = spa_pod_get_array(pod, &n_values);
+	if (v == NULL || max_values == 0 || SPA_POD_ARRAY_VALUE_TYPE(pod) != type)
+		return 0;
+	n_values = SPA_MIN(n_values, max_values);
+	memcpy(values, v, SPA_POD_ARRAY_VALUE_SIZE(pod) * n_values);
+	return n_values;
+}
+
+static inline int spa_pod_is_choice(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Choice &&
+			SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_choice_body));
+}
+
+static inline struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice)
+{
+	if (pod->type == SPA_TYPE_Choice) {
+		*choice = SPA_POD_CHOICE_TYPE(pod);
+		*n_vals = *choice == SPA_CHOICE_None ? 1 : SPA_POD_CHOICE_N_VALUES(pod);
+		return (struct spa_pod*)SPA_POD_CHOICE_CHILD(pod);
+	} else {
+		*n_vals = 1;
+		*choice = SPA_CHOICE_None;
+		return (struct spa_pod*)pod;
+	}
+}
+
+static inline int spa_pod_is_struct(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Struct);
+}
+
+static inline int spa_pod_is_object(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Object &&
+			SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_object_body));
+}
+
+static inline bool spa_pod_is_object_type(const struct spa_pod *pod, uint32_t type)
+{
+	return (pod && spa_pod_is_object(pod) && SPA_POD_OBJECT_TYPE(pod) == type);
+}
+
+static inline bool spa_pod_is_object_id(const struct spa_pod *pod, uint32_t id)
+{
+	return (pod && spa_pod_is_object(pod) && SPA_POD_OBJECT_ID(pod) == id);
+}
+
+static inline int spa_pod_is_sequence(const struct spa_pod *pod)
+{
+	return (SPA_POD_TYPE(pod) == SPA_TYPE_Sequence &&
+			SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_sequence_body));
+}
+
+static inline const struct spa_pod_prop *spa_pod_object_find_prop(const struct spa_pod_object *pod,
+		const struct spa_pod_prop *start, uint32_t key)
+{
+	const struct spa_pod_prop *first, *res;
+
+	first = spa_pod_prop_first(&pod->body);
+	start = start ? spa_pod_prop_next(start) : first;
+
+	for (res = start; spa_pod_prop_is_inside(&pod->body, pod->pod.size, res);
+	     res = spa_pod_prop_next(res)) {
+		if (res->key == key)
+			return res;
+	}
+	for (res = first; res != start; res = spa_pod_prop_next(res)) {
+		if (res->key == key)
+			return res;
+	}
+	return NULL;
+}
+
+static inline const struct spa_pod_prop *spa_pod_find_prop(const struct spa_pod *pod,
+		const struct spa_pod_prop *start, uint32_t key)
+{
+	if (!spa_pod_is_object(pod))
+		return NULL;
+	return spa_pod_object_find_prop((const struct spa_pod_object *)pod, start, key);
+}
+
+static inline int spa_pod_object_fixate(struct spa_pod_object *pod)
+{
+	struct spa_pod_prop *res;
+	SPA_POD_OBJECT_FOREACH(pod, res) {
+		if (res->value.type == SPA_TYPE_Choice)
+			((struct spa_pod_choice*)&res->value)->body.type = SPA_CHOICE_None;
+	}
+	return 0;
+}
+
+static inline int spa_pod_fixate(struct spa_pod *pod)
+{
+	if (!spa_pod_is_object(pod))
+		return -EINVAL;
+	return spa_pod_object_fixate((struct spa_pod_object *)pod);
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_POD_H */
diff --git a/third_party/pipewire/spa/pod/parser.h b/third_party/pipewire/spa/pod/parser.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/pod/parser.h
@@ -0,0 +1,573 @@
+/* Spa
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_POD_PARSER_H
+#define SPA_POD_PARSER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <errno.h>
+#include <stdarg.h>
+
+#include <spa/pod/iter.h>
+#include <spa/pod/vararg.h>
+
+struct spa_pod_parser_state {
+	uint32_t offset;
+	uint32_t flags;
+	struct spa_pod_frame *frame;
+};
+
+struct spa_pod_parser {
+	const void *data;
+	uint32_t size;
+	uint32_t _padding;
+	struct spa_pod_parser_state state;
+};
+
+#define SPA_POD_PARSER_INIT(buffer,size)  (struct spa_pod_parser){ buffer, size, }
+
+static inline void spa_pod_parser_init(struct spa_pod_parser *parser,
+				       const void *data, uint32_t size)
+{
+	*parser = SPA_POD_PARSER_INIT(data, size);
+}
+
+static inline void spa_pod_parser_pod(struct spa_pod_parser *parser,
+				      const struct spa_pod *pod)
+{
+	spa_pod_parser_init(parser, pod, SPA_POD_SIZE(pod));
+}
+
+static inline void
+spa_pod_parser_get_state(struct spa_pod_parser *parser, struct spa_pod_parser_state *state)
+{
+	*state = parser->state;
+}
+
+static inline void
+spa_pod_parser_reset(struct spa_pod_parser *parser, struct spa_pod_parser_state *state)
+{
+	parser->state = *state;
+}
+
+static inline struct spa_pod *
+spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t size)
+{
+	if (offset + 8 <= size) {
+		struct spa_pod *pod = SPA_MEMBER(parser->data, offset, struct spa_pod);
+		if (offset + SPA_POD_SIZE(pod) <= size)
+			return pod;
+	}
+        return NULL;
+}
+
+static inline struct spa_pod *spa_pod_parser_frame(struct spa_pod_parser *parser, struct spa_pod_frame *frame)
+{
+	return SPA_MEMBER(parser->data, frame->offset, struct spa_pod);
+}
+
+static inline void spa_pod_parser_push(struct spa_pod_parser *parser,
+		      struct spa_pod_frame *frame, const struct spa_pod *pod, uint32_t offset)
+{
+	frame->pod = *pod;
+	frame->offset = offset;
+	frame->parent = parser->state.frame;
+	frame->flags = parser->state.flags;
+	parser->state.frame = frame;
+}
+
+static inline struct spa_pod *spa_pod_parser_current(struct spa_pod_parser *parser)
+{
+	struct spa_pod_frame *f = parser->state.frame;
+	uint32_t size = f ? f->offset + SPA_POD_SIZE(&f->pod) : parser->size;
+	return spa_pod_parser_deref(parser, parser->state.offset, size);
+}
+
+static inline void spa_pod_parser_advance(struct spa_pod_parser *parser, const struct spa_pod *pod)
+{
+	parser->state.offset += SPA_ROUND_UP_N(SPA_POD_SIZE(pod), 8);
+}
+
+static inline struct spa_pod *spa_pod_parser_next(struct spa_pod_parser *parser)
+{
+	struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod)
+		spa_pod_parser_advance(parser, pod);
+	return pod;
+}
+
+static inline int spa_pod_parser_pop(struct spa_pod_parser *parser,
+		      struct spa_pod_frame *frame)
+{
+	parser->state.frame = frame->parent;
+	parser->state.offset = frame->offset + SPA_ROUND_UP_N(SPA_POD_SIZE(&frame->pod), 8);
+	return 0;
+}
+
+static inline int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bool *value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_bool(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint32_t *value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_id(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_int(struct spa_pod_parser *parser, int32_t *value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_int(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_long(struct spa_pod_parser *parser, int64_t *value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_long(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_float(struct spa_pod_parser *parser, float *value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_float(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_double(struct spa_pod_parser *parser, double *value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_double(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_string(struct spa_pod_parser *parser, const char **value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_string(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_bytes(struct spa_pod_parser *parser, const void **value, uint32_t *len)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_bytes(pod, value, len)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_pointer(struct spa_pod_parser *parser, uint32_t *type, const void **value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_pointer(pod, type, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_fd(struct spa_pod_parser *parser, int64_t *value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_fd(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_rectangle(struct spa_pod_parser *parser, struct spa_rectangle *value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_rectangle(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_fraction(struct spa_pod_parser *parser, struct spa_fraction *value)
+{
+	int res = -EPIPE;
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod != NULL && (res = spa_pod_get_fraction(pod, value)) >= 0)
+		spa_pod_parser_advance(parser, pod);
+	return res;
+}
+
+static inline int spa_pod_parser_get_pod(struct spa_pod_parser *parser, struct spa_pod **value)
+{
+	struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod == NULL)
+		return -EPIPE;
+	*value = pod;
+	spa_pod_parser_advance(parser, pod);
+	return 0;
+}
+static inline int spa_pod_parser_push_struct(struct spa_pod_parser *parser,
+		struct spa_pod_frame *frame)
+{
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod == NULL)
+		return -EPIPE;
+	if (!spa_pod_is_struct(pod))
+		return -EINVAL;
+	spa_pod_parser_push(parser, frame, pod, parser->state.offset);
+	parser->state.offset += sizeof(struct spa_pod_struct);
+	return 0;
+}
+
+static inline int spa_pod_parser_push_object(struct spa_pod_parser *parser,
+		struct spa_pod_frame *frame, uint32_t type, uint32_t *id)
+{
+	const struct spa_pod *pod = spa_pod_parser_current(parser);
+	if (pod == NULL)
+		return -EPIPE;
+	if (!spa_pod_is_object(pod))
+		return -EINVAL;
+	if (type != SPA_POD_OBJECT_TYPE(pod))
+		return -EPROTO;
+	if (id != NULL)
+		*id = SPA_POD_OBJECT_ID(pod);
+	spa_pod_parser_push(parser, frame, pod, parser->state.offset);
+	parser->state.offset = parser->size;
+	return 0;
+}
+
+static inline bool spa_pod_parser_can_collect(const struct spa_pod *pod, char type)
+{
+	if (pod == NULL)
+		return false;
+
+	if (spa_pod_is_choice(pod) &&
+	    SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_None &&
+	    spa_pod_parser_can_collect(SPA_POD_CHOICE_CHILD(pod), type))
+		return true;
+
+	switch (type) {
+	case 'P':
+		return true;
+	case 'b':
+		return spa_pod_is_bool(pod);
+	case 'I':
+		return spa_pod_is_id(pod);
+	case 'i':
+		return spa_pod_is_int(pod);
+	case 'l':
+		return spa_pod_is_long(pod);
+	case 'f':
+		return spa_pod_is_float(pod);
+	case 'd':
+		return spa_pod_is_double(pod);
+	case 's':
+		return spa_pod_is_string(pod) || spa_pod_is_none(pod);
+	case 'S':
+		return spa_pod_is_string(pod);
+	case 'y':
+		return spa_pod_is_bytes(pod);
+	case 'R':
+		return spa_pod_is_rectangle(pod);
+	case 'F':
+		return spa_pod_is_fraction(pod);
+	case 'B':
+		return spa_pod_is_bitmap(pod);
+	case 'a':
+		return spa_pod_is_array(pod);
+	case 'p':
+		return spa_pod_is_pointer(pod);
+	case 'h':
+		return spa_pod_is_fd(pod);
+	case 'T':
+		return spa_pod_is_struct(pod) || spa_pod_is_none(pod);
+	case 'O':
+		return spa_pod_is_object(pod) || spa_pod_is_none(pod);
+	case 'V':
+		return spa_pod_is_choice(pod);
+	default:
+		return false;
+	}
+}
+
+#define SPA_POD_PARSER_COLLECT(pod,_type,args)						\
+do {											\
+	switch (_type) {								\
+	case 'b':									\
+		*va_arg(args, bool*) = SPA_POD_VALUE(struct spa_pod_bool, pod);		\
+		break;									\
+	case 'I':									\
+	case 'i':									\
+		*va_arg(args, int32_t*) = SPA_POD_VALUE(struct spa_pod_int, pod);	\
+		break;									\
+	case 'l':									\
+		*va_arg(args, int64_t*) = SPA_POD_VALUE(struct spa_pod_long, pod);	\
+		break;									\
+	case 'f':									\
+		*va_arg(args, float*) = SPA_POD_VALUE(struct spa_pod_float, pod);	\
+		break;									\
+	case 'd':									\
+		*va_arg(args, double*) = SPA_POD_VALUE(struct spa_pod_double, pod);	\
+		break;									\
+	case 's':									\
+		*va_arg(args, char**) =							\
+			(pod == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None)		\
+				? NULL							\
+				: (char *)SPA_POD_CONTENTS(struct spa_pod_string, pod));	\
+		break;									\
+	case 'S':									\
+	{										\
+		char *dest = va_arg(args, char*);					\
+		uint32_t maxlen = va_arg(args, uint32_t);				\
+		strncpy(dest, (char *)SPA_POD_CONTENTS(struct spa_pod_string, pod), maxlen-1);	\
+		break;									\
+	}										\
+	case 'y':									\
+		*(va_arg(args, void **)) = SPA_POD_CONTENTS(struct spa_pod_bytes, pod);	\
+		*(va_arg(args, uint32_t *)) = SPA_POD_BODY_SIZE(pod);			\
+		break;									\
+	case 'R':									\
+		*va_arg(args, struct spa_rectangle*) =					\
+				SPA_POD_VALUE(struct spa_pod_rectangle, pod);		\
+		break;									\
+	case 'F':									\
+		*va_arg(args, struct spa_fraction*) =					\
+				SPA_POD_VALUE(struct spa_pod_fraction, pod);		\
+		break;									\
+	case 'B':									\
+		*va_arg(args, uint32_t **) =						\
+			(uint32_t *) SPA_POD_CONTENTS(struct spa_pod_bitmap, pod);	\
+		break;									\
+	case 'a':									\
+		*va_arg(args, uint32_t*) = SPA_POD_ARRAY_VALUE_SIZE(pod);		\
+		*va_arg(args, uint32_t*) = SPA_POD_ARRAY_VALUE_TYPE(pod);		\
+		*va_arg(args, uint32_t*) = SPA_POD_ARRAY_N_VALUES(pod);			\
+		*va_arg(args, void**) = SPA_POD_ARRAY_VALUES(pod);			\
+		break;									\
+	case 'p':									\
+	{										\
+		struct spa_pod_pointer_body *b =					\
+				(struct spa_pod_pointer_body *) SPA_POD_BODY(pod);	\
+		*(va_arg(args, uint32_t *)) = b->type;					\
+		*(va_arg(args, const void **)) = b->value;				\
+		break;									\
+	}										\
+	case 'h':									\
+		*va_arg(args, int64_t*) = SPA_POD_VALUE(struct spa_pod_fd, pod);	\
+		break;									\
+	case 'P':									\
+	case 'T':									\
+	case 'O':									\
+	case 'V':									\
+	{										\
+		const struct spa_pod **d = va_arg(args, const struct spa_pod**);	\
+		if (d)									\
+			*d = (pod == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None)	\
+				? NULL : pod);						\
+		break;									\
+	}										\
+	default:									\
+		break;									\
+	}										\
+} while(false)
+
+#define SPA_POD_PARSER_SKIP(_type,args)							\
+do {											\
+	switch (_type) {								\
+	case 'S':									\
+		va_arg(args, char*);							\
+		va_arg(args, uint32_t);							\
+		break;									\
+	case 'a':									\
+		va_arg(args, void*);							\
+		va_arg(args, void*);							\
+		/* fallthrough */							\
+	case 'p':									\
+	case 'y':									\
+		va_arg(args, void*);							\
+		/* fallthrough */							\
+	case 'b':									\
+	case 'I':									\
+	case 'i':									\
+	case 'l':									\
+	case 'f':									\
+	case 'd':									\
+	case 's':									\
+	case 'R':									\
+	case 'F':									\
+	case 'B':									\
+	case 'h':									\
+	case 'V':									\
+	case 'P':									\
+	case 'T':									\
+	case 'O':									\
+		va_arg(args, void*);							\
+		break;									\
+	}										\
+} while(false)
+
+static inline int spa_pod_parser_getv(struct spa_pod_parser *parser, va_list args)
+{
+	struct spa_pod_frame *f = parser->state.frame;
+        uint32_t ftype = f ? f->pod.type : (uint32_t)SPA_TYPE_Struct;
+	const struct spa_pod_prop *prop = NULL;
+	int count = 0;
+
+	do {
+		bool optional;
+		const struct spa_pod *pod = NULL;
+		const char *format;
+
+		if (ftype == SPA_TYPE_Object) {
+			uint32_t key = va_arg(args, uint32_t);
+			const struct spa_pod_object *object;
+
+			if (key == 0)
+				break;
+
+			object = (const struct spa_pod_object *)spa_pod_parser_frame(parser, f);
+			prop = spa_pod_object_find_prop(object, prop, key);
+			pod = prop ? &prop->value : NULL;
+		}
+
+		if ((format = va_arg(args, char *)) == NULL)
+			break;
+
+		if (ftype == SPA_TYPE_Struct)
+			pod = spa_pod_parser_next(parser);
+
+		if ((optional = (*format == '?')))
+			format++;
+
+		if (!spa_pod_parser_can_collect(pod, *format)) {
+			if (!optional) {
+				if (pod == NULL)
+					return -ESRCH;
+				else
+					return -EPROTO;
+			}
+			SPA_POD_PARSER_SKIP(*format, args);
+		} else {
+			if (pod->type == SPA_TYPE_Choice && *format != 'V' &&
+			    SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_None)
+				pod = SPA_POD_CHOICE_CHILD(pod);
+
+			SPA_POD_PARSER_COLLECT(pod, *format, args);
+			count++;
+		}
+	} while (true);
+
+	return count;
+}
+
+static inline int spa_pod_parser_get(struct spa_pod_parser *parser, ...)
+{
+	int res;
+	va_list args;
+
+	va_start(args, parser);
+	res = spa_pod_parser_getv(parser, args);
+	va_end(args);
+
+	return res;
+}
+
+#define SPA_POD_OPT_Bool(val)				"?" SPA_POD_Bool(val)
+#define SPA_POD_OPT_Id(val)				"?" SPA_POD_Id(val)
+#define SPA_POD_OPT_Int(val)				"?" SPA_POD_Int(val)
+#define SPA_POD_OPT_Long(val)				"?" SPA_POD_Long(val)
+#define SPA_POD_OPT_Float(val)				"?" SPA_POD_Float(val)
+#define SPA_POD_OPT_Double(val)				"?" SPA_POD_Double(val)
+#define SPA_POD_OPT_String(val)				"?" SPA_POD_String(val)
+#define SPA_POD_OPT_Stringn(val,len)			"?" SPA_POD_Stringn(val,len)
+#define SPA_POD_OPT_Bytes(val,len)			"?" SPA_POD_Bytes(val,len)
+#define SPA_POD_OPT_Rectangle(val)			"?" SPA_POD_Rectangle(val)
+#define SPA_POD_OPT_Fraction(val)			"?" SPA_POD_Fraction(val)
+#define SPA_POD_OPT_Array(csize,ctype,n_vals,vals)	"?" SPA_POD_Array(csize,ctype,n_vals,vals)
+#define SPA_POD_OPT_Pointer(type,val)			"?" SPA_POD_Pointer(type,val)
+#define SPA_POD_OPT_Fd(val)				"?" SPA_POD_Fd(val)
+#define SPA_POD_OPT_Pod(val)				"?" SPA_POD_Pod(val)
+#define SPA_POD_OPT_PodObject(val)			"?" SPA_POD_PodObject(val)
+#define SPA_POD_OPT_PodStruct(val)			"?" SPA_POD_PodStruct(val)
+#define SPA_POD_OPT_PodChoice(val)			"?" SPA_POD_PodChoice(val)
+
+#define spa_pod_parser_get_object(p,type,id,...)				\
+({										\
+	struct spa_pod_frame _f;						\
+	int _res;								\
+	if ((_res = spa_pod_parser_push_object(p, &_f, type, id)) == 0) {	\
+		_res = spa_pod_parser_get(p,##__VA_ARGS__, 0);			\
+		spa_pod_parser_pop(p, &_f);					\
+	}									\
+	_res;									\
+})
+
+#define spa_pod_parser_get_struct(p,...)				\
+({									\
+	struct spa_pod_frame _f;					\
+	int _res;							\
+	if ((_res = spa_pod_parser_push_struct(p, &_f)) == 0) {		\
+		_res = spa_pod_parser_get(p,##__VA_ARGS__, NULL);	\
+		spa_pod_parser_pop(p, &_f);				\
+	}								\
+	_res;							\
+})
+
+#define spa_pod_parse_object(pod,type,id,...)			\
+({								\
+	struct spa_pod_parser _p;				\
+	spa_pod_parser_pod(&_p, pod);				\
+	spa_pod_parser_get_object(&_p,type,id,##__VA_ARGS__);	\
+})
+
+#define spa_pod_parse_struct(pod,...)				\
+({								\
+	struct spa_pod_parser _p;				\
+	spa_pod_parser_pod(&_p, pod);				\
+	spa_pod_parser_get_struct(&_p,##__VA_ARGS__);		\
+})
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_POD_PARSER_H */
diff --git a/third_party/pipewire/spa/pod/pod.h b/third_party/pipewire/spa/pod/pod.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/pod/pod.h
@@ -0,0 +1,231 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_POD_H
+#define SPA_POD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/type.h>
+
+#define SPA_POD_BODY_SIZE(pod)			(((struct spa_pod*)(pod))->size)
+#define SPA_POD_TYPE(pod)			(((struct spa_pod*)(pod))->type)
+#define SPA_POD_SIZE(pod)			(sizeof(struct spa_pod) + SPA_POD_BODY_SIZE(pod))
+#define SPA_POD_CONTENTS_SIZE(type,pod)		(SPA_POD_SIZE(pod)-sizeof(type))
+
+#define SPA_POD_CONTENTS(type,pod)		SPA_MEMBER((pod),sizeof(type),void)
+#define SPA_POD_CONTENTS_CONST(type,pod)	SPA_MEMBER((pod),sizeof(type),const void)
+#define SPA_POD_BODY(pod)			SPA_MEMBER((pod),sizeof(struct spa_pod),void)
+#define SPA_POD_BODY_CONST(pod)			SPA_MEMBER((pod),sizeof(struct spa_pod),const void)
+
+struct spa_pod {
+	uint32_t size;		/* size of the body */
+	uint32_t type;		/* a basic id of enum spa_type */
+};
+
+#define SPA_POD_VALUE(type,pod)			(((type*)pod)->value)
+
+struct spa_pod_bool {
+	struct spa_pod pod;
+	int32_t value;
+	int32_t _padding;
+};
+
+struct spa_pod_id {
+	struct spa_pod pod;
+	uint32_t value;
+	int32_t _padding;
+};
+
+struct spa_pod_int {
+	struct spa_pod pod;
+	int32_t value;
+	int32_t _padding;
+};
+
+struct spa_pod_long {
+	struct spa_pod pod;
+	int64_t value;
+};
+
+struct spa_pod_float {
+	struct spa_pod pod;
+	float value;
+	int32_t _padding;
+};
+
+struct spa_pod_double {
+	struct spa_pod pod;
+	double value;
+};
+
+struct spa_pod_string {
+	struct spa_pod pod;
+	/* value here */
+};
+
+struct spa_pod_bytes {
+	struct spa_pod pod;
+	/* value here */
+};
+
+struct spa_pod_rectangle {
+	struct spa_pod pod;
+	struct spa_rectangle value;
+};
+
+struct spa_pod_fraction {
+	struct spa_pod pod;
+	struct spa_fraction value;
+};
+
+struct spa_pod_bitmap {
+	struct spa_pod pod;
+	/* array of uint8_t follows with the bitmap */
+};
+
+#define SPA_POD_ARRAY_CHILD(arr)	(&((struct spa_pod_array*)(arr))->body.child)
+#define SPA_POD_ARRAY_VALUE_TYPE(arr)	(SPA_POD_TYPE(SPA_POD_ARRAY_CHILD(arr)))
+#define SPA_POD_ARRAY_VALUE_SIZE(arr)	(SPA_POD_BODY_SIZE(SPA_POD_ARRAY_CHILD(arr)))
+#define SPA_POD_ARRAY_N_VALUES(arr)	((SPA_POD_BODY_SIZE(arr) - sizeof(struct spa_pod_array_body)) / SPA_POD_ARRAY_VALUE_SIZE(arr))
+#define SPA_POD_ARRAY_VALUES(arr)	SPA_POD_CONTENTS(struct spa_pod_array, arr)
+
+struct spa_pod_array_body {
+	struct spa_pod child;
+	/* array with elements of child.size follows */
+};
+
+struct spa_pod_array {
+	struct spa_pod pod;
+	struct spa_pod_array_body body;
+};
+
+#define SPA_POD_CHOICE_CHILD(choice)		(&((struct spa_pod_choice*)(choice))->body.child)
+#define SPA_POD_CHOICE_TYPE(choice)		(((struct spa_pod_choice*)(choice))->body.type)
+#define SPA_POD_CHOICE_FLAGS(choice)		(((struct spa_pod_choice*)(choice))->body.flags)
+#define SPA_POD_CHOICE_VALUE_TYPE(choice)	(SPA_POD_TYPE(SPA_POD_CHOICE_CHILD(choice)))
+#define SPA_POD_CHOICE_VALUE_SIZE(choice)	(SPA_POD_BODY_SIZE(SPA_POD_CHOICE_CHILD(choice)))
+#define SPA_POD_CHOICE_N_VALUES(choice)		((SPA_POD_BODY_SIZE(choice) - sizeof(struct spa_pod_choice_body)) / SPA_POD_CHOICE_VALUE_SIZE(choice))
+#define SPA_POD_CHOICE_VALUES(choice)		(SPA_POD_CONTENTS(struct spa_pod_choice, choice))
+
+enum spa_choice_type {
+	SPA_CHOICE_None,		/**< no choice, first value is current */
+	SPA_CHOICE_Range,		/**< range: default, min, max */
+	SPA_CHOICE_Step,		/**< range with step: default, min, max, step */
+	SPA_CHOICE_Enum,		/**< list: default, alternative,...  */
+	SPA_CHOICE_Flags,		/**< flags: default, possible flags,... */
+};
+
+struct spa_pod_choice_body {
+	uint32_t type;			/**< type of choice, one of enum spa_choice_type */
+	uint32_t flags;			/**< extra flags */
+	struct spa_pod child;
+	/* array with elements of child.size follows. Note that there might be more
+	 * elements than required by \a type, which should be ignored. */
+};
+
+struct spa_pod_choice {
+	struct spa_pod pod;
+	struct spa_pod_choice_body body;
+};
+
+struct spa_pod_struct {
+	struct spa_pod pod;
+	/* one or more spa_pod follow */
+};
+
+#define SPA_POD_OBJECT_TYPE(obj)	(((struct spa_pod_object*)(obj))->body.type)
+#define SPA_POD_OBJECT_ID(obj)		(((struct spa_pod_object*)(obj))->body.id)
+
+struct spa_pod_object_body {
+	uint32_t type;		/**< one of enum spa_type */
+	uint32_t id;		/**< id of the object, depends on the object type */
+	/* contents follow, series of spa_pod_prop */
+};
+
+struct spa_pod_object {
+	struct spa_pod pod;
+	struct spa_pod_object_body body;
+};
+
+struct spa_pod_pointer_body {
+	uint32_t type;		/**< pointer id, one of enum spa_type */
+	uint32_t _padding;
+	const void *value;
+};
+
+struct spa_pod_pointer {
+	struct spa_pod pod;
+	struct spa_pod_pointer_body body;
+};
+
+struct spa_pod_fd {
+	struct spa_pod pod;
+	int64_t value;
+};
+
+#define SPA_POD_PROP_SIZE(prop)		(sizeof(struct spa_pod_prop) + (prop)->value.size)
+
+/* props can be inside an object */
+struct spa_pod_prop {
+	uint32_t key;			/**< key of property, list of valid keys depends on the
+					  *  object type */
+#define SPA_POD_PROP_FLAG_READONLY	(1u<<0)		/**< is read-only */
+#define SPA_POD_PROP_FLAG_HARDWARE	(1u<<1)		/**< some sort of hardware parameter */
+	uint32_t flags;			/**< flags for property */
+	struct spa_pod value;
+	/* value follows */
+};
+
+#define SPA_POD_CONTROL_SIZE(ev)	(sizeof(struct spa_pod_control) + (ev)->value.size)
+
+/* controls can be inside a sequence and mark timed values */
+struct spa_pod_control {
+	uint32_t offset;	/**< media offset */
+	uint32_t type;		/**< type of control, enum spa_control_type */
+	struct spa_pod value;	/**< control value, depends on type */
+	/* value contents follow */
+};
+
+struct spa_pod_sequence_body {
+	uint32_t unit;
+	uint32_t pad;
+	/* series of struct spa_pod_control follows */
+};
+
+/** a sequence of timed controls */
+struct spa_pod_sequence {
+	struct spa_pod pod;
+	struct spa_pod_sequence_body body;
+};
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_POD_H */
diff --git a/third_party/pipewire/spa/pod/vararg.h b/third_party/pipewire/spa/pod/vararg.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/pod/vararg.h
@@ -0,0 +1,104 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_POD_VARARG_H
+#define SPA_POD_VARARG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+#include <spa/pod/pod.h>
+
+#define SPA_POD_Prop(key,...)				\
+	key, ##__VA_ARGS__
+
+#define SPA_POD_Control(offset,type,...)		\
+	offset, type, ##__VA_ARGS__
+
+#define SPA_CHOICE_RANGE(def,min,max)			3,(def),(min),(max)
+#define SPA_CHOICE_STEP(def,min,max,step)		4,(def),(min),(max),(step)
+#define SPA_CHOICE_ENUM(n_vals,...)			(n_vals),##__VA_ARGS__
+#define SPA_CHOICE_FLAGS(flags)				1, (flags)
+#define SPA_CHOICE_BOOL(def)				3,(def),(def),!(def)
+
+#define SPA_POD_Bool(val)				"b", val
+#define SPA_POD_CHOICE_Bool(def)			"?eb", SPA_CHOICE_BOOL(def)
+
+#define SPA_POD_Id(val)					"I", val
+#define SPA_POD_CHOICE_ENUM_Id(n_vals,...)		"?eI", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__)
+
+#define SPA_POD_Int(val)				"i", val
+#define SPA_POD_CHOICE_ENUM_Int(n_vals,...)		"?ei", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__)
+#define SPA_POD_CHOICE_RANGE_Int(def,min,max)		"?ri", SPA_CHOICE_RANGE(def, min, max)
+#define SPA_POD_CHOICE_STEP_Int(def,min,max,step)	"?si", SPA_CHOICE_STEP(def, min, max, step)
+#define SPA_POD_CHOICE_FLAGS_Int(flags)			"?fi", SPA_CHOICE_FLAGS(flags)
+
+#define SPA_POD_Long(val)				"l", val
+#define SPA_POD_CHOICE_ENUM_Long(n_vals,...)		"?el", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__)
+#define SPA_POD_CHOICE_RANGE_Long(def,min,max)		"?rl", SPA_CHOICE_RANGE(def, min, max)
+#define SPA_POD_CHOICE_STEP_Long(def,min,max,step)	"?sl", SPA_CHOICE_STEP(def, min, max, step)
+#define SPA_POD_CHOICE_FLAGS_Long(flags)		"?fl", SPA_CHOICE_FLAGS(flags)
+
+#define SPA_POD_Float(val)				"f", val
+#define SPA_POD_CHOICE_ENUM_Float(n_vals,...)		"?ef", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__)
+#define SPA_POD_CHOICE_RANGE_Float(def,min,max)		"?rf", SPA_CHOICE_RANGE(def, min, max)
+#define SPA_POD_CHOICE_STEP_Float(def,min,max,step)	"?sf", SPA_CHOICE_STEP(def, min, max, step)
+
+#define SPA_POD_Double(val)				"d", val
+#define SPA_POD_CHOICE_ENUM_Double(n_vals,...)		"?ed", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__)
+#define SPA_POD_CHOICE_RANGE_Double(def,min,max)	"?rd", SPA_CHOICE_RANGE(def, min, max)
+#define SPA_POD_CHOICE_STEP_Double(def,min,max,step)	"?sd", SPA_CHOICE_STEP(def, min, max, step)
+
+#define SPA_POD_String(val)				"s",val
+#define SPA_POD_Stringn(val,len)			"S",val,len
+
+#define SPA_POD_Bytes(val,len)				"y",val,len
+
+#define SPA_POD_Rectangle(val)				"R",val
+#define SPA_POD_CHOICE_ENUM_Rectangle(n_vals,...)	"?eR", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__)
+#define SPA_POD_CHOICE_RANGE_Rectangle(def,min,max)	"?rR", SPA_CHOICE_RANGE((def),(min),(max))
+#define SPA_POD_CHOICE_STEP_Rectangle(def,min,max,step)	"?sR", SPA_CHOICE_STEP((def),(min),(max),(step))
+
+#define SPA_POD_Fraction(val)				"F",val
+#define SPA_POD_CHOICE_ENUM_Fraction(n_vals,...)	"?eF", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__)
+#define SPA_POD_CHOICE_RANGE_Fraction(def,min,max)	"?rF", SPA_CHOICE_RANGE((def),(min),(max))
+#define SPA_POD_CHOICE_STEP_Fraction(def,min,max,step)	"?sF", SPA_CHOICE_STEP(def, min, max, step)
+
+#define SPA_POD_Array(csize,ctype,n_vals,vals)		"a", csize,ctype,n_vals,vals
+#define SPA_POD_Pointer(type,val)			"p", type,val
+#define SPA_POD_Fd(val)					"h", val
+#define SPA_POD_None()					"P", NULL
+#define SPA_POD_Pod(val)				"P", val
+#define SPA_POD_PodObject(val)				"O", val
+#define SPA_POD_PodStruct(val)				"T", val
+#define SPA_POD_PodChoice(val)				"V", val
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_POD_VARARG_H */
diff --git a/third_party/pipewire/spa/support/cpu.h b/third_party/pipewire/spa/support/cpu.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/support/cpu.h
@@ -0,0 +1,126 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_CPU_H
+#define SPA_CPU_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+/**
+ * The CPU features interface
+ */
+#define SPA_TYPE_INTERFACE_CPU	SPA_TYPE_INFO_INTERFACE_BASE "CPU"
+
+#define SPA_VERSION_CPU		0
+struct spa_cpu { struct spa_interface iface; };
+
+/* x86 specific */
+#define SPA_CPU_FLAG_MMX		(1<<0)	/**< standard MMX */
+#define SPA_CPU_FLAG_MMXEXT		(1<<1)	/**< SSE integer or AMD MMX ext */
+#define SPA_CPU_FLAG_3DNOW		(1<<2)	/**< AMD 3DNOW */
+#define SPA_CPU_FLAG_SSE		(1<<3)	/**< SSE */
+#define SPA_CPU_FLAG_SSE2		(1<<4)	/**< SSE2 */
+#define SPA_CPU_FLAG_3DNOWEXT		(1<<5)	/**< AMD 3DNowExt */
+#define SPA_CPU_FLAG_SSE3		(1<<6)	/**< Prescott SSE3 */
+#define SPA_CPU_FLAG_SSSE3		(1<<7)	/**< Conroe SSSE3 */
+#define SPA_CPU_FLAG_SSE41		(1<<8)	/**< Penryn SSE4.1 */
+#define SPA_CPU_FLAG_SSE42		(1<<9)	/**< Nehalem SSE4.2 */
+#define SPA_CPU_FLAG_AESNI		(1<<10)	/**< Advanced Encryption Standard */
+#define SPA_CPU_FLAG_AVX		(1<<11)	/**< AVX */
+#define SPA_CPU_FLAG_XOP		(1<<12)	/**< Bulldozer XOP */
+#define SPA_CPU_FLAG_FMA4		(1<<13)	/**< Bulldozer FMA4 */
+#define SPA_CPU_FLAG_CMOV		(1<<14)	/**< supports cmov */
+#define SPA_CPU_FLAG_AVX2		(1<<15)	/**< AVX2 */
+#define SPA_CPU_FLAG_FMA3		(1<<16)	/**< Haswell FMA3 */
+#define SPA_CPU_FLAG_BMI1		(1<<17)	/**< Bit Manipulation Instruction Set 1 */
+#define SPA_CPU_FLAG_BMI2		(1<<18)	/**< Bit Manipulation Instruction Set 2 */
+#define SPA_CPU_FLAG_AVX512		(1<<19)	/**< AVX-512 */
+#define SPA_CPU_FLAG_SLOW_UNALIGNED	(1<<20)	/**< unaligned loads/stores are slow */
+
+/* PPC specific */
+#define SPA_CPU_FLAG_ALTIVEC		(1<<0)	/**< standard */
+#define SPA_CPU_FLAG_VSX		(1<<1)	/**< ISA 2.06 */
+#define SPA_CPU_FLAG_POWER8		(1<<2)	/**< ISA 2.07 */
+
+/* ARM specific */
+#define SPA_CPU_FLAG_ARMV5TE		(1 << 0)
+#define SPA_CPU_FLAG_ARMV6		(1 << 1)
+#define SPA_CPU_FLAG_ARMV6T2		(1 << 2)
+#define SPA_CPU_FLAG_VFP		(1 << 3)
+#define SPA_CPU_FLAG_VFPV3		(1 << 4)
+#define SPA_CPU_FLAG_NEON		(1 << 5)
+#define SPA_CPU_FLAG_ARMV8		(1 << 6)
+
+#define SPA_CPU_FORCE_AUTODETECT	((uint32_t)-1)
+/**
+ * methods
+ */
+struct spa_cpu_methods {
+	/** the version of the methods. This can be used to expand this
+	  structure in the future */
+#define SPA_VERSION_CPU_METHODS	0
+	uint32_t version;
+
+	/** get CPU flags */
+	uint32_t (*get_flags) (void *object);
+
+	/** force CPU flags, use SPA_CPU_FORCE_AUTODETECT to autodetect CPU flags */
+	int (*force_flags) (void *object, uint32_t flags);
+
+	/** get number of CPU cores */
+	uint32_t (*get_count) (void *object);
+
+	/** get maximum required alignment of data */
+	uint32_t (*get_max_align) (void *object);
+};
+
+#define spa_cpu_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	struct spa_cpu *_c = o;						\
+	spa_interface_call_res(&_c->iface,				\
+			struct spa_cpu_methods, _res,			\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+#define spa_cpu_get_flags(c)		spa_cpu_method(c, get_flags, 0)
+#define spa_cpu_force_flags(c,f)	spa_cpu_method(c, force_flags, 0, f)
+#define spa_cpu_get_count(c)		spa_cpu_method(c, get_count, 0)
+#define spa_cpu_get_max_align(c)	spa_cpu_method(c, get_max_align, 0)
+
+/** keys can be given when initializing the cpu handle */
+#define SPA_KEY_CPU_FORCE		"cpu.force"		/**< force cpu flags */
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_CPU_H */
diff --git a/third_party/pipewire/spa/support/dbus.h b/third_party/pipewire/spa/support/dbus.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/support/dbus.h
@@ -0,0 +1,100 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DBUS_H
+#define SPA_DBUS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/support/loop.h>
+
+#define SPA_TYPE_INTERFACE_DBus		SPA_TYPE_INFO_INTERFACE_BASE "DBus"
+
+#define SPA_VERSION_DBUS		0
+struct spa_dbus { struct spa_interface iface; };
+
+enum spa_dbus_type {
+	SPA_DBUS_TYPE_SESSION,	/**< The login session bus */
+	SPA_DBUS_TYPE_SYSTEM,	/**< The systemwide bus */
+	SPA_DBUS_TYPE_STARTER	/**< The bus that started us, if any */
+};
+
+struct spa_dbus_connection {
+#define SPA_VERSION_DBUS_CONNECTION	0
+        uint32_t version;
+	/**
+	 * Get the DBusConnection from a wrapper
+	 *
+	 * \param conn the spa_dbus_connection wrapper
+	 * \return a pointer of type DBusConnection
+	 */
+	void *(*get) (struct spa_dbus_connection *conn);
+	/**
+	 * Destroy a dbus connection wrapper
+	 *
+	 * \param conn the wrapper to destroy
+	 */
+	void (*destroy) (struct spa_dbus_connection *conn);
+};
+
+#define spa_dbus_connection_get(c)	(c)->get((c))
+#define spa_dbus_connection_destroy(c)	(c)->destroy((c))
+
+struct spa_dbus_methods {
+#define SPA_VERSION_DBUS_METHODS	0
+        uint32_t version;
+
+	/**
+	 * Get a new connection wrapper for the given bus type.
+	 *
+	 * The connection wrapper is completely configured to operate
+	 * in the main context of the handle that manages the spa_dbus
+	 * interface.
+	 *
+	 * \param dbus the dbus manager
+	 * \param type the bus type to wrap
+	 * \param error location for the DBusError
+	 * \return a new dbus connection wrapper or NULL on error
+	 */
+	struct spa_dbus_connection * (*get_connection) (void *object,
+							enum spa_dbus_type type);
+};
+
+static inline struct spa_dbus_connection *
+spa_dbus_get_connection(struct spa_dbus *dbus, enum spa_dbus_type type)
+{
+	struct spa_dbus_connection *res = NULL;
+	spa_interface_call_res(&dbus->iface,
+                        struct spa_dbus_methods, res,
+			get_connection, 0, type);
+	return res;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DBUS_H */
diff --git a/third_party/pipewire/spa/support/log-impl.h b/third_party/pipewire/spa/support/log-impl.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/support/log-impl.h
@@ -0,0 +1,86 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_LOG_IMPL_H
+#define SPA_LOG_IMPL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+#include <spa/utils/type.h>
+#include <spa/support/log.h>
+
+static inline SPA_PRINTF_FUNC(6, 0) void spa_log_impl_logv(void *object,
+				     enum spa_log_level level,
+				     const char *file,
+				     int line,
+				     const char *func,
+				     const char *fmt,
+				     va_list args)
+{
+        char text[512], location[1024];
+        static const char *levels[] = { "-", "E", "W", "I", "D", "T" };
+
+        vsnprintf(text, sizeof(text), fmt, args);
+        snprintf(location, sizeof(location), "[%s][%s:%i %s()] %s\n",
+                levels[level], strrchr(file, '/') + 1, line, func, text);
+        fputs(location, stderr);
+}
+static inline SPA_PRINTF_FUNC(6,7) void spa_log_impl_log(void *object,
+				    enum spa_log_level level,
+				    const char *file,
+				    int line,
+				    const char *func,
+				    const char *fmt, ...)
+{
+	va_list args;
+	va_start(args, fmt);
+	spa_log_impl_logv(object, level, file, line, func, fmt, args);
+	va_end(args);
+}
+
+#define SPA_LOG_IMPL_DEFINE(name)		\
+struct {					\
+	struct spa_log log;			\
+	struct spa_log_methods methods;		\
+} name
+
+#define SPA_LOG_IMPL_INIT(name)				\
+	{ { { SPA_TYPE_INTERFACE_Log, SPA_VERSION_LOG,	\
+	      SPA_CALLBACKS_INIT(&name.methods, &name) },	\
+	    SPA_LOG_LEVEL_INFO,	},			\
+	  { SPA_VERSION_LOG_METHODS,			\
+	    spa_log_impl_log,				\
+	    spa_log_impl_logv,} }
+
+#define SPA_LOG_IMPL(name)			\
+        SPA_LOG_IMPL_DEFINE(name) = SPA_LOG_IMPL_INIT(name)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+#endif /* SPA_LOG_IMPL_H */
diff --git a/third_party/pipewire/spa/support/log.h b/third_party/pipewire/spa/support/log.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/support/log.h
@@ -0,0 +1,179 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_LOG_H
+#define SPA_LOG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+enum spa_log_level {
+	SPA_LOG_LEVEL_NONE = 0,
+	SPA_LOG_LEVEL_ERROR,
+	SPA_LOG_LEVEL_WARN,
+	SPA_LOG_LEVEL_INFO,
+	SPA_LOG_LEVEL_DEBUG,
+	SPA_LOG_LEVEL_TRACE,
+};
+
+/**
+ * The Log interface
+ */
+#define SPA_TYPE_INTERFACE_Log	SPA_TYPE_INFO_INTERFACE_BASE "Log"
+
+#define SPA_VERSION_LOG		0
+
+struct spa_log {
+	/** the version of this log. This can be used to expand this
+	 * structure in the future */
+	struct spa_interface iface;
+	/**
+	 * Logging level, everything above this level is not logged
+	 */
+	enum spa_log_level level;
+};
+
+struct spa_log_methods {
+#define SPA_VERSION_LOG_METHODS	0
+	uint32_t version;
+	/**
+	 * Log a message with the given log level.
+	 *
+	 * \param log a spa_log
+	 * \param level a spa_log_level
+	 * \param file the file name
+	 * \param line the line number
+	 * \param func the function name
+	 * \param fmt printf style format
+	 * \param ... format arguments
+	 */
+	void (*log) (void *object,
+		     enum spa_log_level level,
+		     const char *file,
+		     int line,
+		     const char *func,
+		     const char *fmt, ...) SPA_PRINTF_FUNC(6, 7);
+
+	/**
+	 * Log a message with the given log level.
+	 *
+	 * \param log a spa_log
+	 * \param level a spa_log_level
+	 * \param file the file name
+	 * \param line the line number
+	 * \param func the function name
+	 * \param fmt printf style format
+	 * \param args format arguments
+	 */
+	void (*logv) (void *object,
+		      enum spa_log_level level,
+		      const char *file,
+		      int line,
+		      const char *func,
+		      const char *fmt,
+		      va_list args) SPA_PRINTF_FUNC(6, 0);
+};
+
+#define spa_log_level_enabled(l,lev) ((l) && (l)->level >= (lev))
+
+#if defined(__USE_ISOC11) || defined(__USE_ISOC99) || \
+    (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)
+
+#define spa_log_log(l,lev,...)					\
+({								\
+	struct spa_log *_l = l;					\
+	if (SPA_UNLIKELY(spa_log_level_enabled(_l, lev)))	\
+		spa_interface_call(&_l->iface,			\
+			struct spa_log_methods, log, 0, lev,	\
+			__VA_ARGS__);				\
+})
+
+#define spa_log_logv(l,lev,...)					\
+({								\
+	struct spa_log *_l = l;					\
+	if (SPA_UNLIKELY(spa_log_level_enabled(_l, lev)))	\
+		spa_interface_call(&_l->iface,			\
+			struct spa_log_methods, logv, 0, lev,	\
+			__VA_ARGS__);				\
+})
+
+#define spa_log_error(l,...)	spa_log_log(l,SPA_LOG_LEVEL_ERROR,__FILE__,__LINE__,__func__,__VA_ARGS__)
+#define spa_log_warn(l,...)	spa_log_log(l,SPA_LOG_LEVEL_WARN,__FILE__,__LINE__,__func__,__VA_ARGS__)
+#define spa_log_info(l,...)	spa_log_log(l,SPA_LOG_LEVEL_INFO,__FILE__,__LINE__,__func__,__VA_ARGS__)
+#define spa_log_debug(l,...)	spa_log_log(l,SPA_LOG_LEVEL_DEBUG,__FILE__,__LINE__,__func__,__VA_ARGS__)
+#define spa_log_trace(l,...)	spa_log_log(l,SPA_LOG_LEVEL_TRACE,__FILE__,__LINE__,__func__,__VA_ARGS__)
+
+#ifndef FASTPATH
+#define spa_log_trace_fp(l,...)	spa_log_log(l,SPA_LOG_LEVEL_TRACE,__FILE__,__LINE__,__func__,__VA_ARGS__)
+#else
+#define spa_log_trace_fp(l,...)
+#endif
+
+#else
+
+#define SPA_LOG_FUNC(name,lev)							\
+static inline SPA_PRINTF_FUNC(2,3) void spa_log_##name (struct spa_log *l, const char *format, ...)  \
+{										\
+	if (SPA_UNLIKELY(spa_log_level_enabled(l, lev))) {			\
+		va_list varargs;						\
+		va_start (varargs, format);					\
+		spa_interface_call(&l->iface,					\
+			struct spa_log_methods, logv, 0, lev,			\
+			__FILE__,__LINE__,__func__,format,varargs);		\
+		va_end (varargs);						\
+	}									\
+}
+
+SPA_LOG_FUNC(error, SPA_LOG_LEVEL_ERROR)
+SPA_LOG_FUNC(warn, SPA_LOG_LEVEL_WARN)
+SPA_LOG_FUNC(info, SPA_LOG_LEVEL_INFO)
+SPA_LOG_FUNC(debug, SPA_LOG_LEVEL_DEBUG)
+SPA_LOG_FUNC(trace, SPA_LOG_LEVEL_TRACE)
+
+#ifndef FASTPATH
+SPA_LOG_FUNC(trace_fp, SPA_LOG_LEVEL_TRACE)
+#else
+static inline void spa_log_trace_fp (struct spa_log *l, const char *format, ...) { }
+#endif
+
+#endif
+
+/** keys can be given when initializing the logger handle */
+#define SPA_KEY_LOG_LEVEL		"log.level"		/**< the default log level */
+#define SPA_KEY_LOG_COLORS		"log.colors"		/**< enable colors in the logger */
+#define SPA_KEY_LOG_FILE		"log.file"		/**< log to the specified file instead of
+								  *  stderr. */
+#define SPA_KEY_LOG_TIMESTAMP		"log.timestamp"		/**< log timestamps */
+#define SPA_KEY_LOG_LINE		"log.line"		/**< log file and line numbers */
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+#endif /* SPA_LOG_H */
diff --git a/third_party/pipewire/spa/support/loop.h b/third_party/pipewire/spa/support/loop.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/support/loop.h
@@ -0,0 +1,312 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_LOOP_H
+#define SPA_LOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+#include <spa/support/system.h>
+
+#define SPA_TYPE_INTERFACE_Loop		SPA_TYPE_INFO_INTERFACE_BASE "Loop"
+#define SPA_TYPE_INTERFACE_DataLoop	SPA_TYPE_INFO_INTERFACE_BASE "DataLoop"
+#define SPA_VERSION_LOOP		0
+struct spa_loop { struct spa_interface iface; };
+
+#define SPA_TYPE_INTERFACE_LoopControl	SPA_TYPE_INFO_INTERFACE_BASE "LoopControl"
+#define SPA_VERSION_LOOP_CONTROL	0
+struct spa_loop_control { struct spa_interface iface; };
+
+#define SPA_TYPE_INTERFACE_LoopUtils	SPA_TYPE_INFO_INTERFACE_BASE "LoopUtils"
+#define SPA_VERSION_LOOP_UTILS		0
+struct spa_loop_utils { struct spa_interface iface; };
+
+struct spa_source;
+
+typedef void (*spa_source_func_t) (struct spa_source *source);
+
+struct spa_source {
+	struct spa_loop *loop;
+	spa_source_func_t func;
+	void *data;
+	int fd;
+	uint32_t mask;
+	uint32_t rmask;
+};
+
+typedef int (*spa_invoke_func_t) (struct spa_loop *loop,
+				  bool async,
+				  uint32_t seq,
+				  const void *data,
+				  size_t size,
+				  void *user_data);
+
+/**
+ * Register sources and work items to an event loop
+ */
+struct spa_loop_methods {
+	/* the version of this structure. This can be used to expand this
+	 * structure in the future */
+#define SPA_VERSION_LOOP_METHODS	0
+	uint32_t version;
+
+	/** add a source to the loop */
+	int (*add_source) (void *object,
+			   struct spa_source *source);
+
+	/** update the source io mask */
+	int (*update_source) (void *object,
+			struct spa_source *source);
+
+	/** remove a source from the loop */
+	int (*remove_source) (void *object,
+			struct spa_source *source);
+
+	/** invoke a function in the context of this loop */
+	int (*invoke) (void *object,
+		       spa_invoke_func_t func,
+		       uint32_t seq,
+		       const void *data,
+		       size_t size,
+		       bool block,
+		       void *user_data);
+};
+
+#define spa_loop_method(o,method,version,...)				\
+({									\
+	int _res = -ENOTSUP;						\
+	struct spa_loop *_o = o;					\
+	spa_interface_call_res(&_o->iface,				\
+			struct spa_loop_methods, _res,			\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define spa_loop_add_source(l,...)	spa_loop_method(l,add_source,0,##__VA_ARGS__)
+#define spa_loop_update_source(l,...)	spa_loop_method(l,update_source,0,##__VA_ARGS__)
+#define spa_loop_remove_source(l,...)	spa_loop_method(l,remove_source,0,##__VA_ARGS__)
+#define spa_loop_invoke(l,...)		spa_loop_method(l,invoke,0,##__VA_ARGS__)
+
+
+/** Control hooks. These hooks can't be removed from their
+ *  callbacks and must be removed from a safe place (when the loop
+ *  is not running or when it is locked). */
+struct spa_loop_control_hooks {
+#define SPA_VERSION_LOOP_CONTROL_HOOKS	0
+	uint32_t version;
+	/** Executed right before waiting for events. It is typically used to
+	 * release locks. */
+	void (*before) (void *data);
+	/** Executed right after waiting for events. It is typically used to
+	 * reacquire locks. */
+	void (*after) (void *data);
+};
+
+#define spa_loop_control_hook_before(l)							\
+({											\
+	struct spa_hook_list *_l = l;							\
+	struct spa_hook *_h;								\
+	spa_list_for_each_reverse(_h, &_l->list, link)					\
+		spa_callbacks_call(&_h->cb, struct spa_loop_control_hooks, before, 0);	\
+})
+
+#define spa_loop_control_hook_after(l)							\
+({											\
+	struct spa_hook_list *_l = l;							\
+	struct spa_hook *_h;								\
+	spa_list_for_each(_h, &_l->list, link)						\
+		spa_callbacks_call(&_h->cb, struct spa_loop_control_hooks, after, 0);	\
+})
+
+/**
+ * Control an event loop
+ */
+struct spa_loop_control_methods {
+	/* the version of this structure. This can be used to expand this
+	 * structure in the future */
+#define SPA_VERSION_LOOP_CONTROL_METHODS	0
+	uint32_t version;
+
+	int (*get_fd) (void *object);
+
+	/** Add a hook
+	 * \param ctrl the control to change
+	 * \param hooks the hooks to add
+	 *
+	 * Adds hooks to the loop controlled by \a ctrl.
+	 */
+	void (*add_hook) (void *object,
+			  struct spa_hook *hook,
+			  const struct spa_loop_control_hooks *hooks,
+			  void *data);
+
+	/** Enter a loop
+	 * \param ctrl the control
+	 *
+	 * Start an iteration of the loop. This function should be called
+	 * before calling iterate and is typically used to capture the thread
+	 * that this loop will run in.
+	 */
+	void (*enter) (void *object);
+	/** Leave a loop
+	 * \param ctrl the control
+	 *
+	 * Ends the iteration of a loop. This should be called after calling
+	 * iterate.
+	 */
+	void (*leave) (void *object);
+
+	/** Perform one iteration of the loop.
+	 * \param ctrl the control
+	 * \param timeout an optional timeout in milliseconds.
+	 *	0 for no timeout, -1 for infinite timeout.
+	 *
+	 * This function will block
+	 * up to \a timeout milliseconds and then dispatch the fds with activity.
+	 * The number of dispatched fds is returned.
+	 */
+	int (*iterate) (void *object, int timeout);
+};
+
+#define spa_loop_control_method_v(o,method,version,...)			\
+({									\
+	struct spa_loop_control *_o = o;				\
+	spa_interface_call(&_o->iface,					\
+			struct spa_loop_control_methods,		\
+			method, version, ##__VA_ARGS__);		\
+})
+
+#define spa_loop_control_method_r(o,method,version,...)			\
+({									\
+	int _res = -ENOTSUP;						\
+	struct spa_loop_control *_o = o;				\
+	spa_interface_call_res(&_o->iface,				\
+			struct spa_loop_control_methods, _res,		\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+#define spa_loop_control_get_fd(l)		spa_loop_control_method_r(l,get_fd,0)
+#define spa_loop_control_add_hook(l,...)	spa_loop_control_method_v(l,add_hook,0,__VA_ARGS__)
+#define spa_loop_control_enter(l)		spa_loop_control_method_v(l,enter,0)
+#define spa_loop_control_leave(l)		spa_loop_control_method_v(l,leave,0)
+#define spa_loop_control_iterate(l,...)		spa_loop_control_method_r(l,iterate,0,__VA_ARGS__)
+
+typedef void (*spa_source_io_func_t) (void *data, int fd, uint32_t mask);
+typedef void (*spa_source_idle_func_t) (void *data);
+typedef void (*spa_source_event_func_t) (void *data, uint64_t count);
+typedef void (*spa_source_timer_func_t) (void *data, uint64_t expirations);
+typedef void (*spa_source_signal_func_t) (void *data, int signal_number);
+
+/**
+ * Create sources for an event loop
+ */
+struct spa_loop_utils_methods {
+	/* the version of this structure. This can be used to expand this
+	 * structure in the future */
+#define SPA_VERSION_LOOP_UTILS_METHODS	0
+	uint32_t version;
+
+	struct spa_source *(*add_io) (void *object,
+				      int fd,
+				      uint32_t mask,
+				      bool close,
+				      spa_source_io_func_t func, void *data);
+
+	int (*update_io) (void *object, struct spa_source *source, uint32_t mask);
+
+	struct spa_source *(*add_idle) (void *object,
+					bool enabled,
+					spa_source_idle_func_t func, void *data);
+	int (*enable_idle) (void *object, struct spa_source *source, bool enabled);
+
+	struct spa_source *(*add_event) (void *object,
+					 spa_source_event_func_t func, void *data);
+	int (*signal_event) (void *object, struct spa_source *source);
+
+	struct spa_source *(*add_timer) (void *object,
+					 spa_source_timer_func_t func, void *data);
+	int (*update_timer) (void *object,
+			     struct spa_source *source,
+			     struct timespec *value,
+			     struct timespec *interval,
+			     bool absolute);
+	struct spa_source *(*add_signal) (void *object,
+					  int signal_number,
+					  spa_source_signal_func_t func, void *data);
+
+	/** destroy a source allocated with this interface. This function
+	 * should only be called when the loop is not running or from the
+	 * context of the running loop */
+	void (*destroy_source) (void *object, struct spa_source *source);
+};
+
+#define spa_loop_utils_method_v(o,method,version,...)			\
+({									\
+	struct spa_loop_utils *_o = o;					\
+	spa_interface_call(&_o->iface,					\
+			struct spa_loop_utils_methods,			\
+			method, version, ##__VA_ARGS__);		\
+})
+
+#define spa_loop_utils_method_r(o,method,version,...)			\
+({									\
+	int _res = -ENOTSUP;						\
+	struct spa_loop_utils *_o = o;					\
+	spa_interface_call_res(&_o->iface,				\
+			struct spa_loop_utils_methods, _res,		\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+#define spa_loop_utils_method_s(o,method,version,...)			\
+({									\
+	struct spa_source *_res = NULL;					\
+	struct spa_loop_utils *_o = o;					\
+	spa_interface_call_res(&_o->iface,				\
+			struct spa_loop_utils_methods, _res,		\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+
+#define spa_loop_utils_add_io(l,...)		spa_loop_utils_method_s(l,add_io,0,__VA_ARGS__)
+#define spa_loop_utils_update_io(l,...)		spa_loop_utils_method_r(l,update_io,0,__VA_ARGS__)
+#define spa_loop_utils_add_idle(l,...)		spa_loop_utils_method_s(l,add_idle,0,__VA_ARGS__)
+#define spa_loop_utils_enable_idle(l,...)	spa_loop_utils_method_r(l,enable_idle,0,__VA_ARGS__)
+#define spa_loop_utils_add_event(l,...)		spa_loop_utils_method_s(l,add_event,0,__VA_ARGS__)
+#define spa_loop_utils_signal_event(l,...)	spa_loop_utils_method_r(l,signal_event,0,__VA_ARGS__)
+#define spa_loop_utils_add_timer(l,...)		spa_loop_utils_method_s(l,add_timer,0,__VA_ARGS__)
+#define spa_loop_utils_update_timer(l,...)	spa_loop_utils_method_r(l,update_timer,0,__VA_ARGS__)
+#define spa_loop_utils_add_signal(l,...)	spa_loop_utils_method_s(l,add_signal,0,__VA_ARGS__)
+#define spa_loop_utils_destroy_source(l,...)	spa_loop_utils_method_v(l,destroy_source,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_LOOP_H */
diff --git a/third_party/pipewire/spa/support/plugin.h b/third_party/pipewire/spa/support/plugin.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/support/plugin.h
@@ -0,0 +1,215 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_PLUGIN_H
+#define SPA_PLUGIN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/dict.h>
+
+struct spa_handle {
+	/** Version of this struct */
+#define SPA_VERSION_HANDLE	0
+	uint32_t version;
+
+	/**
+	 * Get the interface provided by \a handle with \a type.
+	 *
+	 * \a interface is always a struct spa_interface but depending on
+	 * \a type, the struct might contain other information.
+	 *
+	 * \param handle a spa_handle
+	 * \param type the interface type
+	 * \param interface result to hold the interface.
+	 * \return 0 on success
+	 *         -ENOTSUP when there are no interfaces
+	 *         -EINVAL when handle or info is NULL
+	 */
+	int (*get_interface) (struct spa_handle *handle, const char *type, void **interface);
+	/**
+	 * Clean up the memory of \a handle. After this, \a handle should not be used
+	 * anymore.
+	 *
+	 * \param handle a pointer to memory
+	 * \return 0 on success
+	 */
+	int (*clear) (struct spa_handle *handle);
+};
+
+#define spa_handle_get_interface(h,...)	(h)->get_interface((h),__VA_ARGS__)
+#define spa_handle_clear(h)		(h)->clear((h))
+
+/**
+ * This structure lists the information about available interfaces on
+ * handles.
+ */
+struct spa_interface_info {
+	const char *type;	/*< the type of the interface, can be
+				 *  used to get the interface */
+};
+
+/**
+ * Extra supporting infrastructure passed to the init() function of
+ * a factory. It can be extra information or interfaces such as logging.
+ */
+struct spa_support {
+	const char *type;	/*< the type of the support item */
+	void *data;		/*< specific data for the item */
+};
+
+/** Find a support item of the given type */
+static inline void *spa_support_find(const struct spa_support *support,
+				     uint32_t n_support,
+				     const char *type)
+{
+	uint32_t i;
+	for (i = 0; i < n_support; i++) {
+		if (strcmp(support[i].type, type) == 0)
+			return support[i].data;
+	}
+	return NULL;
+}
+
+#define SPA_SUPPORT_INIT(type,data) (struct spa_support) { (type), (data) }
+
+struct spa_handle_factory {
+	/** The version of this structure */
+#define SPA_VERSION_HANDLE_FACTORY	1
+	uint32_t version;
+	/**
+	 * The name of the factory contains a logical name that describes
+	 * the function of the handle. Other plugins might contain an alternative
+	 * implementation with the same name.
+	 *
+	 * See utils/names.h for the list of standard names.
+	 *
+	 * Examples include:
+	 *
+	 *  api.alsa.pcm.sink: an object to write PCM samples to an alsa PLAYBACK
+	 *			device
+	 *  api.v4l2.source: an object to read from a v4l2 source.
+	 */
+	const char *name;
+	/**
+	 * Extra information about the handles of this factory.
+	 */
+	const struct spa_dict *info;
+	/**
+	 * Get the size of handles from this factory.
+	 *
+	 * \param factory a spa_handle_factory
+	 * \param params extra parameters that determine the size of the
+	 * handle.
+	 */
+	size_t (*get_size) (const struct spa_handle_factory *factory,
+			    const struct spa_dict *params);
+
+	/**
+	 * Initialize an instance of this factory. The caller should allocate
+	 * memory at least size bytes and pass this as \a handle.
+	 *
+	 * \a support can optionally contain extra interfaces or data items that the
+	 * plugin can use such as a logger.
+	 *
+	 * \param factory a spa_handle_factory
+	 * \param handle a pointer to memory
+	 * \param info extra handle specific information, usually obtained
+	 *        from a spa_device. This can be used to configure the handle.
+	 * \param support support items
+	 * \param n_support number of elements in \a support
+	 * \return 0 on success
+	 *	   < 0 errno type error
+	 */
+	int (*init) (const struct spa_handle_factory *factory,
+		     struct spa_handle *handle,
+		     const struct spa_dict *info,
+		     const struct spa_support *support,
+		     uint32_t n_support);
+
+	/**
+	 * spa_handle_factory::enum_interface_info:
+	 * \param factory: a #spa_handle_factory
+	 * \param info: result to hold spa_interface_info.
+	 * \param index: index to keep track of the enumeration, 0 for first item
+	 *
+	 * Enumerate the interface information for \a factory.
+	 *
+	 * \return 1 when an item is available
+	 *	   0 when no more items are available
+	 *	   < 0 errno type error
+	 */
+	int (*enum_interface_info) (const struct spa_handle_factory *factory,
+				    const struct spa_interface_info **info,
+				    uint32_t *index);
+};
+
+#define spa_handle_factory_get_size(h,...)		(h)->get_size((h),__VA_ARGS__)
+#define spa_handle_factory_init(h,...)			(h)->init((h),__VA_ARGS__)
+#define spa_handle_factory_enum_interface_info(h,...)	(h)->enum_interface_info((h),__VA_ARGS__)
+
+/**
+ * The function signature of the entry point in a plugin.
+ *
+ * \param factory a location to hold the factory result
+ * \param index index to keep track of the enumeration
+ * \return 1 on success
+ *         0 when there are no more factories
+ *         -EINVAL when factory is NULL
+ */
+typedef int (*spa_handle_factory_enum_func_t) (const struct spa_handle_factory **factory,
+					       uint32_t *index);
+
+#define SPA_HANDLE_FACTORY_ENUM_FUNC_NAME "spa_handle_factory_enum"
+
+/**
+ * The entry point in a plugin.
+ *
+ * \param factory a location to hold the factory result
+ * \param index index to keep track of the enumeration
+ * \return 1 on success
+ *	   0 when no more items are available
+ *	   < 0 errno type error
+ */
+int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index);
+
+
+
+#define SPA_KEY_FACTORY_NAME		"factory.name"		/**< the name of a factory */
+#define SPA_KEY_FACTORY_AUTHOR		"factory.author"	/**< a comma separated list of factory authors */
+#define SPA_KEY_FACTORY_DESCRIPTION	"factory.description"	/**< description of a factory */
+#define SPA_KEY_FACTORY_USAGE		"factory.usage"		/**< usage of a factory */
+
+#define SPA_KEY_LIBRARY_NAME		"library.name"		/**< the name of a library. This is usually
+								  *  the filename of the plugin without the
+								  *  path or the plugin extension. */
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_PLUGIN_H */
diff --git a/third_party/pipewire/spa/support/system.h b/third_party/pipewire/spa/support/system.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/support/system.h
@@ -0,0 +1,152 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_SYSTEM_H
+#define SPA_SYSTEM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct itimerspec;
+
+#include <time.h>
+#include <sys/types.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+/**
+ * a collection of core system functions
+ */
+#define SPA_TYPE_INTERFACE_System	SPA_TYPE_INFO_INTERFACE_BASE "System"
+#define SPA_TYPE_INTERFACE_DataSystem	SPA_TYPE_INFO_INTERFACE_BASE "DataSystem"
+
+#define SPA_VERSION_SYSTEM		0
+struct spa_system { struct spa_interface iface; };
+
+/* IO events */
+#define SPA_IO_IN	(1 << 0)
+#define SPA_IO_OUT	(1 << 2)
+#define SPA_IO_ERR	(1 << 3)
+#define SPA_IO_HUP	(1 << 4)
+
+/* flags */
+#define SPA_FD_CLOEXEC			(1<<0)
+#define SPA_FD_NONBLOCK			(1<<1)
+#define SPA_FD_EVENT_SEMAPHORE		(1<<2)
+#define SPA_FD_TIMER_ABSTIME		(1<<3)
+#define SPA_FD_TIMER_CANCEL_ON_SET	(1<<4)
+
+struct spa_poll_event {
+	uint32_t events;
+	void *data;
+};
+
+struct spa_system_methods {
+#define SPA_VERSION_SYSTEM_METHODS	0
+	uint32_t version;
+
+	/* read/write/ioctl */
+	ssize_t (*read) (void *object, int fd, void *buf, size_t count);
+	ssize_t (*write) (void *object, int fd, const void *buf, size_t count);
+	int (*ioctl) (void *object, int fd, unsigned long request, ...);
+	int (*close) (void *object, int fd);
+
+	/* clock */
+	int (*clock_gettime) (void *object,
+			int clockid, struct timespec *value);
+	int (*clock_getres) (void *object,
+			int clockid, struct timespec *res);
+
+	/* poll */
+	int (*pollfd_create) (void *object, int flags);
+	int (*pollfd_add) (void *object, int pfd, int fd, uint32_t events, void *data);
+	int (*pollfd_mod) (void *object, int pfd, int fd, uint32_t events, void *data);
+	int (*pollfd_del) (void *object, int pfd, int fd);
+	int (*pollfd_wait) (void *object, int pfd,
+			struct spa_poll_event *ev, int n_ev, int timeout);
+
+	/* timers */
+	int (*timerfd_create) (void *object, int clockid, int flags);
+	int (*timerfd_settime) (void *object,
+			int fd, int flags,
+			const struct itimerspec *new_value,
+			struct itimerspec *old_value);
+	int (*timerfd_gettime) (void *object,
+			int fd, struct itimerspec *curr_value);
+	int (*timerfd_read) (void *object, int fd, uint64_t *expirations);
+
+	/* events */
+	int (*eventfd_create) (void *object, int flags);
+	int (*eventfd_write) (void *object, int fd, uint64_t count);
+	int (*eventfd_read) (void *object, int fd, uint64_t *count);
+
+	/* signals */
+	int (*signalfd_create) (void *object, int signal, int flags);
+	int (*signalfd_read) (void *object, int fd, int *signal);
+};
+
+#define spa_system_method_r(o,method,version,...)			\
+({									\
+	int _res = -ENOTSUP;						\
+	struct spa_system *_o = o;					\
+	spa_interface_call_res(&_o->iface,				\
+			struct spa_system_methods, _res,		\
+			method, version, ##__VA_ARGS__);		\
+	_res;								\
+})
+
+
+#define spa_system_read(s,...)			spa_system_method_r(s,read,0,__VA_ARGS__)
+#define spa_system_write(s,...)			spa_system_method_r(s,write,0,__VA_ARGS__)
+#define spa_system_ioctl(s,...)			spa_system_method_r(s,ioctl,0,__VA_ARGS__)
+#define spa_system_close(s,...)			spa_system_method_r(s,close,0,__VA_ARGS__)
+
+#define spa_system_clock_gettime(s,...)		spa_system_method_r(s,clock_gettime,0,__VA_ARGS__)
+#define spa_system_clock_getres(s,...)		spa_system_method_r(s,clock_getres,0,__VA_ARGS__)
+
+#define spa_system_pollfd_create(s,...)		spa_system_method_r(s,pollfd_create,0,__VA_ARGS__)
+#define spa_system_pollfd_add(s,...)		spa_system_method_r(s,pollfd_add,0,__VA_ARGS__)
+#define spa_system_pollfd_mod(s,...)		spa_system_method_r(s,pollfd_mod,0,__VA_ARGS__)
+#define spa_system_pollfd_del(s,...)		spa_system_method_r(s,pollfd_del,0,__VA_ARGS__)
+#define spa_system_pollfd_wait(s,...)		spa_system_method_r(s,pollfd_wait,0,__VA_ARGS__)
+
+#define spa_system_timerfd_create(s,...)	spa_system_method_r(s,timerfd_create,0,__VA_ARGS__)
+#define spa_system_timerfd_settime(s,...)	spa_system_method_r(s,timerfd_settime,0,__VA_ARGS__)
+#define spa_system_timerfd_gettime(s,...)	spa_system_method_r(s,timerfd_gettime,0,__VA_ARGS__)
+#define spa_system_timerfd_read(s,...)		spa_system_method_r(s,timerfd_read,0,__VA_ARGS__)
+
+#define spa_system_eventfd_create(s,...)	spa_system_method_r(s,eventfd_create,0,__VA_ARGS__)
+#define spa_system_eventfd_write(s,...)		spa_system_method_r(s,eventfd_write,0,__VA_ARGS__)
+#define spa_system_eventfd_read(s,...)		spa_system_method_r(s,eventfd_read,0,__VA_ARGS__)
+
+#define spa_system_signalfd_create(s,...)	spa_system_method_r(s,signalfd_create,0,__VA_ARGS__)
+#define spa_system_signalfd_read(s,...)		spa_system_method_r(s,signalfd_read,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_SYSTEM_H */
diff --git a/third_party/pipewire/spa/utils/defs.h b/third_party/pipewire/spa/utils/defs.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/defs.h
@@ -0,0 +1,265 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_UTILS_DEFS_H
+#define SPA_UTILS_DEFS_H
+
+#ifdef __cplusplus
+extern "C" {
+#else
+#include <stdbool.h>
+#endif
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#define SPA_FLAG_MASK(field,mask,flag)	(((field) & (mask)) == (flag))
+#define SPA_FLAG_IS_SET(field,flag)	SPA_FLAG_MASK(field,flag,flag)
+#define SPA_FLAG_SET(field,flag)	((field) |= (flag))
+#define SPA_FLAG_CLEAR(field,flag)	((field) &= ~(flag))
+#define SPA_FLAG_UPDATE(field,flag,val)	((val) ? SPA_FLAG_SET(field,flag) : SPA_FLAG_CLEAR(field,flag))
+
+enum spa_direction {
+	SPA_DIRECTION_INPUT = 0,
+	SPA_DIRECTION_OUTPUT = 1,
+};
+
+#define SPA_DIRECTION_REVERSE(d)	((d) ^ 1)
+
+#define SPA_RECTANGLE(width,height) (struct spa_rectangle){ width, height }
+struct spa_rectangle {
+	uint32_t width;
+	uint32_t height;
+};
+
+#define SPA_POINT(x,y) (struct spa_point){ x, y }
+struct spa_point {
+	int32_t x;
+	int32_t y;
+};
+
+#define SPA_REGION(x,y,width,height) (struct spa_region){ SPA_POINT(x,y), SPA_RECTANGLE(width,height) }
+struct spa_region {
+	struct spa_point position;
+	struct spa_rectangle size;
+};
+
+#define SPA_FRACTION(num,denom) (struct spa_fraction){ num, denom }
+struct spa_fraction {
+	uint32_t num;
+	uint32_t denom;
+};
+
+#define SPA_N_ELEMENTS(arr)  (sizeof(arr) / sizeof((arr)[0]))
+
+#define SPA_MIN(a,b)		\
+({				\
+	__typeof__(a) _a = (a);	\
+	__typeof__(b) _b = (b);	\
+	SPA_LIKELY(_a < _b) ? _a : _b;	\
+})
+#define SPA_MAX(a,b)		\
+({				\
+	__typeof__(a) _a = (a);	\
+	__typeof__(b) _b = (b);	\
+	SPA_LIKELY(_a > _b) ? _a : _b;	\
+})
+#define SPA_CLAMP(v,low,high)				\
+({							\
+	__typeof__(v) _v = (v);				\
+	__typeof__(low) _low = (low);			\
+	__typeof__(high) _high = (high);		\
+	SPA_MIN(SPA_MAX(_v, _low), _high);		\
+})
+
+#define SPA_SWAP(a,b)					\
+({							\
+	__typeof__(a) _t = (a);				\
+	a = b; b = _t;					\
+})
+
+#define SPA_TYPECHECK(type,x)		\
+({	type _dummy;			\
+	typeof(x) _dummy2;		\
+	(void)(&_dummy == &_dummy2);	\
+	x;				\
+})
+
+#define SPA_MEMBER(b,o,t) ((t*)((uint8_t*)(b) + (int)(o)))
+#define SPA_MEMBER_ALIGN(b,o,a,t) SPA_PTR_ALIGN(SPA_MEMBER(b,o,t),a,t)
+
+#define SPA_CONTAINER_OF(p,t,m) (t*)((uint8_t*)p - offsetof (t,m))
+
+#define SPA_PTRDIFF(p1,p2) ((uint8_t*)(p1) - (uint8_t*)(p2))
+
+#define SPA_PTR_TO_INT(p) ((int) ((intptr_t) (p)))
+#define SPA_INT_TO_PTR(u) ((void*) ((intptr_t) (u)))
+
+#define SPA_PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p)))
+#define SPA_UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u)))
+
+#define SPA_TIME_INVALID  ((int64_t)INT64_MIN)
+#define SPA_IDX_INVALID  ((unsigned int)-1)
+#define SPA_ID_INVALID  ((uint32_t)0xffffffff)
+
+#define SPA_NSEC_PER_SEC  (1000000000ll)
+#define SPA_NSEC_PER_MSEC (1000000ll)
+#define SPA_NSEC_PER_USEC (1000ll)
+#define SPA_USEC_PER_SEC  (1000000ll)
+#define SPA_USEC_PER_MSEC (1000ll)
+#define SPA_MSEC_PER_SEC  (1000ll)
+
+#define SPA_TIMESPEC_TO_NSEC(ts) ((ts)->tv_sec * SPA_NSEC_PER_SEC + (ts)->tv_nsec)
+#define SPA_TIMESPEC_TO_USEC(ts) ((ts)->tv_sec * SPA_USEC_PER_SEC + (ts)->tv_nsec / SPA_NSEC_PER_USEC)
+#define SPA_TIMEVAL_TO_NSEC(tv)  ((tv)->tv_sec * SPA_NSEC_PER_SEC + (tv)->tv_usec * SPA_NSEC_PER_USEC)
+#define SPA_TIMEVAL_TO_USEC(tv)  ((tv)->tv_sec * SPA_USEC_PER_SEC + (tv)->tv_usec)
+
+#ifdef __GNUC__
+#define SPA_PRINTF_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1)))
+#define SPA_ALIGNED(align) __attribute__((aligned(align)))
+#define SPA_DEPRECATED __attribute__ ((deprecated))
+#define SPA_EXPORT __attribute__((visibility("default")))
+#define SPA_SENTINEL __attribute__((__sentinel__))
+#define SPA_UNUSED __attribute__ ((unused))
+#else
+#define SPA_PRINTF_FUNC(fmt, arg1)
+#define SPA_ALIGNED(align)
+#define SPA_DEPRECATED
+#define SPA_EXPORT
+#define SPA_SENTINEL
+#define SPA_UNUSED
+#endif
+
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+#define SPA_RESTRICT restrict
+#elif defined(__GNUC__) && __GNUC__ >= 4
+#define SPA_RESTRICT __restrict__
+#else
+#define SPA_RESTRICT
+#endif
+
+#define SPA_ROUND_DOWN_N(num,align)	((num) & ~((align) - 1))
+#define SPA_ROUND_UP_N(num,align)	SPA_ROUND_DOWN_N((num) + ((align) - 1),align)
+
+#define SPA_PTR_ALIGNMENT(p,align)	((intptr_t)(p) & ((align)-1))
+#define SPA_IS_ALIGNED(p,align)		(SPA_PTR_ALIGNMENT(p,align) == 0)
+#define SPA_PTR_ALIGN(p,align,type)	(type*)SPA_ROUND_UP_N((intptr_t)(p), (intptr_t)(align))
+
+#ifndef SPA_LIKELY
+#ifdef __GNUC__
+#define SPA_LIKELY(x) (__builtin_expect(!!(x),1))
+#define SPA_UNLIKELY(x) (__builtin_expect(!!(x),0))
+#else
+#define SPA_LIKELY(x) (x)
+#define SPA_UNLIKELY(x) (x)
+#endif
+#endif
+
+#define SPA_STRINGIFY_1(...)	#__VA_ARGS__
+#define SPA_STRINGIFY(...)	SPA_STRINGIFY_1(__VA_ARGS__)
+
+#define spa_return_if_fail(expr)					\
+	do {								\
+		if (SPA_UNLIKELY(!(expr))) {				\
+			fprintf(stderr, "'%s' failed at %s:%u %s()\n",	\
+				#expr , __FILE__, __LINE__, __func__);	\
+			return;						\
+		}							\
+	} while(false)
+
+#define spa_return_val_if_fail(expr, val)				\
+	do {								\
+		if (SPA_UNLIKELY(!(expr))) {				\
+			fprintf(stderr, "'%s' failed at %s:%u %s()\n",	\
+				#expr , __FILE__, __LINE__, __func__);	\
+			return (val);					\
+		}							\
+	} while(false)
+
+/* spa_assert_se() is an assert which guarantees side effects of x,
+ * i.e. is never optimized away, regardless of NDEBUG or FASTPATH. */
+#define spa_assert_se(expr)						\
+	do {								\
+		if (SPA_UNLIKELY(!(expr))) {				\
+			fprintf(stderr, "'%s' failed at %s:%u %s()\n",	\
+				#expr , __FILE__, __LINE__, __func__);	\
+			abort();					\
+		}							\
+	} while (false)
+
+#define spa_assert(expr)						\
+	do {								\
+		if (SPA_UNLIKELY(!(expr))) {				\
+			fprintf(stderr, "'%s' failed at %s:%u %s()\n",	\
+				#expr , __FILE__, __LINE__, __func__);	\
+			abort();					\
+		}							\
+	} while (false)
+
+#define spa_assert_not_reached()						\
+	do {									\
+		fprintf(stderr, "Code should not be reached at %s:%u %s()\n",	\
+				__FILE__, __LINE__, __func__);			\
+		abort();							\
+	} while (false)
+
+/* Does exactly nothing */
+#define spa_nop() do {} while (false)
+
+#define spa_memzero(x,l) (memset((x), 0, (l)))
+#define spa_zero(x) (spa_memzero(&(x), sizeof(x)))
+
+#ifdef SPA_DEBUG_MEMCPY
+#define spa_memcpy(d,s,n)						\
+({									\
+	fprintf(stderr, "%s:%u %s() memcpy(%p, %p, %zd)\n",		\
+		__FILE__, __LINE__, __func__, (d), (s), (size_t)(n));	\
+	memcpy(d,s,n);							\
+})
+#define spa_memmove(d,s,n)						\
+({									\
+	fprintf(stderr, "%s:%u %s() memmove(%p, %p, %zd)\n",		\
+		__FILE__, __LINE__, __func__, (d), (s), (size_t)(n));	\
+	memmove(d,s,n);							\
+})
+#else
+#define spa_memcpy(d,s,n)	memcpy(d,s,n)
+#define spa_memmove(d,s,n)	memmove(d,s,n)
+#endif
+
+#define spa_aprintf(_fmt, ...)						\
+({									\
+	char *_strp;							\
+	if (asprintf(&(_strp), (_fmt), ## __VA_ARGS__ ) == -1)		\
+		_strp = NULL;						\
+	_strp;								\
+})
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_UTILS_DEFS_H */
diff --git a/third_party/pipewire/spa/utils/dict.h b/third_party/pipewire/spa/utils/dict.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/dict.h
@@ -0,0 +1,104 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_DICT_H
+#define SPA_DICT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+
+#include <spa/utils/defs.h>
+
+struct spa_dict_item {
+	const char *key;
+	const char *value;
+};
+
+#define SPA_DICT_ITEM_INIT(key,value) (struct spa_dict_item) { key, value }
+
+struct spa_dict {
+#define SPA_DICT_FLAG_SORTED	(1<<0)		/**< items are sorted */
+	uint32_t flags;
+	uint32_t n_items;
+	const struct spa_dict_item *items;
+};
+
+#define SPA_DICT_INIT(items,n_items) (struct spa_dict) { 0, n_items, items }
+#define SPA_DICT_INIT_ARRAY(items) (struct spa_dict) { 0, SPA_N_ELEMENTS(items), items }
+
+#define spa_dict_for_each(item, dict)				\
+	for ((item) = (dict)->items;				\
+	     (item) < &(dict)->items[(dict)->n_items];		\
+	     (item)++)
+
+static inline int spa_dict_item_compare(const void *i1, const void *i2)
+{
+	const struct spa_dict_item *it1 = (const struct spa_dict_item *)i1,
+	      *it2 = (const struct spa_dict_item *)i2;
+	return strcmp(it1->key, it2->key);
+}
+
+static inline void spa_dict_qsort(struct spa_dict *dict)
+{
+	qsort((void*)dict->items, dict->n_items, sizeof(struct spa_dict_item),
+			spa_dict_item_compare);
+	SPA_FLAG_SET(dict->flags, SPA_DICT_FLAG_SORTED);
+}
+
+static inline const struct spa_dict_item *spa_dict_lookup_item(const struct spa_dict *dict,
+							       const char *key)
+{
+	const struct spa_dict_item *item;
+
+	if (SPA_FLAG_IS_SET(dict->flags, SPA_DICT_FLAG_SORTED)) {
+		struct spa_dict_item k = SPA_DICT_ITEM_INIT(key, NULL);
+		item = (const struct spa_dict_item *)bsearch(&k,
+				(const void *) dict->items, dict->n_items,
+				sizeof(struct spa_dict_item),
+				spa_dict_item_compare);
+		if (item != NULL)
+			return item;
+	} else {
+		spa_dict_for_each(item, dict) {
+			if (!strcmp(item->key, key))
+				return item;
+		}
+	}
+	return NULL;
+}
+
+static inline const char *spa_dict_lookup(const struct spa_dict *dict, const char *key)
+{
+	const struct spa_dict_item *item = spa_dict_lookup_item(dict, key);
+	return item ? item->value : NULL;
+}
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_DICT_H */
diff --git a/third_party/pipewire/spa/utils/hook.h b/third_party/pipewire/spa/utils/hook.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/hook.h
@@ -0,0 +1,198 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_HOOK_H
+#define SPA_HOOK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/list.h>
+
+/** \class spa_hook
+ *
+ * \brief a list of hooks
+ *
+ * The hook list provides a way to keep track of hooks.
+ */
+/** A list of hooks */
+struct spa_hook_list {
+	struct spa_list list;
+};
+
+/** Callbacks, contains the structure with functions and the data passed
+ * to the functions.  The structure should also contain a version field that
+ * is checked. */
+struct spa_callbacks {
+	const void *funcs;
+	void *data;
+};
+
+/** Check if a callback \c has method \m of version \v */
+#define SPA_CALLBACK_CHECK(c,m,v) ((c) && ((v) == 0 || (c)->version > (v)-1) && (c)->m)
+
+#define SPA_CALLBACKS_INIT(_funcs,_data) (struct spa_callbacks){ _funcs, _data, }
+
+struct spa_interface {
+	const char *type;
+	uint32_t version;
+	struct spa_callbacks cb;
+};
+
+#define SPA_INTERFACE_INIT(_type,_version,_funcs,_data) \
+	(struct spa_interface){ _type, _version, SPA_CALLBACKS_INIT(_funcs,_data), }
+
+/** A hook, contains the structure with functions and the data passed
+ * to the functions. */
+struct spa_hook {
+	struct spa_list link;
+	struct spa_callbacks cb;
+	/** callback and data for the hook list */
+	void (*removed) (struct spa_hook *hook);
+	void *priv;
+};
+
+/** Initialize a hook list */
+static inline void spa_hook_list_init(struct spa_hook_list *list)
+{
+	spa_list_init(&list->list);
+}
+
+static inline bool spa_hook_list_is_empty(struct spa_hook_list *list)
+{
+	return spa_list_is_empty(&list->list);
+}
+
+/** Append a hook \memberof spa_hook */
+static inline void spa_hook_list_append(struct spa_hook_list *list,
+					struct spa_hook *hook,
+					const void *funcs, void *data)
+{
+	hook->cb = SPA_CALLBACKS_INIT(funcs, data);
+	spa_list_append(&list->list, &hook->link);
+}
+
+/** Prepend a hook \memberof spa_hook */
+static inline void spa_hook_list_prepend(struct spa_hook_list *list,
+					 struct spa_hook *hook,
+					 const void *funcs, void *data)
+{
+	hook->cb = SPA_CALLBACKS_INIT(funcs, data);
+	spa_list_prepend(&list->list, &hook->link);
+}
+
+/** Remove a hook \memberof spa_hook */
+static inline void spa_hook_remove(struct spa_hook *hook)
+{
+        spa_list_remove(&hook->link);
+	if (hook->removed)
+		hook->removed(hook);
+}
+
+static inline void
+spa_hook_list_isolate(struct spa_hook_list *list,
+		struct spa_hook_list *save,
+		struct spa_hook *hook,
+		const void *funcs, void *data)
+{
+	/* init save list and move hooks to it */
+	spa_hook_list_init(save);
+	spa_list_insert_list(&save->list, &list->list);
+	/* init hooks and add single hook */
+	spa_hook_list_init(list);
+	spa_hook_list_append(list, hook, funcs, data);
+}
+
+static inline void
+spa_hook_list_join(struct spa_hook_list *list,
+		struct spa_hook_list *save)
+{
+	spa_list_insert_list(&list->list, &save->list);
+}
+
+#define spa_callbacks_call(callbacks,type,method,vers,...)			\
+({										\
+	const type *_f = (const type *) (callbacks)->funcs;			\
+	if (SPA_LIKELY(SPA_CALLBACK_CHECK(_f,method,vers)))			\
+		_f->method((callbacks)->data, ## __VA_ARGS__);			\
+})
+
+#define spa_callbacks_call_res(callbacks,type,res,method,vers,...)		\
+({										\
+	const type *_f = (const type *) (callbacks)->funcs;			\
+	if (SPA_LIKELY(SPA_CALLBACK_CHECK(_f,method,vers)))			\
+		res = _f->method((callbacks)->data, ## __VA_ARGS__);		\
+	res;									\
+})
+
+#define spa_interface_call(iface,type,method,vers,...)				\
+	spa_callbacks_call(&(iface)->cb,type,method,vers,##__VA_ARGS__)
+
+#define spa_interface_call_res(iface,type,res,method,vers,...)			\
+	spa_callbacks_call_res(&(iface)->cb,type,res,method,vers,##__VA_ARGS__)
+
+#define spa_hook_list_call_simple(l,type,method,vers,...)			\
+({										\
+	struct spa_hook_list *_l = l;						\
+	struct spa_hook *_h, *_t;						\
+	spa_list_for_each_safe(_h, _t, &_l->list, link)				\
+		spa_callbacks_call(&_h->cb,type,method,vers, ## __VA_ARGS__);	\
+})
+
+/** Call all hooks in a list, starting from the given one and optionally stopping
+ * after calling the first non-NULL function, returns the number of methods
+ * called */
+#define spa_hook_list_do_call(l,start,type,method,vers,once,...)		\
+({										\
+	struct spa_hook_list *list = l;						\
+	struct spa_list *s = start ? (struct spa_list *)start : &list->list;	\
+	struct spa_hook cursor = { 0 }, *ci;					\
+	int count = 0;								\
+	spa_list_cursor_start(cursor, s, link);					\
+	spa_list_for_each_cursor(ci, cursor, &list->list, link) {		\
+		const type *_f = (const type *)ci->cb.funcs;			\
+		if (SPA_LIKELY(SPA_CALLBACK_CHECK(_f,method,vers))) {		\
+			_f->method(ci->cb.data, ## __VA_ARGS__);		\
+			count++;						\
+			if (once)						\
+				break;						\
+		}								\
+	}									\
+	spa_list_cursor_end(cursor, link);					\
+	count;									\
+})
+
+#define spa_hook_list_call(l,t,m,v,...)			spa_hook_list_do_call(l,NULL,t,m,v,false,##__VA_ARGS__)
+#define spa_hook_list_call_once(l,t,m,v,...)		spa_hook_list_do_call(l,NULL,t,m,v,true,##__VA_ARGS__)
+
+#define spa_hook_list_call_start(l,s,t,m,v,...)		spa_hook_list_do_call(l,s,t,m,v,false,##__VA_ARGS__)
+#define spa_hook_list_call_once_start(l,s,t,m,v,...)	spa_hook_list_do_call(l,s,t,m,v,true,##__VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SPA_HOOK_H */
diff --git a/third_party/pipewire/spa/utils/keys.h b/third_party/pipewire/spa/utils/keys.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/keys.h
@@ -0,0 +1,124 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_UTILS_KEYS_H
+#define SPA_UTILS_KEYS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** for objects */
+#define SPA_KEY_OBJECT_PATH		"object.path"			/**< a unique path to
+									  *  identity the object */
+
+#define SPA_KEY_MEDIA_CLASS		"media.class"			/**< Media class
+									  *  Ex. "Audio/Device",
+									  *  "Video/Source",... */
+#define SPA_KEY_MEDIA_ROLE		"media.role"			/**< Role: Movie, Music, Camera,
+									  *  Screen, Communication, Game,
+									  *  Notification, DSP, Production,
+									  *  Accessibility, Test */
+/** keys for udev api */
+#define SPA_KEY_API_UDEV		"api.udev"			/**< key for the udev api */
+#define SPA_KEY_API_UDEV_MATCH		"api.udev.match"		/**< udev subsystem match */
+
+/** keys for alsa api */
+#define SPA_KEY_API_ALSA		"api.alsa"			/**< key for the alsa api */
+#define SPA_KEY_API_ALSA_PATH		"api.alsa.path"			/**< alsa device path as can be
+									  *  used in snd_pcm_open() and
+									  *  snd_ctl_open(). */
+#define SPA_KEY_API_ALSA_CARD		"api.alsa.card"			/**< alsa card number */
+#define SPA_KEY_API_ALSA_USE_UCM	"api.alsa.use-ucm"		/**< if UCM should be used */
+#define SPA_KEY_API_ALSA_IGNORE_DB	"api.alsa.ignore-dB"		/**< if decibel info should be ignored */
+
+/** info from alsa card_info */
+#define SPA_KEY_API_ALSA_CARD_ID	"api.alsa.card.id"		/**< id from card_info */
+#define SPA_KEY_API_ALSA_CARD_COMPONENTS	\
+					"api.alsa.card.components"	/**< components from card_info */
+#define SPA_KEY_API_ALSA_CARD_DRIVER	"api.alsa.card.driver"		/**< driver from card_info */
+#define SPA_KEY_API_ALSA_CARD_NAME	"api.alsa.card.name"		/**< name from card_info */
+#define SPA_KEY_API_ALSA_CARD_LONGNAME	"api.alsa.card.longname"	/**< longname from card_info */
+#define SPA_KEY_API_ALSA_CARD_MIXERNAME	"api.alsa.card.mixername"	/**< mixername from card_info */
+
+/** info from alsa pcm_info */
+#define SPA_KEY_API_ALSA_PCM_ID		"api.alsa.pcm.id"		/**< id from pcm_info */
+#define SPA_KEY_API_ALSA_PCM_CARD	"api.alsa.pcm.card"		/**< card from pcm_info */
+#define SPA_KEY_API_ALSA_PCM_NAME	"api.alsa.pcm.name"		/**< name from pcm_info */
+#define SPA_KEY_API_ALSA_PCM_SUBNAME	"api.alsa.pcm.subname"		/**< subdevice_name from pcm_info */
+#define SPA_KEY_API_ALSA_PCM_STREAM	"api.alsa.pcm.stream"		/**< stream type from pcm_info */
+#define SPA_KEY_API_ALSA_PCM_CLASS	"api.alsa.pcm.class"		/**< class from pcm_info as string */
+#define SPA_KEY_API_ALSA_PCM_DEVICE	"api.alsa.pcm.device"		/**< device from pcm_info */
+#define SPA_KEY_API_ALSA_PCM_SUBDEVICE	"api.alsa.pcm.subdevice"	/**< subdevice from pcm_info */
+#define SPA_KEY_API_ALSA_PCM_SUBCLASS	"api.alsa.pcm.subclass"		/**< subclass from pcm_info as string */
+#define SPA_KEY_API_ALSA_PCM_SYNC_ID	"api.alsa.pcm.sync-id"		/**< sync id */
+
+/** keys for v4l2 api */
+#define SPA_KEY_API_V4L2		"api.v4l2"			/**< key for the v4l2 api */
+#define SPA_KEY_API_V4L2_PATH		"api.v4l2.path"			/**< v4l2 device path as can be
+									  *  used in open() */
+
+/** keys for libcamera api */
+#define SPA_KEY_API_LIBCAMERA		"api.libcamera"			/**< key for the libcamera api */
+#define SPA_KEY_API_LIBCAMERA_PATH	"api.libcamera.path"	/**< libcamera device path as can be
+									  *  used in open() */
+
+/** info from libcamera_capability */
+#define SPA_KEY_API_LIBCAMERA_CAP_DRIVER	"api.libcamera.cap.driver"	/**< driver from capbility */
+#define SPA_KEY_API_LIBCAMERA_CAP_CARD	"api.libcamera.cap.card"		/**< caps from capability */
+#define SPA_KEY_API_LIBCAMERA_CAP_BUS_INFO	"api.libcamera.cap.bus_info"/**< bus_info from capability */
+#define SPA_KEY_API_LIBCAMERA_CAP_VERSION	"api.libcamera.cap.version"	/**< version from capability as %u.%u.%u */
+#define SPA_KEY_API_LIBCAMERA_CAP_CAPABILITIES	\
+					"api.libcamera.cap.capabilities"	/**< capabilities from capability */
+#define SPA_KEY_API_LIBCAMERA_CAP_DEVICE_CAPS	\
+					"api.libcamera.cap.device-caps"	/**< device_caps from capability */
+/** info from v4l2_capability */
+#define SPA_KEY_API_V4L2_CAP_DRIVER	"api.v4l2.cap.driver"		/**< driver from capbility */
+#define SPA_KEY_API_V4L2_CAP_CARD	"api.v4l2.cap.card"		/**< caps from capability */
+#define SPA_KEY_API_V4L2_CAP_BUS_INFO	"api.v4l2.cap.bus_info"		/**< bus_info from capability */
+#define SPA_KEY_API_V4L2_CAP_VERSION	"api.v4l2.cap.version"		/**< version from capability as %u.%u.%u */
+#define SPA_KEY_API_V4L2_CAP_CAPABILITIES	\
+					"api.v4l2.cap.capabilities"	/**< capabilities from capability */
+#define SPA_KEY_API_V4L2_CAP_DEVICE_CAPS	\
+					"api.v4l2.cap.device-caps"	/**< device_caps from capability */
+
+
+/** keys for bluez5 api */
+#define SPA_KEY_API_BLUEZ5		"api.bluez5"			/**< key for the bluez5 api */
+#define SPA_KEY_API_BLUEZ5_PATH		"api.bluez5.path"		/**< a bluez5 path */
+#define SPA_KEY_API_BLUEZ5_DEVICE	"api.bluez5.device"		/**< an internal bluez5 device */
+#define SPA_KEY_API_BLUEZ5_TRANSPORT	"api.bluez5.transport"		/**< an internal bluez5 transport */
+#define SPA_KEY_API_BLUEZ5_PROFILE	"api.bluez5.profile"		/**< a bluetooth profile */
+#define SPA_KEY_API_BLUEZ5_ADDRESS	"api.bluez5.address"		/**< a bluetooth address */
+
+/** keys for jack api */
+#define SPA_KEY_API_JACK		"api.jack"			/**< key for the JACK api */
+#define SPA_KEY_API_JACK_SERVER		"api.jack.server"		/**< a jack server name */
+#define SPA_KEY_API_JACK_CLIENT		"api.jack.client"		/**< an internal jack client */
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_UTILS_KEYS_H */
diff --git a/third_party/pipewire/spa/utils/list.h b/third_party/pipewire/spa/utils/list.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/list.h
@@ -0,0 +1,138 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_LIST_H
+#define SPA_LIST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct spa_list {
+	struct spa_list *next;
+	struct spa_list *prev;
+};
+
+#define SPA_LIST_INIT(list) (struct spa_list){ list, list };
+
+static inline void spa_list_init(struct spa_list *list)
+{
+	*list = SPA_LIST_INIT(list);
+}
+
+#define spa_list_is_empty(l)  ((l)->next == (l))
+
+static inline void spa_list_insert(struct spa_list *list, struct spa_list *elem)
+{
+	elem->prev = list;
+	elem->next = list->next;
+	list->next = elem;
+	elem->next->prev = elem;
+}
+
+static inline void spa_list_insert_list(struct spa_list *list, struct spa_list *other)
+{
+	if (spa_list_is_empty(other))
+		return;
+	other->next->prev = list;
+	other->prev->next = list->next;
+	list->next->prev = other->prev;
+	list->next = other->next;
+}
+
+static inline void spa_list_remove(struct spa_list *elem)
+{
+	elem->prev->next = elem->next;
+	elem->next->prev = elem->prev;
+}
+
+#define spa_list_first(head, type, member)				\
+	SPA_CONTAINER_OF((head)->next, type, member)
+
+#define spa_list_last(head, type, member)				\
+	SPA_CONTAINER_OF((head)->prev, type, member)
+
+#define spa_list_append(list, item)					\
+	spa_list_insert((list)->prev, item)
+
+#define spa_list_prepend(list, item)					\
+	spa_list_insert(list, item)
+
+#define spa_list_is_end(pos, head, member)				\
+	(&(pos)->member == (head))
+
+#define spa_list_next(pos, member)					\
+	SPA_CONTAINER_OF((pos)->member.next, __typeof__(*pos), member)
+
+#define spa_list_prev(pos, member)					\
+	SPA_CONTAINER_OF((pos)->member.prev, __typeof__(*pos), member)
+
+#define spa_list_consume(pos, head, member)				\
+	for (pos = spa_list_first(head, __typeof__(*pos), member);	\
+	     !spa_list_is_empty(head);					\
+	     pos = spa_list_first(head, __typeof__(*pos), member))
+
+#define spa_list_for_each_next(pos, head, curr, member)			\
+	for (pos = spa_list_first(curr, __typeof__(*pos), member);	\
+	     !spa_list_is_end(pos, head, member);			\
+	     pos = spa_list_next(pos, member))
+
+#define spa_list_for_each_prev(pos, head, curr, member)			\
+	for (pos = spa_list_last(curr, __typeof__(*pos), member);	\
+	     !spa_list_is_end(pos, head, member);			\
+	     pos = spa_list_prev(pos, member))
+
+#define spa_list_for_each(pos, head, member)				\
+	spa_list_for_each_next(pos, head, head, member)
+
+#define spa_list_for_each_reverse(pos, head, member)				\
+	spa_list_for_each_prev(pos, head, head, member)
+
+#define spa_list_for_each_safe_next(pos, tmp, head, curr, member)	\
+	for (pos = spa_list_first(curr, __typeof__(*pos), member);	\
+	     tmp = spa_list_next(pos, member),				\
+	     !spa_list_is_end(pos, head, member);			\
+	     pos = tmp)
+
+#define spa_list_for_each_safe(pos, tmp, head, member)			\
+	spa_list_for_each_safe_next(pos, tmp, head, head, member)
+
+#define spa_list_cursor_start(cursor, head, member)                     \
+        spa_list_prepend(head, &(cursor).member)
+
+#define spa_list_for_each_cursor(pos, cursor, head, member)             \
+        for(pos = spa_list_first(&(cursor).member, __typeof__(*(pos)), member); \
+            spa_list_remove(&(pos)->member),                            \
+            spa_list_append(&(cursor).member, &(pos)->member),          \
+            !spa_list_is_end(pos, head, member);                        \
+            pos = spa_list_next(&cursor, member))
+
+#define spa_list_cursor_end(cursor, member)                             \
+        spa_list_remove(&(cursor).member)
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_LIST_H */
diff --git a/third_party/pipewire/spa/utils/names.h b/third_party/pipewire/spa/utils/names.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/names.h
@@ -0,0 +1,137 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_UTILS_NAMES_H
+#define SPA_UTILS_NAMES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** for factory names */
+#define SPA_NAME_SUPPORT_CPU		"support.cpu"			/**< A CPU interface */
+#define SPA_NAME_SUPPORT_DBUS		"support.dbus"			/**< A DBUS interface */
+#define SPA_NAME_SUPPORT_LOG		"support.log"			/**< A Log interface */
+#define SPA_NAME_SUPPORT_LOOP		"support.loop"			/**< A Loop/LoopControl/LoopUtils
+									  *  interface */
+#define SPA_NAME_SUPPORT_SYSTEM		"support.system"		/**< A System interface */
+
+#define SPA_NAME_SUPPORT_NODE_DRIVER	"support.node.driver"		/**< A dummy driver node */
+
+/* control mixer */
+#define SPA_NAME_CONTROL_MIXER		"control.mixer"			/**< mixes control streams */
+
+/* audio mixer */
+#define SPA_NAME_AUDIO_MIXER		"audio.mixer"			/**< mixes the raw audio on N input
+									  *  ports together on the output
+									  *  port */
+#define SPA_NAME_AUDIO_MIXER_DSP	"audio.mixer.dsp"		/**< mixes mono audio with fixed input
+									  *  and output buffer sizes. supported
+									  *  formats must include f32 and
+									  *  optionally f64 and s24_32 */
+
+/** audio processing */
+#define SPA_NAME_AUDIO_PROCESS_FORMAT	"audio.process.format"		/**< processes raw audio from one format
+									  *  to another */
+#define SPA_NAME_AUDIO_PROCESS_CHANNELMIX	\
+					"audio.process.channelmix"	/**< mixes raw audio channels and applies
+									  *  volume change. */
+#define SPA_NAME_AUDIO_PROCESS_RESAMPLE		\
+					"audio.process.resample"	/**< resamples raw audio */
+#define SPA_NAME_AUDIO_PROCESS_DEINTERLEAVE	\
+					"audio.process.deinterleave"	/**< deinterleave raw audio channels */
+#define SPA_NAME_AUDIO_PROCESS_INTERLEAVE	\
+					"audio.process.interleave"	/**< interleave raw audio channels */
+
+
+/** audio convert combines some of the audio processing */
+#define SPA_NAME_AUDIO_CONVERT		"audio.convert"			/**< converts raw audio from one format
+									  *  to another. Must include at least
+									  *  format, channelmix and resample
+									  *  processing */
+#define SPA_NAME_AUDIO_ADAPT		"audio.adapt"			/**< combination of a node and an
+									  *  audio.convert. Does clock slaving */
+
+/** video processing */
+#define SPA_NAME_VIDEO_PROCESS_FORMAT	"video.process.format"		/**< processes raw video from one format
+									  *  to another */
+#define SPA_NAME_VIDEO_PROCESS_SCALE	"video.process.scale"		/**< scales raw video */
+
+/** video convert combines some of the video processing */
+#define SPA_NAME_VIDEO_CONVERT		"video.convert"			/**< converts raw video from one format
+									  *  to another. Must include at least
+									  *  format and scaling */
+#define SPA_NAME_VIDEO_ADAPT		"video.adapt"			/**< combination of a node and a
+									  *  video.convert. */
+/** keys for alsa factory names */
+#define SPA_NAME_API_ALSA_ENUM_UDEV	"api.alsa.enum.udev"		/**< an alsa udev Device interface */
+#define SPA_NAME_API_ALSA_PCM_DEVICE	"api.alsa.pcm.device"		/**< an alsa Device interface */
+#define SPA_NAME_API_ALSA_PCM_SOURCE	"api.alsa.pcm.source"		/**< an alsa Node interface for
+									  *  capturing PCM */
+#define SPA_NAME_API_ALSA_PCM_SINK	"api.alsa.pcm.sink"		/**< an alsa Node interface for
+									  *  playback PCM */
+#define SPA_NAME_API_ALSA_SEQ_DEVICE	"api.alsa.seq.device"		/**< an alsa Midi device */
+#define SPA_NAME_API_ALSA_SEQ_SOURCE	"api.alsa.seq.source"		/**< an alsa Node interface for
+									  *  capture of midi */
+#define SPA_NAME_API_ALSA_SEQ_SINK	"api.alsa.seq.sink"		/**< an alsa Node interface for
+									  *  playback of midi */
+#define SPA_NAME_API_ALSA_SEQ_BRIDGE	"api.alsa.seq.bridge"		/**< an alsa Node interface for
+									  *  bridging midi ports */
+#define SPA_NAME_API_ALSA_ACP_DEVICE	"api.alsa.acp.device"		/**< an alsa ACP Device interface */
+
+/** keys for bluez5 factory names */
+#define SPA_NAME_API_BLUEZ5_ENUM_DBUS	"api.bluez5.enum.dbus"		/**< a dbus Device interface */
+#define SPA_NAME_API_BLUEZ5_DEVICE	"api.bluez5.device"		/**< a Device interface */
+#define SPA_NAME_API_BLUEZ5_A2DP_SINK	"api.bluez5.a2dp.sink"		/**< a playback Node interface for A2DP profiles */
+#define SPA_NAME_API_BLUEZ5_A2DP_SOURCE	"api.bluez5.a2dp.source"	/**< a capture Node interface for A2DP profiles */
+#define SPA_NAME_API_BLUEZ5_SCO_SINK	"api.bluez5.sco.sink"		/**< a playback Node interface for HSP/HFP profiles */
+#define SPA_NAME_API_BLUEZ5_SCO_SOURCE	"api.bluez5.sco.source"		/**< a capture Node interface for HSP/HFP profiles */
+
+/** keys for v4l2 factory names */
+#define SPA_NAME_API_V4L2_ENUM_UDEV	"api.v4l2.enum.udev"		/**< a v4l2 udev Device interface */
+#define SPA_NAME_API_V4L2_DEVICE	"api.v4l2.device"		/**< a v4l2 Device interface */
+#define SPA_NAME_API_V4L2_SOURCE	"api.v4l2.source"		/**< a v4l2 Node interface for
+									  *  capturing */
+
+/** keys for libcamera factory names */
+#define SPA_NAME_API_LIBCAMERA_ENUM_CLIENT	"api.libcamera.enum.client"	/**< a libcamera client Device interface */
+#define SPA_NAME_API_LIBCAMERA_DEVICE		"api.libcamera.device"		/**< a libcamera Device interface */
+#define SPA_NAME_API_LIBCAMERA_SOURCE		"api.libcamera.source"		/**< a libcamera Node interface for
+									  *  capturing */
+
+/** keys for jack factory names */
+#define SPA_NAME_API_JACK_DEVICE	"api.jack.device"		/**< a jack device. This is a
+									  *  client connected to a server */
+#define SPA_NAME_API_JACK_SOURCE	"api.jack.source"		/**< a jack source */
+#define SPA_NAME_API_JACK_SINK		"api.jack.sink"			/**< a jack sink */
+
+/** keys for vulkan factory names */
+#define SPA_NAME_API_VULKAN_COMPUTE_SOURCE	\
+					"api.vulkan.compute.source"	/**< a vulkan compute source. */
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_UTILS_NAMES_H */
diff --git a/third_party/pipewire/spa/utils/result.h b/third_party/pipewire/spa/utils/result.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/result.h
@@ -0,0 +1,58 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_UTILS_RESULT_H
+#define SPA_UTILS_RESULT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/list.h>
+
+#define SPA_ASYNC_BIT			(1 << 30)
+#define SPA_ASYNC_MASK			(3 << 30)
+#define SPA_ASYNC_SEQ_MASK		(SPA_ASYNC_BIT - 1)
+
+#define SPA_RESULT_IS_OK(res)		((res) >= 0)
+#define SPA_RESULT_IS_ERROR(res)	((res) < 0)
+#define SPA_RESULT_IS_ASYNC(res)	(((res) & SPA_ASYNC_MASK) == SPA_ASYNC_BIT)
+
+#define SPA_RESULT_ASYNC_SEQ(res)	((res) & SPA_ASYNC_SEQ_MASK)
+#define SPA_RESULT_RETURN_ASYNC(seq)	(SPA_ASYNC_BIT | SPA_RESULT_ASYNC_SEQ(seq))
+
+#define spa_strerror(err)		\
+({					\
+	int _err = -err;		\
+	if (SPA_RESULT_IS_ASYNC(err))	\
+		_err = EINPROGRESS;	\
+	strerror(_err);			\
+})
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_UTILS_RESULT_H */
diff --git a/third_party/pipewire/spa/utils/ringbuffer.h b/third_party/pipewire/spa/utils/ringbuffer.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/ringbuffer.h
@@ -0,0 +1,174 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_RINGBUFFER_H
+#define SPA_RINGBUFFER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct spa_ringbuffer;
+
+#include <string.h>
+
+#include <spa/utils/defs.h>
+
+/**
+ * A ringbuffer type.
+ */
+struct spa_ringbuffer {
+	uint32_t readindex;	/*< the current read index */
+	uint32_t writeindex;	/*< the current write index */
+};
+
+#define SPA_RINGBUFFER_INIT()	(struct spa_ringbuffer) { 0, 0 }
+
+/**
+ * Initialize a spa_ringbuffer with \a size.
+ *
+ * \param rbuf a spa_ringbuffer
+ * \param size the number of elements in the ringbuffer
+ */
+static inline void spa_ringbuffer_init(struct spa_ringbuffer *rbuf)
+{
+	*rbuf = SPA_RINGBUFFER_INIT();
+}
+
+/**
+ * Sets the pointers so that the ringbuffer contains \a size bytes.
+ *
+ * \param rbuf a spa_ringbuffer
+ */
+static inline void spa_ringbuffer_set_avail(struct spa_ringbuffer *rbuf, uint32_t size)
+{
+	rbuf->readindex = 0;
+	rbuf->writeindex = size;
+}
+
+/**
+ * Get the read index and available bytes for reading.
+ *
+ * \param rbuf a  spa_ringbuffer
+ * \param index the value of readindex, should be taken modulo the size of the
+ *         ringbuffer memory to get the offset in the ringbuffer memory
+ * \return number of available bytes to read. values < 0 mean
+ *         there was an underrun. values > rbuf->size means there
+ *         was an overrun.
+ */
+static inline int32_t spa_ringbuffer_get_read_index(struct spa_ringbuffer *rbuf, uint32_t *index)
+{
+	*index = __atomic_load_n(&rbuf->readindex, __ATOMIC_RELAXED);
+	return (int32_t) (__atomic_load_n(&rbuf->writeindex, __ATOMIC_ACQUIRE) - *index);
+}
+
+/**
+ * Read \a len bytes from \a rbuf starting \a offset. \a offset must be taken
+ * modulo \a size and len should be smaller than \a size.
+ *
+ * \param rbuf a #struct spa_ringbuffer
+ * \param buffer memory to read from
+ * \param size the size of \a buffer
+ * \param offset offset in \a buffer to read from
+ * \param data destination memory
+ * \param len number of bytes to read
+ */
+static inline void
+spa_ringbuffer_read_data(struct spa_ringbuffer *rbuf,
+			 const void *buffer, uint32_t size,
+			 uint32_t offset, void *data, uint32_t len)
+{
+	uint32_t l0 = SPA_MIN(len, size - offset), l1 = len - l0;
+	spa_memcpy(data, SPA_MEMBER(buffer, offset, void), l0);
+	if (SPA_UNLIKELY(l1 > 0))
+		spa_memcpy(SPA_MEMBER(data, l0, void), buffer, l1);
+}
+
+/**
+ * Update the read pointer to \a index.
+ *
+ * \param rbuf a spa_ringbuffer
+ * \param index new index
+ */
+static inline void spa_ringbuffer_read_update(struct spa_ringbuffer *rbuf, int32_t index)
+{
+	__atomic_store_n(&rbuf->readindex, index, __ATOMIC_RELEASE);
+}
+
+/**
+ * Get the write index and the number of bytes inside the ringbuffer.
+ *
+ * \param rbuf a  spa_ringbuffer
+ * \param index the value of writeindex, should be taken modulo the size of the
+ *         ringbuffer memory to get the offset in the ringbuffer memory
+ * \return the fill level of \a rbuf. values < 0 mean
+ *         there was an underrun. values > rbuf->size means there
+ *         was an overrun. Subtract from the buffer size to get
+ *         the number of bytes available for writing.
+ */
+static inline int32_t spa_ringbuffer_get_write_index(struct spa_ringbuffer *rbuf, uint32_t *index)
+{
+	*index = __atomic_load_n(&rbuf->writeindex, __ATOMIC_RELAXED);
+	return (int32_t) (*index - __atomic_load_n(&rbuf->readindex, __ATOMIC_ACQUIRE));
+}
+
+/**
+ * Write \a len bytes to \a buffer starting \a offset. \a offset must be taken
+ * modulo \a size and len should be smaller than \a size.
+ *
+ * \param rbuf a spa_ringbuffer
+ * \param buffer memory to write to
+ * \param size the size of \a buffer
+ * \param offset offset in \a buffer to write to
+ * \param data source memory
+ * \param len number of bytes to write
+ */
+static inline void
+spa_ringbuffer_write_data(struct spa_ringbuffer *rbuf,
+			  void *buffer, uint32_t size,
+			  uint32_t offset, const void *data, uint32_t len)
+{
+	uint32_t l0 = SPA_MIN(len, size - offset), l1 = len - l0;
+	spa_memcpy(SPA_MEMBER(buffer, offset, void), data, l0);
+	if (SPA_UNLIKELY(l1 > 0))
+		spa_memcpy(buffer, SPA_MEMBER(data, l0, void), l1);
+}
+
+/**
+ * Update the write pointer to \a index
+ *
+ * \param rbuf a spa_ringbuffer
+ * \param index new index
+ */
+static inline void spa_ringbuffer_write_update(struct spa_ringbuffer *rbuf, int32_t index)
+{
+	__atomic_store_n(&rbuf->writeindex, index, __ATOMIC_RELEASE);
+}
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_RINGBUFFER_H */
diff --git a/third_party/pipewire/spa/utils/type-info.h b/third_party/pipewire/spa/utils/type-info.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/type-info.h
@@ -0,0 +1,128 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_TYPE_INFO_H
+#define SPA_TYPE_INFO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+
+#ifndef SPA_TYPE_ROOT
+#define SPA_TYPE_ROOT	spa_types
+#endif
+
+static inline bool spa_type_is_a(const char *type, const char *parent)
+{
+	return type != NULL && parent != NULL && strncmp(type, parent, strlen(parent)) == 0;
+}
+
+#include <spa/utils/type.h>
+
+/* base for parameter object enumerations */
+#define SPA_TYPE_INFO_Direction			SPA_TYPE_INFO_ENUM_BASE "Direction"
+#define SPA_TYPE_INFO_DIRECTION_BASE		SPA_TYPE_INFO_Direction ":"
+
+static const struct spa_type_info spa_type_direction[] = {
+	{ SPA_DIRECTION_INPUT, SPA_TYPE_Int, SPA_TYPE_INFO_DIRECTION_BASE "Input", NULL  },
+	{ SPA_DIRECTION_OUTPUT, SPA_TYPE_Int, SPA_TYPE_INFO_DIRECTION_BASE "Output", NULL  },
+	{ 0, 0, NULL, NULL }
+};
+
+#include <spa/node/type-info.h>
+#include <spa/param/type-info.h>
+#include <spa/control/type-info.h>
+
+/* base for parameter object enumerations */
+#define SPA_TYPE_INFO_Choice			SPA_TYPE_INFO_ENUM_BASE "Choice"
+#define SPA_TYPE_INFO_CHOICE_BASE		SPA_TYPE_INFO_Choice ":"
+
+static const struct spa_type_info spa_type_choice[] = {
+	{ SPA_CHOICE_None, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "None", NULL  },
+	{ SPA_CHOICE_Range, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "Range", NULL  },
+	{ SPA_CHOICE_Step, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "Step", NULL  },
+	{ SPA_CHOICE_Enum, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "Enum", NULL  },
+	{ SPA_CHOICE_Flags, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "Flags", NULL  },
+	{ 0, 0, NULL, NULL }
+};
+
+static const struct spa_type_info spa_types[] = {
+        /* Basic types */
+	{ SPA_TYPE_START, SPA_TYPE_START, SPA_TYPE_INFO_BASE, NULL },
+	{ SPA_TYPE_None, SPA_TYPE_None, SPA_TYPE_INFO_BASE "None", NULL },
+	{ SPA_TYPE_Bool, SPA_TYPE_Bool, SPA_TYPE_INFO_BASE "Bool", NULL },
+	{ SPA_TYPE_Id, SPA_TYPE_Int, SPA_TYPE_INFO_BASE "Id", NULL },
+	{ SPA_TYPE_Int, SPA_TYPE_Int, SPA_TYPE_INFO_BASE "Int", NULL },
+	{ SPA_TYPE_Long, SPA_TYPE_Long, SPA_TYPE_INFO_BASE "Long", NULL },
+	{ SPA_TYPE_Float, SPA_TYPE_Float, SPA_TYPE_INFO_BASE "Float", NULL },
+	{ SPA_TYPE_Double, SPA_TYPE_Double, SPA_TYPE_INFO_BASE "Double", NULL },
+	{ SPA_TYPE_String, SPA_TYPE_String, SPA_TYPE_INFO_BASE "String", NULL },
+	{ SPA_TYPE_Bytes, SPA_TYPE_Bytes, SPA_TYPE_INFO_BASE "Bytes", NULL },
+	{ SPA_TYPE_Rectangle, SPA_TYPE_Rectangle, SPA_TYPE_INFO_BASE "Rectangle", NULL },
+	{ SPA_TYPE_Fraction, SPA_TYPE_Fraction, SPA_TYPE_INFO_BASE "Fraction", NULL },
+	{ SPA_TYPE_Bitmap, SPA_TYPE_Bitmap, SPA_TYPE_INFO_BASE "Bitmap", NULL },
+	{ SPA_TYPE_Array, SPA_TYPE_Array, SPA_TYPE_INFO_BASE "Array", NULL },
+	{ SPA_TYPE_Pod, SPA_TYPE_Pod, SPA_TYPE_INFO_Pod, NULL },
+	{ SPA_TYPE_Struct, SPA_TYPE_Pod, SPA_TYPE_INFO_Struct, NULL },
+	{ SPA_TYPE_Object, SPA_TYPE_Pod, SPA_TYPE_INFO_Object, NULL },
+	{ SPA_TYPE_Sequence, SPA_TYPE_Pod, SPA_TYPE_INFO_POD_BASE "Sequence", NULL },
+	{ SPA_TYPE_Pointer, SPA_TYPE_Pointer, SPA_TYPE_INFO_Pointer, NULL },
+	{ SPA_TYPE_Fd, SPA_TYPE_Fd, SPA_TYPE_INFO_BASE "Fd", NULL },
+	{ SPA_TYPE_Choice, SPA_TYPE_Pod, SPA_TYPE_INFO_POD_BASE "Choice", NULL },
+
+	{ SPA_TYPE_POINTER_START, SPA_TYPE_Pointer, SPA_TYPE_INFO_Pointer, NULL },
+	{ SPA_TYPE_POINTER_Buffer, SPA_TYPE_Pointer, SPA_TYPE_INFO_POINTER_BASE "Buffer", NULL },
+	{ SPA_TYPE_POINTER_Meta, SPA_TYPE_Pointer, SPA_TYPE_INFO_POINTER_BASE "Meta", NULL },
+	{ SPA_TYPE_POINTER_Dict, SPA_TYPE_Pointer, SPA_TYPE_INFO_POINTER_BASE "Dict", NULL },
+
+	{ SPA_TYPE_EVENT_START, SPA_TYPE_Object, SPA_TYPE_INFO_Event, NULL },
+	{ SPA_TYPE_EVENT_Device, SPA_TYPE_Object, SPA_TYPE_INFO_EVENT_BASE "Device", NULL },
+	{ SPA_TYPE_EVENT_Node, SPA_TYPE_Object, SPA_TYPE_INFO_EVENT_BASE "Node", spa_type_node_event },
+
+	{ SPA_TYPE_COMMAND_START, SPA_TYPE_Object, SPA_TYPE_INFO_Command, NULL },
+	{ SPA_TYPE_COMMAND_Device, SPA_TYPE_Object, SPA_TYPE_INFO_COMMAND_BASE "Device", NULL },
+	{ SPA_TYPE_COMMAND_Node, SPA_TYPE_Object, SPA_TYPE_INFO_COMMAND_BASE "Node", spa_type_node_command },
+
+	{ SPA_TYPE_OBJECT_START, SPA_TYPE_Object, SPA_TYPE_INFO_Object, NULL },
+	{ SPA_TYPE_OBJECT_PropInfo, SPA_TYPE_Object, SPA_TYPE_INFO_PropInfo, spa_type_prop_info, },
+	{ SPA_TYPE_OBJECT_Props, SPA_TYPE_Object, SPA_TYPE_INFO_Props, spa_type_props },
+	{ SPA_TYPE_OBJECT_Format, SPA_TYPE_Object, SPA_TYPE_INFO_Format, spa_type_format },
+	{ SPA_TYPE_OBJECT_ParamBuffers, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Buffers, spa_type_param_buffers, },
+	{ SPA_TYPE_OBJECT_ParamMeta, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Meta, spa_type_param_meta },
+	{ SPA_TYPE_OBJECT_ParamIO, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_IO, spa_type_param_io },
+	{ SPA_TYPE_OBJECT_ParamProfile, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Profile, spa_type_param_profile },
+	{ SPA_TYPE_OBJECT_ParamPortConfig, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_PortConfig, spa_type_param_port_config },
+	{ SPA_TYPE_OBJECT_ParamRoute, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Route, spa_type_param_route },
+	{ SPA_TYPE_OBJECT_Profiler, SPA_TYPE_Object, SPA_TYPE_INFO_Profiler, spa_type_profiler },
+
+	{ 0, 0, NULL, NULL }
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_TYPE_INFO_H */
diff --git a/third_party/pipewire/spa/utils/type.h b/third_party/pipewire/spa/utils/type.h
new file mode 100644
--- /dev/null
+++ b/third_party/pipewire/spa/utils/type.h
@@ -0,0 +1,138 @@
+/* Simple Plugin API
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef SPA_TYPE_H
+#define SPA_TYPE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+
+enum {
+	/* Basic types */
+	SPA_TYPE_START = 0x00000,
+	SPA_TYPE_None,
+        SPA_TYPE_Bool,
+        SPA_TYPE_Id,
+        SPA_TYPE_Int,
+        SPA_TYPE_Long,
+        SPA_TYPE_Float,
+        SPA_TYPE_Double,
+        SPA_TYPE_String,
+        SPA_TYPE_Bytes,
+        SPA_TYPE_Rectangle,
+        SPA_TYPE_Fraction,
+        SPA_TYPE_Bitmap,
+        SPA_TYPE_Array,
+        SPA_TYPE_Struct,
+        SPA_TYPE_Object,
+        SPA_TYPE_Sequence,
+        SPA_TYPE_Pointer,
+        SPA_TYPE_Fd,
+        SPA_TYPE_Choice,
+        SPA_TYPE_Pod,
+	SPA_TYPE_LAST,				/**< not part of ABI */
+
+	/* Pointers */
+	SPA_TYPE_POINTER_START = 0x10000,
+	SPA_TYPE_POINTER_Buffer,
+	SPA_TYPE_POINTER_Meta,
+	SPA_TYPE_POINTER_Dict,
+	SPA_TYPE_POINTER_LAST,			/**< not part of ABI */
+
+	/* Events */
+	SPA_TYPE_EVENT_START = 0x20000,
+	SPA_TYPE_EVENT_Device,
+	SPA_TYPE_EVENT_Node,
+	SPA_TYPE_EVENT_LAST,			/**< not part of ABI */
+
+	/* Commands */
+	SPA_TYPE_COMMAND_START = 0x30000,
+	SPA_TYPE_COMMAND_Device,
+	SPA_TYPE_COMMAND_Node,
+	SPA_TYPE_COMMAND_LAST,			/**< not part of ABI */
+
+	/* Objects */
+	SPA_TYPE_OBJECT_START = 0x40000,
+	SPA_TYPE_OBJECT_PropInfo,
+	SPA_TYPE_OBJECT_Props,
+	SPA_TYPE_OBJECT_Format,
+	SPA_TYPE_OBJECT_ParamBuffers,
+	SPA_TYPE_OBJECT_ParamMeta,
+	SPA_TYPE_OBJECT_ParamIO,
+	SPA_TYPE_OBJECT_ParamProfile,
+	SPA_TYPE_OBJECT_ParamPortConfig,
+	SPA_TYPE_OBJECT_ParamRoute,
+	SPA_TYPE_OBJECT_Profiler,
+	SPA_TYPE_OBJECT_LAST,			/**< not part of ABI */
+
+	/* vendor extensions */
+	SPA_TYPE_VENDOR_PipeWire	= 0x02000000,
+
+	SPA_TYPE_VENDOR_Other		= 0x7f000000,
+};
+
+#define SPA_TYPE_INFO_BASE			"Spa:"
+
+#define SPA_TYPE_INFO_Flags			SPA_TYPE_INFO_BASE "Flags"
+#define SPA_TYPE_INFO_FLAGS_BASE		SPA_TYPE_INFO_Flags ":"
+
+#define SPA_TYPE_INFO_Enum			SPA_TYPE_INFO_BASE "Enum"
+#define SPA_TYPE_INFO_ENUM_BASE			SPA_TYPE_INFO_Enum ":"
+
+#define SPA_TYPE_INFO_Pod			SPA_TYPE_INFO_BASE "Pod"
+#define SPA_TYPE_INFO_POD_BASE			SPA_TYPE_INFO_Pod ":"
+
+#define SPA_TYPE_INFO_Struct			SPA_TYPE_INFO_POD_BASE "Struct"
+#define SPA_TYPE_INFO_STRUCT_BASE		SPA_TYPE_INFO_Struct ":"
+
+#define SPA_TYPE_INFO_Object			SPA_TYPE_INFO_POD_BASE "Object"
+#define SPA_TYPE_INFO_OBJECT_BASE		SPA_TYPE_INFO_Object ":"
+
+#define SPA_TYPE_INFO_Pointer			SPA_TYPE_INFO_BASE "Pointer"
+#define SPA_TYPE_INFO_POINTER_BASE		SPA_TYPE_INFO_Pointer ":"
+
+#define SPA_TYPE_INFO_Interface			SPA_TYPE_INFO_POINTER_BASE "Interface"
+#define SPA_TYPE_INFO_INTERFACE_BASE		SPA_TYPE_INFO_Interface ":"
+
+#define SPA_TYPE_INFO_Event			SPA_TYPE_INFO_OBJECT_BASE "Event"
+#define SPA_TYPE_INFO_EVENT_BASE		SPA_TYPE_INFO_Event ":"
+
+#define SPA_TYPE_INFO_Command			SPA_TYPE_INFO_OBJECT_BASE "Command"
+#define SPA_TYPE_INFO_COMMAND_BASE		SPA_TYPE_INFO_Command ":"
+
+struct spa_type_info {
+	uint32_t type;
+	uint32_t parent;
+	const char *name;
+	const struct spa_type_info *values;
+};
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif /* SPA_TYPE_H */