diff --git a/pinos/Makefile.am b/pinos/Makefile.am index 2166b935e..bfc04a3ee 100644 --- a/pinos/Makefile.am +++ b/pinos/Makefile.am @@ -31,6 +31,7 @@ dbuspolicydir=$(sysconfdir)/dbus-1/system.d AM_CPPFLAGS = \ -I$(top_srcdir)/ \ + -I$(top_srcdir)/spa/include/ \ -I$(top_srcdir)/pinos/modules \ -I$(top_builddir)/pinos/modules \ -DPINOS_SRCDIR=\"$(abs_srcdir)\" \ @@ -39,8 +40,8 @@ AM_CFLAGS = $(GLIB_CFLAGS) $(GST_CFLAGS) AM_CXXFLAGS = $(AM_CFLAGS) SERVER_CFLAGS = -D__INCLUDED_FROM_PINOS -AM_LIBADD = $(GLIB_LIBS) $(INTLLIBS) $(GST_LIBS) -AM_LDADD = $(GLIB_LIBS) $(GST_LIBS) $(INTLLIBS) +AM_LIBADD = $(GLIB_LIBS) $(INTLLIBS) $(GST_LIBS) #$(top_srcdir)/spa/build/lib/libspa-lib.so +AM_LDADD = $(GLIB_LIBS) $(GST_LIBS) $(INTLLIBS) #$(top_srcdir)/spa/build/lib/libspa-lib.so AM_LDFLAGS = $(NODELETE_LDFLAGS) FOREIGN_CFLAGS = -w @@ -69,6 +70,7 @@ xdgautostart_DATA = $(xdgautostart_in_files:.desktop.in=.desktop) enumtypesincludes = client/context.h \ client/introspect.h \ + client/ringbuffer.h \ client/stream.h \ client/subscribe.h @@ -131,7 +133,7 @@ noinst_LTLIBRARIES = TESTS_default = -TESTS_norun = test-client test-node +TESTS_norun = test-client # These tests need a running pinos daemon TESTS_daemon = @@ -143,11 +145,6 @@ test_client_CFLAGS = $(AM_CFLAGS) test_client_LDADD = $(AM_LDADD) libpinos-@PINOS_MAJORMINOR@.la test_client_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -test_node_SOURCES = tests/test-node.c tests/spi-volume.c tests/spi-alsa-sink.c tests/spi-audiotestsrc.c -test_node_CFLAGS = $(AM_CFLAGS) $(ALSA_CFLAGS) -test_node_LDADD = $(AM_LDADD) libpinos-@PINOS_MAJORMINOR@.la -test_node_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(ALSA_LIBS) - ################################### # Tools programs # ################################### @@ -169,12 +166,14 @@ pinosgstsource = gst/gstpinospay.h gst/gstpinospay.c \ gst/gsttmpfileallocator.h gst/gsttmpfileallocator.c pinosinclude_HEADERS = \ - client/pinos.h \ client/buffer.h \ + client/client-node.h \ + client/client-port.h \ client/context.h \ client/enumtypes.h \ client/introspect.h \ client/mainloop.h \ + client/pinos.h \ client/properties.h \ client/stream.h \ client/subscribe.h @@ -194,6 +193,7 @@ libpinos_@PINOS_MAJORMINOR@_la_SOURCES = \ client/stream.h client/stream.c \ client/pinos.c client/pinos.h \ client/fdmanager.c client/fdmanager.h \ + client/ringbuffer.c client/ringbuffer.h \ client/subscribe.c client/subscribe.h \ $(pinosgstsource) @@ -222,6 +222,7 @@ libpinoscore_@PINOS_MAJORMINOR@_la_SOURCES = \ modules/gst/gst-source.c modules/gst/gst-source.h \ modules/gst/gst-sink.c modules/gst/gst-sink.h \ modules/gst/gst-node-factory.c modules/gst/gst-node-factory.h \ + modules/spa/spa-alsa-sink.c modules/spa/spa-alsa-sink.h \ dbus/org-pinos.c dbus/org-pinos.h libpinoscore_@PINOS_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(SERVER_CFLAGS) diff --git a/pinos/client/client-port.c b/pinos/client/client-port.c index 91c05a0e8..f76905c4f 100644 --- a/pinos/client/client-port.c +++ b/pinos/client/client-port.c @@ -21,6 +21,7 @@ #include #include +#include #include "pinos/client/pinos.h" #include "pinos/client/enumtypes.h" @@ -43,6 +44,74 @@ enum PROP_PROXY, }; +static void +on_ringbuffer (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GTask *task = user_data; + PinosClientPort *port = g_task_get_source_object (task); + PinosClientPortPrivate *priv = port->priv; + GVariant *ret; + GError *error = NULL; + GUnixFDList *fdlist; + gint fd, semfd, fd_idx, sem_idx; + guint fdsize; + PinosRingbuffer *rbuf; + + g_assert (priv->proxy == G_DBUS_PROXY (source_object)); + + ret = g_dbus_proxy_call_with_unix_fd_list_finish (priv->proxy, &fdlist, res, &error); + if (ret == NULL) + goto create_failed; + + g_variant_get (ret, "(huh)", &fd_idx, &fdsize, &sem_idx); + g_variant_unref (ret); + + fd = g_unix_fd_list_get (fdlist, fd_idx, &error); + semfd = g_unix_fd_list_get (fdlist, sem_idx, &error); + g_object_unref (fdlist); + + if (fd == -1 || semfd == -1) + goto create_failed; + + rbuf = pinos_ringbuffer_new_import (PINOS_RINGBUFFER_MODE_WRITE, + fd, fdsize, semfd); + + g_task_return_pointer (task, rbuf, (GDestroyNotify) g_object_unref); + g_object_unref (task); + + return; + + /* ERRORS */ +create_failed: + { + g_warning ("failed to get ringbuffer: %s", error->message); + g_task_return_error (task, error); + g_object_unref (task); + return; + } +} + + +static void +pinos_client_port_get_ringbuffer (PinosPort *port, + PinosProperties *props, + GTask *task) +{ + PinosClientPortPrivate *priv = PINOS_CLIENT_PORT (port)->priv; + + g_dbus_proxy_call (priv->proxy, + "GetRingbuffer", + g_variant_new ("(@a{sv})", + pinos_properties_to_variant (props)), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, /* GCancellable *cancellable */ + on_ringbuffer, + task); +} + static void pinos_client_port_get_property (GObject *_object, guint prop_id, @@ -205,6 +274,7 @@ static void pinos_client_port_class_init (PinosClientPortClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + PinosPortClass *port_class = PINOS_PORT_CLASS (klass); g_type_class_add_private (klass, sizeof (PinosClientPortPrivate)); @@ -223,6 +293,8 @@ pinos_client_port_class_init (PinosClientPortClass * klass) G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + port_class->get_ringbuffer = pinos_client_port_get_ringbuffer; } static void diff --git a/pinos/client/pinos.h b/pinos/client/pinos.h index 00ed54b38..a0f93dbe1 100644 --- a/pinos/client/pinos.h +++ b/pinos/client/pinos.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/pinos/client/ringbuffer.c b/pinos/client/ringbuffer.c new file mode 100644 index 000000000..318d39ce5 --- /dev/null +++ b/pinos/client/ringbuffer.c @@ -0,0 +1,393 @@ +/* Pinos + * Copyright (C) 2015 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#define PINOS_RINGBUFFER_GET_PRIVATE(rb) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((rb), PINOS_TYPE_RINGBUFFER, PinosRingbufferPrivate)) + +typedef struct { + SpaRingbuffer rbuf; + /* ringbuffer starts here */ +} PinosRingbufferData; + +struct _PinosRingbufferPrivate +{ + PinosRingbufferMode mode; + guint size; + guint fdsize; + int fd; + int semaphore; + + PinosRingbufferData *data; +}; + +G_DEFINE_TYPE (PinosRingbuffer, pinos_ringbuffer, G_TYPE_OBJECT); + +enum +{ + PROP_0, + PROP_MODE, + PROP_SIZE, + PROP_FD, + PROP_FDSIZE, + PROP_SEMAPHORE, +}; + +enum +{ + SIGNAL_NONE, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +static void +pinos_ringbuffer_get_property (GObject *_object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + PinosRingbuffer *rb = PINOS_RINGBUFFER (_object); + PinosRingbufferPrivate *priv = rb->priv; + + switch (prop_id) { + case PROP_MODE: + g_value_set_enum (value, priv->mode); + break; + + case PROP_SIZE: + g_value_set_uint (value, priv->size); + break; + + case PROP_FD: + g_value_set_int (value, priv->fd); + break; + + case PROP_FDSIZE: + g_value_set_uint (value, priv->fdsize); + break; + + case PROP_SEMAPHORE: + g_value_set_int (value, priv->semaphore); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (rb, prop_id, pspec); + break; + } +} + +static void +pinos_ringbuffer_set_property (GObject *_object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + PinosRingbuffer *rb = PINOS_RINGBUFFER (_object); + PinosRingbufferPrivate *priv = rb->priv; + + switch (prop_id) { + case PROP_MODE: + priv->mode = g_value_get_enum (value); + break; + + case PROP_SIZE: + priv->size = g_value_get_uint (value); + break; + + case PROP_FD: + priv->fd = g_value_get_int (value); + break; + + case PROP_FDSIZE: + priv->fdsize = g_value_get_uint (value); + break; + + case PROP_SEMAPHORE: + priv->semaphore = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (rb, prop_id, pspec); + break; + } +} + +static int +tmpfile_create (gsize size) +{ + char filename[] = "/dev/shm/tmpfilepay.XXXXXX"; + int fd, result; + + fd = mkostemp (filename, O_CLOEXEC); + if (fd == -1) + return -1; + unlink (filename); + + result = ftruncate (fd, size); + if (result == -1) { + close (fd); + return -1; + } + return fd; +} + +static void +pinos_ringbuffer_constructed (GObject * obj) +{ + PinosRingbuffer *rb = PINOS_RINGBUFFER (obj); + PinosRingbufferPrivate *priv = rb->priv; + + g_debug ("ringbuffer %p: constructed", rb); + + if (priv->fd == -1) { + priv->fdsize = priv->size + sizeof (PinosRingbufferData); + priv->fd = tmpfile_create (priv->fdsize); + priv->semaphore = eventfd (0, EFD_CLOEXEC); + } + priv->data = mmap (NULL, priv->fdsize, PROT_READ | PROT_WRITE, MAP_SHARED, priv->fd, 0); + + spa_ringbuffer_init (&priv->data->rbuf, (guint8 *)priv->data + sizeof (PinosRingbufferData), priv->size); + + G_OBJECT_CLASS (pinos_ringbuffer_parent_class)->constructed (obj); +} + +static void +pinos_ringbuffer_dispose (GObject * obj) +{ + PinosRingbuffer *rb = PINOS_RINGBUFFER (obj); + + g_debug ("ringbuffer %p: dispose", rb); + + G_OBJECT_CLASS (pinos_ringbuffer_parent_class)->dispose (obj); +} + +static void +pinos_ringbuffer_finalize (GObject * obj) +{ + PinosRingbuffer *rb = PINOS_RINGBUFFER (obj); + + g_debug ("ringbuffer %p: finalize", rb); + + G_OBJECT_CLASS (pinos_ringbuffer_parent_class)->finalize (obj); +} + +static void +pinos_ringbuffer_class_init (PinosRingbufferClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (PinosRingbufferPrivate)); + + gobject_class->constructed = pinos_ringbuffer_constructed; + gobject_class->dispose = pinos_ringbuffer_dispose; + gobject_class->finalize = pinos_ringbuffer_finalize; + gobject_class->set_property = pinos_ringbuffer_set_property; + gobject_class->get_property = pinos_ringbuffer_get_property; + + g_object_class_install_property (gobject_class, + PROP_MODE, + g_param_spec_enum ("mode", + "Mode", + "The mode of the ringbuffer", + PINOS_TYPE_RINGBUFFER_MODE, + PINOS_RINGBUFFER_MODE_READ, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_SIZE, + g_param_spec_uint ("size", + "Size", + "The size of the ringbuffer", + 1, + G_MAXUINT, + 64 * 1024, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_FD, + g_param_spec_int ("fd", + "Fd", + "The file descriptor with memory", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_FDSIZE, + g_param_spec_uint ("fdsize", + "Fd Size", + "Size of the memory", + 1, + G_MAXUINT, + -1, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_SEMAPHORE, + g_param_spec_int ("semaphore", + "Semaphore", + "Semaphore file desciptor", + -1, + G_MAXINT, + -1, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); +} + +static void +pinos_ringbuffer_init (PinosRingbuffer * rb) +{ + PinosRingbufferPrivate *priv = rb->priv = PINOS_RINGBUFFER_GET_PRIVATE (rb); + + g_debug ("ringbuffer %p: new %u", rb, priv->size); + + priv->mode = PINOS_RINGBUFFER_MODE_READ; + priv->size = 0; + priv->fd = -1; +} + +PinosRingbuffer * +pinos_ringbuffer_new (PinosRingbufferMode mode, + gsize size) +{ + PinosRingbuffer *rb; + + g_return_val_if_fail (size > 0, NULL); + + rb = g_object_new (PINOS_TYPE_RINGBUFFER, + "size", size, + "mode", mode, + NULL); + return rb; +} + +PinosRingbuffer * +pinos_ringbuffer_new_import (PinosRingbufferMode mode, + guint fdsize, + int fd, + int semaphore) +{ + PinosRingbuffer *rb; + + g_return_val_if_fail (fdsize > 0, NULL); + g_return_val_if_fail (fd >= 0, NULL); + + rb = g_object_new (PINOS_TYPE_RINGBUFFER, + "mode", mode, + "fd", fd, + "fdsize", fdsize, + "semaphore", semaphore, + NULL); + return rb; +} + + +gboolean +pinos_ringbuffer_get_read_areas (PinosRingbuffer *rbuf, + PinosRingbufferArea areas[2]) +{ + PinosRingbufferPrivate *priv; + + g_return_val_if_fail (PINOS_IS_RINGBUFFER (rbuf), FALSE); + priv = rbuf->priv; + + spa_ringbuffer_get_read_areas (&priv->data->rbuf, (SpaRingbufferArea *)areas); + + return TRUE; +} + +gboolean +pinos_ringbuffer_get_write_areas (PinosRingbuffer *rbuf, + PinosRingbufferArea areas[2]) +{ + PinosRingbufferPrivate *priv; + + g_return_val_if_fail (PINOS_IS_RINGBUFFER (rbuf), FALSE); + priv = rbuf->priv; + + spa_ringbuffer_get_write_areas (&priv->data->rbuf, (SpaRingbufferArea *)areas); + + return TRUE; +} + +gboolean +pinos_ringbuffer_read_advance (PinosRingbuffer *rbuf, + gssize len) +{ + PinosRingbufferPrivate *priv; + guint64 val; + + g_return_val_if_fail (PINOS_IS_RINGBUFFER (rbuf), FALSE); + priv = rbuf->priv; + + spa_ringbuffer_read_advance (&priv->data->rbuf, len); + + if (priv->mode == PINOS_RINGBUFFER_MODE_READ) { + val = 1; + write (priv->semaphore, &val, 8); + } + + return TRUE; +} + +gboolean +pinos_ringbuffer_write_advance (PinosRingbuffer *rbuf, + gssize len) +{ + PinosRingbufferPrivate *priv; + guint64 val; + + g_return_val_if_fail (PINOS_IS_RINGBUFFER (rbuf), FALSE); + priv = rbuf->priv; + + spa_ringbuffer_write_advance (&priv->data->rbuf, len); + + if (priv->mode == PINOS_RINGBUFFER_MODE_WRITE) { + val = 1; + write (priv->semaphore, &val, 8); + } + return TRUE; +} diff --git a/pinos/client/ringbuffer.h b/pinos/client/ringbuffer.h new file mode 100644 index 000000000..9eb428a4b --- /dev/null +++ b/pinos/client/ringbuffer.h @@ -0,0 +1,97 @@ +/* Pinos + * Copyright (C) 2015 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PINOS_RINGBUFFER_H__ +#define __PINOS_RINGBUFFER_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _PinosRingbuffer PinosRingbuffer; +typedef struct _PinosRingbufferClass PinosRingbufferClass; +typedef struct _PinosRingbufferPrivate PinosRingbufferPrivate; + +#include +#include + +#define PINOS_TYPE_RINGBUFFER (pinos_ringbuffer_get_type ()) +#define PINOS_IS_RINGBUFFER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PINOS_TYPE_RINGBUFFER)) +#define PINOS_IS_RINGBUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PINOS_TYPE_RINGBUFFER)) +#define PINOS_RINGBUFFER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PINOS_TYPE_RINGBUFFER, PinosRingbufferClass)) +#define PINOS_RINGBUFFER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PINOS_TYPE_RINGBUFFER, PinosRingbuffer)) +#define PINOS_RINGBUFFER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PINOS_TYPE_RINGBUFFER, PinosRingbufferClass)) +#define PINOS_RINGBUFFER_CAST(obj) ((PinosRingbuffer*)(obj)) +#define PINOS_RINGBUFFER_CLASS_CAST(klass) ((PinosRingbufferClass*)(klass)) + +/** + * PinosRingbuffer: + * + * Pinos ringbuffer object class. + */ +struct _PinosRingbuffer { + GObject object; + + PinosRingbufferPrivate *priv; +}; + +/** + * PinosRingbufferClass: + * + * Pinos ringbuffer object class. + */ +struct _PinosRingbufferClass { + GObjectClass parent_class; +}; + +typedef void (*PinosRingbufferCallback) (PinosRingbuffer *rb, gpointer user_data); + +typedef enum { + PINOS_RINGBUFFER_MODE_READ, + PINOS_RINGBUFFER_MODE_WRITE, +} PinosRingbufferMode; + +typedef struct { + gpointer data; + gsize len; +} PinosRingbufferArea; + +/* normal GObject stuff */ +GType pinos_ringbuffer_get_type (void); + +PinosRingbuffer * pinos_ringbuffer_new (PinosRingbufferMode mode, + gsize size); +PinosRingbuffer * pinos_ringbuffer_new_import (PinosRingbufferMode mode, + guint fdsize, + int fd, + int semaphore); + +gboolean pinos_ringbuffer_get_read_areas (PinosRingbuffer *rbuf, + PinosRingbufferArea areas[2]); +gboolean pinos_ringbuffer_read_advance (PinosRingbuffer *rbuf, + gssize len); + +gboolean pinos_ringbuffer_get_write_areas (PinosRingbuffer *rbuf, + PinosRingbufferArea areas[2]); +gboolean pinos_ringbuffer_write_advance (PinosRingbuffer *rbuf, + gssize len); + +G_END_DECLS + +#endif /* __PINOS_RINGBUFFER_H__ */ diff --git a/pinos/client/stream.c b/pinos/client/stream.c index 815f025ba..fa25f445c 100644 --- a/pinos/client/stream.c +++ b/pinos/client/stream.c @@ -851,6 +851,38 @@ pinos_stream_connect (PinosStream *stream, return TRUE; } +static void +on_ringbuffer (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + PinosStream *stream = user_data; + PinosStreamPrivate *priv = stream->priv; + GError *error = NULL; + + g_assert (priv->port == PINOS_PORT (source_object)); + + priv->ringbuffer = pinos_port_get_ringbuffer_finish (priv->port, + res, + &error); + if (priv->ringbuffer == NULL) + goto no_ringbuffer; + + stream_set_state (stream, PINOS_STREAM_STATE_STREAMING, NULL); + g_object_unref (stream); + + return; + + /* ERRORS */ +no_ringbuffer: + { + g_warning ("failed to get ringbuffer: %s", error->message); + stream_set_state (stream, PINOS_STREAM_STATE_ERROR, error); + g_object_unref (stream); + return; + } +} + static gboolean do_start (PinosStream *stream) { @@ -1110,7 +1142,6 @@ pinos_stream_send_buffer (PinosStream *stream, g_return_val_if_fail (buffer != NULL, FALSE); priv = stream->priv; - g_return_val_if_fail (priv->state == PINOS_STREAM_STATE_STREAMING, FALSE); if (!pinos_io_write_buffer (priv->fd, buffer, &error)) { g_warning ("stream %p: failed to read buffer: %s", stream, error->message); diff --git a/pinos/daemon/main.c b/pinos/daemon/main.c index 2d5738bc9..113ac532c 100644 --- a/pinos/daemon/main.c +++ b/pinos/daemon/main.c @@ -45,6 +45,7 @@ main (gint argc, gchar *argv[]) factory = pinos_gst_node_factory_new ("gst-node-factory"); pinos_daemon_add_node_factory (daemon, factory); + pinos_spa_alsa_sink_new (daemon, "alsa-sink", NULL); pinos_daemon_start (daemon); g_main_loop_run (loop); diff --git a/pinos/dbus/org.pinos.xml b/pinos/dbus/org.pinos.xml index eed21e284..633e99a7d 100644 --- a/pinos/dbus/org.pinos.xml +++ b/pinos/dbus/org.pinos.xml @@ -143,5 +143,12 @@ + + + + + + + diff --git a/pinos/gst/gstpinossink.c b/pinos/gst/gstpinossink.c index de7a6e499..ffe24fb0e 100644 --- a/pinos/gst/gstpinossink.c +++ b/pinos/gst/gstpinossink.c @@ -467,23 +467,6 @@ gst_pinos_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) pinos_main_loop_wait (pinossink->loop); } } - { - PinosBufferBuilder builder; - PinosPacketFormatChange change; - PinosBuffer pbuf; - - pinos_stream_buffer_builder_init (pinossink->stream, &builder); - - change.id = 1; - change.format = g_bytes_get_data (format, NULL); - pinos_buffer_builder_add_format_change (&builder, &change); - pinos_buffer_builder_end (&builder, &pbuf); - - g_debug ("sending format"); - res = pinos_stream_send_buffer (pinossink->stream, &pbuf); - pinos_buffer_unref (&pbuf); - } - pinos_main_loop_unlock (pinossink->loop); g_bytes_unref (format); diff --git a/pinos/modules/spa/spa-alsa-sink.c b/pinos/modules/spa/spa-alsa-sink.c new file mode 100644 index 000000000..f5fd312bc --- /dev/null +++ b/pinos/modules/spa/spa-alsa-sink.c @@ -0,0 +1,531 @@ +/* Pinos + * Copyright (C) 2015 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include + +#include +#include + +#include "spa-alsa-sink.h" + +#define PINOS_SPA_ALSA_SINK_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), PINOS_TYPE_SPA_ALSA_SINK, PinosSpaAlsaSinkPrivate)) + +struct _PinosSpaAlsaSinkPrivate +{ + PinosPort *input; + + PinosProperties *props; + PinosRingbuffer *ringbuffer; + + SpaHandle *sink; + const SpaNode *sink_node; +}; + +enum { + PROP_0, + PROP_POSSIBLE_FORMATS +}; + +G_DEFINE_TYPE (PinosSpaAlsaSink, pinos_spa_alsa_sink, PINOS_TYPE_SERVER_NODE); + +static SpaResult +make_node (SpaHandle **handle, const SpaNode **node, const char *lib, const char *name) +{ + SpaResult res; + void *hnd; + SpaEnumHandleFactoryFunc enum_func; + unsigned int i; + + if ((hnd = dlopen (lib, RTLD_NOW)) == NULL) { + g_error ("can't load %s: %s", lib, dlerror()); + return SPA_RESULT_ERROR; + } + if ((enum_func = dlsym (hnd, "spa_enum_handle_factory")) == NULL) { + g_error ("can't find enum function"); + return SPA_RESULT_ERROR; + } + + for (i = 0; ;i++) { + const SpaHandleFactory *factory; + const void *iface; + + if ((res = enum_func (i, &factory)) < 0) { + if (res != SPA_RESULT_ENUM_END) + g_error ("can't enumerate factories: %d", res); + break; + } + if (strcmp (factory->name, name)) + continue; + + if ((res = factory->instantiate (factory, handle)) < 0) { + g_error ("can't make factory instance: %d", res); + return res; + } + if ((res = (*handle)->get_interface (*handle, SPA_INTERFACE_ID_NODE, &iface)) < 0) { + g_error ("can't get interface %d", res); + return res; + } + *node = iface; + return SPA_RESULT_OK; + } + return SPA_RESULT_ERROR; +} + +static void +on_sink_event (SpaHandle *handle, SpaEvent *event, void *user_data) +{ + PinosSpaAlsaSink *this = user_data; + PinosSpaAlsaSinkPrivate *priv = this->priv; + + switch (event->type) { + case SPA_EVENT_TYPE_PULL_INPUT: + { + SpaBuffer *buf; + SpaInputInfo iinfo; + SpaOutputInfo oinfo; + SpaResult res; + PinosRingbufferArea areas[2]; + uint8_t *data; + size_t size, towrite, total; + + buf = event->data; + + oinfo.port_id = 0; + oinfo.flags = SPA_OUTPUT_FLAG_PULL; + oinfo.buffer = buf; + oinfo.event = NULL; + + g_debug ("pull ringbuffer %p", buf); + + size = buf->size; + data = buf->datas[0].data; + + pinos_ringbuffer_get_read_areas (priv->ringbuffer, areas); + + total = MIN (size, areas[0].len + areas[1].len); + g_debug ("total read %zd %zd", total, areas[0].len + areas[1].len); + if (total < size) { + g_warning ("underrun"); + } + towrite = MIN (size, areas[0].len); + memcpy (data, areas[0].data, towrite); + size -= towrite; + data += towrite; + towrite = MIN (size, areas[1].len); + memcpy (data, areas[1].data, towrite); + + pinos_ringbuffer_read_advance (priv->ringbuffer, total); + + buf->size = total; + + iinfo.port_id = event->port_id; + iinfo.flags = SPA_INPUT_FLAG_NONE; + iinfo.buffer = oinfo.buffer; + iinfo.event = oinfo.event; + + g_debug ("push sink %p", iinfo.buffer); + if ((res = priv->sink_node->push_port_input (priv->sink, 1, &iinfo)) < 0) + g_debug ("got error %d", res); + break; + } + default: + g_debug ("got event %d", event->type); + break; + } +} + +static void +create_pipeline (PinosSpaAlsaSink *this) +{ + PinosSpaAlsaSinkPrivate *priv = this->priv; + SpaResult res; + SpaProps *props; + SpaPropValue value; + SpaCommand cmd; + + if ((res = make_node (&priv->sink, &priv->sink_node, "spa/build/plugins/alsa/libspa-alsa.so", "alsa-sink")) < 0) { + g_error ("can't create alsa-sink: %d", res); + return; + } + priv->sink_node->set_event_callback (priv->sink, on_sink_event, this); + + if ((res = priv->sink_node->get_props (priv->sink, &props)) < 0) + g_debug ("got get_props error %d", res); + + value.type = SPA_PROP_TYPE_STRING; + value.size = strlen ("hw:1")+1; + value.value = "hw:1"; + props->set_prop (props, spa_props_index_for_name (props, "device"), &value); + + if ((res = priv->sink_node->set_props (priv->sink, props)) < 0) + g_debug ("got set_props error %d", res); + + cmd.type = SPA_COMMAND_ACTIVATE; + if ((res = priv->sink_node->send_command (priv->sink, &cmd)) < 0) + g_debug ("got activate error %d", res); +} + +static void +start_pipeline (PinosSpaAlsaSink *sink) +{ + PinosSpaAlsaSinkPrivate *priv = sink->priv; + SpaResult res; + SpaCommand cmd; + + g_debug ("spa-alsa-sink %p: starting pipeline", sink); + + cmd.type = SPA_COMMAND_START; + if ((res = priv->sink_node->send_command (priv->sink, &cmd)) < 0) + g_debug ("got error %d", res); +} + +static void +stop_pipeline (PinosSpaAlsaSink *sink) +{ + PinosSpaAlsaSinkPrivate *priv = sink->priv; + SpaResult res; + SpaCommand cmd; + + g_debug ("spa-alsa-sink %p: stopping pipeline", sink); + + cmd.type = SPA_COMMAND_STOP; + if ((res = priv->sink_node->send_command (priv->sink, &cmd)) < 0) + g_debug ("got error %d", res); +} + +static void +destroy_pipeline (PinosSpaAlsaSink *sink) +{ + PinosSpaAlsaSinkPrivate *priv = sink->priv; + SpaResult res; + SpaCommand cmd; + + g_debug ("spa-alsa-sink %p: destroy pipeline", sink); + + cmd.type = SPA_COMMAND_DEACTIVATE; + if ((res = priv->sink_node->send_command (priv->sink, &cmd)) < 0) + g_debug ("got error %d", res); +} + +static gboolean +set_state (PinosNode *node, + PinosNodeState state) +{ + PinosSpaAlsaSink *this = PINOS_SPA_ALSA_SINK (node); + PinosSpaAlsaSinkPrivate *priv = this->priv; + + g_debug ("spa-alsa-sink %p: set state %s", node, pinos_node_state_as_string (state)); + + switch (state) { + case PINOS_NODE_STATE_SUSPENDED: + break; + + case PINOS_NODE_STATE_INITIALIZING: + break; + + case PINOS_NODE_STATE_IDLE: + stop_pipeline (this); + break; + + case PINOS_NODE_STATE_RUNNING: + //start_pipeline (this); + break; + + case PINOS_NODE_STATE_ERROR: + break; + } + pinos_node_update_state (node, state); + return TRUE; +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + PinosSpaAlsaSink *sink = PINOS_SPA_ALSA_SINK (object); + PinosSpaAlsaSinkPrivate *priv = sink->priv; + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + PinosSpaAlsaSink *sink = PINOS_SPA_ALSA_SINK (object); + PinosSpaAlsaSinkPrivate *priv = sink->priv; + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_linked (PinosPort *port, PinosPort *peer, gpointer user_data) +{ + PinosNode *node = user_data; + gint n_peers; + + g_debug ("port %p: linked", port); + + n_peers = pinos_port_get_n_links (port); + if (n_peers == 1) + pinos_node_report_busy (node); +} + +static void +on_unlinked (PinosPort *port, PinosPort *peer, gpointer user_data) +{ + PinosNode *node = user_data; + gint n_peers; + + g_debug ("port %p: unlinked", port); + n_peers = pinos_port_get_n_links (port); + if (n_peers == 0) + pinos_node_report_idle (node); +} + +static SpaResult +negotiate_formats (PinosSpaAlsaSink *this) +{ + PinosSpaAlsaSinkPrivate *priv = this->priv; + SpaResult res; + SpaFormat *format; + SpaProps *props; + uint32_t val; + SpaPropValue value; + + if ((res = priv->sink_node->enum_port_formats (priv->sink, 0, 0, &format)) < 0) + return res; + + props = &format->props; + + value.type = SPA_PROP_TYPE_UINT32; + value.size = sizeof (uint32_t); + value.value = &val; + + val = SPA_AUDIO_FORMAT_S16LE; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_AUDIO_FORMAT), &value)) < 0) + return res; + val = SPA_AUDIO_LAYOUT_INTERLEAVED; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_AUDIO_LAYOUT), &value)) < 0) + return res; + val = 44100; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_AUDIO_RATE), &value)) < 0) + return res; + val = 2; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_AUDIO_CHANNELS), &value)) < 0) + return res; + + if ((res = priv->sink_node->set_port_format (priv->sink, 0, 0, format)) < 0) + return res; + + priv->ringbuffer = pinos_ringbuffer_new (PINOS_RINGBUFFER_MODE_READ, 64 * 1024); + + g_object_set (priv->input, "ringbuffer", priv->ringbuffer, NULL); + + return SPA_RESULT_OK; +} + +static void +on_received_buffer (PinosPort *port, + gpointer user_data) +{ + PinosSpaAlsaSink *this = user_data; + PinosSpaAlsaSinkPrivate *priv = this->priv; + PinosBuffer *pbuf; + PinosBufferIter it; + + pbuf = pinos_port_peek_buffer (port); + + pinos_buffer_iter_init (&it, pbuf); + while (pinos_buffer_iter_next (&it)) { + switch (pinos_buffer_iter_get_type (&it)) { + case PINOS_PACKET_TYPE_HEADER: + { + PinosPacketHeader hdr; + + if (!pinos_buffer_iter_parse_header (&it, &hdr)) + break; + + break; + } + + case PINOS_PACKET_TYPE_FD_PAYLOAD: + { + PinosPacketFDPayload p; + int fd; + PinosRingbufferArea areas[2]; + uint8_t *data, *d; + size_t size, towrite, total; + + if (!pinos_buffer_iter_parse_fd_payload (&it, &p)) + break; + + g_debug ("got fd payload id %d", p.id); + fd = pinos_buffer_get_fd (pbuf, p.fd_index); + if (fd == -1) + break; + + d = data = mmap (NULL, p.size, PROT_READ, MAP_PRIVATE, fd, p.offset); + size = p.size; + + pinos_ringbuffer_get_write_areas (priv->ringbuffer, areas); + + total = MIN (size, areas[0].len + areas[1].len); + g_debug ("total write %zd %zd", total, areas[0].len + areas[1].len); + towrite = MIN (size, areas[0].len); + memcpy (areas[0].data, data, towrite); + size -= towrite; + data += towrite; + towrite = MIN (size, areas[1].len); + memcpy (areas[1].data, data, towrite); + + pinos_ringbuffer_write_advance (priv->ringbuffer, total); + + munmap (d, p.size); + break; + } + case PINOS_PACKET_TYPE_FORMAT_CHANGE: + { + PinosPacketFormatChange change; + + if (!pinos_buffer_iter_parse_format_change (&it, &change)) + break; + g_debug ("got format change %d %s", change.id, change.format); + + negotiate_formats (this); + start_pipeline (this); + break; + } + default: + break; + } + } + pinos_buffer_iter_end (&it); +} + +static void +on_input_port_created (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + PinosNode *node = PINOS_NODE (source_object); + PinosSpaAlsaSink *sink = PINOS_SPA_ALSA_SINK (node); + PinosSpaAlsaSinkPrivate *priv = sink->priv; + + priv->input = pinos_node_create_port_finish (node, res, NULL); + + pinos_port_set_received_buffer_cb (priv->input, on_received_buffer, sink, NULL); + + g_signal_connect (priv->input, "linked", (GCallback) on_linked, node); + g_signal_connect (priv->input, "unlinked", (GCallback) on_unlinked, node); + + create_pipeline (sink); +} + +static void +sink_constructed (GObject * object) +{ + PinosServerNode *node = PINOS_SERVER_NODE (object); + PinosSpaAlsaSink *sink = PINOS_SPA_ALSA_SINK (object); + PinosSpaAlsaSinkPrivate *priv = sink->priv; + + G_OBJECT_CLASS (pinos_spa_alsa_sink_parent_class)->constructed (object); + + pinos_node_create_port (PINOS_NODE (node), + PINOS_DIRECTION_INPUT, + "input", + NULL, + NULL, + NULL, + on_input_port_created, + node); +} + +static void +sink_finalize (GObject * object) +{ + PinosServerNode *node = PINOS_SERVER_NODE (object); + PinosSpaAlsaSink *sink = PINOS_SPA_ALSA_SINK (object); + PinosSpaAlsaSinkPrivate *priv = sink->priv; + + destroy_pipeline (sink); + pinos_node_remove_port (PINOS_NODE (node), priv->input); + pinos_properties_free (priv->props); + + G_OBJECT_CLASS (pinos_spa_alsa_sink_parent_class)->finalize (object); +} + +static void +pinos_spa_alsa_sink_class_init (PinosSpaAlsaSinkClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + PinosNodeClass *node_class = PINOS_NODE_CLASS (klass); + + g_type_class_add_private (klass, sizeof (PinosSpaAlsaSinkPrivate)); + + gobject_class->constructed = sink_constructed; + gobject_class->finalize = sink_finalize; + gobject_class->get_property = get_property; + gobject_class->set_property = set_property; + + node_class->set_state = set_state; +} + +static void +pinos_spa_alsa_sink_init (PinosSpaAlsaSink * sink) +{ + PinosSpaAlsaSinkPrivate *priv; + + priv = sink->priv = PINOS_SPA_ALSA_SINK_GET_PRIVATE (sink); + priv->props = pinos_properties_new (NULL, NULL); +} + +PinosServerNode * +pinos_spa_alsa_sink_new (PinosDaemon *daemon, + const gchar *name, + PinosProperties *properties) +{ + PinosServerNode *node; + + node = g_object_new (PINOS_TYPE_SPA_ALSA_SINK, + "daemon", daemon, + "name", name, + "properties", properties, + NULL); + + return node; +} diff --git a/pinos/modules/spa/spa-alsa-sink.h b/pinos/modules/spa/spa-alsa-sink.h new file mode 100644 index 000000000..9f67c3840 --- /dev/null +++ b/pinos/modules/spa/spa-alsa-sink.h @@ -0,0 +1,61 @@ +/* Pinos + * Copyright (C) 2015 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PINOS_SPA_ALSA_SINK_H__ +#define __PINOS_SPA_ALSA_SINK_H__ + +#include + +#include +#include + +G_BEGIN_DECLS + +#define PINOS_TYPE_SPA_ALSA_SINK (pinos_spa_alsa_sink_get_type ()) +#define PINOS_IS_SPA_ALSA_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PINOS_TYPE_SPA_ALSA_SINK)) +#define PINOS_IS_SPA_ALSA_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PINOS_TYPE_SPA_ALSA_SINK)) +#define PINOS_SPA_ALSA_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PINOS_TYPE_SPA_ALSA_SINK, PinosSpaAlsaSinkClass)) +#define PINOS_SPA_ALSA_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PINOS_TYPE_SPA_ALSA_SINK, PinosSpaAlsaSink)) +#define PINOS_SPA_ALSA_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PINOS_TYPE_SPA_ALSA_SINK, PinosSpaAlsaSinkClass)) +#define PINOS_SPA_ALSA_SINK_CAST(obj) ((PinosSpaAlsaSink*)(obj)) +#define PINOS_SPA_ALSA_SINK_CLASS_CAST(klass) ((PinosSpaAlsaSinkClass*)(klass)) + +typedef struct _PinosSpaAlsaSink PinosSpaAlsaSink; +typedef struct _PinosSpaAlsaSinkClass PinosSpaAlsaSinkClass; +typedef struct _PinosSpaAlsaSinkPrivate PinosSpaAlsaSinkPrivate; + +struct _PinosSpaAlsaSink { + PinosServerNode object; + + PinosSpaAlsaSinkPrivate *priv; +}; + +struct _PinosSpaAlsaSinkClass { + PinosServerNodeClass parent_class; +}; + +GType pinos_spa_alsa_sink_get_type (void); + +PinosServerNode * pinos_spa_alsa_sink_new (PinosDaemon *daemon, + const gchar *name, + PinosProperties *properties); + +G_END_DECLS + +#endif /* __PINOS_SPA_ALSA_SINK_H__ */ diff --git a/pinos/server/port.h b/pinos/server/port.h index 27c2898bd..fb30b6d17 100644 --- a/pinos/server/port.h +++ b/pinos/server/port.h @@ -59,6 +59,10 @@ struct _PinosPort { */ struct _PinosPortClass { GObjectClass parent_class; + + void (*get_ringbuffer) (PinosPort *port, + PinosProperties *props, + GTask *task); }; typedef gboolean (*PinosBufferCallback) (PinosPort *port, PinosBuffer *buffer, GError **error, gpointer user_data); diff --git a/pinos/spi/buffer.h b/pinos/spi/buffer.h deleted file mode 100644 index 16dce1153..000000000 --- a/pinos/spi/buffer.h +++ /dev/null @@ -1,132 +0,0 @@ -/* Simple Plugin Interface - * Copyright (C) 2016 Wim Taymans - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __SPI_BUFFER_H__ -#define __SPI_BUFFER_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct _SpiBuffer SpiBuffer; - -#include - -typedef enum { - SPI_META_TYPE_INVALID = 0, - SPI_META_TYPE_HEADER, -} SpiMetaType; - -typedef struct { - uint32_t flags; - uint32_t seq; - int64_t pts; - int64_t dts_offset; -} SpiMetaHeader; - -typedef struct { - SpiMetaType type; - void *data; - size_t size; -} SpiMeta; - -/** - * SpiDataType: - * @SPI_DATA_TYPE_INVALID: invalid data type, is ignored - * @SPI_DATA_TYPE_MEMPTR: data and size point to memory - * @SPI_DATA_TYPE_FD: data points to SpiDataFd - * @SPI_DATA_TYPE_FD: data points to SpiDataFd - */ -typedef enum { - SPI_DATA_TYPE_INVALID = 0, - SPI_DATA_TYPE_MEMPTR, - SPI_DATA_TYPE_FD, -} SpiDataType; - -/** - * SpiDataFd - * fd: a file descriptor - * offset: offset in the data referenced by @fd - * @size: size of data referenced by fd - */ -typedef struct { - int fd; - unsigned int offset; - size_t size; -} SpiDataFD; - -/** - * SpiData: - * @id: user id - * @type: the type of data - * @data: pointer to data - * @size: size of data - */ -typedef struct { - SpiDataType type; - void *data; - size_t size; -} SpiData; - -/** - * SpiBuffer: - * @refcount: reference counter - * @notify: called when the refcount reaches 0 - * @size: total size of the buffer - * @n_metas: number of metadata - * @metas: array of @n_metas metadata - * @n_datas: number of data pointers - * @datas: array of @n_datas data pointers - */ -struct _SpiBuffer { - volatile int refcount; - SpiNotify notify; - size_t size; - unsigned int n_metas; - SpiMeta *metas; - unsigned int n_datas; - SpiData *datas; -}; - -static inline SpiBuffer * -spi_buffer_ref (SpiBuffer *buffer) -{ - if (buffer != NULL) - buffer->refcount++; - return buffer; -} - -static inline SpiBuffer * -spi_buffer_unref (SpiBuffer *buffer) -{ - if (buffer != NULL) { - if (--buffer->refcount == 0) { - if (buffer->notify) - buffer->notify (buffer); - return NULL; - } - } - return buffer; -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* __SPI_BUFFER_H__ */ diff --git a/pinos/spi/defs.h b/pinos/spi/defs.h deleted file mode 100644 index c1e256ade..000000000 --- a/pinos/spi/defs.h +++ /dev/null @@ -1,69 +0,0 @@ -/* Simple Plugin Interface - * Copyright (C) 2016 Wim Taymans - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __SPI_DEFS_H__ -#define __SPI_DEFS_H__ - -#ifdef __cplusplus -extern "C" { -#else -#include -#endif -#include -#include - -typedef enum { - SPI_RESULT_OK = 0, - SPI_RESULT_ERROR = -1, - SPI_RESULT_INACTIVE = -2, - SPI_RESULT_NO_FORMAT = -3, - SPI_RESULT_INVALID_COMMAND = -4, - SPI_RESULT_INVALID_PORT = -5, - SPI_RESULT_HAVE_ENOUGH_INPUT = -6, - SPI_RESULT_NEED_MORE_INPUT = -7, - SPI_RESULT_PORTS_CHANGED = -9, - SPI_RESULT_FORMAT_CHANGED = -10, - SPI_RESULT_PROPERTIES_CHANGED = -11, - SPI_RESULT_NOT_IMPLEMENTED = -12, - SPI_RESULT_INVALID_PARAM_ID = -13, - SPI_RESULT_PARAM_UNSET = -14, - SPI_RESULT_ENUM_END = -15, - SPI_RESULT_WRONG_PARAM_TYPE = -16, - SPI_RESULT_WRONG_PARAM_SIZE = -17, - SPI_RESULT_INVALID_MEDIA_TYPE = -18, - SPI_RESULT_INVALID_FORMAT_PARAMS = -19, - SPI_RESULT_FORMAT_INCOMPLETE = -20, - SPI_RESULT_INVALID_ARGUMENTS = -21, - SPI_RESULT_UNKNOWN_INTERFACE = -22, -} SpiResult; - -typedef enum { - SPI_DIRECTION_INVALID = 0, - SPI_DIRECTION_INPUT, - SPI_DIRECTION_OUTPUT -} SpiDirection; - -typedef void (*SpiNotify) (void *data); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - - -#endif /* __SPI_DEFS_H__ */ diff --git a/pinos/spi/event.h b/pinos/spi/event.h deleted file mode 100644 index d74813a93..000000000 --- a/pinos/spi/event.h +++ /dev/null @@ -1,74 +0,0 @@ -/* Simple Plugin Interface - * Copyright (C) 2016 Wim Taymans - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __SPI_EVENT_H__ -#define __SPI_EVENT_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct _SpiEvent SpiEvent; - -#include - -/** - * SpiEventType: - * @SPI_EVENT_TYPE_INVALID: invalid event, should be ignored - * @SPI_EVENT_TYPE_ACTIVATED: emited when the ACTIVATE command completes - * @SPI_EVENT_TYPE_DEACTIVATED: emited when the DEACTIVATE command completes - * @SPI_EVENT_TYPE_CAN_PULL_OUTPUT: emited when an async node has output that can be pulled - * @SPI_EVENT_TYPE_CAN_PUSH_INTPUT: emited when more data can be pushed to an async node - * @SPI_EVENT_TYPE_PULL_INPUT: emited when data needs to be provided on an input - * @SPI_EVENT_TYPE_ADD_POLL: emited when a pollfd should be added - * @SPI_EVENT_TYPE_REMOVE_POLL: emited when a pollfd should be removed - * @SPI_EVENT_TYPE_DRAINED: emited when DRAIN command completed - * @SPI_EVENT_TYPE_MARKER: emited when MARK command completed - * @SPI_EVENT_TYPE_ERROR: emited when error occured - * @SPI_EVENT_TYPE_BUFFERING: emited when buffering is in progress - */ -typedef enum { - SPI_EVENT_TYPE_INVALID = 0, - SPI_EVENT_TYPE_ACTIVATED, - SPI_EVENT_TYPE_DEACTIVATED, - SPI_EVENT_TYPE_CAN_PULL_OUTPUT, - SPI_EVENT_TYPE_CAN_PUSH_INTPUT, - SPI_EVENT_TYPE_PULL_INPUT, - SPI_EVENT_TYPE_ADD_POLL, - SPI_EVENT_TYPE_REMOVE_POLL, - SPI_EVENT_TYPE_DRAINED, - SPI_EVENT_TYPE_MARKER, - SPI_EVENT_TYPE_ERROR, - SPI_EVENT_TYPE_BUFFERING, -} SpiEventType; - -struct _SpiEvent { - volatile int refcount; - SpiNotify notify; - SpiEventType type; - uint32_t port_id; - void *data; - size_t size; -}; - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* __SPI_EVENT_H__ */ diff --git a/pinos/spi/node.h b/pinos/spi/node.h deleted file mode 100644 index e863611b8..000000000 --- a/pinos/spi/node.h +++ /dev/null @@ -1,367 +0,0 @@ -/* Simple Plugin Interface - * Copyright (C) 2016 Wim Taymans - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __SPI_NODE_H__ -#define __SPI_NODE_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct _SpiNode SpiNode; - -#include -#include -#include -#include -#include -#include -#include - -/** - * SpiInputFlags: - * @SPI_INPUT_FLAG_NONE: no flag - */ -typedef enum { - SPI_INPUT_FLAG_NONE = 0, -} SpiInputFlags; - -/** - * SpiInputInfo: - * @port_id: the port id - * @flags: extra flags - * @buffer: a buffer - * - * Input information for a node. - */ -typedef struct { - uint32_t port_id; - SpiInputFlags flags; - SpiBuffer *buffer; - SpiEvent *event; - SpiResult status; -} SpiInputInfo; - -/** - * SpiOutputFlags: - * @SPI_OUTPUT_FLAG_NONE: no flag - * @SPI_OUTPUT_FLAG_PULL: force a #SPI_EVENT_NEED_INPUT event on the - * peer input ports when no data is available. - * @SPI_OUTPUT_FLAG_DISCARD: discard the buffer data - */ -typedef enum { - SPI_OUTPUT_FLAG_NONE = 0, - SPI_OUTPUT_FLAG_PULL = (1 << 0), - SPI_OUTPUT_FLAG_DISCARD = (1 << 1), -} SpiOutputFlags; - -/** - * SpiOutputInfo: - * @port_id: the port id - * @flags: extra flags - * @buffer: a buffer - * @event: an event - * - * Output information for a node. - */ -typedef struct { - uint32_t port_id; - SpiOutputFlags flags; - SpiBuffer *buffer; - SpiEvent *event; - SpiResult status; -} SpiOutputInfo; - -/** - * SpiEventCallback: - * @node: a #SpiHandle emiting the event - * @event: the event that was emited - * @user_data: user data provided when registering the callback - * - * This will be called when an out-of-bound event is notified - * on @node. - */ -typedef void (*SpiEventCallback) (SpiHandle *handle, - SpiEvent *event, - void *user_data); - -#define SPI_INTERFACE_ID_NODE 0 -#define SPI_INTERFACE_ID_NODE_NAME "Node interface" -#define SPI_INTERFACE_ID_NODE_DESCRIPTION "Main processing node interface" - -/** - * SpiNode: - * - * The main processing nodes. - */ -struct _SpiNode { - /* the total size of this node. This can be used to expand this - * structure in the future */ - size_t size; - /** - * SpiNode::get_params: - * @handle: a #SpiHandle - * @props: a location for a #SpiParams pointer - * - * Get the configurable parameters of @node. - * - * The returned @props is a snapshot of the current configuration and - * can be modified. The modifications will take effect after a call - * to SpiNode::set_params. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node or props are %NULL - * #SPI_RESULT_NOT_IMPLEMENTED when there are no properties - * implemented on @node - */ - SpiResult (*get_params) (SpiHandle *handle, - SpiParams **props); - /** - * SpiNode::set_params: - * @handle: a #SpiHandle - * @props: a #SpiParams - * - * Set the configurable parameters in @node. - * - * Usually, @props will be obtained from SpiNode::get_params and then - * modified but it is also possible to set another #SpiParams object - * as long as its keys and types match those of SpiParams::get_params. - * - * Properties with keys that are not known are ignored. - * - * If @props is NULL, all the parameters are reset to their defaults. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node is %NULL - * #SPI_RESULT_NOT_IMPLEMENTED when no properties can be - * modified on @node. - * #SPI_RESULT_WRONG_PARAM_TYPE when a property has the wrong - * type. - */ - SpiResult (*set_params) (SpiHandle *handle, - const SpiParams *props); - /** - * SpiNode::send_command: - * @handle: a #SpiHandle - * @command: a #SpiCommand - * - * Send a command to @node. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node or command is %NULL - * #SPI_RESULT_NOT_IMPLEMENTED when this node can't process commands - * #SPI_RESULT_INVALID_COMMAND @command is an invalid command - */ - SpiResult (*send_command) (SpiHandle *handle, - SpiCommand *command); - /** - * SpiNode::set_event_callback: - * @handle: a #SpiHandle - * @callback: a callback - * @user_data: user data passed in the callback - * - * Set a callback to receive events from @node. if @callback is %NULL, the - * current callback is removed. - * - * The callback can be emited from any thread. The caller should take - * appropriate actions to handle the event in other threads when needed. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node is %NULL - */ - SpiResult (*set_event_callback) (SpiHandle *handle, - SpiEventCallback callback, - void *user_data); - /** - * SpiNode::get_n_ports: - * @handle: a #SpiHandle - * @n_input_ports: location to hold the number of input ports or %NULL - * @max_input_ports: location to hold the maximum number of input ports or %NULL - * @n_output_ports: location to hold the number of output ports or %NULL - * @max_output_ports: location to hold the maximum number of output ports or %NULL - * - * Get the current number of input and output ports and also the maximum - * number of ports. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node is %NULL - */ - SpiResult (*get_n_ports) (SpiHandle *handle, - unsigned int *n_input_ports, - unsigned int *max_input_ports, - unsigned int *n_output_ports, - unsigned int *max_output_ports); - /** - * SpiNode::get_port_ids: - * @handle: a #SpiHandle - * @n_input_ports: size of the @input_ids array - * @input_ids: array to store the input stream ids - * @n_output_ports: size of the @output_ids array - * @output_ids: array to store the output stream ids - * - * Get the current number of input and output ports and also the maximum - * number of ports. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node is %NULL - */ - SpiResult (*get_port_ids) (SpiHandle *handle, - unsigned int n_input_ports, - uint32_t *input_ids, - unsigned int n_output_ports, - uint32_t *output_ids); - - SpiResult (*add_port) (SpiHandle *handle, - SpiDirection direction, - uint32_t *port_id); - SpiResult (*remove_port) (SpiHandle *handle, - uint32_t port_id); - - /** - * SpiNode::enum_port_formats: - * @handle: a #SpiHandle - * @port_id: the port to query - * @index: the format index to retrieve - * @format: pointer to a format - * - * Enumerate all possible formats on @port_id of @node. - * - * Use the index to retrieve the formats one by one until the function - * returns #SPI_RESULT_ENUM_END. - * - * The result format can be queried and modified and ultimately be used - * to call SpiNode::set_port_format. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node or format is %NULL - * #SPI_RESULT_INVALID_PORT when port_id is not valid - * #SPI_RESULT_ENUM_END when no format exists for @index - * - */ - SpiResult (*enum_port_formats) (SpiHandle *handle, - uint32_t port_id, - unsigned int index, - SpiParams **format); - /** - * SpiNode::set_port_format: - * @handle: a #SpiHandle - * @port_id: the port to configure - * @format: a #SpiParam with the format - * - * Set a format on @port_id of @node. - * - * When @format is %NULL, the current format will be removed. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node is %NULL - * #SPI_RESULT_INVALID_PORT when port_id is not valid - * #SPI_RESULT_INVALID_MEDIA_TYPE when the media type is not valid - * #SPI_RESULT_INVALID_FORMAT_PARAMS when one of the mandatory format - * parameters is not specified. - * #SPI_RESULT_WRONG_PARAM_TYPE when the type of size of a parameter - * is not correct. - */ - SpiResult (*set_port_format) (SpiHandle *handle, - uint32_t port_id, - int test_only, - const SpiParams *format); - /** - * SpiNode::get_port_format: - * @handle: a #SpiHandle - * @port_id: the port to query - * @format: a pointer to a location to hold the #SpiParam - * - * Get the format on @port_id of @node. The result #SpiParam can - * not be modified. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when @node or @format is %NULL - * #SPI_RESULT_INVALID_PORT when @port_id is not valid - * #SPI_RESULT_INVALID_NO_FORMAT when no format was set - */ - SpiResult (*get_port_format) (SpiHandle *handle, - uint32_t port_id, - const SpiParams **format); - - SpiResult (*get_port_info) (SpiHandle *handle, - uint32_t port_id, - SpiPortInfo *info); - - SpiResult (*get_port_params) (SpiHandle *handle, - uint32_t port_id, - SpiParams **params); - SpiResult (*set_port_params) (SpiHandle *handle, - uint32_t port_id, - const SpiParams *params); - - SpiResult (*get_port_status) (SpiHandle *handle, - uint32_t port_id, - SpiPortStatus *status); - - /** - * SpiNode::push_port_input: - * @handle: a #SpiHandle - * @n_info: number of #SpiInputInfo in @info - * @info: array of #SpiInputInfo - * - * Push a buffer and/or an event into one or more input ports of - * @node. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node or info is %NULL - * #SPI_RESULT_ERROR when one or more of the @info has an - * error result. Check the status of all the - * @info. - * #SPI_RESULT_HAVE_ENOUGH_INPUT when output can be produced. - */ - SpiResult (*push_port_input) (SpiHandle *handle, - unsigned int n_info, - SpiInputInfo *info); - /** - * SpiNode::pull_port_output: - * @handle: a #SpiHandle - * @n_info: number of #SpiOutputInfo in @info - * @info: array of #SpiOutputInfo - * - * Pull a buffer and/or an event from one or more output ports of - * @node. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when node or info is %NULL - * #SPI_RESULT_PORTS_CHANGED the number of ports has changed. None - * of the @info fields are modified - * #SPI_RESULT_FORMAT_CHANGED a format changed on some port. - * the ports that changed are marked in the status. - * #SPI_RESULT_PROPERTIES_CHANGED port properties changed. The - * changed ports are marked in the status. - * #SPI_RESULT_ERROR when one or more of the @info has an - * error result. Check the status of all the @info. - * #SPI_RESULT_NEED_MORE_INPUT when no output can be produced - * because more input is needed. - */ - SpiResult (*pull_port_output) (SpiHandle *handle, - unsigned int n_info, - SpiOutputInfo *info); -}; - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* __SPI_NODE_H__ */ diff --git a/pinos/spi/params.h b/pinos/spi/params.h deleted file mode 100644 index 959be89fc..000000000 --- a/pinos/spi/params.h +++ /dev/null @@ -1,198 +0,0 @@ -/* Simple Plugin Interface - * Copyright (C) 2016 Wim Taymans - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __SPI_PARAMS_H__ -#define __SPI_PARAMS_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct _SpiParams SpiParams; - -#include - -/** - * SpiParamType: - */ -typedef enum { - SPI_PARAM_TYPE_INVALID = 0, - SPI_PARAM_TYPE_BOOL, - SPI_PARAM_TYPE_INT8, - SPI_PARAM_TYPE_UINT8, - SPI_PARAM_TYPE_INT16, - SPI_PARAM_TYPE_UINT16, - SPI_PARAM_TYPE_INT32, - SPI_PARAM_TYPE_UINT32, - SPI_PARAM_TYPE_INT64, - SPI_PARAM_TYPE_UINT64, - SPI_PARAM_TYPE_FLOAT, - SPI_PARAM_TYPE_DOUBLE, - SPI_PARAM_TYPE_STRING, - SPI_PARAM_TYPE_POINTER, - SPI_PARAM_TYPE_FRACTION, - SPI_PARAM_TYPE_BITMASK, - SPI_PARAM_TYPE_BYTES, -} SpiParamType; - -/** - * SpiParamFlags: - * @SPI_PARAM_FLAG_NONE: no flags - * @SPI_PARAM_FLAG_OPTIONAL: the value can be left unset - * @SPI_PARAM_FLAG_READABLE: param is readable - * @SPI_PARAM_FLAG_WRITABLE: param is writable - * @SPI_PARAM_FLAG_READWRITE: param is readable and writable - * @SPI_PARAM_FLAG_DEPRECATED: param is deprecated and should not be used - */ -typedef enum { - SPI_PARAM_FLAG_NONE = 0, - SPI_PARAM_FLAG_OPTIONAL = (1 << 0), - SPI_PARAM_FLAG_READABLE = (1 << 1), - SPI_PARAM_FLAG_WRITABLE = (1 << 2), - SPI_PARAM_FLAG_READWRITE = SPI_PARAM_FLAG_READABLE | SPI_PARAM_FLAG_WRITABLE, - SPI_PARAM_FLAG_DEPRECATED = (1 << 3), -} SpiParamFlags; - -/* SpiParamRangeType: - * @SPI_PARAM_RANGE_TYPE_NONE: no range specified, full range of type applies - * @SPI_PARAM_RANGE_TYPE_MIN_MAX: range contains 2 values, min and max - * @SPI_PARAM_RANGE_TYPE_ENUM: range contains enum of possible values with - * NULL-terminated name - * @SPI_PARAM_RANGE_TYPE_FLAGS: range contains flags of possible values with - * NULL-terminated name - */ -typedef enum { - SPI_PARAM_RANGE_TYPE_NONE = 0, - SPI_PARAM_RANGE_TYPE_MIN_MAX, - SPI_PARAM_RANGE_TYPE_ENUM, - SPI_PARAM_RANGE_TYPE_FLAGS, -} SpiParamRangeType; - -/** - * SpiParamRangeInfo: - * @name: name of this value - * @description: user visible description of this value - * @size: the size of value - * @value: a possible value - */ -typedef struct { - const char *name; - const char *description; - size_t size; - const void *value; -} SpiParamRangeInfo; - -/** - * SpiParamInfo: - * @id: unique id - * @name: human readable name - * @description: description of the param - * @flags: param flags - * @type: param type - * @max_size: maximum size of param value - * @default_size: size of default value - * @default_value: default value of param - * @range_type: type of the range values - * @range_values: array of possible values - * @tags: extra tags, NULL terminated - * @priv: extra private data - */ -typedef struct { - uint32_t id; - const char *name; - const char *description; - SpiParamFlags flags; - SpiParamType type; - size_t maxsize; - size_t default_size; - const void *default_value; - SpiParamRangeType range_type; - const SpiParamRangeInfo *range_values; - const char **tags; - const void *priv; -} SpiParamInfo; - -/** - * SpiParams: - * - * Generic parameters. - */ -struct _SpiParams { - /** - * SpiParams::get_param_info: - * @params: a #SpiParams - * @idx: the param index - * @info: pointer to a result #SpiParamInfo - * - * Gets the information about the parameter at @idx in @params. - * - * Returns: #SPI_RESULT_OK on success. - * #SPI_RESULT_ENM_END when there is no param info - * at @idx. This can be used to iterate the @params. - */ - SpiResult (*enum_param_info) (const SpiParams *params, - unsigned int idx, - const SpiParamInfo **infos); - /** - * SpiParams::set_param - * @params: a #SpiParams - * @id: the param id - * @type: the value type to set - * @size: the value size - * @value: the value to set - * - * Sets @value in @param. @type should match the type specified - * in the #SpiParamInfo for @id or else #SPI_RESULT_WRONG_PARAM_TYPE - * is returned. - * - * Returns: #SPI_RESULT_OK on success. - * #SPI_RESULT_INVALID_PARAM_ID when @id is not valid - * #SPI_RESULT_WRONG_PARAM_TYPE when @type is not correct - */ - SpiResult (*set_param) (SpiParams *params, - uint32_t id, - SpiParamType type, - size_t size, - const void *value); - /** - * SpiParams::get_param - * @params: a #SpiParams - * @id: the param id - * @type: a location for the value type - * @size: a location for the value size - * @value: a location for the value pointer - * - * Get the type, size and value of the parameter with @id. - * - * Returns: #SPI_RESULT_OK on success. - * #SPI_RESULT_INVALID_PARAM_ID when @id is not valid - * #SPI_RESULT_PARAM_UNSET when no value has been set yet - */ - SpiResult (*get_param) (const SpiParams *params, - uint32_t id, - SpiParamType *type, - size_t *size, - const void **value); -}; - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* __SPI_PARAMS_H__ */ diff --git a/pinos/spi/plugin.h b/pinos/spi/plugin.h deleted file mode 100644 index 5f5f3f2ac..000000000 --- a/pinos/spi/plugin.h +++ /dev/null @@ -1,132 +0,0 @@ -/* Simple Plugin Interface - * Copyright (C) 2016 Wim Taymans - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __SPI_PLUGIN_H__ -#define __SPI_PLUGIN_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -typedef struct _SpiHandle SpiHandle; -typedef struct _SpiHandleFactory SpiHandleFactory; - -struct _SpiHandle { - /* user_data that can be set by the application */ - void * user_data; - /** - * SpiHandle::get_interface: - * @handle: a #SpiHandle - * @interface_id: the interface id - * @interface: result to hold the interface. - * - * Get the interface provided by @handle with @interface_id. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_NOT_IMPLEMENTED when there are no extensions - * #SPI_RESULT_INVALID_ARGUMENTS when handle or info is %NULL - */ - SpiResult (*get_interface) (SpiHandle *handle, - uint32_t interface_id, - void **interface); -}; - -/** - * SpiInterfaceInfo: - * @interface_id: the id of the interface, can be used to get the interface - * @name: name of the interface - * @description: Human readable description of the interface. - * - * This structure lists the information about available interfaces on - * handles. - */ -typedef struct { - uint32_t interface_id; - const char *name; - const char *description; -} SpiInterfaceInfo; - -struct _SpiHandleFactory { - /** - * SpiHandleFactory::name - * - * The name - */ - const char * name; - /** - * SpiHandleFactory::info - * - * Extra information about the handles of this factory. - */ - const SpiParams * info; - - /** - * SpiHandleFactory::instantiate - * @factory: a #SpiHandleFactory - * @handle: a pointer to hold the result - * - * Make an instance of this factory. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_NOT_IMPLEMENTED when an instance can't be made - * #SPI_RESULT_INVALID_ARGUMENTS when factory or handle are %NULL - */ - SpiResult (*instantiate) (SpiHandleFactory *factory, - SpiHandle **handle); - /** - * SpiHandle::enum_interface_info: - * @factory: a #SpiHandleFactory - * @index: the interface index - * @info: result to hold SpiInterfaceInfo. - * - * Get the interface information at @index. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_NOT_IMPLEMENTED when there are no interfaces - * #SPI_RESULT_INVALID_ARGUMENTS when handle or info is %NULL - * #SPI_RESULT_ENUM_END when there are no more infos - */ - SpiResult (*enum_interface_info) (SpiHandleFactory *factory, - unsigned int index, - const SpiInterfaceInfo **info); -}; - - -/** - * spi_enum_handle_factory: - * @index: the index to enumerate - * @factory: a location to hold the factory result - * - * The main entry point in a plugin. - * - * Returns: #SPI_RESULT_OK on success - * #SPI_RESULT_INVALID_ARGUMENTS when factory is %NULL - * #SPI_RESULT_ENUM_END when there are no more factories - */ -SpiResult spi_enum_handle_factory (unsigned int index, - const SpiHandleFactory **factory); - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* __SPI_PLUGIN_H__ */ diff --git a/pinos/tests/test-node.c b/pinos/tests/test-node.c deleted file mode 100644 index b95c2c98b..000000000 --- a/pinos/tests/test-node.c +++ /dev/null @@ -1,479 +0,0 @@ -/* Pinos - * Copyright (C) 2016 Wim Taymans - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include -#include -#include -#include - -#include -#include "spi-plugins.h" - -typedef struct { - SpiNode *src_node; - SpiNode *sink_node; - SpiHandle *src; - SpiHandle *sink; -} AppData; - -static void -print_value (const char *prefix, SpiParamType type, int size, const void *value) -{ - printf ("%s", prefix); - switch (type) { - case SPI_PARAM_TYPE_INVALID: - printf ("invalid"); - break; - case SPI_PARAM_TYPE_BOOL: - printf ("%s", *(bool *)value ? "true" : "false"); - break; - case SPI_PARAM_TYPE_INT8: - printf ("%" PRIi8, *(int8_t *)value); - break; - case SPI_PARAM_TYPE_UINT8: - printf ("%" PRIu8, *(uint8_t *)value); - break; - case SPI_PARAM_TYPE_INT16: - printf ("%" PRIi16, *(int16_t *)value); - break; - case SPI_PARAM_TYPE_UINT16: - printf ("%" PRIu16, *(uint16_t *)value); - break; - case SPI_PARAM_TYPE_INT32: - printf ("%" PRIi32, *(int32_t *)value); - break; - case SPI_PARAM_TYPE_UINT32: - printf ("%" PRIu32, *(uint32_t *)value); - break; - case SPI_PARAM_TYPE_INT64: - printf ("%" PRIi64 "\n", *(int64_t *)value); - break; - case SPI_PARAM_TYPE_UINT64: - printf ("%" PRIu64 "\n", *(uint64_t *)value); - break; - case SPI_PARAM_TYPE_FLOAT: - printf ("%f", *(float *)value); - break; - case SPI_PARAM_TYPE_DOUBLE: - printf ("%g", *(double *)value); - break; - case SPI_PARAM_TYPE_STRING: - printf ("%s", (char *)value); - break; - case SPI_PARAM_TYPE_POINTER: - printf ("%p", value); - break; - case SPI_PARAM_TYPE_FRACTION: - break; - case SPI_PARAM_TYPE_BITMASK: - break; - case SPI_PARAM_TYPE_BYTES: - break; - default: - break; - } - printf ("\n"); -} - -static void -print_params (const SpiParams *params, int print_ranges) -{ - SpiResult res; - const SpiParamInfo *info; - int i, j; - SpiParamType type; - - for (i = 0; ; i++) { - const void *value; - size_t size; - - if ((res = params->enum_param_info (params, i, &info)) < 0) { - if (res != SPI_RESULT_ENUM_END) - printf ("got error %d\n", res); - break; - } - - printf ("id:\t\t%d\n", info->id); - printf ("name:\t\t%s\n", info->name); - printf ("description:\t%s\n", info->description); - printf ("flags:\t\t%d\n", info->flags); - printf ("type:\t\t%d\n", info->type); - printf ("maxsize:\t%zu\n", info->maxsize); - - res = params->get_param (params, info->id, &type, &size, &value); - if (res == SPI_RESULT_PARAM_UNSET) - printf ("value:\t\tunset\n"); - else - print_value ("value:\t\t", type, size, value); - - if (print_ranges) { - if (info->default_value) - print_value ("default:\t", info->type, info->default_size, info->default_value); - else - printf ("default:\tunset\n"); - - printf ("range_type:\t%d\n", info->range_type); - if (info->range_values) { - for (j = 0; info->range_values[j].name; j++) { - const SpiParamRangeInfo *rinfo = &info->range_values[j]; - printf (" name:\t%s\n", rinfo->name); - printf (" description:\t%s\n", rinfo->description); - print_value (" value:\t", info->type, rinfo->size, rinfo->value); - } - } - } - if (info->tags) { - for (j = 0; info->tags[j]; j++) { - printf ("tag:\t%s\n", info->tags[j]); - } - } - } -} - -static void -inspect_node (SpiNode *node, SpiHandle *handle) -{ - SpiResult res; - SpiParams *params; - unsigned int n_input, max_input, n_output, max_output, i; - SpiParams *format; - - if ((res = node->get_params (handle, ¶ms)) < 0) - printf ("got error %d\n", res); - else - print_params (params, 1); - - if ((res = node->get_n_ports (handle, &n_input, &max_input, &n_output, &max_output)) < 0) - printf ("got error %d\n", res); - else - printf ("supported ports %d %d %d %d\n", n_input, max_input, n_output, max_output); - - for (i = 0; ; i++) { - if ((res = node->enum_port_formats (handle, 0, i, &format)) < 0) { - if (res != SPI_RESULT_ENUM_END) - printf ("got error %d\n", res); - break; - } - print_params (format, 1); - } - if ((res = node->get_port_params (handle, 0, ¶ms)) < 0) - printf ("get_port_params error: %d\n", res); - else - printf ("got params %p\n", params); -} - -static void -set_format (AppData *data) -{ - SpiParams *format; - SpiResult res; - uint32_t val; - - if ((res = data->src_node->enum_port_formats (data->src, 0, 0, &format)) < 0) - printf ("got error %d\n", res); - - printf ("setting format\n"); - if ((res = format->set_param (format, 1, SPI_PARAM_TYPE_STRING, 5, "S16LE")) < 0) - printf ("got error %d\n", res); - val = 1; - if ((res = format->set_param (format, 2, SPI_PARAM_TYPE_UINT32, 4, &val)) < 0) - printf ("got error %d\n", res); - val = 44100; - if ((res = format->set_param (format, 3, SPI_PARAM_TYPE_UINT32, 4, &val)) < 0) - printf ("got error %d\n", res); - val = 2; - if ((res = format->set_param (format, 4, SPI_PARAM_TYPE_UINT32, 4, &val)) < 0) - printf ("got error %d\n", res); - - if ((res = data->src_node->set_port_format (data->src, 0, 0, format)) < 0) - printf ("set format failed: %d\n", res); - if ((res = data->sink_node->set_port_format (data->sink, 0, 0, format)) < 0) - printf ("set format failed: %d\n", res); -} - -typedef struct _MyBuffer MyBuffer; - -struct _MyBuffer { - SpiBuffer buffer; - SpiMeta meta[1]; - SpiMetaHeader header; - SpiData data[1]; - MyBuffer *next; - uint16_t samples[4096]; -}; - -#if 0 -static MyBuffer my_buffers[4]; -static MyBuffer *free_list = NULL; - -static void -my_buffer_notify (MyBuffer *buffer) -{ - printf ("free buffer %p\n", buffer); - buffer->next = free_list; - free_list = buffer; -} - -static SpiResult -setup_buffers (SpiNode *node) -{ - int i; - - for (i = 0; i < 4; i++) { - my_buffers[i].buffer.refcount = 0; - my_buffers[i].buffer.notify = (SpiNotify) my_buffer_notify; - my_buffers[i].buffer.size = sizeof (MyBuffer); - my_buffers[i].buffer.n_metas = 1; - my_buffers[i].buffer.metas = my_buffers[i].meta; - my_buffers[i].buffer.n_datas = 1; - my_buffers[i].buffer.datas = my_buffers[i].data; - - my_buffers[i].header.flags = 0; - my_buffers[i].header.seq = 0; - my_buffers[i].header.pts = 0; - my_buffers[i].header.dts_offset = 0; - - my_buffers[i].meta[0].type = SPI_META_TYPE_HEADER; - my_buffers[i].meta[0].data = &my_buffers[i].header; - my_buffers[i].meta[0].size = sizeof (my_buffers[i].header); - - my_buffers[i].data[0].type = SPI_DATA_TYPE_MEMPTR; - my_buffers[i].data[0].data = my_buffers[i].samples; - my_buffers[i].data[0].size = sizeof (my_buffers[i].samples); - - my_buffers[i].next = free_list; - free_list = &my_buffers[i]; - } - return SPI_RESULT_OK; -} - -static SpiResult -push_input (SpiNode *node) -{ - SpiResult res; - SpiDataInfo info; - MyBuffer *mybuf; - - mybuf = free_list; - free_list = mybuf->next; - - printf ("alloc input buffer %p\n", mybuf); - mybuf->buffer.refcount = 1; - - info.port_id = 0; - info.flags = SPI_DATA_FLAG_NONE; - info.buffer = &mybuf->buffer; - info.event = NULL; - - res = node->send_port_data (node, &info); - - spi_buffer_unref (&mybuf->buffer); - - return res; -} - -static SpiResult -pull_output (SpiNode *node) -{ - SpiDataInfo info[1] = { { 0, }, }; - SpiResult res; - MyBuffer *mybuf; - SpiBuffer *buf; - - mybuf = free_list; - free_list = mybuf->next; - - printf ("alloc output buffer %p\n", mybuf); - mybuf->buffer.refcount = 1; - - info[0].port_id = 1; - info[0].buffer = &mybuf->buffer; - info[0].event = NULL; - - res = node->receive_port_data (node, 1, info); - - buf = info[0].buffer; - spi_buffer_unref (buf); - - return res; -} - -static void -run_volume (SpiNode *node) -{ - int state; - SpiResult res; - - set_params (node); - set_format (node); - - state = 0; - while (TRUE) { - SpiPortStatus status; - - if (state == 0) { - if ((res = push_input (node)) < 0) { - if (res == SPI_RESULT_HAVE_ENOUGH_INPUT) - state = 1; - else { - printf ("got error %d\n", res); - break; - } - } - if ((res = node->get_port_status (node, 1, &status)) < 0) - printf ("got error %d\n", res); - else if (status.flags & SPI_PORT_STATUS_FLAG_HAVE_OUTPUT) - state = 1; - } - if (state == 1) { - if ((res = pull_output (node)) < 0) { - if (res == SPI_RESULT_NEED_MORE_INPUT) - state = 0; - else { - printf ("got error %d\n", res); - break; - } - } - if ((res = node->get_port_status (node, 0, &status)) < 0) - printf ("got error %d\n", res); - else if (status.flags & SPI_PORT_STATUS_FLAG_NEED_INPUT) - state = 0; - } - } -} -#endif - -static void -on_event (SpiHandle *handle, SpiEvent *event, void *user_data) -{ - AppData *data = user_data; - - switch (event->type) { - case SPI_EVENT_TYPE_PULL_INPUT: - { - SpiBuffer *buf; - SpiInputInfo iinfo; - SpiOutputInfo oinfo; - SpiResult res; - - buf = event->data; - - oinfo.port_id = event->port_id; - oinfo.flags = SPI_OUTPUT_FLAG_NONE; - oinfo.buffer = buf; - oinfo.event = NULL; - - if ((res = data->src_node->pull_port_output (data->src, 1, &oinfo)) < 0) - printf ("got error %d\n", res); - - iinfo.port_id = 0; - iinfo.flags = SPI_INPUT_FLAG_NONE; - iinfo.buffer = oinfo.buffer; - iinfo.event = oinfo.event; - - if ((res = data->sink_node->push_port_input (data->sink, 1, &iinfo)) < 0) - printf ("got error %d\n", res); - break; - } - default: - printf ("got event %d\n", event->type); - break; - } -} - -static void -run_async_sink (AppData *data) -{ - SpiResult res; - SpiCommand cmd; - - set_format (data); - - cmd.type = SPI_COMMAND_START; - if ((res = data->sink_node->send_command (data->sink, &cmd)) < 0) - printf ("got error %d\n", res); - - printf ("sleeping for 10 seconds\n"); - sleep (10); - - cmd.type = SPI_COMMAND_STOP; - if ((res = data->sink_node->send_command (data->sink, &cmd)) < 0) - printf ("got error %d\n", res); -} - -static void -setup_source (AppData *data) -{ - SpiCommand cmd; - SpiResult res; - - data->src = spi_audiotestsrc_new (); - data->src->get_interface (data->src, SPI_INTERFACE_ID_NODE, (void **)&data->src_node); - - cmd.type = SPI_COMMAND_ACTIVATE; - if ((res = data->src_node->send_command (data->src, &cmd)) < 0) - printf ("got error %d\n", res); -} - -static void -setup_sink (AppData *data) -{ - SpiCommand cmd; - SpiResult res; - SpiParams *params; - - data->sink = spi_alsa_sink_new (); - data->sink->get_interface (data->sink, SPI_INTERFACE_ID_NODE, (void **)&data->sink_node); - - data->sink_node->set_event_callback (data->sink, on_event, data); - - if ((res = data->sink_node->get_params (data->sink, ¶ms)) < 0) - printf ("got get_params error %d\n", res); - - params->set_param (params, 0, SPI_PARAM_TYPE_STRING, strlen ("hw:1")+1, "hw:1"); - - if ((res = data->sink_node->set_params (data->sink, params)) < 0) - printf ("got set_params error %d\n", res); - - cmd.type = SPI_COMMAND_ACTIVATE; - if ((res = data->sink_node->send_command (data->sink, &cmd)) < 0) - printf ("got error %d\n", res); -} - -int -main (int argc, char *argv[]) -{ - SpiResult res; - SpiCommand cmd; - AppData data; - - setup_source (&data); - setup_sink (&data); - - run_async_sink (&data); - - cmd.type = SPI_COMMAND_DEACTIVATE; - if ((res = data.sink_node->send_command (data.sink, &cmd)) < 0) - printf ("got error %d\n", res); - cmd.type = SPI_COMMAND_DEACTIVATE; - if ((res = data.src_node->send_command (data.src, &cmd)) < 0) - printf ("got error %d\n", res); - - return 0; -} diff --git a/spa/include/meson.build b/spa/include/meson.build new file mode 100644 index 000000000..68be45661 --- /dev/null +++ b/spa/include/meson.build @@ -0,0 +1 @@ +subdir('spa') diff --git a/spa/include/spa/audio/format.h b/spa/include/spa/audio/format.h new file mode 100644 index 000000000..7acddb0be --- /dev/null +++ b/spa/include/spa/audio/format.h @@ -0,0 +1,44 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_AUDIO_FORMAT_H__ +#define __SPA_AUDIO_FORMAT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef enum { + SPA_PROP_ID_AUDIO_FORMAT = SPA_PROP_ID_MEDIA_CUSTOM_START, + SPA_PROP_ID_AUDIO_FLAGS, + SPA_PROP_ID_AUDIO_LAYOUT, + SPA_PROP_ID_AUDIO_RATE, + SPA_PROP_ID_AUDIO_CHANNELS, + SPA_PROP_ID_AUDIO_CHANNEL_MASK, + SPA_PROP_ID_AUDIO_RAW_INFO, +} SpaPropIdAudio; + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SPA_AUDIO_FORMAT */ diff --git a/spa/include/spa/audio/raw.h b/spa/include/spa/audio/raw.h new file mode 100644 index 000000000..0c1f87abd --- /dev/null +++ b/spa/include/spa/audio/raw.h @@ -0,0 +1,158 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_AUDIO_RAW_H__ +#define __SPA_AUDIO_RAW_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _SpaAudioRawInfo SpaAudioRawInfo; +typedef struct _SpaAudioRawFormat SpaAudioRawFormat; + +#include + +#include + +#if __BYTE_ORDER == __BIG_ENDIAN +#define _SPA_AUDIO_FORMAT_NE(fmt) SPA_AUDIO_FORMAT_ ## fmt ## BE +#define _SPA_AUDIO_FORMAT_OE(fmt) SPA_AUDIO_FORMAT_ ## fmt ## LE +#elif __BYTE_ORDER == __LITTLE_ENDIAN +#define _SPA_AUDIO_FORMAT_NE(fmt) SPA_AUDIO_FORMAT_ ## fmt ## LE +#define _SPA_AUDIO_FORMAT_OE(fmt) SPA_AUDIO_FORMAT_ ## fmt ## BE +#endif + +typedef enum { + SPA_AUDIO_FORMAT_UNKNOWN, + SPA_AUDIO_FORMAT_ENCODED, + /* 8 bit */ + SPA_AUDIO_FORMAT_S8, + SPA_AUDIO_FORMAT_U8, + /* 16 bit */ + SPA_AUDIO_FORMAT_S16LE, + SPA_AUDIO_FORMAT_S16BE, + SPA_AUDIO_FORMAT_U16LE, + SPA_AUDIO_FORMAT_U16BE, + /* 24 bit in low 3 bytes of 32 bits*/ + SPA_AUDIO_FORMAT_S24_32LE, + SPA_AUDIO_FORMAT_S24_32BE, + SPA_AUDIO_FORMAT_U24_32LE, + SPA_AUDIO_FORMAT_U24_32BE, + /* 32 bit */ + SPA_AUDIO_FORMAT_S32LE, + SPA_AUDIO_FORMAT_S32BE, + SPA_AUDIO_FORMAT_U32LE, + SPA_AUDIO_FORMAT_U32BE, + /* 24 bit in 3 bytes*/ + SPA_AUDIO_FORMAT_S24LE, + SPA_AUDIO_FORMAT_S24BE, + SPA_AUDIO_FORMAT_U24LE, + SPA_AUDIO_FORMAT_U24BE, + /* 20 bit in 3 bytes*/ + SPA_AUDIO_FORMAT_S20LE, + SPA_AUDIO_FORMAT_S20BE, + SPA_AUDIO_FORMAT_U20LE, + SPA_AUDIO_FORMAT_U20BE, + /* 18 bit in 3 bytes*/ + SPA_AUDIO_FORMAT_S18LE, + SPA_AUDIO_FORMAT_S18BE, + SPA_AUDIO_FORMAT_U18LE, + SPA_AUDIO_FORMAT_U18BE, + /* float */ + SPA_AUDIO_FORMAT_F32LE, + SPA_AUDIO_FORMAT_F32BE, + SPA_AUDIO_FORMAT_F64LE, + SPA_AUDIO_FORMAT_F64BE, + /* native endianness equivalents */ + SPA_AUDIO_FORMAT_S16 = _SPA_AUDIO_FORMAT_NE(S16), + SPA_AUDIO_FORMAT_U16 = _SPA_AUDIO_FORMAT_NE(U16), + SPA_AUDIO_FORMAT_S24_32 = _SPA_AUDIO_FORMAT_NE(S24_32), + SPA_AUDIO_FORMAT_U24_32 = _SPA_AUDIO_FORMAT_NE(U24_32), + SPA_AUDIO_FORMAT_S32 = _SPA_AUDIO_FORMAT_NE(S32), + SPA_AUDIO_FORMAT_U32 = _SPA_AUDIO_FORMAT_NE(U32), + SPA_AUDIO_FORMAT_S24 = _SPA_AUDIO_FORMAT_NE(S24), + SPA_AUDIO_FORMAT_U24 = _SPA_AUDIO_FORMAT_NE(U24), + SPA_AUDIO_FORMAT_S20 = _SPA_AUDIO_FORMAT_NE(S20), + SPA_AUDIO_FORMAT_U20 = _SPA_AUDIO_FORMAT_NE(U20), + SPA_AUDIO_FORMAT_S18 = _SPA_AUDIO_FORMAT_NE(S18), + SPA_AUDIO_FORMAT_U18 = _SPA_AUDIO_FORMAT_NE(U18), + SPA_AUDIO_FORMAT_F32 = _SPA_AUDIO_FORMAT_NE(F32), + SPA_AUDIO_FORMAT_F64 = _SPA_AUDIO_FORMAT_NE(F64) +} SpaAudioFormat; + + +/** + * SpaAudioFlags: + * @SPA_AUDIO_FLAG_NONE: no valid flag + * @SPA_AUDIO_FLAG_UNPOSITIONED: the position array explicitly + * contains unpositioned channels. + * + * Extra audio flags + */ +typedef enum { + SPA_AUDIO_FLAG_NONE = 0, + SPA_AUDIO_FLAG_UNPOSITIONED = (1 << 0) +} SpaAudioFlags; + +/** + * SpaAudioLayout: + * @SPA_AUDIO_LAYOUT_INTERLEAVED: interleaved audio + * @SPA_AUDIO_LAYOUT_NON_INTERLEAVED: non-interleaved audio + * + * Layout of the audio samples for the different channels. + */ +typedef enum { + SPA_AUDIO_LAYOUT_INTERLEAVED = 0, + SPA_AUDIO_LAYOUT_NON_INTERLEAVED +} SpaAudioLayout; + +/** + * SpaAudioRawInfo: + * @format: the format + * @flags: extra flags + * @layout: the sample layout + * @rate: the sample rate + * @channels: the number of channels + * @channel_mask: the channel mask + */ +struct _SpaAudioRawInfo { + SpaAudioFormat format; + SpaAudioFlags flags; + SpaAudioLayout layout; + uint32_t rate; + uint32_t channels; + uint32_t channel_mask; +}; + +struct _SpaAudioRawFormat { + SpaFormat format; + uint32_t unset_mask; + SpaAudioRawInfo info; +}; + +SpaResult spa_audio_raw_format_init (SpaAudioRawFormat *format); +SpaResult spa_audio_raw_format_parse (const SpaFormat *format, + SpaAudioRawFormat *rawformat); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SPA_AUDIO_RAW_H__ */ diff --git a/spa/include/spa/buffer.h b/spa/include/spa/buffer.h new file mode 100644 index 000000000..9ca658ab6 --- /dev/null +++ b/spa/include/spa/buffer.h @@ -0,0 +1,180 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_BUFFER_H__ +#define __SPA_BUFFER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _SpaBuffer SpaBuffer; + +#include + +/** + * SpaMetaType: + * @SPA_META_TYPE_INVALID: invalid metadata, should be ignored + * @SPA_META_TYPE_HEADER: header metadata + */ +typedef enum { + SPA_META_TYPE_INVALID = 0, + SPA_META_TYPE_HEADER, +} SpaMetaType; + +/** + * SpaBufferFlags: + * @SPA_BUFFER_FLAG_NONE: no flag + * @SPA_BUFFER_FLAG_DISCONT: the buffer marks a data discontinuity + * @SPA_BUFFER_FLAG_CORRUPTED: the buffer data might be corrupted + * @SPA_BUFFER_FLAG_MARKER: the buffer contains a media specific marker + * @SPA_BUFFER_FLAG_HEADER: the buffer contains a header + * @SPA_BUFFER_FLAG_GAP: the buffer has been constructed to fill a gap + * and contains media neutral data + * @SPA_BUFFER_FLAG_DELTA_UNIT: the media cannot be decoded independently + */ +typedef enum { + SPA_BUFFER_FLAG_NONE = 0, + SPA_BUFFER_FLAG_DISCONT = (1 << 0), + SPA_BUFFER_FLAG_CORRUPTED = (1 << 1), + SPA_BUFFER_FLAG_MARKER = (1 << 2), + SPA_BUFFER_FLAG_HEADER = (1 << 3), + SPA_BUFFER_FLAG_GAP = (1 << 4), + SPA_BUFFER_FLAG_DELTA_UNIT = (1 << 5), +} SpaBufferFlags; + +typedef struct { + SpaBufferFlags flags; + uint32_t seq; + int64_t pts; + int64_t dts_offset; +} SpaMetaHeader; + +/** + * SpaMeta: + * @type: metadata type + * @data: pointer to metadata + * @size: size of metadata + */ +typedef struct { + SpaMetaType type; + void *data; + size_t size; +} SpaMeta; + +/** + * SpaDataType: + * @SPA_DATA_TYPE_INVALID: invalid data type, is ignored + * @SPA_DATA_TYPE_MEMPTR: data and size point to memory + * @SPA_DATA_TYPE_FD: data points to SpaDataFd + */ +typedef enum { + SPA_DATA_TYPE_INVALID = 0, + SPA_DATA_TYPE_MEMPTR, + SPA_DATA_TYPE_FD, +} SpaDataType; + +/** + * SpaDataFd + * fd: a file descriptor + * offset: offset in the data referenced by @fd + * @size: size of data referenced by fd + */ +typedef struct { + int fd; + unsigned int offset; + size_t size; +} SpaDataFD; + +/** + * SpaData: + * @id: user id + * @type: the type of data + * @data: pointer to data + * @size: size of data + */ +typedef struct { + SpaDataType type; + void *data; + size_t size; +} SpaData; + +/** + * SpaBuffer: + * @refcount: reference counter + * @notify: called when the refcount reaches 0 + * @size: total size of the buffer data + * @n_metas: number of metadata + * @metas: array of @n_metas metadata + * @n_datas: number of data pointers + * @datas: array of @n_datas data pointers + */ +struct _SpaBuffer { + volatile int refcount; + SpaNotify notify; + size_t size; + unsigned int n_metas; + SpaMeta *metas; + unsigned int n_datas; + SpaData *datas; +}; + +/** + * spa_buffer_ref: + * @buffer: a #SpaBuffer + * + * Increase the refcount on @buffer + * + * Returns: @buffer + */ +static inline SpaBuffer * +spa_buffer_ref (SpaBuffer *buffer) +{ + if (buffer != NULL) + buffer->refcount++; + return buffer; +} + +/** + * spa_buffer_unref: + * @buffer: a #SpaBuffer + * + * Decrease the refcount on buffer. when the refcount is 0, the notify, + * if any, of the buffer will be called. + * + * Returns: @buffer or %NULL when the refcount is 0 + */ +static inline SpaBuffer * +spa_buffer_unref (SpaBuffer *buffer) +{ + if (buffer != NULL) { + if (--buffer->refcount == 0) { + if (buffer->notify) + buffer->notify (buffer); + return NULL; + } + } + return buffer; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SPA_BUFFER_H__ */ diff --git a/pinos/spi/command.h b/spa/include/spa/command.h similarity index 68% rename from pinos/spi/command.h rename to spa/include/spa/command.h index 5905d7226..fff02fed2 100644 --- a/pinos/spi/command.h +++ b/spa/include/spa/command.h @@ -1,4 +1,4 @@ -/* Simple Plugin Interface +/* Simple Plugin API * Copyright (C) 2016 Wim Taymans * * This library is free software; you can redistribute it and/or @@ -17,32 +17,32 @@ * Boston, MA 02110-1301, USA. */ -#ifndef __SPI_COMMAND_H__ -#define __SPI_COMMAND_H__ +#ifndef __SPA_COMMAND_H__ +#define __SPA_COMMAND_H__ #ifdef __cplusplus extern "C" { #endif -typedef struct _SpiCommand SpiCommand; +typedef struct _SpaCommand SpaCommand; -#include +#include typedef enum { - SPI_COMMAND_INVALID = 0, - SPI_COMMAND_ACTIVATE, - SPI_COMMAND_DEACTIVATE, - SPI_COMMAND_START, - SPI_COMMAND_STOP, - SPI_COMMAND_FLUSH, - SPI_COMMAND_DRAIN, - SPI_COMMAND_MARKER, -} SpiCommandType; + SPA_COMMAND_INVALID = 0, + SPA_COMMAND_ACTIVATE, + SPA_COMMAND_DEACTIVATE, + SPA_COMMAND_START, + SPA_COMMAND_STOP, + SPA_COMMAND_FLUSH, + SPA_COMMAND_DRAIN, + SPA_COMMAND_MARKER, +} SpaCommandType; -struct _SpiCommand { +struct _SpaCommand { volatile int refcount; - SpiNotify notify; - SpiCommandType type; + SpaNotify notify; + SpaCommandType type; uint32_t port_id; void *data; size_t size; @@ -52,4 +52,4 @@ struct _SpiCommand { } /* extern "C" */ #endif -#endif /* __SPI_COMMAND_H__ */ +#endif /* __SPA_COMMAND_H__ */ diff --git a/spa/include/spa/defs.h b/spa/include/spa/defs.h new file mode 100644 index 000000000..730f5f953 --- /dev/null +++ b/spa/include/spa/defs.h @@ -0,0 +1,74 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_DEFS_H__ +#define __SPA_DEFS_H__ + +#ifdef __cplusplus +extern "C" { +#else +#include +#endif +#include +#include + +typedef enum { + SPA_RESULT_OK = 0, + SPA_RESULT_ERROR = -1, + SPA_RESULT_INACTIVE = -2, + SPA_RESULT_NO_FORMAT = -3, + SPA_RESULT_INVALID_COMMAND = -4, + SPA_RESULT_INVALID_PORT = -5, + SPA_RESULT_HAVE_ENOUGH_INPUT = -6, + SPA_RESULT_NEED_MORE_INPUT = -7, + SPA_RESULT_PORTS_CHANGED = -9, + SPA_RESULT_FORMAT_CHANGED = -10, + SPA_RESULT_PROPERTIES_CHANGED = -11, + SPA_RESULT_NOT_IMPLEMENTED = -12, + SPA_RESULT_INVALID_PROPERTY_INDEX = -13, + SPA_RESULT_PROPERTY_UNSET = -14, + SPA_RESULT_ENUM_END = -15, + SPA_RESULT_WRONG_PROPERTY_TYPE = -16, + SPA_RESULT_WRONG_PROPERTY_SIZE = -17, + SPA_RESULT_INVALID_MEDIA_TYPE = -18, + SPA_RESULT_INVALID_FORMAT_PROPERTIES = -19, + SPA_RESULT_FORMAT_INCOMPLETE = -20, + SPA_RESULT_INVALID_ARGUMENTS = -21, + SPA_RESULT_UNKNOWN_INTERFACE = -22, + SPA_RESULT_INVALID_DIRECTION = -23, + SPA_RESULT_TOO_MANY_PORTS = -24, + SPA_RESULT_INVALID_PROPERTY_ACCESS = -25, +} SpaResult; + +typedef enum { + SPA_DIRECTION_INVALID = 0, + SPA_DIRECTION_INPUT, + SPA_DIRECTION_OUTPUT +} SpaDirection; + +typedef void (*SpaNotify) (void *data); + +#define SPA_N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +#endif /* __SPA_DEFS_H__ */ diff --git a/spa/include/spa/event.h b/spa/include/spa/event.h new file mode 100644 index 000000000..211adb077 --- /dev/null +++ b/spa/include/spa/event.h @@ -0,0 +1,76 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_EVENT_H__ +#define __SPA_EVENT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _SpaEvent SpaEvent; + +#include + +/** + * SpaEventType: + * @SPA_EVENT_TYPE_INVALID: invalid event, should be ignored + * @SPA_EVENT_TYPE_ACTIVATED: emited when the ACTIVATE command completes + * @SPA_EVENT_TYPE_DEACTIVATED: emited when the DEACTIVATE command completes + * @SPA_EVENT_TYPE_CAN_PULL_OUTPUT: emited when an async node has output that can be pulled + * @SPA_EVENT_TYPE_CAN_PUSH_INTPUT: emited when more data can be pushed to an async node + * @SPA_EVENT_TYPE_PULL_INPUT: emited when data needs to be provided on an input + * @SPA_EVENT_TYPE_ALLOC_OUTPUT: emited when an output buffer needs to be allocated + * @SPA_EVENT_TYPE_ADD_POLL: emited when a pollfd should be added + * @SPA_EVENT_TYPE_REMOVE_POLL: emited when a pollfd should be removed + * @SPA_EVENT_TYPE_DRAINED: emited when DRAIN command completed + * @SPA_EVENT_TYPE_MARKER: emited when MARK command completed + * @SPA_EVENT_TYPE_ERROR: emited when error occured + * @SPA_EVENT_TYPE_BUFFERING: emited when buffering is in progress + */ +typedef enum { + SPA_EVENT_TYPE_INVALID = 0, + SPA_EVENT_TYPE_ACTIVATED, + SPA_EVENT_TYPE_DEACTIVATED, + SPA_EVENT_TYPE_CAN_PULL_OUTPUT, + SPA_EVENT_TYPE_CAN_PUSH_INTPUT, + SPA_EVENT_TYPE_PULL_INPUT, + SPA_EVENT_TYPE_ALLOC_OUTPUT, + SPA_EVENT_TYPE_ADD_POLL, + SPA_EVENT_TYPE_REMOVE_POLL, + SPA_EVENT_TYPE_DRAINED, + SPA_EVENT_TYPE_MARKER, + SPA_EVENT_TYPE_ERROR, + SPA_EVENT_TYPE_BUFFERING, +} SpaEventType; + +struct _SpaEvent { + volatile int refcount; + SpaNotify notify; + SpaEventType type; + uint32_t port_id; + void *data; + size_t size; +}; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SPA_EVENT_H__ */ diff --git a/spa/include/spa/format.h b/spa/include/spa/format.h new file mode 100644 index 000000000..711568d44 --- /dev/null +++ b/spa/include/spa/format.h @@ -0,0 +1,57 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_FORMAT_H__ +#define __SPA_FORMAT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _SpaFormat SpaFormat; + +#include +#include + +typedef enum { + SPA_MEDIA_TYPE_INVALID = 0, + SPA_MEDIA_TYPE_AUDIO = 1, +} SpaMediaType; + +typedef enum { + SPA_MEDIA_SUBTYPE_INVALID = 0, + SPA_MEDIA_SUBTYPE_RAW = 1, +} SpaMediaSubType; + +struct _SpaFormat { + SpaProps props; + SpaMediaType media_type; + SpaMediaSubType media_subtype; +}; + +typedef enum { + SPA_PROP_ID_INVALID = 0, + SPA_PROP_ID_MEDIA_CUSTOM_START = 200, +} SpaFormatProps; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SPA_FORMAT_H__ */ diff --git a/spa/include/spa/meson.build b/spa/include/spa/meson.build new file mode 100644 index 000000000..6d3afa170 --- /dev/null +++ b/spa/include/spa/meson.build @@ -0,0 +1,8 @@ +install_headers('buffer.h', + 'command.h', + 'defs.h', + 'event.h', + 'node.h', + 'plugin.h', + 'port.h', + 'props.h') diff --git a/spa/include/spa/node.h b/spa/include/spa/node.h new file mode 100644 index 000000000..eb5ca91dc --- /dev/null +++ b/spa/include/spa/node.h @@ -0,0 +1,370 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_NODE_H__ +#define __SPA_NODE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _SpaNode SpaNode; + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * SpaInputFlags: + * @SPA_INPUT_FLAG_NONE: no flag + */ +typedef enum { + SPA_INPUT_FLAG_NONE = 0, +} SpaInputFlags; + +/** + * SpaInputInfo: + * @port_id: the port id + * @flags: extra flags + * @buffer: a buffer + * + * Input information for a node. + */ +typedef struct { + uint32_t port_id; + SpaInputFlags flags; + SpaBuffer *buffer; + SpaEvent *event; + SpaResult status; +} SpaInputInfo; + +/** + * SpaOutputFlags: + * @SPA_OUTPUT_FLAG_NONE: no flag + * @SPA_OUTPUT_FLAG_PULL: force a #SPA_EVENT_NEED_INPUT event on the + * peer input ports when no data is available. + * @SPA_OUTPUT_FLAG_DISCARD: discard the buffer data + * @SPA_OUTPUT_FLAG_NO_BUFFER: no buffer was produced on the port + */ +typedef enum { + SPA_OUTPUT_FLAG_NONE = 0, + SPA_OUTPUT_FLAG_PULL = (1 << 0), + SPA_OUTPUT_FLAG_DISCARD = (1 << 1), + SPA_OUTPUT_FLAG_NO_BUFFER = (1 << 2), +} SpaOutputFlags; + +/** + * SpaOutputInfo: + * @port_id: the port id + * @flags: extra flags + * @buffer: a buffer + * @event: an event + * + * Output information for a node. + */ +typedef struct { + uint32_t port_id; + SpaOutputFlags flags; + SpaBuffer *buffer; + SpaEvent *event; + SpaResult status; +} SpaOutputInfo; + +/** + * SpaEventCallback: + * @node: a #SpaHandle emiting the event + * @event: the event that was emited + * @user_data: user data provided when registering the callback + * + * This will be called when an out-of-bound event is notified + * on @node. + */ +typedef void (*SpaEventCallback) (SpaHandle *handle, + SpaEvent *event, + void *user_data); + +#define SPA_INTERFACE_ID_NODE 0 +#define SPA_INTERFACE_ID_NODE_NAME "Node interface" +#define SPA_INTERFACE_ID_NODE_DESCRIPTION "Main processing node interface" + +/** + * SpaNode: + * + * The main processing nodes. + */ +struct _SpaNode { + /* the total size of this node. This can be used to expand this + * structure in the future */ + size_t size; + /** + * SpaNode::get_props: + * @handle: a #SpaHandle + * @props: a location for a #SpaProps pointer + * + * Get the configurable properties of @node. + * + * The returned @props is a snapshot of the current configuration and + * can be modified. The modifications will take effect after a call + * to SpaNode::set_props. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node or props are %NULL + * #SPA_RESULT_NOT_IMPLEMENTED when there are no properties + * implemented on @node + */ + SpaResult (*get_props) (SpaHandle *handle, + SpaProps **props); + /** + * SpaNode::set_props: + * @handle: a #SpaHandle + * @props: a #SpaProps + * + * Set the configurable properties in @node. + * + * Usually, @props will be obtained from SpaNode::get_props and then + * modified but it is also possible to set another #SpaProps object + * as long as its keys and types match those of SpaProps::get_props. + * + * Properties with keys that are not known are ignored. + * + * If @props is NULL, all the properties are reset to their defaults. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node is %NULL + * #SPA_RESULT_NOT_IMPLEMENTED when no properties can be + * modified on @node. + * #SPA_RESULT_WRONG_PROPERTY_TYPE when a property has the wrong + * type. + */ + SpaResult (*set_props) (SpaHandle *handle, + const SpaProps *props); + /** + * SpaNode::send_command: + * @handle: a #SpaHandle + * @command: a #SpaCommand + * + * Send a command to @node. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node or command is %NULL + * #SPA_RESULT_NOT_IMPLEMENTED when this node can't process commands + * #SPA_RESULT_INVALID_COMMAND @command is an invalid command + */ + SpaResult (*send_command) (SpaHandle *handle, + SpaCommand *command); + /** + * SpaNode::set_event_callback: + * @handle: a #SpaHandle + * @callback: a callback + * @user_data: user data passed in the callback + * + * Set a callback to receive events from @node. if @callback is %NULL, the + * current callback is removed. + * + * The callback can be emited from any thread. The caller should take + * appropriate actions to handle the event in other threads when needed. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node is %NULL + */ + SpaResult (*set_event_callback) (SpaHandle *handle, + SpaEventCallback callback, + void *user_data); + /** + * SpaNode::get_n_ports: + * @handle: a #SpaHandle + * @n_input_ports: location to hold the number of input ports or %NULL + * @max_input_ports: location to hold the maximum number of input ports or %NULL + * @n_output_ports: location to hold the number of output ports or %NULL + * @max_output_ports: location to hold the maximum number of output ports or %NULL + * + * Get the current number of input and output ports and also the maximum + * number of ports. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node is %NULL + */ + SpaResult (*get_n_ports) (SpaHandle *handle, + unsigned int *n_input_ports, + unsigned int *max_input_ports, + unsigned int *n_output_ports, + unsigned int *max_output_ports); + /** + * SpaNode::get_port_ids: + * @handle: a #SpaHandle + * @n_input_ports: size of the @input_ids array + * @input_ids: array to store the input stream ids + * @n_output_ports: size of the @output_ids array + * @output_ids: array to store the output stream ids + * + * Get the current number of input and output ports and also the maximum + * number of ports. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node is %NULL + */ + SpaResult (*get_port_ids) (SpaHandle *handle, + unsigned int n_input_ports, + uint32_t *input_ids, + unsigned int n_output_ports, + uint32_t *output_ids); + + SpaResult (*add_port) (SpaHandle *handle, + SpaDirection direction, + uint32_t *port_id); + SpaResult (*remove_port) (SpaHandle *handle, + uint32_t port_id); + + /** + * SpaNode::enum_port_formats: + * @handle: a #SpaHandle + * @port_id: the port to query + * @index: the format index to retrieve + * @format: pointer to a format + * + * Enumerate all possible formats on @port_id of @node. + * + * Use the index to retrieve the formats one by one until the function + * returns #SPA_RESULT_ENUM_END. + * + * The result format can be queried and modified and ultimately be used + * to call SpaNode::set_port_format. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node or format is %NULL + * #SPA_RESULT_INVALID_PORT when port_id is not valid + * #SPA_RESULT_ENUM_END when no format exists for @index + * + */ + SpaResult (*enum_port_formats) (SpaHandle *handle, + uint32_t port_id, + unsigned int index, + SpaFormat **format); + /** + * SpaNode::set_port_format: + * @handle: a #SpaHandle + * @port_id: the port to configure + * @format: a #SpaFormat with the format + * + * Set a format on @port_id of @node. + * + * When @format is %NULL, the current format will be removed. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node is %NULL + * #SPA_RESULT_INVALID_PORT when port_id is not valid + * #SPA_RESULT_INVALID_MEDIA_TYPE when the media type is not valid + * #SPA_RESULT_INVALID_FORMAT_PROPERTIES when one of the mandatory format + * properties is not specified. + * #SPA_RESULT_WRONG_PROPERTY_TYPE when the type or size of a property + * is not correct. + */ + SpaResult (*set_port_format) (SpaHandle *handle, + uint32_t port_id, + int test_only, + const SpaFormat *format); + /** + * SpaNode::get_port_format: + * @handle: a #SpaHandle + * @port_id: the port to query + * @format: a pointer to a location to hold the #SpaFormat + * + * Get the format on @port_id of @node. The result #SpaFormat can + * not be modified. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when @node or @format is %NULL + * #SPA_RESULT_INVALID_PORT when @port_id is not valid + * #SPA_RESULT_INVALID_NO_FORMAT when no format was set + */ + SpaResult (*get_port_format) (SpaHandle *handle, + uint32_t port_id, + const SpaFormat **format); + + SpaResult (*get_port_info) (SpaHandle *handle, + uint32_t port_id, + const SpaPortInfo **info); + + SpaResult (*get_port_props) (SpaHandle *handle, + uint32_t port_id, + SpaProps **props); + SpaResult (*set_port_props) (SpaHandle *handle, + uint32_t port_id, + const SpaProps *props); + + SpaResult (*get_port_status) (SpaHandle *handle, + uint32_t port_id, + const SpaPortStatus **status); + + /** + * SpaNode::push_port_input: + * @handle: a #SpaHandle + * @n_info: number of #SpaInputInfo in @info + * @info: array of #SpaInputInfo + * + * Push a buffer and/or an event into one or more input ports of + * @node. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node or info is %NULL + * #SPA_RESULT_ERROR when one or more of the @info has an + * error result. Check the status of all the + * @info. + * #SPA_RESULT_HAVE_ENOUGH_INPUT when output can be produced. + */ + SpaResult (*push_port_input) (SpaHandle *handle, + unsigned int n_info, + SpaInputInfo *info); + /** + * SpaNode::pull_port_output: + * @handle: a #SpaHandle + * @n_info: number of #SpaOutputInfo in @info + * @info: array of #SpaOutputInfo + * + * Pull a buffer and/or an event from one or more output ports of + * @node. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when node or info is %NULL + * #SPA_RESULT_PORTS_CHANGED the number of ports has changed. None + * of the @info fields are modified + * #SPA_RESULT_FORMAT_CHANGED a format changed on some port. + * the ports that changed are marked in the status. + * #SPA_RESULT_PROPERTIES_CHANGED port properties changed. The + * changed ports are marked in the status. + * #SPA_RESULT_ERROR when one or more of the @info has an + * error result. Check the status of all the @info. + * #SPA_RESULT_NEED_MORE_INPUT when no output can be produced + * because more input is needed. + */ + SpaResult (*pull_port_output) (SpaHandle *handle, + unsigned int n_info, + SpaOutputInfo *info); +}; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SPA_NODE_H__ */ diff --git a/spa/include/spa/plugin.h b/spa/include/spa/plugin.h new file mode 100644 index 000000000..9747d34b4 --- /dev/null +++ b/spa/include/spa/plugin.h @@ -0,0 +1,145 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_PLUGIN_H__ +#define __SPA_PLUGIN_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct _SpaHandle SpaHandle; +typedef struct _SpaHandleFactory SpaHandleFactory; + +struct _SpaHandle { + /* user_data that can be set by the application */ + void * user_data; + /** + * SpaHandle::get_interface: + * @handle: a #SpaHandle + * @interface_id: the interface id + * @interface: result to hold the interface. + * + * Get the interface provided by @handle with @interface_id. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_NOT_IMPLEMENTED when there are no extensions + * #SPA_RESULT_INVALID_ARGUMENTS when handle or info is %NULL + */ + SpaResult (*get_interface) (SpaHandle *handle, + uint32_t interface_id, + const void **interface); +}; + +/** + * SpaInterfaceInfo: + * @interface_id: the id of the interface, can be used to get the interface + * @name: name of the interface + * @description: Human readable description of the interface. + * + * This structure lists the information about available interfaces on + * handles. + */ +typedef struct { + uint32_t interface_id; + const char *name; + const char *description; +} SpaInterfaceInfo; + +struct _SpaHandleFactory { + /** + * SpaHandleFactory::name + * + * The name + */ + const char * name; + /** + * SpaHandleFactory::info + * + * Extra information about the handles of this factory. + */ + const SpaProps * info; + + /** + * SpaHandleFactory::instantiate + * @factory: a #SpaHandleFactory + * @handle: a pointer to hold the result + * + * Make an instance of this factory. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_NOT_IMPLEMENTED when an instance can't be made + * #SPA_RESULT_INVALID_ARGUMENTS when factory or handle are %NULL + */ + SpaResult (*instantiate) (const SpaHandleFactory *factory, + SpaHandle **handle); + /** + * SpaHandle::enum_interface_info: + * @factory: a #SpaHandleFactory + * @index: the interface index + * @info: result to hold SpaInterfaceInfo. + * + * Get the interface information at @index. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_NOT_IMPLEMENTED when there are no interfaces + * #SPA_RESULT_INVALID_ARGUMENTS when handle or info is %NULL + * #SPA_RESULT_ENUM_END when there are no more infos + */ + SpaResult (*enum_interface_info) (const SpaHandleFactory *factory, + unsigned int index, + const SpaInterfaceInfo **info); +}; + +/** + * SpaEnumHandleFactoryFunc: + * @index: the index to enumerate + * @factory: a location to hold the factory result + * + * The function signature of the entry point in a plugin. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when factory is %NULL + * #SPA_RESULT_ENUM_END when there are no more factories + */ +typedef SpaResult (*SpaEnumHandleFactoryFunc) (unsigned int index, + const SpaHandleFactory **factory); + +/** + * spa_enum_handle_factory: + * @index: the index to enumerate + * @factory: a location to hold the factory result + * + * The entry point in a plugin. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when factory is %NULL + * #SPA_RESULT_ENUM_END when there are no more factories + */ +SpaResult spa_enum_handle_factory (unsigned int index, + const SpaHandleFactory **factory); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SPA_PLUGIN_H__ */ diff --git a/pinos/spi/port.h b/spa/include/spa/port.h similarity index 52% rename from pinos/spi/port.h rename to spa/include/spa/port.h index 85a012997..b65445ca8 100644 --- a/pinos/spi/port.h +++ b/spa/include/spa/port.h @@ -1,4 +1,4 @@ -/* Simple Plugin Interface +/* Simple Plugin API * Copyright (C) 2016 Wim Taymans * * This library is free software; you can redistribute it and/or @@ -17,38 +17,38 @@ * Boston, MA 02110-1301, USA. */ -#ifndef __SPI_PORT_H__ -#define __SPI_PORT_H__ +#ifndef __SPA_PORT_H__ +#define __SPA_PORT_H__ #ifdef __cplusplus extern "C" { #endif -#include +#include /** - * SpiPortInfoFlags: - * @SPI_PORT_INFO_FLAG_NONE: no flags - * @SPI_PORT_INFO_FLAG_REMOVABLE: port can be removed - * @SPI_PORT_INFO_FLAG_OPTIONAL: processing on port is optional - * @SPI_PORT_INFO_FLAG_CAN_GIVE_BUFFER: the port can give a buffer - * @SPI_PORT_INFO_FLAG_CAN_USE_BUFFER: the port can use a provided buffer - * @SPI_PORT_INFO_FLAG_IN_PLACE: the port can process data in-place and will need + * SpaPortInfoFlags: + * @SPA_PORT_INFO_FLAG_NONE: no flags + * @SPA_PORT_INFO_FLAG_REMOVABLE: port can be removed + * @SPA_PORT_INFO_FLAG_OPTIONAL: processing on port is optional + * @SPA_PORT_INFO_FLAG_CAN_GIVE_BUFFER: the port can give a buffer + * @SPA_PORT_INFO_FLAG_CAN_USE_BUFFER: the port can use a provided buffer + * @SPA_PORT_INFO_FLAG_IN_PLACE: the port can process data in-place and will need * a writable input buffer when no output buffer is specified. - * @SPI_PORT_INFO_FLAG_NO_REF: the port does not keep a ref on the buffer + * @SPA_PORT_INFO_FLAG_NO_REF: the port does not keep a ref on the buffer */ typedef enum { - SPI_PORT_INFO_FLAG_NONE = 0, - SPI_PORT_INFO_FLAG_REMOVABLE = 1 << 0, - SPI_PORT_INFO_FLAG_OPTIONAL = 1 << 1, - SPI_PORT_INFO_FLAG_CAN_GIVE_BUFFER = 1 << 2, - SPI_PORT_INFO_FLAG_CAN_USE_BUFFER = 1 << 3, - SPI_PORT_INFO_FLAG_IN_PLACE = 1 << 4, - SPI_PORT_INFO_FLAG_NO_REF = 1 << 5, -} SpiPortInfoFlags; + SPA_PORT_INFO_FLAG_NONE = 0, + SPA_PORT_INFO_FLAG_REMOVABLE = 1 << 0, + SPA_PORT_INFO_FLAG_OPTIONAL = 1 << 1, + SPA_PORT_INFO_FLAG_CAN_GIVE_BUFFER = 1 << 2, + SPA_PORT_INFO_FLAG_CAN_USE_BUFFER = 1 << 3, + SPA_PORT_INFO_FLAG_IN_PLACE = 1 << 4, + SPA_PORT_INFO_FLAG_NO_REF = 1 << 5, +} SpaPortInfoFlags; /** - * SpiPortInfo + * SpaPortInfo * @flags: extra port flags * @size: minimum size of the buffers or 0 when not specified * @align: required alignment of the data @@ -59,33 +59,37 @@ typedef enum { * */ typedef struct { - SpiPortInfoFlags flags; + SpaPortInfoFlags flags; size_t minsize; uint32_t align; unsigned int maxbuffering; uint64_t latency; const char **features; -} SpiPortInfo; +} SpaPortInfo; /** - * SpiPortStatusFlags: - * @SPI_PORT_STATUS_FLAG_NONE: no status flags - * @SPI_PORT_STATUS_FLAG_HAVE_OUTPUT: port has output - * @SPI_PORT_STATUS_FLAG_NEED_INPUT: port needs input + * SpaPortStatusFlags: + * @SPA_PORT_STATUS_FLAG_NONE: no status flags + * @SPA_PORT_STATUS_FLAG_HAVE_OUTPUT: port has output + * @SPA_PORT_STATUS_FLAG_NEED_INPUT: port needs input */ typedef enum { - SPI_PORT_STATUS_FLAG_NONE = 0, - SPI_PORT_STATUS_FLAG_HAVE_OUTPUT = 1 << 0, - SPI_PORT_STATUS_FLAG_NEED_INPUT = 1 << 1, -} SpiPortStatusFlags; + SPA_PORT_STATUS_FLAG_NONE = 0, + SPA_PORT_STATUS_FLAG_HAVE_OUTPUT = 1 << 0, + SPA_PORT_STATUS_FLAG_NEED_INPUT = 1 << 1, +} SpaPortStatusFlags; +/** + * SpaPortStatus: + * @flags: port status flags + */ typedef struct { - SpiPortStatusFlags flags; -} SpiPortStatus; + SpaPortStatusFlags flags; +} SpaPortStatus; #ifdef __cplusplus } /* extern "C" */ #endif -#endif /* __SPI_PORT_H__ */ +#endif /* __SPA_PORT_H__ */ diff --git a/spa/include/spa/props.h b/spa/include/spa/props.h new file mode 100644 index 000000000..4a93641ec --- /dev/null +++ b/spa/include/spa/props.h @@ -0,0 +1,242 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_PROPS_H__ +#define __SPA_PROPS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _SpaProps SpaProps; + +#include +#include + +/** + * SpaPropType: + */ +typedef enum { + SPA_PROP_TYPE_INVALID = 0, + SPA_PROP_TYPE_BOOL, + SPA_PROP_TYPE_INT8, + SPA_PROP_TYPE_UINT8, + SPA_PROP_TYPE_INT16, + SPA_PROP_TYPE_UINT16, + SPA_PROP_TYPE_INT32, + SPA_PROP_TYPE_UINT32, + SPA_PROP_TYPE_INT64, + SPA_PROP_TYPE_UINT64, + SPA_PROP_TYPE_FLOAT, + SPA_PROP_TYPE_DOUBLE, + SPA_PROP_TYPE_STRING, + SPA_PROP_TYPE_POINTER, + SPA_PROP_TYPE_FRACTION, + SPA_PROP_TYPE_BITMASK, + SPA_PROP_TYPE_BYTES, + SPA_PROP_TYPE_STRUCT, +} SpaPropType; + +/** + * SpaPropFlags: + * @SPA_PROP_FLAG_NONE: no flags + * @SPA_PROP_FLAG_OPTIONAL: the value can be left unset + * @SPA_PROP_FLAG_READABLE: property is readable + * @SPA_PROP_FLAG_WRITABLE: property is writable + * @SPA_PROP_FLAG_READWRITE: property is readable and writable + * @SPA_PROP_FLAG_DEPRECATED: property is deprecated and should not be used + */ +typedef enum { + SPA_PROP_FLAG_NONE = 0, + SPA_PROP_FLAG_OPTIONAL = (1 << 0), + SPA_PROP_FLAG_READABLE = (1 << 1), + SPA_PROP_FLAG_WRITABLE = (1 << 2), + SPA_PROP_FLAG_READWRITE = SPA_PROP_FLAG_READABLE | SPA_PROP_FLAG_WRITABLE, + SPA_PROP_FLAG_DEPRECATED = (1 << 3), +} SpaPropFlags; + +/* SpaPropRangeType: + * @SPA_PROP_RANGE_TYPE_NONE: no range specified, full range of type applies + * @SPA_PROP_RANGE_TYPE_MIN_MAX: range contains 2 values, min and max + * @SPA_PROP_RANGE_TYPE_ENUM: range contains enum of possible values with + * NULL-terminated name + * @SPA_PROP_RANGE_TYPE_FLAGS: range contains flags of possible values with + * NULL-terminated name + */ +typedef enum { + SPA_PROP_RANGE_TYPE_NONE = 0, + SPA_PROP_RANGE_TYPE_MIN_MAX, + SPA_PROP_RANGE_TYPE_ENUM, + SPA_PROP_RANGE_TYPE_FLAGS, +} SpaPropRangeType; + +/** + * SpaPropRangeInfo: + * @name: name of this value + * @description: user visible description of this value + * @size: the size of value + * @value: a possible value + */ +typedef struct { + const char *name; + const char *description; + size_t size; + const void *value; +} SpaPropRangeInfo; + +/** + * SpaPropInfo: + * @id: unique id + * @name: human readable name + * @description: description of the property + * @flags: property flags + * @type: property type + * @max_size: maximum size of property value + * @default_size: size of default value + * @default_value: default value of property + * @range_type: type of the range values + * @n_range_values: number of elements in @range_values + * @range_values: array of possible values + * @tags: extra tags, NULL terminated + * @offset: offset in structure with data + * @unset_mask: mask to clear when value is set + * @priv: extra private data + */ +typedef struct { + uint32_t id; + const char *name; + const char *description; + SpaPropFlags flags; + SpaPropType type; + size_t maxsize; + size_t default_size; + const void *default_value; + SpaPropRangeType range_type; + unsigned int n_range_values; + const SpaPropRangeInfo *range_values; + const char **tags; + size_t offset; + size_t mask_offset; + uint32_t unset_mask; + const void *priv; +} SpaPropInfo; + +typedef struct { + SpaPropType type; + size_t size; + const void *value; +} SpaPropValue; + +/** + * SpaProps: + * + * Generic propertiers. + */ +struct _SpaProps { + /** + * SpaProps::n_prop_info: + * + * The number of items in @prop_info. + */ + unsigned int n_prop_info; + /** + * SpaProps::prop_info: + * + * Info about the properties. Can be %NULL when unspecified. + */ + const SpaPropInfo *prop_info; + + /** + * SpaProps::set_prop + * @props: a #SpaProps + * @index: the index of the property in the prop_info array + * @value: the value to set + * + * Sets @value in @prop. type should match the type specified + * in the #SpaPropInfo at @index or else #SPA_RESULT_WRONG_PROPERTY_TYPE + * is returned. + * + * Returns: #SPA_RESULT_OK on success. + * #SPA_RESULT_INVALID_PROPERTY_INDEX when @index is not valid + * #SPA_RESULT_WRONG_PROPERTY_TYPE when type is not correct + */ + SpaResult (*set_prop) (SpaProps *props, + unsigned int index, + const SpaPropValue *value); + /** + * SpaProps::get_prop + * @props: a #SpaProps + * @index: the property index in the prop_info array + * @value: a location for the type, size and value + * + * Get the type, size and value of the property at @index. + * + * Returns: #SPA_RESULT_OK on success. + * #SPA_RESULT_INVALID_PROPERTY_INDEX when @index is not valid + * #SPA_RESULT_PROPERTY_UNSET when no value has been set yet + */ + SpaResult (*get_prop) (const SpaProps *props, + unsigned int index, + SpaPropValue *value); + + void *priv; +}; + +static inline unsigned int +spa_props_index_for_id (const SpaProps *props, uint32_t id) +{ + unsigned int i; + + for (i = 0; i < props->n_prop_info; i++) { + if (props->prop_info[i].id == id) + return i; + } + return -1; +} + +static inline unsigned int +spa_props_index_for_name (const SpaProps *props, const char *name) +{ + unsigned int i; + + for (i = 0; i < props->n_prop_info; i++) { + if (strcmp (props->prop_info[i].name, name) == 0) + return i; + } + return -1; +} + + +SpaResult spa_props_generic_set_prop (SpaProps *props, + unsigned int index, + const SpaPropValue *value); +SpaResult spa_props_generic_get_prop (const SpaProps *props, + unsigned int index, + SpaPropValue *value); + +SpaResult spa_props_copy (const SpaProps *src, + SpaProps *dest); + + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SPA_PROPS_H__ */ diff --git a/spa/include/spa/ringbuffer.h b/spa/include/spa/ringbuffer.h new file mode 100644 index 000000000..7b7320d1e --- /dev/null +++ b/spa/include/spa/ringbuffer.h @@ -0,0 +1,71 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __SPA_RINGBUFFER_H__ +#define __SPA_RINGBUFFER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _SpaRingbuffer SpaRingbuffer; + +#include + +typedef struct { + uint8_t *data; + size_t len; +} SpaRingbufferArea; + +/** + * SpaRingbuffer: + * @data: pointer to data + * @readindex: the current read index + * @writeindex: the current write index + * @size: the size of the ringbuffer + * @size_mask: mask if @size is power of 2 + */ +struct _SpaRingbuffer { + uint8_t *data; + volatile size_t readindex; + volatile size_t writeindex; + size_t size; + size_t size_mask; +}; + +SpaResult spa_ringbuffer_init (SpaRingbuffer *rbuf, + uint8_t *data, size_t size); + +SpaResult spa_ringbuffer_clear (SpaRingbuffer *rbuf); + +SpaResult spa_ringbuffer_get_read_areas (SpaRingbuffer *rbuf, + SpaRingbufferArea areas[2]); +SpaResult spa_ringbuffer_read_advance (SpaRingbuffer *rbuf, + ssize_t len); + +SpaResult spa_ringbuffer_get_write_areas (SpaRingbuffer *rbuf, + SpaRingbufferArea areas[2]); +SpaResult spa_ringbuffer_write_advance (SpaRingbuffer *rbuf, + ssize_t len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __SPA_RINGBUFFER_H__ */ diff --git a/spa/lib/audio-raw.c b/spa/lib/audio-raw.c new file mode 100644 index 000000000..670a6c172 --- /dev/null +++ b/spa/lib/audio-raw.c @@ -0,0 +1,254 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include + +static const uint32_t format_values[] = { + SPA_AUDIO_FORMAT_S8, + SPA_AUDIO_FORMAT_U8, + /* 16 bit */ + SPA_AUDIO_FORMAT_S16LE, + SPA_AUDIO_FORMAT_S16BE, + SPA_AUDIO_FORMAT_U16LE, + SPA_AUDIO_FORMAT_U16BE, + /* 24 bit in low 3 bytes of 32 bits*/ + SPA_AUDIO_FORMAT_S24_32LE, + SPA_AUDIO_FORMAT_S24_32BE, + SPA_AUDIO_FORMAT_U24_32LE, + SPA_AUDIO_FORMAT_U24_32BE, + /* 32 bit */ + SPA_AUDIO_FORMAT_S32LE, + SPA_AUDIO_FORMAT_S32BE, + SPA_AUDIO_FORMAT_U32LE, + SPA_AUDIO_FORMAT_U32BE, + /* 24 bit in 3 bytes*/ + SPA_AUDIO_FORMAT_S24LE, + SPA_AUDIO_FORMAT_S24BE, + SPA_AUDIO_FORMAT_U24LE, + SPA_AUDIO_FORMAT_U24BE, + /* 20 bit in 3 bytes*/ + SPA_AUDIO_FORMAT_S20LE, + SPA_AUDIO_FORMAT_S20BE, + SPA_AUDIO_FORMAT_U20LE, + SPA_AUDIO_FORMAT_U20BE, + /* 18 bit in 3 bytes*/ + SPA_AUDIO_FORMAT_S18LE, + SPA_AUDIO_FORMAT_S18BE, + SPA_AUDIO_FORMAT_U18LE, + SPA_AUDIO_FORMAT_U18BE, + /* float */ + SPA_AUDIO_FORMAT_F32LE, + SPA_AUDIO_FORMAT_F32BE, + SPA_AUDIO_FORMAT_F64LE, + SPA_AUDIO_FORMAT_F64BE +}; + +static const SpaPropRangeInfo format_format_range[] = { + { "S8", "S8", sizeof (uint32_t), &format_values[0] }, + { "U8", "U8", sizeof (uint32_t), &format_values[1] }, + { "S16LE", "S16LE", sizeof (uint32_t), &format_values[2] }, + { "S16BE", "S16BE", sizeof (uint32_t), &format_values[3] }, + { "U16LE", "U16LE", sizeof (uint32_t), &format_values[4] }, + { "U16BE", "U16BE", sizeof (uint32_t), &format_values[5] }, + { "S24_32LE", "S24_32LE", sizeof (uint32_t), &format_values[6] }, + { "S24_32BE", "S24_32BE", sizeof (uint32_t), &format_values[7] }, + { "U24_32LE", "U24_32LE", sizeof (uint32_t), &format_values[8] }, + { "U24_32BE", "U24_32BE", sizeof (uint32_t), &format_values[9] }, + { "S32LE", "S32LE", sizeof (uint32_t), &format_values[10] }, + { "S32BE", "S32BE", sizeof (uint32_t), &format_values[11] }, + { "U32LE", "U32LE", sizeof (uint32_t), &format_values[12] }, + { "U32BE", "U32BE", sizeof (uint32_t), &format_values[13] }, + { "S24LE", "S24LE", sizeof (uint32_t), &format_values[14] }, + { "S24BE", "S24BE", sizeof (uint32_t), &format_values[15] }, + { "U24LE", "U24LE", sizeof (uint32_t), &format_values[16] }, + { "U24BE", "U24BE", sizeof (uint32_t), &format_values[17] }, + { "S20LE", "S20LE", sizeof (uint32_t), &format_values[18] }, + { "S20BE", "S20BE", sizeof (uint32_t), &format_values[19] }, + { "U20LE", "U20LE", sizeof (uint32_t), &format_values[20] }, + { "U20BE", "U20BE", sizeof (uint32_t), &format_values[21] }, + { "S18LE", "S18LE", sizeof (uint32_t), &format_values[22] }, + { "S18BE", "S18BE", sizeof (uint32_t), &format_values[23] }, + { "U18LE", "U18LE", sizeof (uint32_t), &format_values[24] }, + { "U18BE", "U18BE", sizeof (uint32_t), &format_values[25] }, + { "F32LE", "F32LE", sizeof (uint32_t), &format_values[26] }, + { "F32BE", "F32BE", sizeof (uint32_t), &format_values[27] }, + { "F64LE", "F64LE", sizeof (uint32_t), &format_values[28] }, + { "F64BE", "F64BE", sizeof (uint32_t), &format_values[29] }, +}; + +static const uint32_t default_format = SPA_AUDIO_FORMAT_S16; +static const uint32_t default_flags = SPA_AUDIO_FLAG_NONE; +static const uint32_t default_layout = SPA_AUDIO_LAYOUT_INTERLEAVED; +static const uint32_t default_rate = 44100; +static const uint32_t default_channels = 2; +static const uint32_t default_channel_mask = 0; + +static const uint32_t format_layouts[] = { + SPA_AUDIO_LAYOUT_INTERLEAVED, + SPA_AUDIO_LAYOUT_NON_INTERLEAVED, +}; + +static const SpaPropRangeInfo layouts_range[] = { + { "interleaved", "Interleaved samples", sizeof (uint32_t), &format_layouts[0] }, + { "non-interleaved", "Non-interleaved samples", sizeof (uint32_t), &format_layouts[1] }, +}; + +static const uint32_t format_flags[] = { + SPA_AUDIO_FLAG_NONE, + SPA_AUDIO_FLAG_UNPOSITIONED, +}; + +static const SpaPropRangeInfo flags_range[] = { + { "none", "No flags", sizeof (uint32_t), &format_flags[0] }, + { "unpositioned", "Unpositioned channels", sizeof (uint32_t), &format_flags[1] }, +}; + +static const uint32_t min_uint32 = 1; +static const uint32_t max_uint32 = UINT32_MAX; + +static const SpaPropRangeInfo uint32_range[] = { + { "min", "Minimum value", 4, &min_uint32 }, + { "max", "Maximum value", 4, &max_uint32 }, +}; + +static const SpaPropInfo raw_format_prop_info[] = +{ + { SPA_PROP_ID_AUDIO_FORMAT, "format", "The media format", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_UINT32, sizeof (uint32_t), + sizeof (uint32_t), &default_format, + SPA_PROP_RANGE_TYPE_ENUM, SPA_N_ELEMENTS (format_format_range), format_format_range, + NULL, + offsetof (SpaAudioRawFormat, info.format), + offsetof (SpaAudioRawFormat, unset_mask), 1 << 0, + NULL }, + { SPA_PROP_ID_AUDIO_FLAGS, "flags", "Sample Flags", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_UINT32, sizeof (uint32_t), + sizeof (uint32_t), &default_flags, + SPA_PROP_RANGE_TYPE_FLAGS, SPA_N_ELEMENTS (flags_range), flags_range, + NULL, + offsetof (SpaAudioRawFormat, info.flags), + offsetof (SpaAudioRawFormat, unset_mask), 1 << 1, + NULL }, + { SPA_PROP_ID_AUDIO_LAYOUT, "layout", "Sample Layout", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_UINT32, sizeof (uint32_t), + sizeof (uint32_t), &default_layout, + SPA_PROP_RANGE_TYPE_ENUM, SPA_N_ELEMENTS (layouts_range), layouts_range, + NULL, + offsetof (SpaAudioRawFormat, info.layout), + offsetof (SpaAudioRawFormat, unset_mask), 1 << 2, + NULL }, + { SPA_PROP_ID_AUDIO_RATE, "rate", "Audio sample rate", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_UINT32, sizeof (uint32_t), + sizeof (uint32_t), &default_rate, + SPA_PROP_RANGE_TYPE_MIN_MAX, 2, uint32_range, + NULL, + offsetof (SpaAudioRawFormat, info.rate), + offsetof (SpaAudioRawFormat, unset_mask), 1 << 3, + NULL }, + { SPA_PROP_ID_AUDIO_CHANNELS, "channels", "Audio channels", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_UINT32, sizeof (uint32_t), + sizeof (uint32_t), &default_channels, + SPA_PROP_RANGE_TYPE_MIN_MAX, 2, uint32_range, + NULL, + offsetof (SpaAudioRawFormat, info.channels), + offsetof (SpaAudioRawFormat, unset_mask), 1 << 4, + NULL }, + { SPA_PROP_ID_AUDIO_CHANNEL_MASK, "channel-mask", "Audio channel mask", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_BITMASK, sizeof (uint32_t), + sizeof (uint32_t), &default_channel_mask, + SPA_PROP_RANGE_TYPE_NONE, 0, NULL, + NULL, + offsetof (SpaAudioRawFormat, info.channel_mask), + offsetof (SpaAudioRawFormat, unset_mask), 1 << 5, + NULL }, + { SPA_PROP_ID_AUDIO_RAW_INFO, "info", "the SpaAudioRawInfo structure", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_STRUCT, sizeof (SpaAudioRawInfo), + 0, NULL, + SPA_PROP_RANGE_TYPE_NONE, 0, NULL, + NULL, + offsetof (SpaAudioRawFormat, info), + offsetof (SpaAudioRawFormat, unset_mask), ~0, + NULL }, +}; + +SpaResult +spa_audio_raw_format_init (SpaAudioRawFormat *format) +{ + format->format.media_type = SPA_MEDIA_TYPE_AUDIO; + format->format.media_subtype = SPA_MEDIA_SUBTYPE_RAW; + format->format.props.n_prop_info = SPA_N_ELEMENTS (raw_format_prop_info); + format->format.props.prop_info = raw_format_prop_info; + format->format.props.set_prop = spa_props_generic_set_prop; + format->format.props.get_prop = spa_props_generic_get_prop; + format->unset_mask = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 4); + format->info.format = default_format; + format->info.flags = default_flags; + format->info.layout = default_layout; + format->info.rate = default_rate; + format->info.channels = default_channels; + format->info.channel_mask = default_channel_mask; + + return SPA_RESULT_OK; +} + +SpaResult +spa_audio_raw_format_parse (const SpaFormat *format, + SpaAudioRawFormat *rawformat) +{ + SpaPropValue value; + const SpaProps *props; + SpaResult res; + + if ((void *)format == (void *)rawformat) + return SPA_RESULT_OK; + + if (format->media_type != SPA_MEDIA_TYPE_AUDIO || + format->media_subtype != SPA_MEDIA_SUBTYPE_RAW) + return SPA_RESULT_INVALID_MEDIA_TYPE; + + spa_audio_raw_format_init (rawformat); + + props = &format->props; + if ((res = props->get_prop (props, SPA_PROP_ID_AUDIO_RAW_INFO, &value)) < 0) + goto fallback; + + if (value.type != SPA_PROP_TYPE_STRUCT || value.size != sizeof (SpaAudioRawInfo)) + goto fallback; + + memcpy (&rawformat->info, value.value, sizeof (SpaAudioRawInfo)); + + return SPA_RESULT_OK; + +fallback: + res = spa_props_copy (props, &rawformat->format.props); + + return res; +} diff --git a/spa/lib/meson.build b/spa/lib/meson.build new file mode 100644 index 000000000..6a4588a32 --- /dev/null +++ b/spa/lib/meson.build @@ -0,0 +1,8 @@ +spalib_sources = ['audio-raw.c', + 'props.c', + 'ringbuffer.c'] + +spalib = shared_library('spa-lib', + spalib_sources, + include_directories : inc, + install : true) diff --git a/spa/lib/props.c b/spa/lib/props.c new file mode 100644 index 000000000..c72980a11 --- /dev/null +++ b/spa/lib/props.c @@ -0,0 +1,118 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include + +SpaResult +spa_props_generic_set_prop (SpaProps *props, + unsigned int index, + const SpaPropValue *value) +{ + const SpaPropInfo *info; + + if (props == NULL || value == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + if (index >= props->n_prop_info) + return SPA_RESULT_INVALID_PROPERTY_INDEX; + + info = &props->prop_info[index]; + if ((info->flags & SPA_PROP_FLAG_WRITABLE) == 0) + return SPA_RESULT_INVALID_PROPERTY_ACCESS; + if (info->type != value->type) + return SPA_RESULT_WRONG_PROPERTY_TYPE; + if (info->maxsize < value->size) + return SPA_RESULT_WRONG_PROPERTY_SIZE; + + if (info->offset != 0) + memcpy ((uint8_t*)props + info->offset, value->value, value->size); + + if (info->mask_offset != 0) { + uint32_t *mask = (uint32_t *)((uint8_t *)props + info->mask_offset); + *mask &= ~info->unset_mask; + } + return SPA_RESULT_OK; +} + + +SpaResult +spa_props_generic_get_prop (const SpaProps *props, + unsigned int index, + SpaPropValue *value) +{ + const SpaPropInfo *info; + + if (props == NULL || value == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + if (index >= props->n_prop_info) + return SPA_RESULT_INVALID_PROPERTY_INDEX; + + info = &props->prop_info[index]; + if ((info->flags & SPA_PROP_FLAG_READABLE) == 0) + return SPA_RESULT_INVALID_PROPERTY_ACCESS; + + if (info->mask_offset != 0) { + uint32_t *mask = (uint32_t *)((uint8_t *)props + info->mask_offset); + if ((*mask & info->unset_mask)) + return SPA_RESULT_PROPERTY_UNSET; + } + value->type = info->type; + value->size = info->maxsize; + if (info->offset != 0) + value->value = (uint8_t*)props + info->offset; + + return SPA_RESULT_OK; +} + +SpaResult +spa_props_copy (const SpaProps *src, + SpaProps *dest) +{ + int i; + SpaResult res; + + if (src == dest) + return SPA_RESULT_OK; + + for (i = 0; i < dest->n_prop_info; i++) { + const SpaPropInfo *info = &dest->prop_info[i]; + SpaPropValue value; + + if (!(info->flags & SPA_PROP_FLAG_WRITABLE)) + continue; + if ((res = src->get_prop (src, spa_props_index_for_id (src, info->id), &value)) < 0) + continue; + if (value.type != info->type) + return SPA_RESULT_WRONG_PROPERTY_TYPE; + if (value.size > info->maxsize) + return SPA_RESULT_WRONG_PROPERTY_SIZE; + + if (info->offset) + memcpy ((uint8_t*)dest + info->offset, value.value, value.size); + if (info->mask_offset != 0) { + uint32_t *mask = (uint32_t *)((uint8_t *)dest + info->mask_offset); + *mask &= ~info->unset_mask; + } + } + return SPA_RESULT_OK; +} diff --git a/spa/lib/ringbuffer.c b/spa/lib/ringbuffer.c new file mode 100644 index 000000000..28d762af2 --- /dev/null +++ b/spa/lib/ringbuffer.c @@ -0,0 +1,185 @@ +/* Simple Plugin API + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +/** + * spa_ringbuffer_init: + * @rbuf: a #SpaRingbuffer + * @data: pointer to data + * @size: size of @data + * + * Initialize a #SpaRingbuffer with @data and @size. + * When size is a power of 2, size_mask will be set with the mask to + * efficiently wrap around the indexes. + * + * Returns: #SPA_RESULT_OK on success + * #SPA_RESULT_INVALID_ARGUMENTS when data or rbuf is %NULL + */ +SpaResult +spa_ringbuffer_init (SpaRingbuffer *rbuf, + uint8_t *data, size_t size) +{ + if (rbuf == NULL || data == NULL || size == 0) + return SPA_RESULT_INVALID_ARGUMENTS; + + rbuf->data = data; + rbuf->size = size; + rbuf->readindex = 0; + rbuf->writeindex = 0; + if ((size & (size - 1)) == 0) + rbuf->size_mask = size - 1; + else + rbuf->size_mask = 0; + + return SPA_RESULT_OK; +} + +/** + * spa_ringbuffer_clear: + * @rbuf: a #SpaRingbuffer + * + * Clear @rbuf + * + * Returns: #SPA_RESULT_OK + */ +SpaResult +spa_ringbuffer_clear (SpaRingbuffer *rbuf) +{ + rbuf->readindex = 0; + rbuf->writeindex = 0; + return SPA_RESULT_OK; +} + +/** + * spa_ringbuffer_get_read_areas: + * @rbuf: a #SpaRingbuffer + * @areas: an array of #SpaRingbufferArea + * + * Fill @areas with pointers to read from. The total amount of + * bytes that can be read can be obtained by summing the areas len fields. + * + * Returns: #SPA_RESULT_OK + */ +SpaResult +spa_ringbuffer_get_read_areas (SpaRingbuffer *rbuf, + SpaRingbufferArea areas[2]) +{ + size_t avail, end, w, r; + + w = rbuf->writeindex; + r = rbuf->readindex; + + if (w > r) { + avail = w - r; + } else { + avail = (w - r + rbuf->size); + avail = (rbuf->size_mask ? avail & rbuf->size_mask : avail % rbuf->size); + } + end = r + avail; + if (end > rbuf->size) { + areas[0].data = &rbuf->data[r]; + areas[0].len = rbuf->size - r; + areas[1].data = rbuf->data; + areas[1].len = end - rbuf->size; + } else { + areas[0].data = &rbuf->data[r]; + areas[0].len = avail; + areas[1].len = 0; + } + return SPA_RESULT_OK; +} + +/** + * spa_ringbuffer_read_advance: + * @rbuf: a #SpaRingbuffer + * @len: number of bytes to advance + * + * Advance the read pointer by @len + * + * Returns: #SPA_RESULT_OK + */ +SpaResult +spa_ringbuffer_read_advance (SpaRingbuffer *rbuf, + ssize_t len) +{ + size_t tmp = rbuf->readindex + len; + rbuf->readindex = (rbuf->size_mask ? tmp & rbuf->size_mask : tmp % rbuf->size); + return SPA_RESULT_OK; +} + +/** + * spa_ringbuffer_get_write_areas: + * @rbuf: a #SpaRingbuffer + * @areas: an array of #SpaRingbufferArea + * + * Fill @areas with pointers to write to. The total amount of + * bytes that can be written can be obtained by summing the areas len fields. + * + * Returns: #SPA_RESULT_OK + */ +SpaResult +spa_ringbuffer_get_write_areas (SpaRingbuffer *rbuf, + SpaRingbufferArea areas[2]) +{ + size_t avail, end, w, r; + + w = rbuf->writeindex; + r = rbuf->readindex; + + if (w > r) { + avail = (r - w + rbuf->size); + avail = (rbuf->size_mask ? avail & rbuf->size_mask : avail % rbuf->size); + avail -= 1; + } else if (w < r) { + avail = r - w - 1; + } else { + avail = rbuf->size - 1; + } + end = w + avail; + if (end > rbuf->size) { + areas[0].data = &rbuf->data[w]; + areas[0].len = rbuf->size - w; + areas[1].data = rbuf->data; + areas[1].len = end - rbuf->size; + } else { + areas[0].data = &rbuf->data[w]; + areas[0].len = avail; + areas[1].len = 0; + } + return SPA_RESULT_OK; +} + +/** + * spa_ringbuffer_write_advance: + * @rbuf: a #SpaRingbuffer + * @len: number of bytes to advance + * + * Advance the write pointer by @len + * + * Returns: #SPA_RESULT_OK + */ +SpaResult +spa_ringbuffer_write_advance (SpaRingbuffer *rbuf, + ssize_t len) +{ + size_t tmp = rbuf->writeindex + len; + rbuf->writeindex = (rbuf->size_mask ? tmp & rbuf->size_mask : tmp % rbuf->size); + return SPA_RESULT_OK; +} diff --git a/spa/meson.build b/spa/meson.build new file mode 100644 index 000000000..5c0de29c2 --- /dev/null +++ b/spa/meson.build @@ -0,0 +1,12 @@ +project('spa', 'c') + +alsa_dep = dependency('alsa') +dl_lib = find_library('dl', required : true) + +inc = include_directories('include') + +subdir('include') +subdir('lib') +subdir('plugins') +subdir('tools') +subdir('tests') diff --git a/spa/plugins/alsa/alsa-sink.c b/spa/plugins/alsa/alsa-sink.c new file mode 100644 index 000000000..87cd8ef9c --- /dev/null +++ b/spa/plugins/alsa/alsa-sink.c @@ -0,0 +1,587 @@ +/* Spa ALSA Sink + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include + +#include +#include + +#include +#include + +typedef struct _SpaALSASink SpaALSASink; + +static const char default_device[] = "default"; +static const uint32_t default_buffer_time = 10000; +static const uint32_t default_period_time = 5000; +static const bool default_period_event = 0; + +typedef struct { + SpaProps props; + char device[64]; + char device_name[128]; + char card_name[128]; + uint32_t buffer_time; + uint32_t period_time; + bool period_event; +} SpaALSASinkProps; + +static void +reset_alsa_sink_props (SpaALSASinkProps *props) +{ + strncpy (props->device, default_device, 64); + props->buffer_time = default_buffer_time; + props->period_time = default_period_time; + props->period_event = default_period_event; +} + +typedef struct { + snd_pcm_t *handle; + snd_output_t *output; + snd_pcm_sframes_t buffer_size; + snd_pcm_sframes_t period_size; + snd_pcm_channel_area_t areas[16]; + pthread_t thread; + bool running; +} SpaALSAState; + +typedef struct _ALSABuffer ALSABuffer; + +struct _ALSABuffer { + SpaBuffer buffer; + SpaMeta meta[1]; + SpaMetaHeader header; + SpaData data[1]; + ALSABuffer *next; +}; + +struct _SpaALSASink { + SpaHandle handle; + + SpaALSASinkProps tmp_props; + SpaALSASinkProps props; + + bool activated; + + SpaEventCallback event_cb; + void *user_data; + + bool have_format; + SpaAudioRawFormat query_format; + SpaAudioRawFormat current_format; + + SpaALSAState state; + + SpaPortInfo info; + SpaPortStatus status; + + SpaBuffer *input_buffer; + + ALSABuffer buffer; +}; + +#include "alsa-utils.c" + +static const uint32_t min_uint32 = 1; +static const uint32_t max_uint32 = UINT32_MAX; + +static const SpaPropRangeInfo uint32_range[] = { + { "min", "Minimum value", 4, &min_uint32 }, + { "max", "Maximum value", 4, &max_uint32 }, +}; + +enum { + PROP_ID_DEVICE, + PROP_ID_DEVICE_NAME, + PROP_ID_CARD_NAME, + PROP_ID_BUFFER_TIME, + PROP_ID_PERIOD_TIME, + PROP_ID_PERIOD_EVENT, + PROP_ID_LAST, +}; + +static const SpaPropInfo prop_info[] = +{ + { PROP_ID_DEVICE, "device", "ALSA device, as defined in an asound configuration file", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_STRING, 63, + strlen (default_device)+1, default_device, + SPA_PROP_RANGE_TYPE_NONE, 0, NULL, + NULL, + offsetof (SpaALSASinkProps, device), + 0, 0, + NULL }, + { PROP_ID_DEVICE_NAME, "device-name", "Human-readable name of the sound device", + SPA_PROP_FLAG_READABLE, + SPA_PROP_TYPE_STRING, 127, + 0, NULL, + SPA_PROP_RANGE_TYPE_NONE, 0, NULL, + NULL, + offsetof (SpaALSASinkProps, device_name), + 0, 0, + NULL }, + { PROP_ID_CARD_NAME, "card-name", "Human-readable name of the sound card", + SPA_PROP_FLAG_READABLE, + SPA_PROP_TYPE_STRING, 127, + 0, NULL, + SPA_PROP_RANGE_TYPE_NONE, 0, NULL, + NULL, + offsetof (SpaALSASinkProps, card_name), + 0, 0, + NULL }, + { PROP_ID_BUFFER_TIME, "buffer-time", "The total size of the buffer in time", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_UINT32, sizeof (uint32_t), + sizeof (uint32_t), &default_buffer_time, + SPA_PROP_RANGE_TYPE_MIN_MAX, 2, uint32_range, + NULL, + offsetof (SpaALSASinkProps, buffer_time), + 0, 0, + NULL }, + { PROP_ID_PERIOD_TIME, "period-time", "The size of a period in time", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_UINT32, sizeof (uint32_t), + sizeof (uint32_t), &default_period_time, + SPA_PROP_RANGE_TYPE_MIN_MAX, 2, uint32_range, + NULL, + offsetof (SpaALSASinkProps, period_time), + 0, 0, + NULL }, + { PROP_ID_PERIOD_EVENT, "period-event", "Generate an event each period", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_BOOL, sizeof (bool), + sizeof (bool), &default_period_event, + SPA_PROP_RANGE_TYPE_NONE, 0, NULL, + NULL, + offsetof (SpaALSASinkProps, period_event), + 0, 0, + NULL }, +}; + +static SpaResult +spa_alsa_sink_node_get_props (SpaHandle *handle, + SpaProps **props) +{ + SpaALSASink *this = (SpaALSASink *) handle; + + if (handle == NULL || props == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + memcpy (&this->tmp_props, &this->props, sizeof (this->props)); + *props = &this->tmp_props.props; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_set_props (SpaHandle *handle, + const SpaProps *props) +{ + SpaALSASink *this = (SpaALSASink *) handle; + SpaALSASinkProps *p = &this->props; + SpaResult res; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (props == NULL) { + reset_alsa_sink_props (p); + return SPA_RESULT_OK; + } + + res = spa_props_copy (props, &p->props); + + return res; +} + +static SpaResult +spa_alsa_sink_node_send_command (SpaHandle *handle, + SpaCommand *command) +{ + SpaALSASink *this = (SpaALSASink *) handle; + + if (handle == NULL || command == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + switch (command->type) { + case SPA_COMMAND_INVALID: + return SPA_RESULT_INVALID_COMMAND; + + case SPA_COMMAND_ACTIVATE: + if (!this->activated) { + spa_alsa_open (this); + this->activated = true; + } + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_ACTIVATED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + case SPA_COMMAND_DEACTIVATE: + if (this->activated) { + spa_alsa_close (this); + this->activated = false; + } + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_DEACTIVATED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + case SPA_COMMAND_START: + spa_alsa_start (this); + break; + case SPA_COMMAND_STOP: + spa_alsa_stop (this); + break; + case SPA_COMMAND_FLUSH: + case SPA_COMMAND_DRAIN: + case SPA_COMMAND_MARKER: + return SPA_RESULT_NOT_IMPLEMENTED; + } + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_set_event_callback (SpaHandle *handle, + SpaEventCallback event, + void *user_data) +{ + SpaALSASink *this = (SpaALSASink *) handle; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this->event_cb = event; + this->user_data = user_data; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_get_n_ports (SpaHandle *handle, + unsigned int *n_input_ports, + unsigned int *max_input_ports, + unsigned int *n_output_ports, + unsigned int *max_output_ports) +{ + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (n_input_ports) + *n_input_ports = 1; + if (n_output_ports) + *n_output_ports = 0; + if (max_input_ports) + *max_input_ports = 1; + if (max_output_ports) + *max_output_ports = 0; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_get_port_ids (SpaHandle *handle, + unsigned int n_input_ports, + uint32_t *input_ids, + unsigned int n_output_ports, + uint32_t *output_ids) +{ + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (n_input_ports > 0) + input_ids[0] = 0; + + return SPA_RESULT_OK; +} + + +static SpaResult +spa_alsa_sink_node_add_port (SpaHandle *handle, + SpaDirection direction, + uint32_t *port_id) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_alsa_sink_node_remove_port (SpaHandle *handle, + uint32_t port_id) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_alsa_sink_node_enum_port_formats (SpaHandle *handle, + uint32_t port_id, + unsigned int index, + SpaFormat **format) +{ + SpaALSASink *this = (SpaALSASink *) handle; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + switch (index) { + case 0: + spa_audio_raw_format_init (&this->query_format); + break; + case 1: + spa_audio_raw_format_init (&this->query_format); + break; + default: + return SPA_RESULT_ENUM_END; + } + *format = &this->query_format.format; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_set_port_format (SpaHandle *handle, + uint32_t port_id, + int test_only, + const SpaFormat *format) +{ + SpaALSASink *this = (SpaALSASink *) handle; + SpaResult res; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + if (format == NULL) { + this->have_format = false; + return SPA_RESULT_OK; + } + + if ((res = spa_audio_raw_format_parse (format, &this->current_format)) < 0) + return res; + + printf ("format %d\n", this->current_format.info.rate); + + this->have_format = true; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_get_port_format (SpaHandle *handle, + uint32_t port_id, + const SpaFormat **format) +{ + SpaALSASink *this = (SpaALSASink *) handle; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + if (!this->have_format) + return SPA_RESULT_NO_FORMAT; + + *format = &this->current_format.format; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_get_port_info (SpaHandle *handle, + uint32_t port_id, + const SpaPortInfo **info) +{ + SpaALSASink *this = (SpaALSASink *) handle; + + if (handle == NULL || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + *info = &this->info; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_get_port_props (SpaHandle *handle, + uint32_t port_id, + SpaProps **props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_alsa_sink_node_set_port_props (SpaHandle *handle, + uint32_t port_id, + const SpaProps *props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_alsa_sink_node_get_port_status (SpaHandle *handle, + uint32_t port_id, + const SpaPortStatus **status) +{ + SpaALSASink *this = (SpaALSASink *) handle; + + if (handle == NULL || status == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + *status = &this->status; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_push_port_input (SpaHandle *handle, + unsigned int n_info, + SpaInputInfo *info) +{ + SpaALSASink *this = (SpaALSASink *) handle; + unsigned int i; + bool have_error = false, have_enough = false; + + if (handle == NULL || n_info == 0 || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + for (i = 0; i < n_info; i++) { + if (info[i].port_id != 0) { + info[i].status = SPA_RESULT_INVALID_PORT; + have_error = true; + continue; + } + + if (info[i].buffer != NULL) { + if (!this->have_format) { + info[i].status = SPA_RESULT_NO_FORMAT; + have_error = true; + continue; + } + + if (this->input_buffer != NULL) { + info[i].status = SPA_RESULT_HAVE_ENOUGH_INPUT; + have_enough = true; + continue; + } + this->input_buffer = spa_buffer_ref (info[i].buffer); + } + info[i].status = SPA_RESULT_OK; + } + if (have_error) + return SPA_RESULT_ERROR; + if (have_enough) + return SPA_RESULT_HAVE_ENOUGH_INPUT; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_alsa_sink_node_pull_port_output (SpaHandle *handle, + unsigned int n_info, + SpaOutputInfo *info) +{ + return SPA_RESULT_INVALID_PORT; +} + +static const SpaNode alsasink_node = { + sizeof (SpaNode), + spa_alsa_sink_node_get_props, + spa_alsa_sink_node_set_props, + spa_alsa_sink_node_send_command, + spa_alsa_sink_node_set_event_callback, + spa_alsa_sink_node_get_n_ports, + spa_alsa_sink_node_get_port_ids, + spa_alsa_sink_node_add_port, + spa_alsa_sink_node_remove_port, + spa_alsa_sink_node_enum_port_formats, + spa_alsa_sink_node_set_port_format, + spa_alsa_sink_node_get_port_format, + spa_alsa_sink_node_get_port_info, + spa_alsa_sink_node_get_port_props, + spa_alsa_sink_node_set_port_props, + spa_alsa_sink_node_get_port_status, + spa_alsa_sink_node_push_port_input, + spa_alsa_sink_node_pull_port_output, +}; + +static SpaResult +spa_alsa_sink_get_interface (SpaHandle *handle, + uint32_t interface_id, + const void **interface) +{ + if (handle == NULL || interface == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + switch (interface_id) { + case SPA_INTERFACE_ID_NODE: + *interface = &alsasink_node; + break; + default: + return SPA_RESULT_UNKNOWN_INTERFACE; + } + return SPA_RESULT_OK; +} + +SpaHandle * +spa_alsa_sink_new (void) +{ + SpaHandle *handle; + SpaALSASink *this; + + handle = calloc (1, sizeof (SpaALSASink)); + handle->get_interface = spa_alsa_sink_get_interface; + + this = (SpaALSASink *) handle; + this->props.props.n_prop_info = PROP_ID_LAST; + this->props.props.prop_info = prop_info; + this->props.props.set_prop = spa_props_generic_set_prop; + this->props.props.get_prop = spa_props_generic_get_prop; + reset_alsa_sink_props (&this->props); + + this->info.flags = SPA_PORT_INFO_FLAG_NONE; + this->status.flags = SPA_PORT_STATUS_FLAG_NEED_INPUT; + return handle; +} diff --git a/spa/plugins/alsa/alsa-utils.c b/spa/plugins/alsa/alsa-utils.c new file mode 100644 index 000000000..f5cb1de35 --- /dev/null +++ b/spa/plugins/alsa/alsa-utils.c @@ -0,0 +1,367 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static int verbose = 0; /* verbose flag */ + +#define CHECK(s,msg) if ((err = (s)) < 0) { printf (msg ": %s\n", snd_strerror(err)); return err; } + +static snd_pcm_format_t +spa_alsa_format_to_alsa (SpaAudioFormat format) +{ + switch (format) { + case SPA_AUDIO_FORMAT_S8: + return SND_PCM_FORMAT_S8; + case SPA_AUDIO_FORMAT_U8: + return SND_PCM_FORMAT_U8; + /* 16 bit */ + case SPA_AUDIO_FORMAT_S16LE: + return SND_PCM_FORMAT_S16_LE; + case SPA_AUDIO_FORMAT_S16BE: + return SND_PCM_FORMAT_S16_BE; + case SPA_AUDIO_FORMAT_U16LE: + return SND_PCM_FORMAT_U16_LE; + case SPA_AUDIO_FORMAT_U16BE: + return SND_PCM_FORMAT_U16_BE; + /* 24 bit in low 3 bytes of 32 bits */ + case SPA_AUDIO_FORMAT_S24_32LE: + return SND_PCM_FORMAT_S24_LE; + case SPA_AUDIO_FORMAT_S24_32BE: + return SND_PCM_FORMAT_S24_BE; + case SPA_AUDIO_FORMAT_U24_32LE: + return SND_PCM_FORMAT_U24_LE; + case SPA_AUDIO_FORMAT_U24_32BE: + return SND_PCM_FORMAT_U24_BE; + /* 24 bit in 3 bytes */ + case SPA_AUDIO_FORMAT_S24LE: + return SND_PCM_FORMAT_S24_3LE; + case SPA_AUDIO_FORMAT_S24BE: + return SND_PCM_FORMAT_S24_3BE; + case SPA_AUDIO_FORMAT_U24LE: + return SND_PCM_FORMAT_U24_3LE; + case SPA_AUDIO_FORMAT_U24BE: + return SND_PCM_FORMAT_U24_3BE; + /* 32 bit */ + case SPA_AUDIO_FORMAT_S32LE: + return SND_PCM_FORMAT_S32_LE; + case SPA_AUDIO_FORMAT_S32BE: + return SND_PCM_FORMAT_S32_BE; + case SPA_AUDIO_FORMAT_U32LE: + return SND_PCM_FORMAT_U32_LE; + case SPA_AUDIO_FORMAT_U32BE: + return SND_PCM_FORMAT_U32_BE; + default: + break; + } + + return SND_PCM_FORMAT_UNKNOWN; +} + +static int +set_hwparams (SpaALSASink *this) +{ + unsigned int rrate; + snd_pcm_uframes_t size; + int err, dir; + snd_pcm_hw_params_t *params; + snd_pcm_format_t format; + SpaALSAState *state = &this->state; + SpaAudioRawFormat *fmt = &this->current_format; + SpaAudioRawInfo *info = &fmt->info; + snd_pcm_t *handle = state->handle; + unsigned int buffer_time; + unsigned int period_time; + + snd_pcm_hw_params_alloca (¶ms); + /* choose all parameters */ + CHECK (snd_pcm_hw_params_any (handle, params), "Broken configuration for playback: no configurations available"); + /* set hardware resampling */ + CHECK (snd_pcm_hw_params_set_rate_resample (handle, params, 0), "set_rate_resample"); + /* set the interleaved read/write format */ + CHECK (snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_MMAP_INTERLEAVED), "set_access"); + + /* set the sample format */ + format = spa_alsa_format_to_alsa (info->format); + printf ("Stream parameters are %iHz, %s, %i channels\n", info->rate, snd_pcm_format_name(format), info->channels); + CHECK (snd_pcm_hw_params_set_format (handle, params, format), "set_format"); + /* set the count of channels */ + CHECK (snd_pcm_hw_params_set_channels (handle, params, info->channels), "set_channels"); + /* set the stream rate */ + rrate = info->rate; + CHECK (snd_pcm_hw_params_set_rate_near (handle, params, &rrate, 0), "set_rate_near"); + if (rrate != info->rate) { + printf("Rate doesn't match (requested %iHz, get %iHz)\n", info->rate, rrate); + return -EINVAL; + } + /* set the buffer time */ + buffer_time = this->props.buffer_time; + CHECK (snd_pcm_hw_params_set_buffer_time_near (handle, params, &buffer_time, &dir), "set_buffer_time_near"); + CHECK (snd_pcm_hw_params_get_buffer_size (params, &size), "get_buffer_size"); + state->buffer_size = size; + + /* set the period time */ + period_time = this->props.period_time; + CHECK (snd_pcm_hw_params_set_period_time_near (handle, params, &period_time, &dir), "set_period_time_near"); + CHECK (snd_pcm_hw_params_get_period_size (params, &size, &dir), "get_period_size"); + state->period_size = size; + + /* write the parameters to device */ + CHECK (snd_pcm_hw_params (handle, params), "set_hw_params"); + + return 0; +} + +static int +set_swparams (SpaALSASink *this) +{ + SpaALSAState *state = &this->state; + snd_pcm_t *handle = state->handle; + int err = 0; + snd_pcm_sw_params_t *params; + + snd_pcm_sw_params_alloca (¶ms); + + /* get the current params */ + CHECK (snd_pcm_sw_params_current (handle, params), "sw_params_current"); + /* start the transfer when the buffer is almost full: */ + /* (buffer_size / avail_min) * avail_min */ + CHECK (snd_pcm_sw_params_set_start_threshold (handle, params, + (state->buffer_size / state->period_size) * state->period_size), "set_start_threshold"); + + /* allow the transfer when at least period_size samples can be processed */ + /* or disable this mechanism when period event is enabled (aka interrupt like style processing) */ + CHECK (snd_pcm_sw_params_set_avail_min (handle, params, + this->props.period_event ? state->buffer_size : state->period_size), "set_avail_min"); + /* enable period events when requested */ + if (this->props.period_event) { + CHECK (snd_pcm_sw_params_set_period_event (handle, params, 1), "set_period_event"); + } + /* write the parameters to the playback device */ + CHECK (snd_pcm_sw_params (handle, params), "sw_params"); + + return 0; +} + +/* + * Underrun and suspend recovery + */ +static int +xrun_recovery (snd_pcm_t *handle, int err) +{ + if (verbose) + printf("stream recovery\n"); + if (err == -EPIPE) { /* under-run */ + err = snd_pcm_prepare(handle); + if (err < 0) + printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err)); + return 0; + } else if (err == -ESTRPIPE) { + while ((err = snd_pcm_resume(handle)) == -EAGAIN) + sleep(1); /* wait until the suspend flag is released */ + if (err < 0) { + err = snd_pcm_prepare(handle); + if (err < 0) + printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err)); + } + return 0; + } + return err; +} + +/* + * Transfer method - direct write only + */ +static void * +direct_loop (void *user_data) +{ + SpaALSASink *this = user_data; + SpaALSAState *state = &this->state; + snd_pcm_t *handle = state->handle; + const snd_pcm_channel_area_t *my_areas; + snd_pcm_uframes_t offset, frames, size; + snd_pcm_sframes_t avail, commitres; + snd_pcm_state_t st; + int err, first = 1; + + while (state->running) { + st = snd_pcm_state(handle); + if (st == SND_PCM_STATE_XRUN) { + err = xrun_recovery(handle, -EPIPE); + if (err < 0) { + printf("XRUN recovery failed: %s\n", snd_strerror(err)); + return NULL; + } + first = 1; + } else if (st == SND_PCM_STATE_SUSPENDED) { + err = xrun_recovery(handle, -ESTRPIPE); + if (err < 0) { + printf("SUSPEND recovery failed: %s\n", snd_strerror(err)); + return NULL; + } + } + avail = snd_pcm_avail_update(handle); + if (avail < 0) { + err = xrun_recovery(handle, avail); + if (err < 0) { + printf("avail update failed: %s\n", snd_strerror(err)); + return NULL; + } + first = 1; + continue; + } + if (avail < state->period_size) { + if (first) { + first = 0; + err = snd_pcm_start(handle); + if (err < 0) { + printf("Start error: %s\n", snd_strerror(err)); + exit(EXIT_FAILURE); + } + } else { + err = snd_pcm_wait(handle, -1); + if (err < 0) { + if ((err = xrun_recovery(handle, err)) < 0) { + printf("snd_pcm_wait error: %s\n", snd_strerror(err)); + exit(EXIT_FAILURE); + } + first = 1; + } + } + continue; + } + size = state->period_size; + while (size > 0) { + frames = size; + err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames); + if (err < 0) { + if ((err = xrun_recovery(handle, err)) < 0) { + printf("MMAP begin avail error: %s\n", snd_strerror(err)); + exit(EXIT_FAILURE); + } + first = 1; + } + + { + SpaEvent event; + ALSABuffer *buffer = &this->buffer; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_PULL_INPUT; + event.port_id = 0; + event.size = frames * sizeof (uint16_t) * 2; + event.data = buffer; + + buffer->buffer.refcount = 1; + buffer->buffer.notify = NULL; + buffer->buffer.size = frames * sizeof (uint16_t) * 2; + buffer->buffer.n_metas = 1; + buffer->buffer.metas = buffer->meta; + buffer->buffer.n_datas = 1; + buffer->buffer.datas = buffer->data; + + buffer->header.flags = 0; + buffer->header.seq = 0; + buffer->header.pts = 0; + buffer->header.dts_offset = 0; + + buffer->meta[0].type = SPA_META_TYPE_HEADER; + buffer->meta[0].data = &buffer->header; + buffer->meta[0].size = sizeof (buffer->header); + + buffer->data[0].type = SPA_DATA_TYPE_MEMPTR; + buffer->data[0].data = (uint8_t *)my_areas[0].addr + (offset * sizeof (uint16_t) * 2); + buffer->data[0].size = frames * sizeof (uint16_t) * 2; + + this->event_cb (&this->handle, &event,this->user_data); + + spa_buffer_unref ((SpaBuffer *)event.data); + } + if (this->input_buffer) { + if (this->input_buffer != &this->buffer.buffer) { + /* FIXME, copy input */ + } + spa_buffer_unref (this->input_buffer); + this->input_buffer = NULL; + } + + commitres = snd_pcm_mmap_commit(handle, offset, frames); + if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) { + if ((err = xrun_recovery(handle, commitres >= 0 ? -EPIPE : commitres)) < 0) { + printf("MMAP commit error: %s\n", snd_strerror(err)); + exit(EXIT_FAILURE); + } + first = 1; + } + size -= frames; + } + } + return NULL; +} + +static int +spa_alsa_open (SpaALSASink *this) +{ + SpaALSAState *state = &this->state; + int err; + + CHECK (snd_output_stdio_attach (&state->output, stdout, 0), "attach failed"); + + printf ("Playback device is '%s'\n", this->props.device); + CHECK (snd_pcm_open (&state->handle, + this->props.device, + SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK | + SND_PCM_NO_AUTO_RESAMPLE | + SND_PCM_NO_AUTO_CHANNELS | + SND_PCM_NO_AUTO_FORMAT), "open failed"); + + return 0; +} + +static int +spa_alsa_start (SpaALSASink *this) +{ + SpaALSAState *state = &this->state; + int err; + + CHECK (set_hwparams (this), "hwparams"); + CHECK (set_swparams (this), "swparams"); + + snd_pcm_dump (this->state.handle, this->state.output); + + state->running = true; + if ((err = pthread_create (&state->thread, NULL, direct_loop, this)) != 0) { + printf ("can't create thread: %d", err); + state->running = false; + } + return err; +} + +static int +spa_alsa_stop (SpaALSASink *this) +{ + SpaALSAState *state = &this->state; + + if (state->running) { + state->running = false; + pthread_join (state->thread, NULL); + } + return 0; +} + +static int +spa_alsa_close (SpaALSASink *this) +{ + SpaALSAState *state = &this->state; + int err = 0; + + CHECK (snd_pcm_close (state->handle), "close failed"); + + return err; +} diff --git a/spa/plugins/alsa/alsa.c b/spa/plugins/alsa/alsa.c new file mode 100644 index 000000000..f6ebd2a15 --- /dev/null +++ b/spa/plugins/alsa/alsa.c @@ -0,0 +1,78 @@ +/* Spa ALSA support + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +SpaHandle * spa_alsa_sink_new (void); + +static SpaResult +alsa_sink_instantiate (const SpaHandleFactory *factory, + SpaHandle **handle) +{ + if (factory == NULL || handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + *handle = spa_alsa_sink_new (); + + return SPA_RESULT_OK; +} + +static const SpaInterfaceInfo alsa_sink_interfaces[] = +{ + { SPA_INTERFACE_ID_NODE, + SPA_INTERFACE_ID_NODE_NAME, + SPA_INTERFACE_ID_NODE_DESCRIPTION, + }, +}; + +static SpaResult +alsa_sink_enum_interface_info (const SpaHandleFactory *factory, + unsigned int index, + const SpaInterfaceInfo **info) +{ + if (index >= 1) + return SPA_RESULT_ENUM_END; + + *info = &alsa_sink_interfaces[index]; + + return SPA_RESULT_OK; +} + +static const SpaHandleFactory factories[] = +{ + { "alsa-sink", + NULL, + alsa_sink_instantiate, + alsa_sink_enum_interface_info, + }, +}; + + +SpaResult +spa_enum_handle_factory (unsigned int index, + const SpaHandleFactory **factory) +{ + if (index >= 1) + return SPA_RESULT_ENUM_END; + + *factory = &factories[index]; + + return SPA_RESULT_OK; +} diff --git a/spa/plugins/alsa/meson.build b/spa/plugins/alsa/meson.build new file mode 100644 index 000000000..ceddaf72f --- /dev/null +++ b/spa/plugins/alsa/meson.build @@ -0,0 +1,9 @@ +alsa_sources = ['alsa.c', + 'alsa-sink.c'] + +alsalib = shared_library('spa-alsa', + alsa_sources, + include_directories : inc, + dependencies : alsa_dep, + link_with : spalib, + install : true) diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c new file mode 100644 index 000000000..692cbd9d0 --- /dev/null +++ b/spa/plugins/audiomixer/audiomixer.c @@ -0,0 +1,740 @@ +/* Spa + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include +#include + +#define MAX_PORTS 128 + +typedef struct _SpaAudioMixer SpaAudioMixer; + +typedef struct { + SpaProps props; +} SpaAudioMixerProps; + +typedef struct { + SpaProps prop; +} SpaAudioMixerPortProps; + +typedef struct _MixerBuffer MixerBuffer; + +struct _MixerBuffer { + SpaBuffer buffer; + SpaMeta meta[1]; + SpaMetaHeader header; + SpaData data[1]; + uint16_t samples[4096]; +}; + +typedef struct { + bool valid; + SpaAudioMixerPortProps props; + SpaPortInfo info; + SpaPortStatus status; + SpaBuffer *buffer; + size_t buffer_index; + size_t buffer_offset; + size_t buffer_queued; + MixerBuffer mix; +} SpaAudioMixerPort; + +struct _SpaAudioMixer { + SpaHandle handle; + + SpaAudioMixerProps props; + SpaAudioMixerProps tmp_props; + + SpaEventCallback event_cb; + void *user_data; + + bool have_format; + SpaAudioRawFormat query_format; + SpaAudioRawFormat current_format; + + int port_count; + int port_queued; + SpaAudioMixerPort ports[MAX_PORTS]; +}; + +enum { + PROP_ID_LAST, +}; + +static const SpaPropInfo prop_info[] = +{ + { PROP_ID_LAST, }, +}; + +static void +reset_audiomixer_props (SpaAudioMixerProps *props) +{ +} + +static SpaResult +spa_audiomixer_node_get_props (SpaHandle *handle, + SpaProps **props) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + + if (handle == NULL || props == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + memcpy (&this->tmp_props, &this->props, sizeof (this->tmp_props)); + *props = &this->tmp_props.props; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_set_props (SpaHandle *handle, + const SpaProps *props) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + SpaAudioMixerProps *p = &this->props; + SpaResult res; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (props == NULL) { + reset_audiomixer_props (p); + return SPA_RESULT_OK; + } + res = spa_props_copy (props, &p->props); + + return res; +} + +static SpaResult +spa_audiomixer_node_send_command (SpaHandle *handle, + SpaCommand *command) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + + if (handle == NULL || command == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + switch (command->type) { + case SPA_COMMAND_INVALID: + return SPA_RESULT_INVALID_COMMAND; + + case SPA_COMMAND_ACTIVATE: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_ACTIVATED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + + case SPA_COMMAND_DEACTIVATE: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_DEACTIVATED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + + case SPA_COMMAND_START: + case SPA_COMMAND_STOP: + case SPA_COMMAND_FLUSH: + case SPA_COMMAND_DRAIN: + case SPA_COMMAND_MARKER: + return SPA_RESULT_NOT_IMPLEMENTED; + } + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_set_event_callback (SpaHandle *handle, + SpaEventCallback event, + void *user_data) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this->event_cb = event; + this->user_data = user_data; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_get_n_ports (SpaHandle *handle, + unsigned int *n_input_ports, + unsigned int *max_input_ports, + unsigned int *n_output_ports, + unsigned int *max_output_ports) +{ + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (n_input_ports) + *n_input_ports = 0; + if (n_output_ports) + *n_output_ports = 1; + if (max_input_ports) + *max_input_ports = MAX_PORTS; + if (max_output_ports) + *max_output_ports = 1; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_get_port_ids (SpaHandle *handle, + unsigned int n_input_ports, + uint32_t *input_ids, + unsigned int n_output_ports, + uint32_t *output_ids) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + int i, idx; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (input_ids) { + for (i = 1, idx = 0; i < MAX_PORTS && idx < n_input_ports; i++) { + if (this->ports[i].valid) + input_ids[idx++] = i; + } + } + if (n_output_ports > 0 && output_ids) + output_ids[0] = 0; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_add_port (SpaHandle *handle, + SpaDirection direction, + uint32_t *port_id) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + int i; + + if (handle == NULL || port_id == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (direction != SPA_DIRECTION_INPUT) + return SPA_RESULT_INVALID_DIRECTION; + + for (i = 1; i < MAX_PORTS; i++) + if (!this->ports[i].valid) + break; + if (i == MAX_PORTS) + return SPA_RESULT_TOO_MANY_PORTS; + + this->ports[i].valid = true; + *port_id = i; + this->port_count++; + + this->ports[i].info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFER | + SPA_PORT_INFO_FLAG_REMOVABLE | + SPA_PORT_INFO_FLAG_OPTIONAL | + SPA_PORT_INFO_FLAG_IN_PLACE; + this->ports[i].status.flags = SPA_PORT_STATUS_FLAG_NEED_INPUT; + + this->ports[0].status.flags &= ~SPA_PORT_STATUS_FLAG_HAVE_OUTPUT; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_remove_port (SpaHandle *handle, + uint32_t port_id) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id == 0 || port_id >= MAX_PORTS || !this->ports[port_id].valid) + return SPA_RESULT_INVALID_PORT; + + this->ports[port_id].valid = false; + this->port_count--; + if (this->ports[port_id].buffer) { + spa_buffer_unref (this->ports[port_id].buffer); + this->ports[port_id].buffer = NULL; + this->port_queued--; + } + if (this->port_count == this->port_queued) + this->ports[0].status.flags |= SPA_PORT_STATUS_FLAG_HAVE_OUTPUT; + + return SPA_RESULT_OK; +} + + +static SpaResult +spa_audiomixer_node_enum_port_formats (SpaHandle *handle, + uint32_t port_id, + unsigned int index, + SpaFormat **format) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id > MAX_PORTS) + return SPA_RESULT_INVALID_PORT; + + switch (index) { + case 0: + spa_audio_raw_format_init (&this->query_format); + break; + default: + return SPA_RESULT_ENUM_END; + } + *format = &this->query_format.format; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_set_port_format (SpaHandle *handle, + uint32_t port_id, + int test_only, + const SpaFormat *format) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + SpaResult res; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id > MAX_PORTS) + return SPA_RESULT_INVALID_PORT; + + if (format == NULL) { + this->have_format = false; + return SPA_RESULT_OK; + } + + if ((res = spa_audio_raw_format_parse (format, &this->current_format)) < 0) + return res; + + this->have_format = true; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_get_port_format (SpaHandle *handle, + uint32_t port_id, + const SpaFormat **format) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id >= MAX_PORTS || !this->ports[port_id].valid) + return SPA_RESULT_INVALID_PORT; + + if (!this->have_format) + return SPA_RESULT_NO_FORMAT; + + *format = &this->current_format.format; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_get_port_info (SpaHandle *handle, + uint32_t port_id, + const SpaPortInfo **info) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + + if (handle == NULL || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id >= MAX_PORTS || !this->ports[port_id].valid) + return SPA_RESULT_INVALID_PORT; + + *info = &this->ports[port_id].info; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_get_port_props (SpaHandle *handle, + uint32_t port_id, + SpaProps **props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_audiomixer_node_set_port_props (SpaHandle *handle, + uint32_t port_id, + const SpaProps *props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_audiomixer_node_get_port_status (SpaHandle *handle, + uint32_t port_id, + const SpaPortStatus **status) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + + if (handle == NULL || status == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id >= MAX_PORTS || !this->ports[port_id].valid) + return SPA_RESULT_INVALID_PORT; + + if (!this->have_format) + return SPA_RESULT_NO_FORMAT; + + *status = &this->ports[port_id].status; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_push_port_input (SpaHandle *handle, + unsigned int n_info, + SpaInputInfo *info) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + SpaBuffer *buffer; + SpaEvent *event; + unsigned int i; + bool have_error = false; + + if (handle == NULL || n_info == 0 || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (this->ports[0].status.flags & SPA_PORT_STATUS_FLAG_HAVE_OUTPUT) + return SPA_RESULT_HAVE_ENOUGH_INPUT; + + for (i = 0; i < n_info; i++) { + int idx = info[i].port_id; + + if (idx >= MAX_PORTS || !this->ports[idx].valid) { + info[i].status = SPA_RESULT_INVALID_PORT; + have_error = true; + continue; + } + + event = info[i].event; + buffer = info[i].buffer; + + if (buffer == NULL && event == NULL) { + info[i].status = SPA_RESULT_INVALID_ARGUMENTS; + have_error = true; + continue; + } + + if (buffer) { + if (!this->have_format) { + info[i].status = SPA_RESULT_NO_FORMAT; + have_error = true; + continue; + } + + if (this->ports[idx].buffer != NULL) { + info[i].status = SPA_RESULT_HAVE_ENOUGH_INPUT; + have_error = true; + continue; + } + this->ports[idx].buffer = spa_buffer_ref (buffer); + this->ports[idx].buffer_queued = buffer->size; + this->ports[idx].buffer_index = 0; + this->ports[idx].buffer_offset = 0; + this->port_queued++; + + if (this->port_queued == this->port_count) + this->ports[0].status.flags |= SPA_PORT_STATUS_FLAG_HAVE_OUTPUT; + } + if (event) { + info[i].status = SPA_RESULT_NOT_IMPLEMENTED; + have_error = true; + continue; + } + info[i].status = SPA_RESULT_OK; + } + if (have_error) + return SPA_RESULT_ERROR; + + return SPA_RESULT_OK; +} + + +static void +pull_port (SpaAudioMixer *this, uint32_t port_id, SpaOutputInfo *info, size_t pull_size) +{ + SpaEvent event; + MixerBuffer *buffer = &this->ports[port_id].mix; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_PULL_INPUT; + event.port_id = port_id; + event.data = buffer; + + buffer->buffer.refcount = 1; + buffer->buffer.notify = NULL; + buffer->buffer.size = pull_size; + buffer->buffer.n_metas = 1; + buffer->buffer.metas = buffer->meta; + buffer->buffer.n_datas = 1; + buffer->buffer.datas = buffer->data; + + buffer->header.flags = 0; + buffer->header.seq = 0; + buffer->header.pts = 0; + buffer->header.dts_offset = 0; + + buffer->meta[0].type = SPA_META_TYPE_HEADER; + buffer->meta[0].data = &buffer->header; + buffer->meta[0].size = sizeof (buffer->header); + + buffer->data[0].type = SPA_DATA_TYPE_MEMPTR; + buffer->data[0].data = buffer->samples; + buffer->data[0].size = pull_size; + + this->event_cb (&this->handle, &event, this->user_data); +} + +static void +add_port_data (SpaAudioMixer *this, SpaBuffer *out, SpaAudioMixerPort *port) +{ + int i, oi = 0; + uint8_t *op, *ip; + size_t os, is, chunk; + + op = ip = NULL; + + while (true) { + if (op == NULL) { + op = out->datas[oi].data; + os = out->datas[oi].size; + } + if (ip == NULL) { + ip = port->buffer->datas[port->buffer_index].data; + is = port->buffer->datas[port->buffer_index].size; + ip += port->buffer_offset; + is -= port->buffer_offset; + } + + chunk = os < is ? os : is; + + for (i = 0; i < chunk; i++) + op[i] += ip[i]; + + if ((is -= chunk) == 0) { + if (++port->buffer_index == port->buffer->n_datas) { + spa_buffer_unref (port->buffer); + port->buffer = NULL; + port->status.flags = SPA_PORT_STATUS_FLAG_NEED_INPUT; + this->ports[0].status.flags &= ~SPA_PORT_STATUS_FLAG_HAVE_OUTPUT; + break; + } + port->buffer_offset = 0; + ip = NULL; + } else { + port->buffer_offset += chunk; + } + port->buffer_queued -= chunk; + + if ((os -= chunk) == 0) { + if (++oi == out->n_datas) + break; + op = NULL; + } + } +} + +static SpaResult +mix_data (SpaAudioMixer *this, SpaOutputInfo *info) +{ + int i, min_size, min_port, pull_size; + + if (info->port_id != 0) + return SPA_RESULT_INVALID_PORT; + + if (info->buffer) { + pull_size = info->buffer->size; + } else { + pull_size = 0; + } + + min_size = 0; + min_port = 0; + for (i = 1; i < MAX_PORTS; i++) { + if (!this->ports[i].valid) + continue; + + if (this->ports[i].buffer == NULL) { + if (pull_size && info->flags & SPA_OUTPUT_FLAG_PULL) { + pull_port (this, i, info, pull_size); + } + if (this->ports[i].buffer == NULL) + return SPA_RESULT_NEED_MORE_INPUT; + } + + if (min_size == 0 || this->ports[i].buffer_queued < min_size) { + min_size = this->ports[i].buffer_queued; + min_port = i; + } + } + if (min_port == 0) + return SPA_RESULT_NEED_MORE_INPUT; + + if (info->buffer) { + if (info->buffer->size < min_size) + min_size = info->buffer->size; + else + info->buffer->size = min_size; + } else { + info->buffer = this->ports[min_port].buffer; + this->ports[min_port].buffer = NULL; + this->ports[min_port].status.flags = SPA_PORT_STATUS_FLAG_NEED_INPUT; + this->ports[0].status.flags &= ~SPA_PORT_STATUS_FLAG_HAVE_OUTPUT; + } + + for (i = 1; i < MAX_PORTS; i++) { + if (!this->ports[i].valid || this->ports[i].buffer == NULL) + continue; + + add_port_data (this, info->buffer, &this->ports[i]); + } + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiomixer_node_pull_port_output (SpaHandle *handle, + unsigned int n_info, + SpaOutputInfo *info) +{ + SpaAudioMixer *this = (SpaAudioMixer *) handle; + int i; + bool have_error = false; + + if (handle == NULL || n_info == 0 || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (info->port_id != 0) + return SPA_RESULT_INVALID_PORT; + + if (!this->have_format) + return SPA_RESULT_NO_FORMAT; + +// if (!(this->ports[0].status.flags & SPA_PORT_STATUS_FLAG_HAVE_OUTPUT)) +// return SPA_RESULT_NEED_MORE_INPUT; + + for (i = 0; i < n_info; i++) { + if ((info[i].status = mix_data (this, &info[i])) < 0) { + printf ("error mixing: %d\n", info[i].status); + have_error = true; + continue; + } + } + if (have_error) + return SPA_RESULT_ERROR; + + return SPA_RESULT_OK; +} + +static const SpaNode audiomixer_node = { + sizeof (SpaNode), + spa_audiomixer_node_get_props, + spa_audiomixer_node_set_props, + spa_audiomixer_node_send_command, + spa_audiomixer_node_set_event_callback, + spa_audiomixer_node_get_n_ports, + spa_audiomixer_node_get_port_ids, + spa_audiomixer_node_add_port, + spa_audiomixer_node_remove_port, + spa_audiomixer_node_enum_port_formats, + spa_audiomixer_node_set_port_format, + spa_audiomixer_node_get_port_format, + spa_audiomixer_node_get_port_info, + spa_audiomixer_node_get_port_props, + spa_audiomixer_node_set_port_props, + spa_audiomixer_node_get_port_status, + spa_audiomixer_node_push_port_input, + spa_audiomixer_node_pull_port_output, +}; + +static SpaResult +spa_audiomixer_get_interface (SpaHandle *handle, + uint32_t interface_id, + const void **interface) +{ + if (handle == NULL || interface == 0) + return SPA_RESULT_INVALID_ARGUMENTS; + + switch (interface_id) { + case SPA_INTERFACE_ID_NODE: + *interface = &audiomixer_node; + break; + default: + return SPA_RESULT_UNKNOWN_INTERFACE; + } + return SPA_RESULT_OK; +} + +SpaHandle * +spa_audiomixer_new (void) +{ + SpaHandle *handle; + SpaAudioMixer *this; + + handle = calloc (1, sizeof (SpaAudioMixer)); + handle->get_interface = spa_audiomixer_get_interface; + + this = (SpaAudioMixer *) handle; + this->props.props.n_prop_info = PROP_ID_LAST; + this->props.props.prop_info = prop_info; + this->props.props.set_prop = spa_props_generic_set_prop; + this->props.props.get_prop = spa_props_generic_get_prop; + reset_audiomixer_props (&this->props); + + this->ports[0].valid = true; + this->ports[0].info.flags = SPA_PORT_INFO_FLAG_CAN_GIVE_BUFFER | + SPA_PORT_INFO_FLAG_CAN_USE_BUFFER | + SPA_PORT_INFO_FLAG_NO_REF; + return handle; +} diff --git a/spa/plugins/audiomixer/meson.build b/spa/plugins/audiomixer/meson.build new file mode 100644 index 000000000..5c51205bc --- /dev/null +++ b/spa/plugins/audiomixer/meson.build @@ -0,0 +1,7 @@ +audiomixer_sources = ['audiomixer.c', 'plugin.c'] + +audiomixerlib = shared_library('spa-audiomixer', + audiomixer_sources, + include_directories : inc, + link_with : spalib, + install : true) diff --git a/spa/plugins/audiomixer/plugin.c b/spa/plugins/audiomixer/plugin.c new file mode 100644 index 000000000..4e8f626de --- /dev/null +++ b/spa/plugins/audiomixer/plugin.c @@ -0,0 +1,78 @@ +/* Spa Audiomixer plugin + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +SpaHandle * spa_audiomixer_new (void); + +static SpaResult +audiomixer_instantiate (const SpaHandleFactory *factory, + SpaHandle **handle) +{ + if (factory == NULL || handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + *handle = spa_audiomixer_new (); + + return SPA_RESULT_OK; +} + +static const SpaInterfaceInfo audiomixer_interfaces[] = +{ + { SPA_INTERFACE_ID_NODE, + SPA_INTERFACE_ID_NODE_NAME, + SPA_INTERFACE_ID_NODE_DESCRIPTION, + }, +}; + +static SpaResult +audiomixer_enum_interface_info (const SpaHandleFactory *factory, + unsigned int index, + const SpaInterfaceInfo **info) +{ + if (index >= 1) + return SPA_RESULT_ENUM_END; + + *info = &audiomixer_interfaces[index]; + + return SPA_RESULT_OK; +} + +static const SpaHandleFactory factories[] = +{ + { "audiomixer", + NULL, + audiomixer_instantiate, + audiomixer_enum_interface_info, + }, +}; + + +SpaResult +spa_enum_handle_factory (unsigned int index, + const SpaHandleFactory **factory) +{ + if (index >= 1) + return SPA_RESULT_ENUM_END; + + *factory = &factories[index]; + + return SPA_RESULT_OK; +} diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c new file mode 100644 index 000000000..1b7b6a7be --- /dev/null +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -0,0 +1,526 @@ +/* Spa + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include +#include + +typedef struct _SpaAudioTestSrc SpaAudioTestSrc; + +typedef struct { + SpaProps props; + uint32_t wave; + double freq; + double volume; +} SpaAudioTestSrcProps; + +struct _SpaAudioTestSrc { + SpaHandle handle; + + SpaAudioTestSrcProps props; + SpaAudioTestSrcProps tmp_props; + + SpaEventCallback event_cb; + void *user_data; + + SpaPortInfo info; + SpaPortStatus status; + + bool have_format; + SpaAudioRawFormat query_format; + SpaAudioRawFormat current_format; +}; + +static const uint32_t default_wave = 1.0; +static const double default_volume = 1.0; +static const double min_volume = 0.0; +static const double max_volume = 10.0; +static const double default_freq = 440.0; +static const double min_freq = 0.0; +static const double max_freq = 50000000.0; + +static const SpaPropRangeInfo volume_range[] = { + { "min", "Minimum value", sizeof (double), &min_volume }, + { "max", "Maximum value", sizeof (double), &max_volume }, +}; + +static const uint32_t wave_val_sine = 0; +static const uint32_t wave_val_square = 1; + +static const SpaPropRangeInfo wave_range[] = { + { "sine", "Sine", sizeof (uint32_t), &wave_val_sine }, + { "square", "Square", sizeof (uint32_t), &wave_val_square }, +}; + +static const SpaPropRangeInfo freq_range[] = { + { "min", "Minimum value", sizeof (double), &min_freq }, + { "max", "Maximum value", sizeof (double), &max_freq }, +}; + +enum { + PROP_ID_WAVE, + PROP_ID_FREQ, + PROP_ID_VOLUME, + PROP_ID_LAST, +}; + +static const SpaPropInfo prop_info[] = +{ + { PROP_ID_WAVE, "wave", "Oscillator waveform", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_UINT32, sizeof (uint32_t), + sizeof (uint32_t), &default_wave, + SPA_PROP_RANGE_TYPE_ENUM, SPA_N_ELEMENTS (wave_range), wave_range, + NULL, + offsetof (SpaAudioTestSrcProps, wave), + 0, 0, + NULL }, + { PROP_ID_FREQ, "freq", "Frequency of test signal. The sample rate needs to be at least 4 times higher", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_DOUBLE, sizeof (double), + sizeof (double), &default_freq, + SPA_PROP_RANGE_TYPE_MIN_MAX, 2, freq_range, + NULL, + offsetof (SpaAudioTestSrcProps, freq), + 0, 0, + NULL }, + { PROP_ID_VOLUME, "volume", "The Volume factor", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_DOUBLE, sizeof (double), + sizeof (double), &default_volume, + SPA_PROP_RANGE_TYPE_MIN_MAX, 2, volume_range, + NULL, + offsetof (SpaAudioTestSrcProps, volume), + 0, 0, + NULL }, +}; + +static void +reset_audiotestsrc_props (SpaAudioTestSrcProps *props) +{ + props->wave = default_wave; + props->freq = default_freq; + props->volume = default_volume; +} + +static SpaResult +spa_audiotestsrc_node_get_props (SpaHandle *handle, + SpaProps **props) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + + if (handle == NULL || props == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + memcpy (&this->tmp_props, &this->props, sizeof (this->tmp_props)); + *props = &this->tmp_props.props; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_set_props (SpaHandle *handle, + const SpaProps *props) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + SpaAudioTestSrcProps *p = &this->props; + SpaResult res; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (props == NULL) { + reset_audiotestsrc_props (p); + return SPA_RESULT_OK; + } + res = spa_props_copy (props, &p->props); + + return res; +} + +static SpaResult +spa_audiotestsrc_node_send_command (SpaHandle *handle, + SpaCommand *command) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + + if (handle == NULL || command == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + switch (command->type) { + case SPA_COMMAND_INVALID: + return SPA_RESULT_INVALID_COMMAND; + + case SPA_COMMAND_ACTIVATE: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_ACTIVATED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + + case SPA_COMMAND_DEACTIVATE: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_DEACTIVATED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + + case SPA_COMMAND_START: + case SPA_COMMAND_STOP: + case SPA_COMMAND_FLUSH: + case SPA_COMMAND_DRAIN: + case SPA_COMMAND_MARKER: + return SPA_RESULT_NOT_IMPLEMENTED; + } + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_set_event_callback (SpaHandle *handle, + SpaEventCallback event, + void *user_data) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this->event_cb = event; + this->user_data = user_data; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_get_n_ports (SpaHandle *handle, + unsigned int *n_input_ports, + unsigned int *max_input_ports, + unsigned int *n_output_ports, + unsigned int *max_output_ports) +{ + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (n_input_ports) + *n_input_ports = 0; + if (n_output_ports) + *n_output_ports = 1; + if (max_input_ports) + *max_input_ports = 0; + if (max_output_ports) + *max_output_ports = 1; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_get_port_ids (SpaHandle *handle, + unsigned int n_input_ports, + uint32_t *input_ids, + unsigned int n_output_ports, + uint32_t *output_ids) +{ + if (handle == NULL || input_ids == NULL || output_ids == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (n_output_ports > 0) + output_ids[0] = 0; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_add_port (SpaHandle *handle, + SpaDirection direction, + uint32_t *port_id) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_audiotestsrc_node_remove_port (SpaHandle *handle, + uint32_t port_id) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_audiotestsrc_node_enum_port_formats (SpaHandle *handle, + uint32_t port_id, + unsigned int index, + SpaFormat **format) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + switch (index) { + case 0: + spa_audio_raw_format_init (&this->query_format); + break; + default: + return SPA_RESULT_ENUM_END; + } + *format = &this->query_format.format; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_set_port_format (SpaHandle *handle, + uint32_t port_id, + int test_only, + const SpaFormat *format) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + SpaResult res; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + if (format == NULL) { + this->have_format = false; + return SPA_RESULT_OK; + } + + if ((res = spa_audio_raw_format_parse (format, &this->current_format)) < 0) + return res; + + this->have_format = true; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_get_port_format (SpaHandle *handle, + uint32_t port_id, + const SpaFormat **format) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + if (!this->have_format) + return SPA_RESULT_NO_FORMAT; + + *format = &this->current_format.format; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_get_port_info (SpaHandle *handle, + uint32_t port_id, + const SpaPortInfo **info) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + + if (handle == NULL || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + *info = &this->info; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_get_port_props (SpaHandle *handle, + uint32_t port_id, + SpaProps **props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_audiotestsrc_node_set_port_props (SpaHandle *handle, + uint32_t port_id, + const SpaProps *props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_audiotestsrc_node_get_port_status (SpaHandle *handle, + uint32_t port_id, + const SpaPortStatus **status) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + + if (handle == NULL || status == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + if (!this->have_format) + return SPA_RESULT_NO_FORMAT; + + *status = &this->status; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_audiotestsrc_node_push_port_input (SpaHandle *handle, + unsigned int n_info, + SpaInputInfo *info) +{ + return SPA_RESULT_INVALID_PORT; +} + +static SpaResult +spa_audiotestsrc_node_pull_port_output (SpaHandle *handle, + unsigned int n_info, + SpaOutputInfo *info) +{ + SpaAudioTestSrc *this = (SpaAudioTestSrc *) handle; + size_t j, size; + uint8_t *ptr; + unsigned int i; + bool have_error = false; + + if (handle == NULL || n_info == 0 || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + for (i = 0; i < n_info; i++) { + if (info[i].port_id != 0) { + info[i].status = SPA_RESULT_INVALID_PORT; + have_error = true; + continue; + } + + if (!this->have_format) { + info[i].status = SPA_RESULT_NO_FORMAT; + have_error = true; + continue; + } + + if (info[i].buffer == NULL || info[i].buffer->n_datas == 0) { + info[i].status = SPA_RESULT_INVALID_ARGUMENTS; + have_error = true; + continue; + } + + ptr = info[i].buffer->datas[0].data; + size = info[i].buffer->datas[0].size; + + for (j = 0; j < size; j++) + ptr[j] = rand(); + + info[i].status = SPA_RESULT_OK; + } + if (have_error) + return SPA_RESULT_ERROR; + + return SPA_RESULT_OK; +} + +static const SpaNode audiotestsrc_node = { + sizeof (SpaNode), + spa_audiotestsrc_node_get_props, + spa_audiotestsrc_node_set_props, + spa_audiotestsrc_node_send_command, + spa_audiotestsrc_node_set_event_callback, + spa_audiotestsrc_node_get_n_ports, + spa_audiotestsrc_node_get_port_ids, + spa_audiotestsrc_node_add_port, + spa_audiotestsrc_node_remove_port, + spa_audiotestsrc_node_enum_port_formats, + spa_audiotestsrc_node_set_port_format, + spa_audiotestsrc_node_get_port_format, + spa_audiotestsrc_node_get_port_info, + spa_audiotestsrc_node_get_port_props, + spa_audiotestsrc_node_set_port_props, + spa_audiotestsrc_node_get_port_status, + spa_audiotestsrc_node_push_port_input, + spa_audiotestsrc_node_pull_port_output, +}; + +static SpaResult +spa_audiotestsrc_get_interface (SpaHandle *handle, + uint32_t interface_id, + const void **interface) +{ + if (handle == NULL || interface == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + switch (interface_id) { + case SPA_INTERFACE_ID_NODE: + *interface = &audiotestsrc_node; + break; + default: + return SPA_RESULT_UNKNOWN_INTERFACE; + } + return SPA_RESULT_OK; +} + +SpaHandle * +spa_audiotestsrc_new (void) +{ + SpaHandle *handle; + SpaAudioTestSrc *this; + + handle = calloc (1, sizeof (SpaAudioTestSrc)); + handle->get_interface = spa_audiotestsrc_get_interface; + + this = (SpaAudioTestSrc *) handle; + this->props.props.n_prop_info = PROP_ID_LAST; + this->props.props.prop_info = prop_info; + this->props.props.set_prop = spa_props_generic_set_prop; + this->props.props.get_prop = spa_props_generic_get_prop; + reset_audiotestsrc_props (&this->props); + + this->info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFER | + SPA_PORT_INFO_FLAG_NO_REF; + this->status.flags = SPA_PORT_STATUS_FLAG_HAVE_OUTPUT; + + return handle; +} diff --git a/spa/plugins/audiotestsrc/meson.build b/spa/plugins/audiotestsrc/meson.build new file mode 100644 index 000000000..130b4b7ef --- /dev/null +++ b/spa/plugins/audiotestsrc/meson.build @@ -0,0 +1,7 @@ +audiotestsrc_sources = ['audiotestsrc.c', 'plugin.c'] + +audiotestsrclib = shared_library('spa-audiotestsrc', + audiotestsrc_sources, + include_directories : inc, + link_with : spalib, + install : true) diff --git a/spa/plugins/audiotestsrc/plugin.c b/spa/plugins/audiotestsrc/plugin.c new file mode 100644 index 000000000..5c9afa496 --- /dev/null +++ b/spa/plugins/audiotestsrc/plugin.c @@ -0,0 +1,78 @@ +/* Spa Volume plugin + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +SpaHandle * spa_audiotestsrc_new (void); + +static SpaResult +audiotestsrc_instantiate (const SpaHandleFactory *factory, + SpaHandle **handle) +{ + if (factory == NULL || handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + *handle = spa_audiotestsrc_new (); + + return SPA_RESULT_OK; +} + +static const SpaInterfaceInfo audiotestsrc_interfaces[] = +{ + { SPA_INTERFACE_ID_NODE, + SPA_INTERFACE_ID_NODE_NAME, + SPA_INTERFACE_ID_NODE_DESCRIPTION, + }, +}; + +static SpaResult +audiotestsrc_enum_interface_info (const SpaHandleFactory *factory, + unsigned int index, + const SpaInterfaceInfo **info) +{ + if (index >= 1) + return SPA_RESULT_ENUM_END; + + *info = &audiotestsrc_interfaces[index]; + + return SPA_RESULT_OK; +} + +static const SpaHandleFactory factories[] = +{ + { "audiotestsrc", + NULL, + audiotestsrc_instantiate, + audiotestsrc_enum_interface_info, + }, +}; + + +SpaResult +spa_enum_handle_factory (unsigned int index, + const SpaHandleFactory **factory) +{ + if (index >= 1) + return SPA_RESULT_ENUM_END; + + *factory = &factories[index]; + + return SPA_RESULT_OK; +} diff --git a/spa/plugins/meson.build b/spa/plugins/meson.build new file mode 100644 index 000000000..52a3f253e --- /dev/null +++ b/spa/plugins/meson.build @@ -0,0 +1,4 @@ +subdir('alsa') +subdir('audiomixer') +subdir('audiotestsrc') +subdir('volume') diff --git a/spa/plugins/volume/meson.build b/spa/plugins/volume/meson.build new file mode 100644 index 000000000..e0d63cfe8 --- /dev/null +++ b/spa/plugins/volume/meson.build @@ -0,0 +1,7 @@ +volume_sources = ['volume.c', 'plugin.c'] + +volumelib = shared_library('spa-volume', + volume_sources, + include_directories : inc, + link_with : spalib, + install : true) diff --git a/spa/plugins/volume/plugin.c b/spa/plugins/volume/plugin.c new file mode 100644 index 000000000..7b22036c9 --- /dev/null +++ b/spa/plugins/volume/plugin.c @@ -0,0 +1,78 @@ +/* Spa Volume plugin + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +SpaHandle * spa_volume_new (void); + +static SpaResult +volume_instantiate (const SpaHandleFactory *factory, + SpaHandle **handle) +{ + if (factory == NULL || handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + *handle = spa_volume_new (); + + return SPA_RESULT_OK; +} + +static const SpaInterfaceInfo volume_interfaces[] = +{ + { SPA_INTERFACE_ID_NODE, + SPA_INTERFACE_ID_NODE_NAME, + SPA_INTERFACE_ID_NODE_DESCRIPTION, + }, +}; + +static SpaResult +volume_enum_interface_info (const SpaHandleFactory *factory, + unsigned int index, + const SpaInterfaceInfo **info) +{ + if (index >= 1) + return SPA_RESULT_ENUM_END; + + *info = &volume_interfaces[index]; + + return SPA_RESULT_OK; +} + +static const SpaHandleFactory factories[] = +{ + { "volume", + NULL, + volume_instantiate, + volume_enum_interface_info, + }, +}; + + +SpaResult +spa_enum_handle_factory (unsigned int index, + const SpaHandleFactory **factory) +{ + if (index >= 1) + return SPA_RESULT_ENUM_END; + + *factory = &factories[index]; + + return SPA_RESULT_OK; +} diff --git a/spa/plugins/volume/volume.c b/spa/plugins/volume/volume.c new file mode 100644 index 000000000..14f3ee8ef --- /dev/null +++ b/spa/plugins/volume/volume.c @@ -0,0 +1,605 @@ +/* Spa + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include + +#include +#include + +typedef struct _SpaVolume SpaVolume; + +typedef struct { + SpaProps props; + double volume; + bool mute; +} SpaVolumeProps; + +typedef struct { + bool have_format; + SpaPortInfo info; + SpaPortStatus status; +} SpaVolumePort; + +struct _SpaVolume { + SpaHandle handle; + + SpaVolumeProps props; + SpaVolumeProps tmp_props; + + SpaEventCallback event_cb; + void *user_data; + + bool have_format; + SpaAudioRawFormat query_format; + SpaAudioRawFormat current_format; + + SpaVolumePort ports[2]; + SpaBuffer *input_buffer; +}; + +static const double default_volume = 1.0; +static const double min_volume = 0.0; +static const double max_volume = 10.0; +static const bool default_mute = false; + +static const SpaPropRangeInfo volume_range[] = { + { "min", "Minimum value", sizeof (double), &min_volume }, + { "max", "Maximum value", sizeof (double), &max_volume }, +}; + +enum { + PROP_ID_VOLUME, + PROP_ID_MUTE, + PROP_ID_LAST, +}; + +static const SpaPropInfo prop_info[] = +{ + { PROP_ID_VOLUME, "volume", "The Volume factor", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_DOUBLE, sizeof (double), + sizeof (double), &default_volume, + SPA_PROP_RANGE_TYPE_MIN_MAX, 2, volume_range, + NULL, + offsetof (SpaVolumeProps, volume), + 0, 0, + NULL }, + { PROP_ID_MUTE, "mute", "Mute", + SPA_PROP_FLAG_READWRITE, + SPA_PROP_TYPE_BOOL, sizeof (bool), + sizeof (bool), &default_mute, + SPA_PROP_RANGE_TYPE_NONE, 0, NULL, + NULL, + offsetof (SpaVolumeProps, mute), + 0, 0, + NULL }, +}; + +static void +reset_volume_props (SpaVolumeProps *props) +{ + props->volume = default_volume; + props->mute = default_mute; +} + +static SpaResult +spa_volume_node_get_props (SpaHandle *handle, + SpaProps **props) +{ + SpaVolume *this = (SpaVolume *) handle; + + if (handle == NULL || props == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + memcpy (&this->tmp_props, &this->props, sizeof (this->tmp_props)); + *props = &this->tmp_props.props; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_volume_node_set_props (SpaHandle *handle, + const SpaProps *props) +{ + SpaVolume *this = (SpaVolume *) handle; + SpaVolumeProps *p = &this->props; + SpaResult res; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (props == NULL) { + reset_volume_props (p); + return SPA_RESULT_OK; + } + res = spa_props_copy (props, &p->props); + + return res; +} + +static SpaResult +spa_volume_node_send_command (SpaHandle *handle, + SpaCommand *command) +{ + SpaVolume *this = (SpaVolume *) handle; + + if (handle == NULL || command == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + switch (command->type) { + case SPA_COMMAND_INVALID: + return SPA_RESULT_INVALID_COMMAND; + + case SPA_COMMAND_ACTIVATE: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_ACTIVATED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + + case SPA_COMMAND_DEACTIVATE: + if (this->event_cb) { + SpaEvent event; + + event.refcount = 1; + event.notify = NULL; + event.type = SPA_EVENT_TYPE_DEACTIVATED; + event.port_id = -1; + event.data = NULL; + event.size = 0; + + this->event_cb (handle, &event, this->user_data); + } + break; + + case SPA_COMMAND_START: + case SPA_COMMAND_STOP: + case SPA_COMMAND_FLUSH: + case SPA_COMMAND_DRAIN: + case SPA_COMMAND_MARKER: + return SPA_RESULT_NOT_IMPLEMENTED; + } + return SPA_RESULT_OK; +} + +static SpaResult +spa_volume_node_set_event_callback (SpaHandle *handle, + SpaEventCallback event, + void *user_data) +{ + SpaVolume *this = (SpaVolume *) handle; + + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this->event_cb = event; + this->user_data = user_data; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_volume_node_get_n_ports (SpaHandle *handle, + unsigned int *n_input_ports, + unsigned int *max_input_ports, + unsigned int *n_output_ports, + unsigned int *max_output_ports) +{ + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (n_input_ports) + *n_input_ports = 1; + if (n_output_ports) + *n_output_ports = 1; + if (max_input_ports) + *max_input_ports = 1; + if (max_output_ports) + *max_output_ports = 1; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_volume_node_get_port_ids (SpaHandle *handle, + unsigned int n_input_ports, + uint32_t *input_ids, + unsigned int n_output_ports, + uint32_t *output_ids) +{ + if (handle == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (n_input_ports > 0 && input_ids) + input_ids[0] = 0; + if (n_output_ports > 0 && output_ids) + output_ids[0] = 1; + + return SPA_RESULT_OK; +} + + +static SpaResult +spa_volume_node_add_port (SpaHandle *handle, + SpaDirection direction, + uint32_t *port_id) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_volume_node_remove_port (SpaHandle *handle, + uint32_t port_id) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_volume_node_enum_port_formats (SpaHandle *handle, + uint32_t port_id, + unsigned int index, + SpaFormat **format) +{ + SpaVolume *this = (SpaVolume *) handle; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id != 0) + return SPA_RESULT_INVALID_PORT; + + switch (index) { + case 0: + spa_audio_raw_format_init (&this->query_format); + break; + default: + return SPA_RESULT_ENUM_END; + } + *format = &this->query_format.format; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_volume_node_set_port_format (SpaHandle *handle, + uint32_t port_id, + int test_only, + const SpaFormat *format) +{ + SpaVolume *this = (SpaVolume *) handle; + SpaResult res; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id >= 2) + return SPA_RESULT_INVALID_PORT; + + if (format == NULL) { + this->ports[port_id].have_format = false; + return SPA_RESULT_OK; + } + + if ((res = spa_audio_raw_format_parse (format, &this->current_format)) < 0) + return res; + + this->ports[port_id].have_format = true; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_volume_node_get_port_format (SpaHandle *handle, + uint32_t port_id, + const SpaFormat **format) +{ + SpaVolume *this = (SpaVolume *) handle; + + if (handle == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id >= 2) + return SPA_RESULT_INVALID_PORT; + + if (!this->ports[port_id].have_format) + return SPA_RESULT_NO_FORMAT; + + *format = &this->current_format.format; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_volume_node_get_port_info (SpaHandle *handle, + uint32_t port_id, + const SpaPortInfo **info) +{ + SpaVolume *this = (SpaVolume *) handle; + + if (handle == NULL || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id >= 2) + return SPA_RESULT_INVALID_PORT; + + *info = &this->ports[port_id].info; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_volume_node_get_port_props (SpaHandle *handle, + uint32_t port_id, + SpaProps **props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_volume_node_set_port_props (SpaHandle *handle, + uint32_t port_id, + const SpaProps *props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static SpaResult +spa_volume_node_get_port_status (SpaHandle *handle, + uint32_t port_id, + const SpaPortStatus **status) +{ + SpaVolume *this = (SpaVolume *) handle; + + if (handle == NULL || status == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (port_id >= 2) + return SPA_RESULT_INVALID_PORT; + + if (!this->ports[port_id].have_format) + return SPA_RESULT_NO_FORMAT; + + *status = &this->ports[port_id].status; + + return SPA_RESULT_OK; +} + +static SpaResult +spa_volume_node_push_port_input (SpaHandle *handle, + unsigned int n_info, + SpaInputInfo *info) +{ + SpaVolume *this = (SpaVolume *) handle; + SpaBuffer *buffer; + SpaEvent *event; + unsigned int i; + bool have_error = false; + bool have_enough = false; + + if (handle == NULL || n_info == 0 || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + for (i = 0; i < n_info; i++) { + if (info[i].port_id != 0) { + info[i].status = SPA_RESULT_INVALID_PORT; + have_error = true; + continue; + } + + event = info[i].event; + buffer = info[i].buffer; + + if (buffer == NULL && event == NULL) { + info[i].status = SPA_RESULT_INVALID_ARGUMENTS; + have_error = true; + continue; + } + + if (buffer) { + if (!this->ports[0].have_format) { + info[i].status = SPA_RESULT_NO_FORMAT; + have_error = true; + continue; + } + + if (this->input_buffer != NULL) { + info[i].status = SPA_RESULT_HAVE_ENOUGH_INPUT; + have_enough = true; + continue; + } + this->input_buffer = spa_buffer_ref (buffer); + + this->ports[0].status.flags &= ~SPA_PORT_STATUS_FLAG_NEED_INPUT; + this->ports[1].status.flags |= SPA_PORT_STATUS_FLAG_HAVE_OUTPUT; + } + if (event) { + switch (event->type) { + default: + break; + } + } + info[i].status = SPA_RESULT_OK; + } + if (have_error) + return SPA_RESULT_ERROR; + if (have_enough) + return SPA_RESULT_HAVE_ENOUGH_INPUT; + + return SPA_RESULT_OK; +} + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +static SpaResult +spa_volume_node_pull_port_output (SpaHandle *handle, + unsigned int n_info, + SpaOutputInfo *info) +{ + SpaVolume *this = (SpaVolume *) handle; + unsigned int si, di, i, n_samples, n_bytes, soff, doff ; + SpaBuffer *sbuf, *dbuf; + SpaData *sd, *dd; + uint16_t *src, *dst; + double volume; + + if (handle == NULL || n_info == 0 || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + if (info->port_id != 1) + return SPA_RESULT_INVALID_PORT; + + if (!this->ports[1].have_format) + return SPA_RESULT_NO_FORMAT; + + if (this->input_buffer == NULL) + return SPA_RESULT_NEED_MORE_INPUT; + + volume = this->props.volume; + + sbuf = this->input_buffer; + dbuf = info->buffer ? info->buffer : this->input_buffer; + + si = di = 0; + soff = doff = 0; + + while (true) { + if (si == sbuf->n_datas || di == dbuf->n_datas) + break; + + sd = &sbuf->datas[si]; + dd = &dbuf->datas[di]; + + if (sd->type != SPA_DATA_TYPE_MEMPTR) { + si++; + continue; + } + if (dd->type != SPA_DATA_TYPE_MEMPTR) { + di++; + continue; + } + src = (uint16_t*) ((uint8_t*)sd->data + soff); + dst = (uint16_t*) ((uint8_t*)dd->data + doff); + + n_bytes = MIN (sd->size - soff, dd->size - doff); + n_samples = n_bytes / sizeof (uint16_t); + + for (i = 0; i < n_samples; i++) + *src++ = *dst++ * volume; + + soff += n_bytes; + doff += n_bytes; + + if (soff >= sd->size) { + si++; + soff = 0; + } + if (doff >= dd->size) { + di++; + doff = 0; + } + } + + if (sbuf != dbuf) + spa_buffer_unref (sbuf); + + this->input_buffer = NULL; + info->buffer = dbuf; + + this->ports[0].status.flags |= SPA_PORT_STATUS_FLAG_NEED_INPUT; + this->ports[1].status.flags &= ~SPA_PORT_STATUS_FLAG_HAVE_OUTPUT; + + return SPA_RESULT_OK; +} + +static const SpaNode volume_node = { + sizeof (SpaNode), + spa_volume_node_get_props, + spa_volume_node_set_props, + spa_volume_node_send_command, + spa_volume_node_set_event_callback, + spa_volume_node_get_n_ports, + spa_volume_node_get_port_ids, + spa_volume_node_add_port, + spa_volume_node_remove_port, + spa_volume_node_enum_port_formats, + spa_volume_node_set_port_format, + spa_volume_node_get_port_format, + spa_volume_node_get_port_info, + spa_volume_node_get_port_props, + spa_volume_node_set_port_props, + spa_volume_node_get_port_status, + spa_volume_node_push_port_input, + spa_volume_node_pull_port_output, +}; + +static SpaResult +spa_volume_get_interface (SpaHandle *handle, + uint32_t interface_id, + const void **interface) +{ + if (handle == NULL || interface == 0) + return SPA_RESULT_INVALID_ARGUMENTS; + + switch (interface_id) { + case SPA_INTERFACE_ID_NODE: + *interface = &volume_node; + break; + default: + return SPA_RESULT_UNKNOWN_INTERFACE; + + } + return SPA_RESULT_OK; +} + +SpaHandle * +spa_volume_new (void) +{ + SpaHandle *handle; + SpaVolume *this; + + handle = calloc (1, sizeof (SpaVolume)); + handle->get_interface = spa_volume_get_interface; + + this = (SpaVolume *) handle; + this->props.props.n_prop_info = PROP_ID_LAST; + this->props.props.prop_info = prop_info; + this->props.props.set_prop = spa_props_generic_set_prop; + this->props.props.get_prop = spa_props_generic_get_prop; + reset_volume_props (&this->props); + + this->ports[0].info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFER | + SPA_PORT_INFO_FLAG_IN_PLACE; + this->ports[1].info.flags = SPA_PORT_INFO_FLAG_CAN_GIVE_BUFFER | + SPA_PORT_INFO_FLAG_CAN_USE_BUFFER | + SPA_PORT_INFO_FLAG_NO_REF; + + this->ports[0].status.flags = SPA_PORT_STATUS_FLAG_NEED_INPUT; + this->ports[1].status.flags = SPA_PORT_STATUS_FLAG_NONE; + + return handle; +} diff --git a/spa/tests/meson.build b/spa/tests/meson.build new file mode 100644 index 000000000..0cce8cc0b --- /dev/null +++ b/spa/tests/meson.build @@ -0,0 +1,4 @@ +executable('test-mixer', 'test-mixer.c', + include_directories : inc, + dependencies : [dl_lib], + install : false) diff --git a/spa/tests/test-mixer.c b/spa/tests/test-mixer.c new file mode 100644 index 000000000..b9a6ea2ac --- /dev/null +++ b/spa/tests/test-mixer.c @@ -0,0 +1,353 @@ +/* Spa + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +#include +#include + +typedef struct { + SpaHandle *sink; + const SpaNode *sink_node; + SpaHandle *mix; + const SpaNode *mix_node; + uint32_t mix_ports[2]; + SpaHandle *source1; + const SpaNode *source1_node; + SpaHandle *source2; + const SpaNode *source2_node; +} AppData; + +static SpaResult +make_node (SpaHandle **handle, const SpaNode **node, const char *lib, const char *name) +{ + SpaResult res; + void *hnd; + SpaEnumHandleFactoryFunc enum_func; + unsigned int i; + + if ((hnd = dlopen (lib, RTLD_NOW)) == NULL) { + printf ("can't load %s: %s\n", lib, dlerror()); + return SPA_RESULT_ERROR; + } + if ((enum_func = dlsym (hnd, "spa_enum_handle_factory")) == NULL) { + printf ("can't find enum function\n"); + return SPA_RESULT_ERROR; + } + + for (i = 0; ;i++) { + const SpaHandleFactory *factory; + const void *iface; + + if ((res = enum_func (i, &factory)) < 0) { + if (res != SPA_RESULT_ENUM_END) + printf ("can't enumerate factories: %d\n", res); + break; + } + if (strcmp (factory->name, name)) + continue; + + if ((res = factory->instantiate (factory, handle)) < 0) { + printf ("can't make factory instance: %d\n", res); + return res; + } + if ((res = (*handle)->get_interface (*handle, SPA_INTERFACE_ID_NODE, &iface)) < 0) { + printf ("can't get interface %d\n", res); + return res; + } + *node = iface; + return SPA_RESULT_OK; + } + return SPA_RESULT_ERROR; +} + +static void +on_mix_event (SpaHandle *handle, SpaEvent *event, void *user_data) +{ + AppData *data = user_data; + + switch (event->type) { + case SPA_EVENT_TYPE_PULL_INPUT: + { + SpaBuffer *buf; + SpaInputInfo iinfo; + SpaOutputInfo oinfo; + SpaResult res; + + buf = event->data; + + oinfo.port_id = 0; + oinfo.flags = SPA_OUTPUT_FLAG_NONE; + oinfo.buffer = buf; + oinfo.event = NULL; + + printf ("pull source %p\n", buf); + if (event->port_id == data->mix_ports[0]) { + if ((res = data->source1_node->pull_port_output (data->source1, 1, &oinfo)) < 0) + printf ("got error %d\n", res); + } else { + if ((res = data->source2_node->pull_port_output (data->source2, 1, &oinfo)) < 0) + printf ("got error %d\n", res); + } + + iinfo.port_id = event->port_id; + iinfo.flags = SPA_INPUT_FLAG_NONE; + iinfo.buffer = oinfo.buffer; + iinfo.event = oinfo.event; + + printf ("push mixer %p\n", iinfo.buffer); + if ((res = data->mix_node->push_port_input (data->mix, 1, &iinfo)) < 0) + printf ("got error from mixer %d\n", res); + break; + } + default: + printf ("got event %d\n", event->type); + break; + } +} + +static void +on_sink_event (SpaHandle *handle, SpaEvent *event, void *user_data) +{ + AppData *data = user_data; + + switch (event->type) { + case SPA_EVENT_TYPE_PULL_INPUT: + { + SpaBuffer *buf; + SpaInputInfo iinfo; + SpaOutputInfo oinfo; + SpaResult res; + + buf = event->data; + + oinfo.port_id = 0; + oinfo.flags = SPA_OUTPUT_FLAG_PULL; + oinfo.buffer = buf; + oinfo.event = NULL; + + printf ("pull mixer %p\n", buf); + if ((res = data->mix_node->pull_port_output (data->mix, 1, &oinfo)) < 0) + printf ("got error %d\n", res); + + iinfo.port_id = event->port_id; + iinfo.flags = SPA_INPUT_FLAG_NONE; + iinfo.buffer = oinfo.buffer; + iinfo.event = oinfo.event; + + printf ("push sink %p\n", iinfo.buffer); + if ((res = data->sink_node->push_port_input (data->sink, 1, &iinfo)) < 0) + printf ("got error %d\n", res); + break; + } + default: + printf ("got event %d\n", event->type); + break; + } +} + +static SpaResult +make_nodes (AppData *data) +{ + SpaResult res; + SpaProps *props; + SpaPropValue value; + + if ((res = make_node (&data->sink, &data->sink_node, "plugins/alsa/libspa-alsa.so", "alsa-sink")) < 0) { + printf ("can't create alsa-sink: %d\n", res); + return res; + } + data->sink_node->set_event_callback (data->sink, on_sink_event, data); + + if ((res = data->sink_node->get_props (data->sink, &props)) < 0) + printf ("got get_props error %d\n", res); + + value.type = SPA_PROP_TYPE_STRING; + value.size = strlen ("hw:1")+1; + value.value = "hw:1"; + props->set_prop (props, spa_props_index_for_name (props, "device"), &value); + + if ((res = data->sink_node->set_props (data->sink, props)) < 0) + printf ("got set_props error %d\n", res); + + + if ((res = make_node (&data->mix, &data->mix_node, "plugins/audiomixer/libspa-audiomixer.so", "audiomixer")) < 0) { + printf ("can't create audiomixer: %d\n", res); + return res; + } + data->mix_node->set_event_callback (data->mix, on_mix_event, data); + + if ((res = make_node (&data->source1, &data->source1_node, "plugins/audiotestsrc/libspa-audiotestsrc.so", "audiotestsrc")) < 0) { + printf ("can't create audiotestsrc: %d\n", res); + return res; + } + if ((res = make_node (&data->source2, &data->source2_node, "plugins/audiotestsrc/libspa-audiotestsrc.so", "audiotestsrc")) < 0) { + printf ("can't create audiotestsrc: %d\n", res); + return res; + } + return res; +} + +static SpaResult +negotiate_formats (AppData *data) +{ + SpaResult res; + SpaFormat *format; + SpaProps *props; + uint32_t val; + SpaPropValue value; + + if ((res = data->sink_node->enum_port_formats (data->sink, 0, 0, &format)) < 0) + return res; + + props = &format->props; + + value.type = SPA_PROP_TYPE_UINT32; + value.size = sizeof (uint32_t); + value.value = &val; + + val = SPA_AUDIO_FORMAT_S16LE; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_AUDIO_FORMAT), &value)) < 0) + return res; + val = 1; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_AUDIO_LAYOUT), &value)) < 0) + return res; + val = 44100; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_AUDIO_RATE), &value)) < 0) + return res; + val = 2; + if ((res = props->set_prop (props, spa_props_index_for_id (props, SPA_PROP_ID_AUDIO_CHANNELS), &value)) < 0) + return res; + + if ((res = data->sink_node->set_port_format (data->sink, 0, 0, format)) < 0) + return res; + + if ((res = data->mix_node->set_port_format (data->mix, 0, 0, format)) < 0) + return res; + + if ((res = data->mix_node->add_port (data->mix, SPA_DIRECTION_INPUT, &data->mix_ports[0])) < 0) + return res; + + if ((res = data->mix_node->set_port_format (data->mix, data->mix_ports[0], 0, format)) < 0) + return res; + + if ((res = data->source1_node->set_port_format (data->source1, 0, 0, format)) < 0) + return res; + + if ((res = data->mix_node->add_port (data->mix, SPA_DIRECTION_INPUT, &data->mix_ports[1])) < 0) + return res; + + if ((res = data->mix_node->set_port_format (data->mix, data->mix_ports[1], 0, format)) < 0) + return res; + + if ((res = data->source2_node->set_port_format (data->source2, 0, 0, format)) < 0) + return res; + + + return SPA_RESULT_OK; +} + +static SpaResult +start_nodes (AppData *data) +{ + SpaResult res; + SpaCommand cmd; + + cmd.type = SPA_COMMAND_ACTIVATE; + if ((res = data->sink_node->send_command (data->sink, &cmd)) < 0) + return res; + if ((res = data->mix_node->send_command (data->mix, &cmd)) < 0) + return res; + if ((res = data->source1_node->send_command (data->source1, &cmd)) < 0) + return res; + if ((res = data->source2_node->send_command (data->source1, &cmd)) < 0) + return res; + + return res; +} + +static SpaResult +stop_nodes (AppData *data) +{ + SpaResult res; + SpaCommand cmd; + + cmd.type = SPA_COMMAND_DEACTIVATE; + if ((res = data->sink_node->send_command (data->sink, &cmd)) < 0) + return res; + if ((res = data->mix_node->send_command (data->mix, &cmd)) < 0) + return res; + if ((res = data->source1_node->send_command (data->source1, &cmd)) < 0) + return res; + if ((res = data->source2_node->send_command (data->source1, &cmd)) < 0) + return res; + + return res; +} + +static void +run_async_sink (AppData *data) +{ + SpaResult res; + SpaCommand cmd; + + cmd.type = SPA_COMMAND_START; + if ((res = data->sink_node->send_command (data->sink, &cmd)) < 0) + printf ("got error %d\n", res); + + printf ("sleeping for 10 seconds\n"); + sleep (10); + + cmd.type = SPA_COMMAND_STOP; + if ((res = data->sink_node->send_command (data->sink, &cmd)) < 0) + printf ("got error %d\n", res); +} + +int +main (int argc, char *argv[]) +{ + AppData data; + SpaResult res; + + if ((res = make_nodes (&data)) < 0) { + printf ("can't make nodes: %d\n", res); + return -1; + } + if ((res = negotiate_formats (&data)) < 0) { + printf ("can't negotiate nodes: %d\n", res); + return -1; + } + if ((res = start_nodes (&data)) < 0) { + printf ("can't start nodes: %d\n", res); + return -1; + } + + run_async_sink (&data); + + if ((res = stop_nodes (&data)) < 0) { + printf ("can't stop nodes: %d\n", res); + return -1; + } + + return 0; +} diff --git a/spa/tools/meson.build b/spa/tools/meson.build new file mode 100644 index 000000000..ca0f14f4f --- /dev/null +++ b/spa/tools/meson.build @@ -0,0 +1,4 @@ +executable('spa-inspect', 'spa-inspect.c', + include_directories : inc, + dependencies : [dl_lib], + install : true) diff --git a/spa/tools/spa-inspect.c b/spa/tools/spa-inspect.c new file mode 100644 index 000000000..3a76d1190 --- /dev/null +++ b/spa/tools/spa-inspect.c @@ -0,0 +1,255 @@ +/* Pinos + * Copyright (C) 2016 Wim Taymans + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +#include + +static void +print_value (const char *prefix, SpaPropType type, int size, const void *value) +{ + printf ("%s", prefix); + switch (type) { + case SPA_PROP_TYPE_INVALID: + printf ("invalid"); + break; + case SPA_PROP_TYPE_BOOL: + printf ("%s", *(bool *)value ? "true" : "false"); + break; + case SPA_PROP_TYPE_INT8: + printf ("%" PRIi8, *(int8_t *)value); + break; + case SPA_PROP_TYPE_UINT8: + printf ("%" PRIu8, *(uint8_t *)value); + break; + case SPA_PROP_TYPE_INT16: + printf ("%" PRIi16, *(int16_t *)value); + break; + case SPA_PROP_TYPE_UINT16: + printf ("%" PRIu16, *(uint16_t *)value); + break; + case SPA_PROP_TYPE_INT32: + printf ("%" PRIi32, *(int32_t *)value); + break; + case SPA_PROP_TYPE_UINT32: + printf ("%" PRIu32, *(uint32_t *)value); + break; + case SPA_PROP_TYPE_INT64: + printf ("%" PRIi64 "\n", *(int64_t *)value); + break; + case SPA_PROP_TYPE_UINT64: + printf ("%" PRIu64 "\n", *(uint64_t *)value); + break; + case SPA_PROP_TYPE_FLOAT: + printf ("%f", *(float *)value); + break; + case SPA_PROP_TYPE_DOUBLE: + printf ("%g", *(double *)value); + break; + case SPA_PROP_TYPE_STRING: + printf ("%s", (char *)value); + break; + case SPA_PROP_TYPE_POINTER: + printf ("%p", value); + break; + case SPA_PROP_TYPE_FRACTION: + break; + case SPA_PROP_TYPE_BITMASK: + break; + case SPA_PROP_TYPE_BYTES: + break; + default: + break; + } + printf ("\n"); +} + +static void +print_props (const SpaProps *props, int print_ranges) +{ + SpaResult res; + const SpaPropInfo *info; + int i, j; + + for (i = 0; i < props->n_prop_info; i++) { + SpaPropValue value; + + info = &props->prop_info[i]; + + printf ("id:\t\t%d\n", info->id); + printf ("name:\t\t%s\n", info->name); + printf ("description:\t%s\n", info->description); + printf ("flags:\t\t%d\n", info->flags); + printf ("type:\t\t%d\n", info->type); + printf ("maxsize:\t%zu\n", info->maxsize); + + res = props->get_prop (props, info->id, &value); + if (res == SPA_RESULT_PROPERTY_UNSET) + printf ("value:\t\tunset\n"); + else + print_value ("value:\t\t", value.type, value.size, value.value); + + if (print_ranges) { + if (info->default_value) + print_value ("default:\t", info->type, info->default_size, info->default_value); + else + printf ("default:\tunset\n"); + + printf ("range_type:\t%d\n", info->range_type); + if (info->range_values) { + for (j = 0; j < info->n_range_values; j++) { + const SpaPropRangeInfo *rinfo = &info->range_values[j]; + printf (" name:\t%s\n", rinfo->name); + printf (" description:\t%s\n", rinfo->description); + print_value (" value:\t", info->type, rinfo->size, rinfo->value); + } + } + } + if (info->tags) { + for (j = 0; info->tags[j]; j++) { + printf ("tag:\t%s\n", info->tags[j]); + } + } + } +} + +static void +print_format (const SpaFormat *format, int print_ranges) +{ + printf ("media-type:\t\t%d\n", format->media_type); + printf ("media-subtype:\t\t%d\n", format->media_subtype); + print_props (&format->props, print_ranges); +} + +static void +inspect_node (const SpaNode *node, SpaHandle *handle) +{ + SpaResult res; + SpaProps *props; + unsigned int n_input, max_input, n_output, max_output, i; + SpaFormat *format; + + if ((res = node->get_props (handle, &props)) < 0) + printf ("can't get properties: %d\n", res); + else + print_props (props, 1); + + if ((res = node->get_n_ports (handle, &n_input, &max_input, &n_output, &max_output)) < 0) + printf ("can't get n_ports: %d\n", res); + else + printf ("supported ports %d %d %d %d\n", n_input, max_input, n_output, max_output); + + for (i = 0; ; i++) { + if ((res = node->enum_port_formats (handle, 0, i, &format)) < 0) { + if (res != SPA_RESULT_ENUM_END) + printf ("got error %d\n", res); + break; + } + print_format (format, 1); + } + if ((res = node->get_port_props (handle, 0, &props)) < 0) + printf ("get_port_props error: %d\n", res); + else + print_props (props, 1); +} + +static void +inspect_factory (const SpaHandleFactory *factory) +{ + SpaResult res; + unsigned int i; + SpaHandle *handle; + const void *interface; + + printf ("factory name:\t\t'%s'\n", factory->name); + printf ("factory info:\n"); + if (factory->info) + print_props (factory->info, 1); + else + printf (" none\n"); + + if ((res = factory->instantiate (factory, &handle)) < 0) { + printf ("can't make factory instance: %d\n", res); + return; + } + + printf ("factory interfaces:\n"); + + for (i = 0; ; i++) { + const SpaInterfaceInfo *info; + + if ((res = factory->enum_interface_info (factory, i, &info)) < 0) { + if (res == SPA_RESULT_ENUM_END) + break; + else + printf ("can't enumerate interfaces: %d\n", res); + } + printf (" interface: %d, (%d) '%s' : '%s'\n", i, info->interface_id, info->name, info->description); + + if ((res = handle->get_interface (handle, info->interface_id, &interface)) < 0) { + printf ("can't get interface: %d\n", res); + continue; + } + + switch (info->interface_id) { + case SPA_INTERFACE_ID_NODE: + inspect_node (interface, handle); + break; + default: + printf ("skipping unknown interface\n"); + break; + } + } +} + +int +main (int argc, char *argv[]) +{ + SpaResult res; + void *handle; + SpaEnumHandleFactoryFunc enum_func; + unsigned int i; + + if ((handle = dlopen (argv[1], RTLD_NOW)) == NULL) { + printf ("can't load %s\n", argv[1]); + return -1; + } + if ((enum_func = dlsym (handle, "spa_enum_handle_factory")) == NULL) { + printf ("can't find function\n"); + return -1; + } + + for (i = 0; ;i++) { + const SpaHandleFactory *factory; + + if ((res = enum_func (i, &factory)) < 0) { + if (res == SPA_RESULT_ENUM_END) + break; + else + printf ("can't enumerate factories\n"); + } + inspect_factory (factory); + } + + return 0; +}