alsa: Support enumerating virtual PCM sinks
Add support to the ALSA device provider to enumerate PCM outputs that do not correspond to a physical sound device i.e. they are "virtual" sinks, like the plug, dmix, or softvol PCM outputs that can be setup in the ALSA configuration files. The main use-case for this is allowing usage of GstDeviceMonitor in setups where there is no audio server and have custom ALSA audio configurations. As those are likely to be uncommon, the feature is opt-in: a list of device names and wildcard patterns separated by semicolons must be assigned to the GST_ALSA_PCM_ALLOW environment variable before such PCM outputs will be enumerated by the ALSA device provider. This allows either scanning all PCM outputs, listing individual outputs, providing simple patterns with '*' wildcards (which match only at the start or end of the name), or a combination of them: GST_ALSA_PCM_ALLOW=1 # Enable listing PCM outputs. GST_ALSA_PCM_ALLOW='*' # Same, using a wildcard. GST_ALSA_PCM_ALLOW='out_1;out_1' # Exact listing. GST_ALSA_PCM_ALLOW='out_*' # Using a wildcard. GST_ALSA_PCM_ALLOW='out_*;other_*;line_out' # Multiple items. The main motivation for this patch is supporting enumeration of PCM outputs in the WebKit GTK and WPE ports, which use GstDeviceMonitor to determine which devices may be chosen for sound output. While on desktops typically PulseAudio or PipeWire are used nowadays, on embedded devices it is often desirable to avoid them and use custom configurations that perform audio routing and processing using only ALSA. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8831>
This commit is contained in:
parent
d33107226c
commit
3968dd92a5
@ -24,10 +24,8 @@
|
||||
#endif
|
||||
|
||||
#include "gstalsadeviceprovider.h"
|
||||
#include <string.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
|
||||
static GstDevice *gst_alsa_device_new (const gchar * device_name,
|
||||
GstCaps * caps, const gchar * internal_name, snd_pcm_stream_t stream,
|
||||
GstStructure * properties);
|
||||
@ -96,6 +94,171 @@ add_device (GstDeviceProvider * provider, snd_ctl_t * info,
|
||||
return device;
|
||||
}
|
||||
|
||||
static GList *
|
||||
gst_alsa_parse_pcm_allow_patterns (const gchar * patterns)
|
||||
{
|
||||
if (!patterns)
|
||||
return NULL;
|
||||
|
||||
GList *pattern_list = NULL;
|
||||
gchar *patterns_trimmed = g_strchomp (g_strdup (patterns));
|
||||
|
||||
if (g_strcmp0 (patterns_trimmed, "1") == 0) {
|
||||
pattern_list = g_list_append (pattern_list, g_strdup ("*"));
|
||||
} else {
|
||||
gchar **pattern_strings = g_strsplit (patterns_trimmed, ";", -1);
|
||||
for (size_t i = 0; pattern_strings[i]; i++) {
|
||||
gchar *pattern = g_strchomp (pattern_strings[i]);
|
||||
if (*pattern)
|
||||
pattern_list = g_list_append (pattern_list, g_strdup (pattern));
|
||||
}
|
||||
g_strfreev (pattern_strings);
|
||||
}
|
||||
|
||||
g_free (patterns_trimmed);
|
||||
return pattern_list;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_alsa_pcm_name_matches_pattern (const gchar * name, const gchar * pattern)
|
||||
{
|
||||
g_assert (name);
|
||||
g_assert (pattern);
|
||||
|
||||
size_t pattern_len = strlen (pattern);
|
||||
g_assert (pattern_len > 0);
|
||||
|
||||
if (pattern[0] == '*') {
|
||||
if (pattern_len == 1) /* pattern == "*", matches any input */
|
||||
return TRUE;
|
||||
|
||||
if (pattern[pattern_len - 1] == '*') {
|
||||
if (pattern_len == 2) /* pattern == "**", matches any input */
|
||||
return TRUE;
|
||||
|
||||
/* pattern == "*<text>*", matches if <text> is contained in the name */
|
||||
g_autofree gchar *needle = g_strndup (pattern + 1, pattern_len - 2);
|
||||
return strstr (name, needle) != NULL;
|
||||
}
|
||||
|
||||
/* pattern == "*<text>", matches if <text> is the name suffix */
|
||||
return g_str_has_suffix (name, pattern + 1);
|
||||
}
|
||||
|
||||
/* pattern == "<text>*", matches if <text> is the name prefix */
|
||||
if (pattern[pattern_len - 1] == '*') {
|
||||
g_autofree gchar *prefix = g_strndup (pattern, pattern_len - 1);
|
||||
return g_str_has_prefix (name, prefix);
|
||||
}
|
||||
|
||||
/* pattern == "<text>", matches if <text> is the same as the name */
|
||||
return strcmp (name, pattern) == 0;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_alsa_pcm_name_matches_any_pattern (const gchar * name, GList * patterns)
|
||||
{
|
||||
g_assert (name);
|
||||
g_assert (patterns);
|
||||
|
||||
for (GList * item = g_list_first (patterns); item; item = g_list_next (item))
|
||||
if (gst_alsa_pcm_name_matches_pattern (name, item->data))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GList *
|
||||
gst_alsa_device_provider_probe_pcm_sinks (GstDeviceProvider * provider,
|
||||
GList * list)
|
||||
{
|
||||
GList *allow_patterns =
|
||||
gst_alsa_parse_pcm_allow_patterns (g_getenv ("GST_ALSA_PCM_ALLOW"));
|
||||
if (!allow_patterns)
|
||||
return list;
|
||||
|
||||
void **hints;
|
||||
if (snd_device_name_hint (-1, "pcm", &hints) < 0)
|
||||
goto beach;
|
||||
|
||||
snd_pcm_info_t *pcm_info;
|
||||
snd_pcm_info_malloc (&pcm_info);
|
||||
|
||||
for (void **n = hints; *n; n++) {
|
||||
char *name = snd_device_name_get_hint (*n, "NAME");
|
||||
char *desc = snd_device_name_get_hint (*n, "DESC");
|
||||
char *io = snd_device_name_get_hint (*n, "IOID");
|
||||
|
||||
if (!gst_alsa_pcm_name_matches_any_pattern (name, allow_patterns))
|
||||
goto next_hint;
|
||||
|
||||
/*
|
||||
* Skip devices without description or that have a valid IOID hint.
|
||||
* The latter seems to be always NULL for "virtual" PCM sinks.
|
||||
*/
|
||||
if (!desc || io) {
|
||||
GST_DEBUG_OBJECT (provider, "No io or desc hint for %s", name);
|
||||
goto next_hint;
|
||||
}
|
||||
|
||||
snd_pcm_t *pcm_handle = NULL;
|
||||
int err = snd_pcm_open (&pcm_handle, name, SND_PCM_STREAM_PLAYBACK,
|
||||
SND_PCM_NONBLOCK);
|
||||
if (err < 0) {
|
||||
GST_ERROR_OBJECT (provider,
|
||||
"Could not open PCM device '%s' for inspection! (%s)", name,
|
||||
snd_strerror (err));
|
||||
goto next_hint;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_info (pcm_handle, pcm_info)) < 0) {
|
||||
GST_WARNING_OBJECT (provider,
|
||||
"Cannot obtain PCM info for device '%s' (%s)", name,
|
||||
snd_strerror (err));
|
||||
snd_pcm_close (pcm_handle);
|
||||
goto next_hint;
|
||||
}
|
||||
|
||||
GstCaps *template = gst_static_caps_get (&alsa_caps);
|
||||
GstCaps *caps = gst_alsa_probe_supported_formats (GST_OBJECT (provider),
|
||||
name, pcm_handle, template);
|
||||
g_clear_pointer (&template, gst_caps_unref);
|
||||
|
||||
GstStructure *props = gst_structure_new ("alsa-proplist",
|
||||
"device.api", G_TYPE_STRING, "alsa",
|
||||
"device.class", G_TYPE_STRING, "sound",
|
||||
NULL);
|
||||
GstAlsaDevice *gstdev = g_object_new (GST_TYPE_ALSA_DEVICE,
|
||||
"display-name", desc,
|
||||
"caps", caps,
|
||||
"device-class", "Audio/Sink",
|
||||
"internal-name", name,
|
||||
"properties", props,
|
||||
NULL);
|
||||
gstdev->stream = SND_PCM_STREAM_PLAYBACK;
|
||||
gstdev->element = "alsasink";
|
||||
|
||||
gst_clear_structure (&props);
|
||||
gst_clear_caps (&caps);
|
||||
|
||||
snd_pcm_close (pcm_handle);
|
||||
list = g_list_prepend (list, gstdev);
|
||||
|
||||
next_hint:
|
||||
free (name);
|
||||
free (desc);
|
||||
free (io);
|
||||
}
|
||||
|
||||
snd_pcm_info_free (pcm_info);
|
||||
snd_device_name_free_hint (hints);
|
||||
|
||||
g_list_free_full (allow_patterns, g_free);
|
||||
|
||||
beach:
|
||||
return list;
|
||||
}
|
||||
|
||||
static GList *
|
||||
gst_alsa_device_provider_probe (GstDeviceProvider * provider)
|
||||
{
|
||||
@ -164,7 +327,7 @@ beach:
|
||||
snd_ctl_card_info_free (info);
|
||||
snd_pcm_info_free (pcminfo);
|
||||
|
||||
return list;
|
||||
return gst_alsa_device_provider_probe_pcm_sinks (provider, list);
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user