You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
8.1 KiB
305 lines
8.1 KiB
/*****************************************************************************
|
|
* @file pipewire.c
|
|
* @brief List of PipeWire sources for VLC media player
|
|
*****************************************************************************
|
|
* Copyright (C) 2024 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Ayush Dey <deyayush6@gmail.com>
|
|
* Thomas Guillem <tguillem@videolan.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <search.h>
|
|
#include <assert.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_services_discovery.h>
|
|
#include <pipewire/pipewire.h>
|
|
#include "audio_output/vlc_pipewire.h"
|
|
|
|
struct services_discovery_sys_t
|
|
{
|
|
struct vlc_pw_context *context;
|
|
struct spa_hook listener;
|
|
void *nodes;
|
|
void *root_card;
|
|
};
|
|
|
|
struct vlc_pw_node
|
|
{
|
|
uint32_t id;
|
|
uint64_t serial;
|
|
input_item_t *item;
|
|
services_discovery_t *sd;
|
|
};
|
|
|
|
struct card
|
|
{
|
|
input_item_t *item;
|
|
services_discovery_t *sd;
|
|
char name[];
|
|
};
|
|
|
|
static void DestroySource (void *data)
|
|
{
|
|
struct vlc_pw_node *node = data;
|
|
|
|
services_discovery_RemoveItem (node->sd, node->item);
|
|
input_item_Release (node->item);
|
|
free (node);
|
|
}
|
|
|
|
static void DestroyCard(void *data)
|
|
{
|
|
struct card *c = data;
|
|
|
|
services_discovery_RemoveItem(c->sd, c->item);
|
|
input_item_Release(c->item);
|
|
free(c);
|
|
}
|
|
|
|
/**
|
|
* Compares two nodes by ID (to support binary search).
|
|
*/
|
|
static int node_by_id(const void *a, const void *b)
|
|
{
|
|
const struct vlc_pw_node *na = a, *nb = b;
|
|
|
|
if (na->id > nb->id)
|
|
return 1;
|
|
if (na->id < nb->id)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int cmpcard (const void *a, const void *b)
|
|
{
|
|
const struct card *ca = a, *cb = b;
|
|
return strcmp(ca->name, cb->name);
|
|
}
|
|
|
|
static input_item_t *AddCard (services_discovery_t *sd, const struct spa_dict *props)
|
|
{
|
|
struct services_discovery_sys_t *sys = sd->p_sys;
|
|
|
|
const char *card_name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
|
|
if (unlikely(card_name == NULL))
|
|
card_name = N_("Generic");
|
|
|
|
struct card *c = malloc(sizeof(*c) + strlen(card_name) + 1);
|
|
if (unlikely(c == NULL))
|
|
return NULL;
|
|
strcpy(c->name, card_name);
|
|
|
|
void **cp = tsearch(c, &sys->root_card, cmpcard);
|
|
if (cp == NULL) /* Out-of-memory */
|
|
{
|
|
free(c);
|
|
return NULL;
|
|
}
|
|
if (*cp != c)
|
|
{
|
|
free(c);
|
|
c = *cp;
|
|
assert(c->item != NULL);
|
|
return c->item;
|
|
}
|
|
|
|
c->item = input_item_NewExt("vlc://nop", c->name,
|
|
INPUT_DURATION_INDEFINITE,
|
|
ITEM_TYPE_NODE, ITEM_LOCAL);
|
|
|
|
if (unlikely(c->item == NULL))
|
|
{
|
|
tdelete(c, &sys->root_card, cmpcard);
|
|
free(c);
|
|
return NULL;
|
|
}
|
|
services_discovery_AddItem(sd, c->item);
|
|
c->sd = sd;
|
|
|
|
return c->item;
|
|
}
|
|
|
|
static void registry_node(services_discovery_t *sd, uint32_t id, uint32_t perms,
|
|
uint32_t version, const struct spa_dict *props)
|
|
{
|
|
struct services_discovery_sys_t *sys = sd->p_sys;
|
|
uint64_t serial;
|
|
char *mrl;
|
|
|
|
if (props == NULL)
|
|
return;
|
|
|
|
const char *class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
|
|
if (class == NULL)
|
|
return;
|
|
|
|
if (strstr(class, "Source") == NULL)
|
|
return; /* Not a source */
|
|
|
|
if (!spa_atou64(spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL), &serial, 0))
|
|
return;
|
|
|
|
const char *name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
|
|
if (unlikely(name == NULL))
|
|
return;
|
|
|
|
if (unlikely(asprintf (&mrl, "pipewire://%s", name) == -1))
|
|
return;
|
|
|
|
const char *desc = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION);
|
|
if (unlikely(desc == NULL))
|
|
desc = "???";
|
|
|
|
vlc_pw_debug(sys->context, "adding %s (%s)", name, desc);
|
|
input_item_t *item = input_item_NewCard (mrl, desc);
|
|
free (mrl);
|
|
if (unlikely(item == NULL))
|
|
return;
|
|
|
|
struct vlc_pw_node *node = malloc(sizeof (*node));
|
|
if (unlikely(node == NULL))
|
|
{
|
|
input_item_Release (item);
|
|
return;
|
|
}
|
|
|
|
node->id = id;
|
|
node->serial = serial;
|
|
node->item = item;
|
|
|
|
struct vlc_pw_node **pp = tsearch(node, &sys->nodes, node_by_id);
|
|
if (unlikely(pp == NULL))
|
|
{ /* Memory allocation error in the tree */
|
|
free(node);
|
|
input_item_Release (item);
|
|
return;
|
|
}
|
|
if (*pp != node)
|
|
{ /* Existing node, update it */
|
|
free(node);
|
|
node = *pp;
|
|
input_item_SetURI (node->item, item->psz_uri);
|
|
input_item_SetName (node->item, item->psz_name);
|
|
input_item_Release (item);
|
|
return;
|
|
}
|
|
|
|
input_item_t *card = AddCard(sd, props);
|
|
services_discovery_AddSubItem(sd, card, item);
|
|
node->sd = sd;
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Global callback.
|
|
*
|
|
* This gets called for every initial, then every new object from the PipeWire
|
|
* server. We can find the usable sources through this.
|
|
*/
|
|
static void registry_global(void *data, uint32_t id, uint32_t perms,
|
|
const char *name, uint32_t version,
|
|
const struct spa_dict *props)
|
|
{
|
|
services_discovery_t *sd = data;
|
|
|
|
if (strcmp(name, PW_TYPE_INTERFACE_Node) == 0)
|
|
registry_node(sd, id, perms, version, props);
|
|
}
|
|
|
|
/**
|
|
* Global removal callback.
|
|
*
|
|
* This gets called when an object disappers. We can detect when a source is unplugged here.
|
|
*/
|
|
static void registry_global_remove(void *data, uint32_t id)
|
|
{
|
|
services_discovery_t *sd = data;
|
|
struct services_discovery_sys_t *sys = sd->p_sys;
|
|
struct vlc_pw_node key = { .id = id };
|
|
struct vlc_pw_node **pp = tfind(&key, &sys->nodes, node_by_id);
|
|
if (pp == NULL)
|
|
return;
|
|
|
|
struct vlc_pw_node *node = *pp;
|
|
tdelete(node, &sys->nodes, node_by_id);
|
|
DestroySource (node);
|
|
}
|
|
|
|
static const struct pw_registry_events events = {
|
|
PW_VERSION_REGISTRY_EVENTS,
|
|
.global = registry_global,
|
|
.global_remove = registry_global_remove
|
|
};
|
|
|
|
static int Open (vlc_object_t *obj)
|
|
{
|
|
services_discovery_t *sd = (services_discovery_t *)obj;
|
|
|
|
struct services_discovery_sys_t *sys = malloc(sizeof (*sys));
|
|
if (unlikely(sys == NULL))
|
|
return VLC_ENOMEM;
|
|
|
|
sys->context = vlc_pw_connect(obj, "services discovery");
|
|
if (sys->context == NULL)
|
|
{
|
|
free(sys);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
sd->p_sys = sys;
|
|
sd->description = _("Audio and Video capture");
|
|
sys->nodes = NULL;
|
|
sys->root_card = NULL;
|
|
sys->listener = (struct spa_hook){ };
|
|
|
|
vlc_pw_lock(sys->context);
|
|
/* Subscribe for source events */
|
|
vlc_pw_registry_listen(sys->context, &sys->listener, &events, sd);
|
|
vlc_pw_roundtrip_unlocked(sys->context); /* Enumerate existing sources */
|
|
vlc_pw_unlock(sys->context);
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static void Close (vlc_object_t *obj)
|
|
{
|
|
services_discovery_t *sd = (services_discovery_t *)obj;
|
|
struct services_discovery_sys_t *sys = sd->p_sys;
|
|
|
|
vlc_pw_disconnect(sys->context);
|
|
tdestroy(sys->nodes, DestroySource);
|
|
tdestroy(sys->root_card, DestroyCard);
|
|
free (sys);
|
|
}
|
|
|
|
VLC_SD_PROBE_HELPER("pipewire", N_("Audio and Video capture"), SD_CAT_DEVICES);
|
|
|
|
vlc_module_begin ()
|
|
set_shortname (N_("Audio and Video capture"))
|
|
set_description (N_("Audio and Video capture (PipeWire)"))
|
|
set_subcategory (SUBCAT_PLAYLIST_SD)
|
|
set_capability ("services_discovery", 0)
|
|
set_callbacks (Open, Close)
|
|
add_shortcut ("pipewire", "pw")
|
|
|
|
VLC_SD_PROBE_SUBMODULE
|
|
vlc_module_end ()
|
|
|