/* * Copyright (c) 2011- Osmo Antero. * * 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 3 of the License (GPL3), or 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 3 for more details. * * You should have received a copy of the GNU Library General Public * License 3 along with this program; if not, see /usr/share/common-licenses/GPL file * or . */ #include "gst-devices.h" #include "support.h" // _(x) #include "log.h" #include "utility.h" #include "rec-manager-struct.h" #include #include #include // Collect audio input devices and audio output devices w/ connected loudspeakers. // This module is using gst_device_monitor_* functions that were introduced in GStreamer 1.4. // You may also check the output of these pa (pulseaudio) commands. // Get list of input devices: // $ pactl list short sources | cut -f2 // $ pactl list | grep -A6 'Source #' | egrep "Name: |Description: " // // Get list of sink (output) devices: // $ pactl list short sinks | cut -f2 // $ pactl list | grep -A6 'Sink #' | egrep "Name: |Description: " // Audio sink devices static GList *g_sink_list = NULL; // Audio source devices G_LOCK_DEFINE_STATIC(g_source_list); static GList *g_source_list = NULL; static GstDeviceMonitor *g_dev_monitor = NULL; static void gstdev_get_devices(); static void gstdev_read_fields(GstDevice *dev, gchar **dev_id, gchar **dev_descr, gchar **dev_class, gchar **dev_caps_str); static void gstdev_add_to_list(GstDevice *dev); static void gstdev_remove_from_list(GstDevice *dev); static void gstdev_clear_lists(); void gstdev_module_init() { LOG_DEBUG("Init gst-devices.c.\n"); g_source_list = NULL; g_sink_list = NULL; g_dev_monitor = NULL; } void gstdev_module_exit() { LOG_DEBUG("Clean up gst-devices.c.\n"); if (GST_IS_DEVICE_MONITOR(g_dev_monitor)) { gst_device_monitor_stop(g_dev_monitor); gst_object_unref(g_dev_monitor); } g_dev_monitor = NULL; // Clear lists gstdev_clear_lists(); } GList *gstdev_get_source_list() { // Return g_source_list to the caller gstdev_get_devices(); return g_source_list; } static void gstdev_clear_lists() { LOG_DEBUG("gstdev_clear_lists(). Clear g_sink_list and g_source_list.\n"); G_LOCK(g_source_list); // Free g_sink_list audio_sources_free_list(g_sink_list); g_sink_list = NULL; // Free g_source_list audio_sources_free_list(g_source_list); g_source_list = NULL; G_UNLOCK(g_source_list); } void gstdev_update_GUI() { // Device list changed. Update GUI. RecorderCommand *cmd = g_malloc0(sizeof(RecorderCommand)); cmd->type = RECORDING_DEVICE_CHANGED; // Send command to rec-manager.c and GUI. // It will free the cmd structure after processing. rec_manager_send_command(cmd); } static gboolean message_func(GstBus *bus, GstMessage *message, gpointer user_data) { GstDevice *device = NULL; gchar *name = NULL; LOG_DEBUG("message_func(): function to add or remove device called.\n"); switch (GST_MESSAGE_TYPE (message)) { case GST_MESSAGE_DEVICE_ADDED: gst_message_parse_device_added(message, &device); name = gst_device_get_display_name(device); LOG_DEBUG("Audio device added: %s\n", name); g_free (name); gstdev_add_to_list(device); gstdev_update_GUI(); break; case GST_MESSAGE_DEVICE_REMOVED: gst_message_parse_device_removed(message, &device); name = gst_device_get_display_name(device); LOG_DEBUG("Audio device removed: %s\n", name); g_free (name); gstdev_remove_from_list(device); gstdev_update_GUI(); break; default: break; } return G_SOURCE_CONTINUE; } GstDeviceMonitor *setup_raw_audio_source_device_monitor() { LOG_DEBUG("Setup monitor to detect new and unplugged devices.\n"); GstDeviceMonitor *monitor = gst_device_monitor_new (); GstBus *bus = gst_device_monitor_get_bus(monitor); gst_bus_add_watch(bus, message_func, NULL); gst_object_unref(bus); GstCaps *caps = gst_caps_new_empty_simple ("audio/x-raw"); gst_device_monitor_add_filter(monitor, NULL, caps); // "Audio/Source", "Audio/Sink" gst_caps_unref (caps); gst_device_monitor_start(monitor); return monitor; } static void gstdev_read_fields(GstDevice *dev, gchar **dev_id, gchar **dev_descr, gchar **dev_class, gchar **dev_caps_str) { gchar *disp_name = gst_device_get_display_name(dev); // Cut it nicely *dev_descr = g_strdup(disp_name); str_cut_nicely(*dev_descr, 39/*to len*/, 25/*minimum len*/); g_free(disp_name); *dev_class = gst_device_get_device_class(dev); GstCaps *caps = gst_device_get_caps(dev); *dev_caps_str = gst_caps_to_string(caps); gst_caps_unref(caps); // Read device id (this should work for pulsesrc and alsasrc) GstElement *e = gst_device_create_element(dev, "element"); GValue value = {0, }; g_value_init (&value, G_TYPE_STRING); g_object_get_property(G_OBJECT(e), "device", &value); *dev_id = g_value_dup_string(&value); g_value_unset(&value); gst_object_unref(GST_OBJECT(e)); } GList *remove_item(GList *list, gchar *dev_id) { GList *item = g_list_first(list); while (item) { DeviceItem *rec = (DeviceItem*)item->data; if (rec && g_strcmp0(rec->id, dev_id) == 0) { list = g_list_delete_link(list, item); device_item_free(rec); return list; } item = g_list_next(item); } // Return unmodified list return list; } static void gstdev_remove_from_list(GstDevice *dev) { gchar *dev_id = NULL; gchar *dev_descr = NULL; gchar *dev_class = NULL; gchar *dev_caps_str = NULL; LOG_DEBUG("Remove (input or output) device from the list.\n"); G_LOCK(g_source_list); gstdev_read_fields(dev, &dev_id, &dev_descr, &dev_class, &dev_caps_str); gchar *dev_class_l = g_ascii_strdown(dev_class, -1); // Audio/Source if (g_str_has_prefix(dev_class_l, "audio/source")) { LOG_DEBUG("Remove audio input device (from g_source_list):%s, decr:%s, class:%s\n", dev_id, dev_descr, dev_class); g_source_list = remove_item(g_source_list, dev_id); } // Audio/Sink else if (g_str_has_prefix(dev_class_l, "audio/sink")) { LOG_DEBUG("Remove audio output device (from g_sink_list):%s, decr:%s, class:%s\n", dev_id, dev_descr, dev_class); g_sink_list = remove_item(g_sink_list, dev_id); } g_free(dev_id); g_free(dev_descr); g_free(dev_class); g_free(dev_class_l); g_free(dev_caps_str); G_UNLOCK(g_source_list); } static void gstdev_add_to_list(GstDevice *dev) { gchar *dev_descr = NULL; gchar *dev_id = NULL; gchar *dev_class = NULL; gchar *dev_caps_str = NULL; LOG_DEBUG("Add new (input or output) device to the list.\n"); G_LOCK(g_source_list); gstdev_read_fields(dev, &dev_id, &dev_descr, &dev_class, &dev_caps_str); // Create new DeviceItem DeviceItem *item = device_item_create(dev_id, dev_descr); gchar *dev_class_l = g_ascii_strdown(dev_class, -1); // Audio/Source if (g_str_has_prefix(dev_class_l, "audio/source")) { LOG_DEBUG("Add audio input device (to g_source_list):%s, decr:%s, class:%s\n", dev_id, dev_descr, dev_class); if (g_str_has_suffix(dev_id, ".monitor")) { // Monitor device for a real sound-card (we can record from this) item->type = AUDIO_SINK_MONITOR; // Set icon (audio card/loudspeaker) item->icon_name = g_strdup("loudspeaker.png"); } else { // Device with audio input (microphone) item->type = AUDIO_INPUT; // Add "Microphone" to the device description gchar *descr = g_strdup_printf("%s %s", item->description, _("(Microphone)")); g_free(item->description); item->description = descr; // Find a suitable icon. // TODO: Make this test better. if (audio_sources_device_is_webcam(item->description)) { // Most likely a webcam item->icon_name = g_strdup("webcam.png"); } else { // Ordinary microphone item->icon_name = g_strdup("microphone.png"); } } // Add to g_sources list g_source_list = g_list_append(g_source_list, item); } // Audio/Sink else if (g_str_has_prefix(dev_class_l, "audio/sink")) { LOG_DEBUG("Add audio output device (to g_sink_list):%s, decr:%s, class:%s\n", dev_id, dev_descr, dev_class); // This is a sound sink, normally real audio card with loudspeakers item->type = AUDIO_SINK; // Add to g_sinks list g_sink_list = g_list_append(g_sink_list, item); // Set icon (audio card) item->icon_name = g_strdup("audio-card.png"); } g_free(dev_id); g_free(dev_descr); g_free(dev_class); g_free(dev_class_l); g_free(dev_caps_str); G_UNLOCK(g_source_list); } void gstdev_fix_description() { // Remove "Monitor of" from the description text, and add "(Audio ouput)" word to it. It means "loudspeakers". // For example: "Monitor of Audio Stereo Card" becomes "Audio Stereo Card (Audio ouput)" // This is easier to understand. // Note: Check listing of these commands: // // Input devices: // pactl list | grep -A6 'Source #' | egrep "Name: |Description: " // pactl list short sources | cut -f2 // // And sink (output) devices: // pactl list | grep -A6 'Sink #' | egrep "Name: |Description: " // pactl list short sinks | cut -f2 G_LOCK(g_source_list); GList *item = g_list_first(g_source_list); while (item) { DeviceItem *rec = (DeviceItem*)item->data; // Take device-id without ".monitor" suffix if (g_str_has_suffix(rec->id, ".monitor")) { gchar *tmp = g_strdup(rec->id); gchar *p = g_strrstr(tmp, ".monitor"); if (p) { // NULL terminate *p = '\0'; // Find equivalent sink device (real audio card) and steal its description + add "(Audio output)" to it. DeviceItem *sink_rec = audio_sources_find_in_list(g_sink_list, tmp); if (sink_rec) { g_free(rec->description); rec->description = g_strdup_printf("%s %s", sink_rec->description, _("(Audio output)")); } } g_free(tmp); } item = g_list_next(item); } G_UNLOCK(g_source_list); } static void gstdev_get_devices() { LOG_DEBUG("Get list of audio input/output devices from GStreamer.\n"); gstdev_clear_lists(); // Set up device monitor if (!GST_IS_DEVICE_MONITOR(g_dev_monitor)) { g_dev_monitor = setup_raw_audio_source_device_monitor(); } GList *list = gst_device_monitor_get_devices(g_dev_monitor); // Ref: http://code.metager.de/source/xref/freedesktop/gstreamer/gstreamer/gst/gstdevice.c GList *item = g_list_first(list); while (item) { GstDevice *dev = (GstDevice*)item->data; gstdev_add_to_list(dev); item = g_list_next(item); } g_list_free_full(list, (GDestroyNotify)gst_object_unref); // Remove "Monitor of" from the description text. gstdev_fix_description(); }