/* * 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.evice * * 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 #include #include #include "audio-sources.h" #include "gst-devices.h" #include "support.h" // _(x) #include "dconf.h" #include "utility.h" #include "log.h" #include "dbus-player.h" // A List of audio devices and media players G_LOCK_DEFINE_STATIC(g_device_list); static GList *g_device_list = NULL; // Contains: // * Real audio hardware, such as audio cards, microphones, webcams. // * Media-players like RhythmBox, Totem, Amarok and Banshee. // * Add also Skype if it's installed. Media-players and Skype can START/PAUSE/STOP recording via DBus. static GList *get_audio_devices(); static gchar *audio_sources_get_GNOME_default(); static GList *audio_sources_get_device_for_players(); static gchar *audio_sources_get_first_microphone(gboolean find_webcam); static GList *audio_sources_get_device_for_comm_programs(); static GList *audio_sources_get_device_from_settings(gint type); static gchar *FIXME_get_default_sink_dev(); static gchar *FIXME_find_sink_file(); void audio_sources_init() { LOG_DEBUG("Init audio-sources.c.\n"); g_device_list = NULL; // Init Puleaudio module gstdev_module_init(); // Init DBus/Media Player modules dbus_player_init(); } void audio_sources_exit() { LOG_DEBUG("Clean up audio-sources.c.\n"); // Clean up pulseaudio molule gstdev_module_exit(); // Clean up media players/DBus dbus_player_exit(); audio_sources_free_list(g_device_list); g_device_list = NULL; } // --------------------------------------------------- // DeviceItem functions DeviceItem *device_item_create(gchar *id, gchar *description) { DeviceItem *item = g_malloc0(sizeof(DeviceItem)); if (id) item->id = g_strdup(id); if (description) item->description = g_strdup(description); item->icon_name = NULL; return item; } DeviceItem *device_item_copy(DeviceItem *item) { // Make a copy of DeviceItem DeviceItem *copy = (DeviceItem*)g_memdup(item, sizeof(DeviceItem)); copy->id = g_strdup(item->id); copy->description = g_strdup(item->description); copy->icon_name = g_strdup(item->icon_name); return copy; } void device_item_free(DeviceItem *item) { if (!item) return; // Free values if (item->id) g_free(item->id); if (item->description) g_free(item->description); if (item->icon_name) g_free(item->icon_name); // Free the node g_free(item); } const gchar *device_item_get_type_name(guint type) { switch (type) { case NOT_DEFINED: return "NOT_DEFINED"; case DEFAULT_DEVICE: return "DEFAULT_DEVICE"; case AUDIO_SINK: return "AUDIO_SINK"; case AUDIO_SINK_MONITOR: return "AUDIO_SINK_MONITOR"; case AUDIO_INPUT: return "AUDIO_INPUT"; case MEDIA_PLAYER: return "MEDIA_PLAYER"; case COMM_PROGRAM: return "COMM_PROGRAM"; case USER_DEFINED: return "USER_DEFINED"; } return "UNKNOWN TYPE"; } // --------------------------------------------------- DeviceItem *audio_sources_find_in_list(GList *lst, gchar *device_id) { // Return DeviceItem for the given device_id. GList *l = g_list_first(lst); while (l) { DeviceItem *item = (DeviceItem*)l->data; // Compare ids // if (!g_strcmp0(item->id, device_id) || (device_id == NULL && item->id == NULL)) { if (!g_strcmp0(item->id, device_id)) { return item; } l = g_list_next(l); } return NULL; } DeviceItem *audio_sources_find_id(gchar *device_id) { // Return DeviceItem for the given device_id. // Lock G_LOCK(g_device_list); DeviceItem *found_item = audio_sources_find_in_list(g_device_list, device_id); // Unlock G_UNLOCK(g_device_list); return found_item; } // --------------------------------------------------- // Functions related to audio sources void audio_sources_free_list(GList *lst) { // Free the list and all its DeviceItems g_list_foreach(lst, (GFunc)device_item_free, NULL); g_list_free(lst); lst = NULL; } void audio_sources_print_list_ex() { // Print device list audio_sources_print_list(g_device_list, "Device list"); } void audio_sources_print_list(GList *list, gchar *tag) { // Print device list LOG_MSG("\n%s:\n", tag); GList *l = g_list_first(list); gint i = 0; while (l) { DeviceItem *item = (DeviceItem*)l->data; const gchar *type_name = device_item_get_type_name(item->type); LOG_MSG("#%d:type=%s(%d) id=%s, descr=%s\n", i++, type_name, item->type, item->id, item->description); LOG_MSG("\ticon_name=%s\n",item->icon_name); l = g_list_next(l); } LOG_MSG("-------------------------------------------\n"); } GList *audio_sources_get_for_type(gint type) { // Return a GList of DeviceItems that match the type. The type can be a OR'ed value. // Eg. GList *lst = audio_sources_get_for_type(AUDIO_INPUT | AUDIO_SINK_MONITOR | DEFAULT_DEVICE); // ... // audio_sources_free_list(lst); // lst = NULL; // Set lock G_LOCK(g_device_list); GList *lst = NULL; GList *n = g_list_first(g_device_list); while (n) { DeviceItem *item = (DeviceItem*)n->data; // Test the type if (item->type & type) { DeviceItem *copy = device_item_copy(item); lst = g_list_append(lst, copy); } // Next item n = g_list_next(n); } // Free the lock G_UNLOCK(g_device_list); // Notice: // The returned list is a deep copy. You must free it with // audio_sources_free_list(lst); // lst = NULL; return lst; } void audio_sources_device_changed(gchar *device_id) { // Device selection in the main window has changed. // Disconnect/re-connect DBus signals (if the device_id is Media Player or Skype) dbus_player_player_changed(device_id/*=player->service_name*/); } GList *audio_sources_wash_device_list(GList *dev_list) { // Wash the given device list. // User may have unplugged webcams and microphones, etc. // This function gets a fresh device list from GStreamer and removes invalid/unplugged items from the list. // Invalid devices may crash the GStreamer pipeline. // Create a new list for valid devs GList *new_list = NULL; // Get fresh device list from pulseaudio (or gstreamer) GList *fresh_dev_list = get_audio_devices(); GList *n = g_list_first(dev_list); while (n) { gchar *dev_id = (gchar*)n->data; // dev_id is in fresh_dev_list? if (audio_sources_find_in_list(fresh_dev_list, dev_id)) { // Yes new_list = g_list_append(new_list, g_strdup(dev_id)); } n = g_list_next(n); } // Free fresh_dev_list audio_sources_free_list(fresh_dev_list); // Return new_list. // Caller should free this with free_str_list(new_list). return new_list; } GList *audio_sources_get_device_NEW(gchar **audio_source) { // Return audio_source and device list as set in the GUI and [Additional settings] dialog. *audio_source = NULL; gchar *dev_id = NULL; GList *dev_list = NULL; gchar *source = NULL; gint type = -1; // These are set in the main window (in device/player combo) conf_get_string_value("audio-device-id", &dev_id); conf_get_int_value("audio-device-type", &type); // Normally "pulsesrc" source = g_strdup(DEFAULT_AUDIO_SOURCE); // Type is Media Player? if (type == MEDIA_PLAYER) { dev_list = audio_sources_get_device_for_players(); // Type is Skype or similar comm program? } else if (type == COMM_PROGRAM) { dev_list = audio_sources_get_device_for_comm_programs(); // Type is "User defined audio source" ? } else if (type == USER_DEFINED) { // Devices are assigned to media-players in the [Additional settings] dialog. // See also: dconf-editor, key: /apps/audio-recorder/players/ dev_list = audio_sources_get_device_from_settings(type); // Type is "System's default device"? } else if (type == DEFAULT_DEVICE) { g_free(source); source = audio_sources_get_GNOME_default(); // Let the audio-source settle the device dev_list = NULL; } else { // Type is AUDIO_SINK_MONITOR or AUDIO_INPUT device // Add dev_id to dev_list dev_list = g_list_append(dev_list, g_strdup(dev_id)); } g_free(dev_id); // Return values *audio_source = source; // Debug print: // print_str_glist("Final device list", new_list); // Caller should g_free() the returned audio_source. // Caller should free dev_list with str_list_free(dev_list). return dev_list; } static gchar *audio_sources_get_GNOME_default() { // Try first "gconfaudiosrc" // Its real value is probably set in the DConf registry. // Start gconf-editor and browse to key /system/gstreamer/0.10/default (audiosrc). gchar *source = g_strdup("gconfaudiosrc"); GstElement *elem = gst_element_factory_make(source, "test-audio-source"); gboolean ret = (elem != NULL); if (elem) g_object_unref(elem); if (!ret) { g_free(source); // Try "autoaudiosrc" source = g_strdup("autoaudiosrc"); elem = gst_element_factory_make(source, "test-audio-source"); ret = (elem != NULL); if (elem) g_object_unref(elem); } if (!ret) { g_free(source); source = g_strdup(DEFAULT_AUDIO_SOURCE); } // Caller should g_free() this value return source; } #if 0 static gchar *audio_sources_get_first_audio_card() { // Return the first AUDIO_SINK_MONITOR (audiocard with loudspeakers that we can record from) gchar *device_id = NULL; GList *lst = audio_sources_get_for_type(AUDIO_SINK_MONITOR); GList *first = g_list_first(lst); if (first) { DeviceItem *item = (DeviceItem*)first->data; device_id = g_strdup(item->id); } // Free lst audio_sources_free_list(lst); // Caller should g_free() this value return device_id; } #endif static gchar *audio_sources_get_last_audio_card() { // Return the last AUDIO_SINK_MONITOR (MONITOR = audiocard with loudspeakers that we can record from) gchar *device_id = NULL; GList *lst = audio_sources_get_for_type(AUDIO_SINK_MONITOR); GList *last = g_list_last(lst); if (last) { DeviceItem *item = (DeviceItem*)last->data; device_id = g_strdup(item->id); } // Free lst audio_sources_free_list(lst); // Caller should g_free() this value return device_id; } static gchar *audio_sources_get_default_monitor_dev() { // Return default .monitor device (audio card that we can record from). gchar *def_sink = get_default_sink_device(); if (str_length0(def_sink) < 2) { // Take last audio card. Try to record from it. return audio_sources_get_last_audio_card(); } // Make source_dev from sink_dev by adding a ".monitor" suffix. gchar *def_source = g_strdup_printf("%s.monitor", def_sink); g_free(def_sink); // Is it valid id? DeviceItem *item = audio_sources_find_id(def_source); if (item) { return def_source; } g_free(def_source); return NULL; } static gchar *audio_sources_get_first_microphone(gboolean find_webcam) { // Return first AUDIO_INPUT device (input device; microphone or webcam with microphone, etc.) GList *lst = audio_sources_get_for_type(AUDIO_INPUT); if (!lst) return NULL; gchar *device_id = NULL; if (find_webcam) { GList *n = g_list_first(lst); while (n) { DeviceItem *item = (DeviceItem*)n->data; // Stupid test to find a "webcam". if (audio_sources_device_is_webcam(item->description)) { device_id = g_strdup(item->id); break; } n = g_list_next(n); } } if (!device_id) { // Take the first one in the list DeviceItem *item = (DeviceItem*)lst->data; device_id = g_strdup(item->id); } // Free lst audio_sources_free_list(lst); // Caller should g_free() this value return device_id; } static GList *audio_sources_get_device_for_players() { // Return device list for Media Players (RhythmBox, Totem, Banshee, Amarok, etc.) // See [Additional settings] dialog. GList *dev_list = audio_sources_get_device_from_settings(MEDIA_PLAYER); // The list is empty? // Then find default .monitor device (=audio card with loudspeakers that we can record from). if (!dev_list) { gchar *dev_id = audio_sources_get_default_monitor_dev(); if (dev_id) { dev_list = g_list_append(dev_list, dev_id/*steal the string*/); } } return dev_list; } static GList *audio_sources_get_device_for_comm_programs() { // Return device list for communication programs like Skype GList *dev_list = audio_sources_get_device_from_settings(COMM_PROGRAM); // The list is empty? // There were no devices in GSettings for COMM_PROGRAM. // See [Additional settings] dialog. if (!dev_list) { // For COMM_PROGRAMs, such as Skype, we need to record from two devices; audio-card and microphone. // The list is empty? // Then find default .monitor device (=audio card with loudspeakers that we can record from). gchar *dev_id = audio_sources_get_default_monitor_dev(); if (dev_id) { dev_list = g_list_append(dev_list, dev_id/*steal the string*/); } // + add first microphone/webcam dev_id = audio_sources_get_first_microphone(TRUE/*find webcam if possible*/); if (dev_id) { dev_list = g_list_append(dev_list, dev_id/*steal the string*/); } } return dev_list; } static GList *audio_sources_get_device_from_settings(gint type) { // Get the device list from GSettings. // This is set in the [Additional settings] dialog. // See dconf-editor, key: /apps/audio-recorder/players/ GList *dev_lst = NULL; gchar *conf_key = g_strdup_printf("players/device-type-%d", type); conf_get_string_list(conf_key, &dev_lst); g_free(conf_key); return dev_lst; } gboolean audio_sources_device_is_webcam(gchar *dev_name) { // A very primitive test to determine if dev_name is a web-camera // URGENT: // FIXME, TODO: Find a better way to check if the device is a video or webcam with microphone. // Some typical words we find in names of webcams // See: http://www.ideasonboard.org/uvc/ gchar *cam_names[] = {"cam ", "amera", "amcorder", "web", "motion", "islim", "eface", "pix ", "pixel", NULL}; // To lower case gchar *name = g_utf8_strdown(dev_name, -1); gint i = 0; for (i=0; cam_names[i]; i++) { gchar *p = g_strrstr(name, cam_names[i]); if (p) return TRUE; } return FALSE; } void audio_sources_load_device_list() { // Reload device list // Lock G_LOCK(g_device_list); // Free the old list audio_sources_free_list(g_device_list); g_device_list = NULL; // 1) Get list of real audio devices (fill to g_device_list) g_device_list = get_audio_devices(); // 2) Add Media Players, Skype etc. to g_device_list (these can control the recording via DBus) // See the dbus_xxxx.c modules // ---------------------------- // Get the player list (it's a GHashTable) GHashTable *player_list = dbus_player_get_player_list(); GHashTableIter hash_iter; gpointer key, value; // Add to the g_device_list g_hash_table_iter_init(&hash_iter, player_list); while (g_hash_table_iter_next(&hash_iter, &key, &value)) { MediaPlayerRec *p = (MediaPlayerRec*)value; gchar *t = p->app_name; if (!t) t = p->service_name; // New DeviceItem DeviceItem *item = device_item_create(p->service_name, p->app_name); item->icon_name = g_strdup(p->icon_name); // Set type (COMM_PROGRAM or MEDIA_PLAYER) if (p->type) item->type = p->type; else item->type = MEDIA_PLAYER; g_device_list = g_list_append(g_device_list, item); } // 3) Add "User defined audio source" (user defined group of devices, selected for recording) // ---------------------------- gchar *name = g_strdup(_("User defined audio source")); DeviceItem *item = device_item_create("user-defined", name); item->type = USER_DEFINED; item->icon_name = g_strdup("audio-card.png"); g_device_list = g_list_append(g_device_list, item); g_free(name); #if defined(ACTIVE_DEBUGGING) || defined(DEBUG_ALL) // Print device list audio_sources_print_list_ex(); #endif // The g_device_list is now loaded and ready // Unlock G_UNLOCK(g_device_list); } // -------------------------------------------- // Combobox related functions // -------------------------------------------- GtkWidget *audio_sources_create_combo() { // Create a GtkComboBox with N_DEVICE_COLUMNS // Create list store GtkListStore *store = gtk_list_store_new(N_DEVICE_COLUMNS, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING); GtkWidget *combo = gtk_combo_box_new(); gtk_combo_box_set_model(GTK_COMBO_BOX(combo),GTK_TREE_MODEL(store)); // Unref store g_object_unref(G_OBJECT(store)); // Device type (see the description of DeviceType enum) GtkCellRenderer *cell = gtk_cell_renderer_text_new(); g_object_set(cell, "visible", 0, NULL); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, FALSE); gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), cell, "text", COL_DEVICE_TYPE, NULL); // Device id column, invisible cell = gtk_cell_renderer_text_new(); g_object_set(cell, "visible", 0, NULL); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, FALSE); gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), cell, "text", COL_DEVICE_ID, NULL); // Pixbuf (device icon) cell = gtk_cell_renderer_pixbuf_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, FALSE); gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), cell, "pixbuf", COL_DEVICE_ICON, NULL); // Device description column, visible cell = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), cell, FALSE); gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), cell, "text", COL_DEVICE_DESCR, NULL); return combo; } void audio_source_fill_combo(GtkWidget *combo) { GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo)); GtkTreeIter iter; if (!GTK_IS_LIST_STORE(GTK_LIST_STORE(model))) { return; } // Disable "changed" signal gulong signal_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(combo), "selection-changed-signal")); g_signal_handler_block(combo, signal_id); // Clear the old combo list gtk_list_store_clear(GTK_LIST_STORE(model)); // Get a new list of: // 1) Audio devices, audio-card w/ loudspeakers, webcams, microphones, etc. // 2) + Add media players that we see on the DBus. audio_sources_load_device_list(); // Set lock G_LOCK(g_device_list); // Then add items to the ComboBox GList *n = g_list_first(g_device_list); while (n) { DeviceItem *item = (DeviceItem*)n->data; // Exclude AUDIO_SINK devices if (item->type == AUDIO_SINK) { // Next item thanks n = g_list_next(n); continue; } // Add new row gtk_list_store_append(GTK_LIST_STORE(model), &iter); // Load icon const gchar *p = item->icon_name; GdkPixbuf *pixbuf = NULL; if (item->type == MEDIA_PLAYER || item->type == COMM_PROGRAM) { pixbuf = load_icon_pixbuf((gchar*)p, 22); // Got icon?? if (!GDK_IS_PIXBUF(pixbuf)) { // No. Display default icon. p = "mediaplayer.png"; } } if (!p) { p = "loudspeaker.png"; } if (!GDK_IS_PIXBUF(pixbuf)) { // No. Load a default icon. gchar *path = get_image_path(p); pixbuf = get_pixbuf_from_file(path, 22, 22); g_free(path); } // Set column data gtk_list_store_set(GTK_LIST_STORE(model), &iter, COL_DEVICE_TYPE, item->type, /* type */ COL_DEVICE_ID, item->id, /* internal device id or media player id */ COL_DEVICE_ICON, pixbuf, /* icon pixbuf */ COL_DEVICE_DESCR, item->description /* visible text */, -1); // Pixbuf has a reference count of 2 now, as the list store has added its own if (GDK_IS_PIXBUF(pixbuf)) { g_object_unref(pixbuf); } // Next item n = g_list_next(n); } // Enable changed-signal g_signal_handler_unblock(combo, signal_id); // Free the lock G_UNLOCK(g_device_list); } void audio_sources_combo_set_id(GtkWidget *combo, gchar *device_id) { // Set combo selection by device id GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo)); if (!GTK_IS_TREE_MODEL(model)) { return; } GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first(model, &iter); while (device_id && valid) { gchar *id = NULL; gtk_tree_model_get(model, &iter, COL_DEVICE_ID, &id, -1); // Compare ids if (!g_strcmp0(device_id, id)) { // Select it gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter); return; } valid = gtk_tree_model_iter_next(model, &iter); } // str_value was not found. Select the first, 0'th row valid = gtk_tree_model_get_iter_first(model, &iter); if (valid) { gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter); } } gboolean audio_sources_combo_get_values(GtkWidget *combo, gchar **device_name, gchar **device_id, gint *device_type) { *device_name = NULL; *device_id = NULL; *device_type = -1; GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo)); if (!GTK_IS_TREE_MODEL(model)) { return FALSE; } GtkTreeIter iter; if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) return FALSE; gtk_tree_model_get(model, &iter, COL_DEVICE_ID, device_id, COL_DEVICE_DESCR, device_name, COL_DEVICE_TYPE, device_type, -1); // The caller should g_free() both device_name and device_id. return TRUE; } // Choose one of these get_audio_devices() functions static GList *get_audio_devices() { // Get list of audio input devices (ids and names). GList *pa_list = gstdev_get_source_list(); GList *lst = NULL; GList *n = g_list_first(pa_list); while (n) { DeviceItem *item = (DeviceItem*)n->data; // Take a copy DeviceItem *copy = device_item_copy(item); // Add to our private list lst = g_list_append(lst, copy); n = g_list_next(n); } // Add "Default" device // Translators: This is system's default audio device. DeviceItem *item = device_item_create("default-device"/*id*/, _("System's default device")/*description*/); item->type = DEFAULT_DEVICE; lst = g_list_append(lst, item); // Return lst return lst; } gchar *get_default_sink_device() { // Find default sink-device. return FIXME_get_default_sink_dev(); } static gchar *FIXME_find_sink_file() { // FIXME because this function pokes directly pulseaudio's config file. // We should not do this! Do not even mension you've seen this horrible piece of code ;-) // // The question is: How to find default (or running) sink-device by using GStreamer functions? // Please tell me if you know. // // Play some sound, and type: // $ pactl list | grep -A6 State // // State: RUNNING // Name: alsa_output.pci-0000_00_1b.0.analog-stereo // Description: Built-in Audio Analog Stereo #define PULSEAUDIO_LOCAL_CONFIG ".config/pulse/" // $ cat ~/.config/pulse/*-default-sink gchar *home = get_home_dir(); gchar *path = g_build_path("/", home, PULSEAUDIO_LOCAL_CONFIG, NULL); gchar *ret = NULL; GError *error = NULL; GDir *dir = g_dir_open(path, 0, &error); if (error) { g_error_free(error); goto LBL_1; } const gchar *fname = g_dir_read_name(dir); while (fname) { if (g_str_has_suffix(fname, "-default-sink")) { ret = g_build_path("/", path, fname, NULL); goto LBL_1; } fname = g_dir_read_name(dir); } LBL_1: g_free(home); g_free(path); if (dir) { g_dir_close(dir); dir = NULL; } return ret; } static gchar *FIXME_get_default_sink_dev() { // FIXME because this function pokes directly pulseaudio's config file. // We should not do this! Do not even mension you've seen this horrible piece of code ;-) // // Possible solutions: // 1) Create a pipeline with pulsesink element, make it rolling and read its device id (it may still be NULL !). // // 2) Better idea: // Create a pipeline with a LEVEL element for each audio-card (that are .monitor devices). And check if it produces sound (if we find pulse on the pipe). // // 3) There must be even better solution. FIXME please! // gchar *fname = FIXME_find_sink_file(); gchar *sink_dev = NULL; GError *error = NULL; gsize siz = 0L; g_file_get_contents(fname, &sink_dev, &siz, &error); g_free(fname); if (error) { g_error_free(error); } str_trim(sink_dev); return sink_dev; } #if 0 audiotestsrc wave=sine freq=512 ! audioconvert ! audioresample ! pulsesink gst-launch audiotestsrc ! audioconvert ! pulsesink #endif #if 0 Ideas: How to find DEFAULT sink-device by using GStreamer functions? gchar *name = NULL; GError *error = NULL; GstElement *p = gst_parse_launch("fakesrc ! audioconvert ! audioresample ! pulsesink name=plssnk", &error); gst_element_set_state(p, GST_STATE_PLAYING); GstElement *sink = gst_bin_get_by_name(GST_BIN(p), "plssnk"); if (sink) { g_object_get(G_OBJECT(sink), "device", &dev_id, "name", &name, NULL); GValue value = {0, }; g_value_init(&value, G_TYPE_STRING); g_object_get_property(G_OBJECT(sink), "device", &value); dev_id = g_value_dup_string(&value); g_value_unset(&value); } GstElement *e = gst_element_factory_make("pulsesink", "pulsesink"); if (GST_IS_ELEMENT(e)) { g_object_get(G_OBJECT(e), "device", &dev_id, NULL); dev_id = (gchar*)g_object_get(G_OBJECT(e), "device", NULL); 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)); } return dev_id; #endif