add media session example beginnings

Add an example media session that runs as a separate program instead
of a module
This commit is contained in:
Wim Taymans 2018-08-02 11:25:27 +02:00
parent 8f8ed7270a
commit 17cc9d2039
5 changed files with 655 additions and 2 deletions

View file

@ -8,5 +8,6 @@ load-module libpipewire-module-spa-monitor bluez5/libspa-bluez5 bluez5-monitor b
#load-module libpipewire-module-spa-node videotestsrc/libspa-videotestsrc videotestsrc videotestsrc Spa:POD:Object:Props:patternType=Spa:POD:Object:Props:patternType:snow #load-module libpipewire-module-spa-node videotestsrc/libspa-videotestsrc videotestsrc videotestsrc Spa:POD:Object:Props:patternType=Spa:POD:Object:Props:patternType:snow
load-module libpipewire-module-client-node load-module libpipewire-module-client-node
load-module libpipewire-module-flatpak load-module libpipewire-module-flatpak
load-module libpipewire-module-media-session #load-module libpipewire-module-media-session
load-module libpipewire-module-audio-dsp
load-module libpipewire-module-link-factory load-module libpipewire-module-link-factory

View file

@ -0,0 +1,419 @@
/* PipeWire
* Copyright (C) 2018 Wim Taymans <wim.taymans@gmail.com>
*
* 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 <string.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include "config.h"
#include <spa/node/node.h>
#include <spa/utils/hook.h>
#include <spa/param/audio/format-utils.h>
#include <spa/lib/pod.h>
#include <spa/lib/debug.h>
#include "pipewire/core.h"
#include "pipewire/control.h"
#include "pipewire/link.h"
#include "pipewire/log.h"
#include "pipewire/module.h"
#include "pipewire/type.h"
#include "pipewire/private.h"
#define DEFAULT_CHANNELS 2
#define DEFAULT_SAMPLE_RATE 48000
#define MIN_QUANTUM_SIZE 64
#define MAX_QUANTUM_SIZE 1024
struct type {
struct spa_type_media_type media_type;
struct spa_type_media_subtype media_subtype;
struct spa_type_format_audio format_audio;
struct spa_type_audio_format audio_format;
struct spa_type_media_subtype_audio media_subtype_audio;
};
static inline void init_type(struct type *type, struct spa_type_map *map)
{
spa_type_media_type_map(map, &type->media_type);
spa_type_media_subtype_map(map, &type->media_subtype);
spa_type_format_audio_map(map, &type->format_audio);
spa_type_audio_format_map(map, &type->audio_format);
spa_type_media_subtype_audio_map(map, &type->media_subtype_audio);
}
struct impl {
struct type type;
struct timespec now;
struct pw_main_loop *loop;
struct pw_core *core;
struct pw_type *t;
struct pw_remote *remote;
struct spa_hook remote_listener;
struct pw_core_proxy *core_proxy;
struct spa_hook core_listener;
struct pw_registry_proxy *registry_proxy;
struct spa_hook registry_listener;
struct spa_list stream_list;
struct spa_list session_list;
uint32_t seq;
};
struct session {
struct spa_list l;
uint32_t id;
struct impl *impl;
enum pw_direction direction;
uint64_t plugged;
struct pw_node_proxy *node;
struct spa_hook node_listener;
struct pw_node_info *info;
struct pw_port_proxy *node_port;
struct pw_node_proxy *dsp;
struct spa_hook dsp_listener;
struct pw_port_proxy *dsp_port;
struct pw_link_proxy *link;
bool enabled;
bool busy;
bool exclusive;
bool need_dsp;
struct spa_list stream_list;
};
struct stream {
struct spa_list l;
struct impl *impl;
uint32_t id;
uint32_t parent_id;
enum pw_direction direction;
struct pw_node_proxy *node_proxy;
struct spa_hook node_listener;
struct pw_node_info *info;
struct session *session;
uint32_t sample_rate;
uint32_t quantum_size;
struct spa_list links;
};
struct port {
struct spa_list l;
struct impl *impl;
uint32_t id;
uint32_t parent_id;
enum pw_direction direction;
struct node *parent;
struct pw_port_proxy *port_proxy;
struct spa_hook port_listener;
};
struct link {
struct spa_list l;
struct node *node;
struct pw_link_proxy *link_proxy;
struct spa_hook link_listener;
};
static void schedule_rescan(struct impl *impl)
{
pw_core_proxy_sync(impl->core_proxy, ++impl->seq);
}
static void stream_node_event_info(void *object, struct pw_node_info *info)
{
struct stream *s = object;
pw_log_debug("update %d", s->id);
s->info = pw_node_info_update(s->info, info);
}
static const struct pw_node_proxy_events stream_node_events = {
PW_VERSION_NODE_PROXY_EVENTS,
.info = stream_node_event_info,
};
static void sess_node_event_info(void *object, struct pw_node_info *info)
{
struct session *s = object;
pw_log_debug("update %d", s->id);
s->info = pw_node_info_update(s->info, info);
}
static const struct pw_node_proxy_events sess_node_events = {
PW_VERSION_NODE_PROXY_EVENTS,
.info = sess_node_event_info,
};
static int
handle_node(struct impl *impl, uint32_t id, uint32_t parent_id,
uint32_t type, const struct spa_dict *props)
{
const char *str;
bool need_dsp = false;
enum pw_direction direction;
struct pw_proxy *p;
if (props == NULL)
return 0;
if ((str = spa_dict_lookup(props, "media.class")) == NULL)
return 0;
if (strstr(str, "Stream/") == str) {
struct stream *stream;
str += strlen("Stream/");
if (strcmp(str, "Playback") == 0)
direction = PW_DIRECTION_OUTPUT;
else if (strcmp(str, "Capture") == 0)
direction = PW_DIRECTION_INPUT;
else
return 0;
p = pw_registry_proxy_bind(impl->registry_proxy,
id, type, PW_VERSION_NODE,
sizeof(struct stream));
stream = pw_proxy_get_user_data(p);
stream->impl = impl;
stream->id = id;
stream->parent_id = parent_id;
stream->direction = direction;
stream->node_proxy = (struct pw_node_proxy *) p;
pw_proxy_add_proxy_listener(p, &stream->node_listener, &stream_node_events, stream);
spa_list_append(&impl->stream_list, &stream->l);
pw_log_debug("new stream %p for node %d", stream, id);
}
else {
struct session *sess;
if (strstr(str, "Audio/") == str) {
need_dsp = true;
str += strlen("Audio/");
}
else if (strstr(str, "Video/") == str) {
str += strlen("Video/");
}
else
return 0;
if (strcmp(str, "Sink") == 0)
direction = PW_DIRECTION_OUTPUT;
else if (strcmp(str, "Source") == 0)
direction = PW_DIRECTION_INPUT;
else
return 0;
p = pw_registry_proxy_bind(impl->registry_proxy,
id, type, PW_VERSION_NODE,
sizeof(struct session));
sess = pw_proxy_get_user_data(p);
sess->impl = impl;
sess->direction = direction;
sess->id = id;
sess->need_dsp = need_dsp;
pw_proxy_add_proxy_listener(p, &sess->node_listener, &sess_node_events, sess);
spa_list_init(&sess->stream_list);
spa_list_append(&impl->session_list, &sess->l);
pw_log_debug("new session %p for node %d", sess, id);
}
return 1;
}
static int
handle_port(struct impl *impl, uint32_t id, uint32_t parent_id, uint32_t type,
const struct spa_dict *props)
{
struct port *port;
struct pw_proxy *p;
p = pw_registry_proxy_bind(impl->registry_proxy,
id, type, PW_VERSION_PORT,
sizeof(struct port));
port = pw_proxy_get_user_data(p);
port->impl = impl;
port->id = id;
port->parent_id = parent_id;
pw_log_debug("new port %p for node %d", port, parent_id);
return 0;
}
static void
registry_global(void *data,uint32_t id, uint32_t parent_id,
uint32_t permissions, uint32_t type, uint32_t version,
const struct spa_dict *props)
{
struct impl *impl = data;
struct pw_type *t = impl->t;
clock_gettime(CLOCK_MONOTONIC, &impl->now);
if (type == t->node) {
handle_node(impl, id, parent_id, type, props);
}
else if (type == t->port) {
handle_port(impl, id, parent_id, type, props);
}
schedule_rescan(impl);
}
static void
registry_global_remove(void *data,uint32_t id)
{
}
static const struct pw_registry_proxy_events registry_events = {
PW_VERSION_REGISTRY_PROXY_EVENTS,
.global = registry_global,
.global_remove = registry_global_remove,
};
static void rescan_session(struct impl *impl)
{
struct session *sess;
struct pw_type *t = impl->t;
pw_log_debug("rescan session");
spa_list_for_each(sess, &impl->session_list, l) {
if (sess->need_dsp && sess->dsp == NULL) {
struct pw_properties *props;
if (sess->info->props == NULL)
continue;
props = pw_properties_new_dict(sess->info->props);
pw_properties_setf(props, "audio-dsp.direction", "%d", sess->direction);
pw_properties_setf(props, "audio-dsp.channels", "2");
pw_properties_setf(props, "audio-dsp.rate", "48000");
pw_properties_setf(props, "audio-dsp.maxbuffer", "8192");
sess->dsp = pw_core_proxy_create_object(impl->core_proxy,
"audio-dsp",
t->node,
PW_VERSION_NODE,
&props->dict,
0);
}
}
}
static void on_state_changed(void *_data, enum pw_remote_state old, enum pw_remote_state state, const char *error)
{
struct impl *impl = _data;
switch (state) {
case PW_REMOTE_STATE_ERROR:
printf("remote error: %s\n", error);
pw_main_loop_quit(impl->loop);
break;
case PW_REMOTE_STATE_CONNECTED:
impl->core_proxy = pw_remote_get_core_proxy(impl->remote);
impl->registry_proxy = pw_core_proxy_get_registry(impl->core_proxy,
impl->t->registry,
PW_VERSION_REGISTRY, 0);
pw_registry_proxy_add_listener(impl->registry_proxy,
&impl->registry_listener,
&registry_events, impl);
schedule_rescan(impl);
break;
default:
printf("remote state: \"%s\"\n", pw_remote_state_as_string(state));
break;
}
}
static void remote_sync_reply(void *data, uint32_t seq)
{
struct impl *impl = data;
pw_log_debug("done %d", seq);
if (impl->seq == seq)
rescan_session(impl);
}
static const struct pw_remote_events remote_events = {
PW_VERSION_REMOTE_EVENTS,
.state_changed = on_state_changed,
.sync_reply = remote_sync_reply
};
int main(int argc, char *argv[])
{
struct impl impl = { 0, };
pw_init(&argc, &argv);
impl.loop = pw_main_loop_new(NULL);
impl.core = pw_core_new(pw_main_loop_get_loop(impl.loop), NULL);
impl.t = pw_core_get_type(impl.core);
impl.remote = pw_remote_new(impl.core, NULL, 0);
init_type(&impl.type, impl.t->map);
spa_list_init(&impl.session_list);
clock_gettime(CLOCK_MONOTONIC, &impl.now);
pw_remote_add_listener(impl.remote, &impl.remote_listener, &remote_events, &impl);
pw_remote_connect(impl.remote);
pw_main_loop_run(impl.loop);
pw_core_destroy(impl.core);
pw_main_loop_destroy(impl.loop);
return 0;
}

View file

@ -20,6 +20,12 @@ executable('export-spa',
dependencies : [pipewire_dep, mathlib], dependencies : [pipewire_dep, mathlib],
) )
executable('media-session',
'media-session.c',
install: false,
dependencies : [pipewire_dep, mathlib],
)
if sdl_dep.found() if sdl_dep.found()
executable('video-play', executable('video-play',
'video-play.c', 'video-play.c',

View file

@ -72,7 +72,20 @@ pipewire_module_protocol_native = shared_library('pipewire-module-protocol-nativ
dependencies : [mathlib, dl_lib, pipewire_dep], dependencies : [mathlib, dl_lib, pipewire_dep],
) )
pipewire_module_audio_session = shared_library('pipewire-module-media-session', pipewire_module_audio_dsp = shared_library('pipewire-module-audio-dsp',
[ 'module-audio-dsp.c',
'module-media-session/audio-dsp.c',
'module-media-session/floatmix.c',
'spa/spa-node.c' ],
c_args : pipewire_module_c_args,
include_directories : [configinc, spa_inc],
link_with : spalib,
install : true,
install_dir : modules_install_dir,
dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
)
pipewire_module_media_session = shared_library('pipewire-module-media-session',
[ 'module-media-session.c', [ 'module-media-session.c',
'module-media-session/audio-dsp.c', 'module-media-session/audio-dsp.c',
'module-media-session/floatmix.c', 'module-media-session/floatmix.c',

View file

@ -0,0 +1,214 @@
/* PipeWire
* Copyright (C) 2018 Wim Taymans <wim.taymans@gmail.com>
*
* 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 <string.h>
#include <stdio.h>
#include <errno.h>
#include <dlfcn.h>
#include "config.h"
#include "pipewire/core.h"
#include "pipewire/interfaces.h"
#include "pipewire/log.h"
#include "pipewire/module.h"
#include "module-media-session/audio-dsp.h"
static const struct spa_dict_item module_props[] = {
{ PW_MODULE_PROP_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
{ PW_MODULE_PROP_DESCRIPTION, "Manage audio DSP nodes" },
{ PW_MODULE_PROP_VERSION, PACKAGE_VERSION },
};
struct factory_data {
struct pw_factory *this;
struct pw_properties *properties;
struct pw_module *module;
struct spa_hook module_listener;
};
struct resource_data {
struct factory_data *data;
struct pw_resource *resource;
struct spa_hook resource_listener;
struct pw_node *dsp;
};
static void resource_destroy(void *data)
{
struct resource_data *d = data;
if (d->dsp)
pw_node_destroy(d->dsp);
}
static struct pw_resource_events resource_events =
{
PW_VERSION_RESOURCE_EVENTS,
.destroy = resource_destroy
};
static void *create_object(void *_data,
struct pw_resource *resource,
uint32_t type,
uint32_t version,
struct pw_properties *properties,
uint32_t new_id)
{
struct factory_data *d = _data;
struct resource_data *rd;
struct pw_resource *node_resource;
struct pw_client *client;
int channels, rate, maxbuffer;
const char *str;
enum pw_direction direction;
if (resource == NULL)
goto no_resource;
client = pw_resource_get_client(resource);
node_resource = pw_resource_new(client,
new_id, PW_PERM_RWX, type, version,
sizeof(struct resource_data));
if (node_resource == NULL)
goto no_mem;
rd = pw_resource_get_user_data(node_resource);
rd->data = d;
rd->resource = node_resource;
pw_resource_add_listener(node_resource, &rd->resource_listener, &resource_events, rd);
if ((str = pw_properties_get(properties, "audio-dsp.direction")) == NULL)
goto no_props;
direction = pw_properties_parse_int(str);
if ((str = pw_properties_get(properties, "audio-dsp.channels")) == NULL)
goto no_props;
channels = pw_properties_parse_int(str);
if ((str = pw_properties_get(properties, "audio-dsp.rate")) == NULL)
goto no_props;
rate = pw_properties_parse_int(str);
if ((str = pw_properties_get(properties, "audio-dsp.maxbuffer")) == NULL)
goto no_props;
maxbuffer = pw_properties_parse_int(str);
rd->dsp = pw_audio_dsp_new(pw_module_get_core(d->module),
properties,
direction,
channels, rate, maxbuffer, 0);
if (rd->dsp == NULL)
goto no_mem;
pw_node_register(rd->dsp, client, pw_module_get_global(d->module), NULL);
pw_node_set_active(rd->dsp, true);
return rd->dsp;
no_resource:
pw_log_error("audio-dsp needs a resource");
pw_resource_error(resource, -EINVAL, "no resource");
goto done;
no_props:
pw_log_error("audio-dsp needs a property");
pw_resource_error(resource, -EINVAL, "no property");
goto done;
no_mem:
pw_log_error("can't create node");
pw_resource_error(resource, -ENOMEM, "no memory");
goto done;
done:
if (properties)
pw_properties_free(properties);
return NULL;
}
static const struct pw_factory_implementation impl_factory = {
PW_VERSION_FACTORY_IMPLEMENTATION,
.create_object = create_object,
};
static void module_destroy(void *data)
{
struct factory_data *d = data;
spa_hook_remove(&d->module_listener);
if (d->properties)
pw_properties_free(d->properties);
pw_factory_destroy(d->this);
}
static const struct pw_module_events module_events = {
PW_VERSION_MODULE_EVENTS,
.destroy = module_destroy,
};
static int module_init(struct pw_module *module, struct pw_properties *properties)
{
struct pw_core *core = pw_module_get_core(module);
struct pw_type *t = pw_core_get_type(core);
struct pw_factory *factory;
struct factory_data *data;
factory = pw_factory_new(core,
"audio-dsp",
t->node,
PW_VERSION_NODE,
NULL,
sizeof(*data));
if (factory == NULL)
return -ENOMEM;
data = pw_factory_get_user_data(factory);
data->this = factory;
data->module = module;
data->properties = properties;
pw_log_debug("module %p: new", module);
pw_factory_set_implementation(factory,
&impl_factory,
data);
pw_factory_register(factory, NULL, pw_module_get_global(module), NULL);
pw_module_add_listener(module, &data->module_listener, &module_events, data);
pw_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
return 0;
}
int pipewire__module_init(struct pw_module *module, const char *args)
{
return module_init(module, NULL);
}