Linux GNOME 桌面系统音频设置实现

在 Ubuntu 等使用了 GNOME 桌面系统的 Linux 系统中,通过 设置 应用的 声音 面板设置系统的音频相关配置,如下图:

Linux GNOME 桌面系统音频设置

音频设置可以设置的音频选项主要有如下这些:

  • 系统音量:默认不允许将音量提高到 100% 以上,但有一个开关,当开关打开时,允许将音量提高到 100% 以上。当拖动条拖到最左边时,执行静音操作。
  • 音量级别:当拖动条拖到最左边时,执行静音操作。
  • 音频播放输出设置。
    • 默认输出设备:从系统支持的多个输出设备中选择默认的输出设备。
    • 配置。
    • 均衡。
  • 音频录制输入设置。
    • 默认输入设备:从系统支持的多个输入设备中选择默认的输入设备。
    • 输入音量:当拖动条拖到最左边时,执行静音操作。
  • 警报声设置:设置警报声,选中对应警报声的按钮,对应警报声将播放出来,让人可以听一下实际的效果。

Linux GNOME 桌面系统的 设置 应用,它的可执行程序名为 gnome-control-center,其路径通常为 /usr/bin/gnome-control-center。GitHub 上有 GNOME Settings gnome-control-center 程序源码的只读镜像,GNOME Settings@GitHb。

gnome-control-center 与众多 GNOME 项目一样,通过 meson + ninja 的方式构建。将 gnome-control-center 的源码克隆到本地,切换到合适的分支,安装 meson 和 ninja 构建工具,并解决依赖问题之后,构建 gnome-control-center 的过程比较简单。

Ubuntu 上,meson 和 ninja 构建工具可以通过 apt 安装,安装方法如下:

data$ sudo apt install meson ninja-build

较新版本的 gnome-control-center 源码可能对 meson 的版本也有更高的要求。可通过 pip 工具安装指定版本的 meson。如安装 1.3.2 版的 meson:

data$ pip3 install meson==1.3.2

构建和本地环境所用 GNOME 版本相同的 GNOME Settings,依赖问题相对比较容易解决。如笔者本地环境 Ubuntu 20.04 的 GNOME 版本为 3.36.8,选择的 GNOME Settings 版本为 gnome-3-36

GNOME Settings 依赖两个模块,如它的 .gitmodules 文件所描述的那样:

[submodule "subprojects/gvc"]path = subprojects/gvcurl = https://gitlab.gnome.org/GNOME/libgnome-volume-control.git
[submodule "subprojects/libhandy"]path = subprojects/libhandyurl = https://source.puri.sm/Librem5/libhandy.git

如果本地网络访问上面的项目不是很方便,也可以从 GitHub 克隆对应项目的代码到指定位置。

为了加速依赖问题的处理,可以搜索 GNOME Settings 及其子项目的 meson.build 构建配置文件中的 dependency 语句,集中安装这些依赖。如:

gnome-control-center$ find . | grep meson.build  | xargs grep "dependency('"
./panels/online-accounts/meson.build:  dependency('goa-backend-1.0', version: goa_req_version)
./panels/sharing/meson.build:libsecret_dep = dependency('libsecret-1')
./panels/printers/meson.build:  dependency('smbclient')
./panels/background/meson.build:  dependency('cairo-gobject'),
./panels/background/meson.build:  dependency('grilo-0.3', version: '>= 0.3.0')
./panels/network/meson.build:  dependency('gmodule-2.0')
./panels/info-overview/meson.build:  dependency('udisks2', version: '>= 2.1.8'),
./panels/info-overview/meson.build:  dependency('libgtop-2.0')
./panels/color/meson.build:  dependency('colord-gtk', version: '>= 0.1.24'),
./panels/color/meson.build:  dependency('libsoup-2.4')
./panels/user-accounts/meson.build:krb_dep = dependency('krb5', required: false)
./panels/user-accounts/meson.build:  dependency('pwquality', version: '>= 1.2.2')
./panels/common/meson.build:  dependency('fontconfig')
./panels/sound/meson.build:  dependency('gsound'),
./panels/removable-media/meson.build:  dependency('libgtop-2.0')
./meson.build:libhandy_dep = dependency('libhandy-0.0', version: '>= 0.0.9', required: false)
./meson.build:accounts_dep = dependency('accountsservice', version: '>= 0.6.39')
./meson.build:colord_dep = dependency('colord', version: '>= 0.1.34')
./meson.build:gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0', version: '>= 2.23.0')
./meson.build:gio_dep = dependency('gio-2.0')
./meson.build:glib_dep = dependency('glib-2.0', version: '>= 2.56.0')
./meson.build:gnome_desktop_dep = dependency('gnome-desktop-3.0', version: '>= 3.27.90')
./meson.build:gnome_settings_dep = dependency('gnome-settings-daemon', version: '>= 3.27.90')
./meson.build:goa_dep = dependency('goa-1.0', version: goa_req_version)
./meson.build:gsettings_desktop_dep = dependency('gsettings-desktop-schemas', version: '>= 3.31.0')
./meson.build:libxml_dep = dependency('libxml-2.0')
./meson.build:polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.105')
./meson.build:pulse_dep = dependency('libpulse', version: pulse_req_version)
./meson.build:pulse_mainloop_dep = dependency('libpulse-mainloop-glib', version: pulse_req_version)
./meson.build:upower_glib_dep = dependency('upower-glib', version: '>= 0.99.8')
./meson.build:gudev_dep = dependency('gudev-1.0', version: '>= 232')
./meson.build:x11_dep = dependency('x11')
./meson.build:xi_dep = dependency('xi', version: '>= 1.2')
./meson.build:epoxy_dep = dependency('epoxy')
./meson.build:  dependency('gio-unix-2.0'),
./meson.build:  dependency('gthread-2.0'),
./meson.build:  dependency('gtk+-3.0', version: '>= 3.22.20')
./meson.build:cups_dep = dependency('cups', version : '>= 1.4', required: false)
./meson.build:    dependency('cheese', version: '>= 3.28.0'),
./meson.build:    dependency('cheese-gtk', version: '>= 3.5.91')
./meson.build:  ibus_dep = dependency('ibus-1.0', version: '>= 1.5.2')
./meson.build:    dependency('snapd-glib', version: '>= 1.49')
./meson.build:    dependency('libnm', version: '>= 1.12.0'),
./meson.build:    dependency('libnma', version: '>= 1.8.0'),
./meson.build:    dependency('mm-glib', version: '>= 0.7')
./meson.build:  gnome_bluetooth_dep = dependency('gnome-bluetooth-1.0', version: '>= 3.18.2')
./meson.build:  libwacom_dep = dependency('libwacom', version: '>= 0.7')
./subprojects/libhandy/doc/meson.build:glib_prefix  = dependency('glib-2.0').get_pkgconfig_variable('prefix')
./subprojects/libhandy/src/meson.build:gio_dep = dependency('gio-2.0', version: glib_min_version)
./subprojects/libhandy/src/meson.build:gtk_dep = dependency('gtk+-3.0', version: '>= 3.24.1')
./subprojects/libhandy/src/meson.build:  dependency('glib-2.0', version: glib_min_version),
./subprojects/libhandy/src/meson.build:  dependency('gmodule-2.0', version: glib_min_version),
./subprojects/libhandy/src/meson.build:  dependency('fribidi'),
./subprojects/libhandy/meson.build:gladeui_dep = dependency('gladeui-2.0', required : glade_catalog_feature)
./subprojects/gvc/meson.build:  dependency('gio-2.0'),
./subprojects/gvc/meson.build:  dependency('gobject-2.0'),
./subprojects/gvc/meson.build:  dependency('libpulse', version: '>= 12.99.3'),
./subprojects/gvc/meson.build:  dependency('libpulse-mainloop-glib')
./subprojects/gvc/meson.build:  libgvc_deps += dependency('alsa')

笔者本地环境要编译 GNOME Settings,需要安装如下这些依赖:

data$ sudo apt install libhandy-0.0-dev libaccountsservice-dev libcolord-dev \
libcolord-gtk-dev libgnome-desktop-3-dev gnome-settings-daemon-dev \
libgoa-1.0-dev libxml++2.6-dev libpolkit-gobject-1-dev libpulse-dev \
libupower-glib-dev libgudev-1.0-dev libx11-dev libxi-dev libepoxy-dev \
libcups2-dev libcheese-dev libcheese-gtk-dev libsnapd-glib-dev libnm-dev \
libnma-dev libmm-glib-dev libgnome-bluetooth-dev libwacom-dev libgrilo-0.3-dev \
libudisks2-dev libsmbclient-dev libgoa-backend-1.0-dev libsecret-1-dev \
libgrilo-0.3-dev libgtop2-dev libsoup2.4-dev libkrb5-dev libpwquality-dev \
libfontconfig1-dev libgsound-dev libfribidi-dev libgladeui-dev

构建 GNOME Settings 的方法如下:

data$ git clone https://github.com/GNOME/gnome-control-center.git
data$ cd gnome-control-center
gnome-control-center$ git checkout -t origin/gnome-3-36
gnome-control-center$ mkdir build
gnome-control-center$ cd build
build$ meson ..
build$ ninja -C .

构建生成的可执行文件位于 ./build/shell/gnome-control-center

GNOME Settings 应用的 声音 面板相关代码位于 gnome-control-center/panels/sound,其中 cc-sound-panel.c声音 面板主入口文件。声音 面板用 CcSoundPanel 对象描述,cc_sound_panel_init() 为这个对象的初始化函数。gnome-control-center/shell/cc-panel-loader.c 文件中定义了各个设置功能面板与其实现对象类型之间的映射关系,如:

static CcPanelLoaderVtable default_panels[] =
{PANEL_TYPE("applications",     cc_applications_panel_get_type,         NULL),PANEL_TYPE("background",       cc_background_panel_get_type,           NULL),
#ifdef BUILD_BLUETOOTHPANEL_TYPE("bluetooth",        cc_bluetooth_panel_get_type,            NULL),
#endifPANEL_TYPE("camera",           cc_camera_panel_get_type,               NULL),PANEL_TYPE("color",            cc_color_panel_get_type,                NULL),PANEL_TYPE("datetime",         cc_date_time_panel_get_type,            NULL),PANEL_TYPE("default-apps",     cc_default_apps_panel_get_type,         NULL),PANEL_TYPE("diagnostics",      cc_diagnostics_panel_get_type,          cc_diagnostics_panel_static_init_func),PANEL_TYPE("display",          cc_display_panel_get_type,              NULL),PANEL_TYPE("info-overview",    cc_info_overview_panel_get_type,        NULL),PANEL_TYPE("keyboard",         cc_keyboard_panel_get_type,             NULL),PANEL_TYPE("location",         cc_location_panel_get_type,             NULL),PANEL_TYPE("lock",             cc_lock_panel_get_type,                 NULL),PANEL_TYPE("microphone",       cc_microphone_panel_get_type,           NULL),PANEL_TYPE("mouse",            cc_mouse_panel_get_type,                NULL),
#ifdef BUILD_NETWORKPANEL_TYPE("network",          cc_network_panel_get_type,              NULL),PANEL_TYPE("wifi",             cc_wifi_panel_get_type,                 cc_wifi_panel_static_init_func),
#endifPANEL_TYPE("notifications",    cc_notifications_panel_get_type,        NULL),PANEL_TYPE("online-accounts",  cc_goa_panel_get_type,                  NULL),PANEL_TYPE("power",            cc_power_panel_get_type,                NULL),PANEL_TYPE("printers",         cc_printers_panel_get_type,             NULL),PANEL_TYPE("region",           cc_region_panel_get_type,               NULL),PANEL_TYPE("removable-media",  cc_removable_media_panel_get_type,      NULL),PANEL_TYPE("search",           cc_search_panel_get_type,               NULL),PANEL_TYPE("sharing",          cc_sharing_panel_get_type,              NULL),PANEL_TYPE("sound",            cc_sound_panel_get_type,                NULL),
#ifdef BUILD_THUNDERBOLTPANEL_TYPE("thunderbolt",      cc_bolt_panel_get_type,                 NULL),
#endifPANEL_TYPE("universal-access", cc_ua_panel_get_type,                   NULL),PANEL_TYPE("usage",            cc_usage_panel_get_type,                NULL),PANEL_TYPE("user-accounts",    cc_user_panel_get_type,                 NULL),
#ifdef BUILD_WACOMPANEL_TYPE("wacom",            cc_wacom_panel_get_type,                cc_wacom_panel_static_init_func),
#endif
};

CcSoundPanel 对象在 GNOME Settings 应用初始化各个设置功能面板时创建,如(去掉调用栈中 glib libgobject 相关的项):

#0  cc_sound_panel_init (self=0x555555af6360) at ../panels/sound/cc-sound-panel.c:285
#5  0x00005555555b4b05 in cc_panel_loader_load_by_name (shell=0x5555564804d0, name=0x555556783040 "sound", parameters=0x0) at ../shell/cc-panel-loader.c:219
#6  0x00005555555b8af1 in activate_panel(self=0x5555564804d0, id=0x555556783040 "sound", parameters=0x0, name=0x555556792180 "声音", gicon=0x5555565fb150, visibility=CC_PANEL_VISIBLE)at ../shell/cc-window.c:184
#7  0x00005555555b969c in set_active_panel_from_id(self=0x5555564804d0, start_id=0x555556783040 "sound", parameters=0x0, add_to_history=1, force_moving_to_the_panel=0, error=0x0) at ../shell/cc-window.c:445
#8  0x00005555555b9ba0 in show_panel_cb (self=0x5555564804d0, panel_id=0x555556783040 "sound") at ../shell/cc-window.c:562
#13 0x00005555555b7014 in row_activated_cb (listbox=0x555556544360, row=0x555556747c50, self=0x5555565402d0) at ../shell/cc-panel-list.c:591
#21 0x00005555555b8088 in cc_panel_list_set_active_panel (self=0x5555565402d0, id=0x5555567921a0 "sound") at ../shell/cc-panel-list.c:1028
#22 0x00005555555ba5f8 in cc_window_constructed (object=0x5555564804d0) at ../shell/cc-window.c:832
#26 0x00005555555baf96 in cc_window_new (application=0x555556447940, model=0x5555563c3a50) at ../shell/cc-window.c:961
#27 0x00005555555b2e6d in cc_application_startup (application=0x555556447940) at ../shell/cc-application.c:231
#34 0x00005555555bb152 in main (argc=1, argv=0x7fffffffdeb8) at ../shell/main.c:70

cc_sound_panel_init() 函数定义 (位于 gnome-control-center/panels/sound/cc-sound-panel.c) 如下:

static void
cc_sound_panel_init (CcSoundPanel *self)
{g_resources_register (cc_sound_get_resource ());gtk_widget_init_template (GTK_WIDGET (self));gtk_list_box_set_header_func (self->input_list_box,cc_list_box_update_header_func,NULL, NULL);gtk_list_box_set_header_func (self->output_list_box,cc_list_box_update_header_func,NULL, NULL);gtk_list_box_set_header_func (GTK_LIST_BOX (self->stream_list_box),cc_list_box_update_header_func,NULL, NULL);self->sound_settings = g_settings_new (KEY_SOUNDS_SCHEMA);g_signal_connect_object (self->sound_settings,"changed::allow-volume-above-100-percent",G_CALLBACK (allow_amplified_changed_cb),self,G_CONNECT_SWAPPED);allow_amplified_changed_cb (self);self->mixer_control = gvc_mixer_control_new ("GNOME Settings");gvc_mixer_control_open (self->mixer_control);cc_stream_list_box_set_mixer_control (self->stream_list_box, self->mixer_control);cc_volume_slider_set_mixer_control (self->input_volume_slider, self->mixer_control);cc_volume_slider_set_mixer_control (self->output_volume_slider, self->mixer_control);cc_subwoofer_slider_set_mixer_control (self->subwoofer_slider, self->mixer_control);cc_device_combo_box_set_mixer_control (self->input_device_combo_box, self->mixer_control, FALSE);cc_device_combo_box_set_mixer_control (self->output_device_combo_box, self->mixer_control, TRUE);g_signal_connect_object (self->mixer_control,"active-output-update",G_CALLBACK (output_device_update_cb),self,G_CONNECT_SWAPPED);g_signal_connect_object (self->mixer_control,"active-input-update",G_CALLBACK (input_device_update_cb),self,G_CONNECT_SWAPPED);
}

GNOME Settings 应用的 声音 面板相关代码实现 UI 显示和用户控制逻辑,实际的音频相关设置通过另外一个库来实现,即 libgnome-volume-control,简称 gvc,gvc 在 GitHub 的只读仓库为 libgnome-volume-control。

gvc 的核心是 GvcMixerControl 对象,它获取系统中音频设备相关的各项信息,用以创建 gvc 的其它各种对象,当系统中音频设备状态改变时,GvcMixerControl 对象首先得到通知,并进而将信息传递给 GNOME Settings 应用的 声音 面板相关逻辑。设置默认输出设备和输入设备的动作,也由 GvcMixerControl 对象执行。

cc_sound_panel_init() 函数中,调用 gvc_mixer_control_new() 创建 GvcMixerControl 对象,并调用 gvc_mixer_control_open() 执行其 open 操作。gvc 实际是对 pulseaudio 的封装。GvcMixerControl 对象的创建过程实现 (位于 gvc/gvc-mixer-control.c) 如下:

static void
gvc_mixer_new_pa_context (GvcMixerControl *self)
{pa_proplist     *proplist;g_return_if_fail (self);g_return_if_fail (!self->priv->pa_context);proplist = pa_proplist_new ();pa_proplist_sets (proplist,PA_PROP_APPLICATION_NAME,self->priv->name);pa_proplist_sets (proplist,PA_PROP_APPLICATION_ID,"org.gnome.VolumeControl");pa_proplist_sets (proplist,PA_PROP_APPLICATION_ICON_NAME,"multimedia-volume-control");pa_proplist_sets (proplist,PA_PROP_APPLICATION_VERSION,PACKAGE_VERSION);g_warning ("mixer control create context with name %s", self->priv->name);self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist);pa_proplist_free (proplist);g_assert (self->priv->pa_context);
}. . . . . .
static GObject *
gvc_mixer_control_constructor (GType                  type,guint                  n_construct_properties,GObjectConstructParam *construct_params)
{GObject         *object;GvcMixerControl *self;object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params);self = GVC_MIXER_CONTROL (object);gvc_mixer_new_pa_context (self);self->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;return object;
}. . . . . .
static void
gvc_mixer_control_init (GvcMixerControl *control)
{control->priv = gvc_mixer_control_get_instance_private (control);control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ());g_assert (control->priv->pa_mainloop);control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop);g_assert (control->priv->pa_api);control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);control->priv->ui_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);control->priv->ui_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);#ifdef HAVE_ALSAcontrol->priv->headset_card = -1;
#endif /* HAVE_ALSA */control->priv->state = GVC_STATE_CLOSED;
}. . . . . .
GvcMixerControl *
gvc_mixer_control_new (const char *name)
{GObject *control;g_warning ("gvc mixer control: %s", name);control = g_object_new (GVC_TYPE_MIXER_CONTROL,"name", name,NULL);return GVC_MIXER_CONTROL (control);
}

GvcMixerControl 对象创建时创建或获得调用 pulseaudio 接口所需的基础对象,主要包括 pa_mainlooppa_mainloop_apipa_context 对象。gvc 预设其将被应用于 GLib 应用程序中,因而pa_mainlooppa_mainloop_api 对象通过 pa_glib_mainloop_new()pa_glib_mainloop_get_api() 创建和获取,这与许多 pulseaudio 客户端应用程序通过 pa_mainloop_new()pa_mainloop_get_api() 创建和获取 pa_mainlooppa_mainloop_api 对象不同。

gvc_mixer_control_open() 函数定义 (位于 gvc/gvc-mixer-control.c) 如下:

static void
gvc_mixer_control_ready (GvcMixerControl *control)
{pa_operation *o;pa_context_set_subscribe_callback (control->priv->pa_context,_pa_context_subscribe_cb,control);o = pa_context_subscribe (control->priv->pa_context,(pa_subscription_mask_t)(PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|PA_SUBSCRIPTION_MASK_CLIENT|PA_SUBSCRIPTION_MASK_SERVER|PA_SUBSCRIPTION_MASK_CARD),NULL,NULL);if (o == NULL) {g_warning ("pa_context_subscribe() failed");return;}pa_operation_unref (o);req_update_server_info (control, -1);req_update_card (control, -1);req_update_client_info (control, -1);req_update_sink_info (control, -1);req_update_source_info (control, -1);req_update_sink_input_info (control, -1);req_update_source_output_info (control, -1);control->priv->server_protocol_version = pa_context_get_server_protocol_version (control->priv->pa_context);control->priv->n_outstanding = 6;/* This call is not always supported */o = pa_ext_stream_restore_read (control->priv->pa_context,_pa_ext_stream_restore_read_cb,control);if (o != NULL) {pa_operation_unref (o);control->priv->n_outstanding++;pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context,_pa_ext_stream_restore_subscribe_cb,control);o = pa_ext_stream_restore_subscribe (control->priv->pa_context,1,NULL,NULL);if (o != NULL) {pa_operation_unref (o);}} else {g_debug ("Failed to initialized stream_restore extension: %s",pa_strerror (pa_context_errno (control->priv->pa_context)));}
}. . . . . .
static void
_pa_context_state_cb (pa_context *context,void       *userdata)
{GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);switch (pa_context_get_state (context)) {case PA_CONTEXT_UNCONNECTED:case PA_CONTEXT_CONNECTING:case PA_CONTEXT_AUTHORIZING:case PA_CONTEXT_SETTING_NAME:break;case PA_CONTEXT_READY:gvc_mixer_control_ready (control);break;case PA_CONTEXT_FAILED:control->priv->state = GVC_STATE_FAILED;g_signal_emit (control, signals[STATE_CHANGED], 0, GVC_STATE_FAILED);if (control->priv->reconnect_id == 0)control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control);break;case PA_CONTEXT_TERMINATED:default:/* FIXME: */break;}
}gboolean
gvc_mixer_control_open (GvcMixerControl *control)
{int res;g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE);pa_context_set_state_callback (control->priv->pa_context,_pa_context_state_cb,control);control->priv->state = GVC_STATE_CONNECTING;g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CONNECTING);res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, NULL);if (res < 0) {g_warning ("Failed to connect context: %s",pa_strerror (pa_context_errno (control->priv->pa_context)));}return res;
}

gvc_mixer_control_open()pa_context 设置状态回调,并建立与 pulseaudio 服务之间的连接。连接建立成功,完成必要的握手后,状态回调被调用。GvcMixerControl 对象在状态回调中订阅 pulseaudio 的 sink、source、sink_input、source_output、client、server、card 和 ext stream restore 等信息,并调用如下这些函数由 pulseaduio 获取这些相关信息:

  • pa_context_get_server_info()
  • pa_context_get_card_info_list()/pa_context_get_card_info_by_index()
  • pa_context_get_client_info_list()/pa_context_get_client_info()
  • pa_context_get_sink_info_list()/pa_context_get_sink_info_by_index()
  • pa_context_get_source_info_list()/pa_context_get_source_info_by_index()
  • pa_context_get_sink_input_info_list()/pa_context_get_sink_input_info()
  • pa_context_get_source_output_info_list()/pa_context_get_source_output_info()
  • pa_ext_stream_restore_read()/pa_ext_stream_restore_set_subscribe_cb()/pa_ext_stream_restore_subscribe()

获得的 sink 信息由 pa_sink_info 结构描述,被用于创建 GvcMixerSink 对象。获得的 source 信息由 pa_source_info 结构描述,被用于创建 GvcMixerSource 对象。获得的 sink_input 信息由 pa_sink_input_info 结构描述,被用于创建 GvcMixerSinkInput 对象。获得的 source_output 信息由 pa_source_output_info 结构描述,被用于创建 GvcMixerSourceOutput 对象。创建的这些对象将被添加到 GvcMixerControl 对象的对应类型的 stream 的哈希表中,并被添加到它的 all_streams 哈希表中。

sink 和 source 还可能具有 ports 信息,sink 的 port 信息用 pa_sink_port_info 结构描述,source 的 port 信息用 pa_source_port_info 结构描述,各个 port 的信息被用来创建 GvcMixerStreamPort 对象。

当 sink 和 source 没有 ports 信息时,则基于 stream 创建 GvcMixerUIDevice 对象。根据 stream 是 source 还是 sink,创建的 GvcMixerUIDevice 对象被添加到 GvcMixerControl 对象的 ui_inputsui_outputs 哈希表中。

当 sink 和 source 有 ports 信息时,这些 port 信息用来匹配基于 card 中 port 信息创建的 GvcMixerUIDevice 对象。在 update_source()/update_sink() -> sync_devices() 中,遍历 port 信息,并调用 match_stream_with_devices() 函数,根据 port、card id 等信息,匹配 stream 和 GvcMixerUIDevice 对象,即设置 GvcMixerUIDevice 对象的 stream_id 字段。sink 和 source 有 ports 信息时,需要先获取 card 信息,否则它们要匹配的 GvcMixerUIDevice 对象还未创建,后面修改默认输出输入设备的操作将无法正常工作。

client 信息由 pa_client_info 结构描述,包含连接到 pulseaudio 服务的应用程序相关的信息,如:

Updating client: index=0 name='Login Session 2'
Updating client: index=4 name='XSMP Session on gnome-session as 10628215e953f352f171755279469869900000017280061'
Updating client: index=5 name='GNOME Shell Volume Control'
Updating client: index=6 name='GNOME Volume Control Media Keys'
Updating client: index=7 name='更新通知'
Updating client: index=8 name='终端'
Updating client: index=9 name='Eclipse'
Updating client: index=11 name='Linux volume control'

在通过 pa_context_new_with_proplist() 创建 pa_context 对象时,传入的 pa_proplist 参数添加了 PA_PROP_APPLICATION_NAME 属性,这里显示的各个客户端名称即为各个应用程序传给 pulseaudio 服务的这个应用名称。

server 信息由 pa_server_info 结构描述,包含主机和 pulseaudio 服务本身的信息,一般来说,需要特别关注的是默认 source 和 sink 信息,如:

get server info
update server user_name plgabc, host_name plgabcvm, server_version 13.99.1, server_name pulseaudio
update server default_source_name alsa_input.pci-0000_00_05.0.analog-stereo
update server default_sink_name alsa_output.pci-0000_00_05.0.analog-surround-40

card 包含声卡相关的各种信息,如:

Updating card alsa_card.pci-0000_00_05.0 (index: 0 driver: module-alsa-card.c):Profile 'input:analog-stereo': 1 sources 0 sinksProfile 'output:analog-stereo': 0 sources 1 sinksProfile 'output:analog-stereo+input:analog-stereo': 1 sources 1 sinksProfile 'output:analog-surround-21': 0 sources 1 sinksProfile 'output:analog-surround-21+input:analog-stereo': 1 sources 1 sinksProfile 'output:analog-surround-40': 0 sources 1 sinksProfile 'output:analog-surround-40+input:analog-stereo': 1 sources 1 sinks (Current)Profile 'output:analog-surround-41': 0 sources 1 sinksProfile 'output:analog-surround-41+input:analog-stereo': 1 sources 1 sinksProfile 'output:analog-surround-50': 0 sources 1 sinksProfile 'output:analog-surround-50+input:analog-stereo': 1 sources 1 sinksProfile 'output:analog-surround-51': 0 sources 1 sinksProfile 'output:analog-surround-51+input:analog-stereo': 1 sources 1 sinksProfile 'off': 0 sources 0 sinksProperty: 'alsa.card' = '0'Property: 'alsa.card_name' = 'Intel 82801AA-ICH'Property: 'alsa.long_card_name' = 'Intel 82801AA-ICH with AD1980 at irq 21'Property: 'alsa.driver_name' = 'snd_intel8x0'Property: 'device.bus_path' = 'pci-0000:00:05.0'Property: 'sysfs.path' = '/devices/pci0000:00/0000:00:05.0/sound/card0'Property: 'device.bus' = 'pci'Property: 'device.vendor.id' = '8086'Property: 'device.vendor.name' = 'Intel Corporation'Property: 'device.product.id' = '2415'Property: 'device.product.name' = '82801AA AC'97 Audio Controller'Property: 'device.form_factor' = 'internal'Property: 'device.string' = '0'Property: 'device.description' = '内置音频'Property: 'module-udev-detect.discovered' = '1'Property: 'device.icon_name' = 'audio-card-pci'n_ports 7Port 'analog-input-mic;input-microphone-1': description 话筒 / 话筒 1Port 'analog-input-mic;input-microphone-2': description 话筒 / 话筒 2Port 'analog-input-linein': description 输入插孔Port 'analog-input-aux': description 模拟输入Port 'analog-input-video': description 视频Port 'analog-output;output-amplifier-on': description 模拟输出 / 功放Port 'analog-output;output-amplifier-off': description 模拟输出 / 无功放

这里包含许多信息,它们被用来创建 GvcMixerCard 对象。其中 profile 信息,即我们在 GNOME Settings 应用的 声音 -> 输出 -> 配置 中看到的那些,它们被用来创建 GvcMixerCardProfile 对象。

port 信息被用来创建 GvcMixerCardPort 对象。GvcMixerCardPort 对象进一步被用来创建 GvcMixerUIDevice 对象。根据 port 的类型,创建的 GvcMixerUIDevice 对象被添加到 GvcMixerControl 对象的 ui_outputsui_inputs 哈希表中。card 的 ports 信息与上面看到的 sink 和 source 的 ports 信息是相同的。

我们在 GNOME Settings 应用的 声音 -> 输出 -> 输出设备声音 -> 输入 -> 输入设备 中看到的那些音频设备项对应 GvcMixerUIDevice 对象。GvcMixerUIDevice 对象有两种类型,一种是 stream 型,即获取到 sink 和 source 信息,通过 update_source()/update_sink() -> sync_devices(),在 stream 没有 port 信息时,基于 stream 创建。另一种是 port 型,即基于 card 的 port 信息创建。

GvcMixerCardProfileGvcMixerCardPort 对象被保存在 GvcMixerCard 对象对应的列表中。GvcMixerCard 对象会被添加到 GvcMixerControl 对象的 cards 哈希表中。

ext stream restore 信息由 pa_ext_stream_restore_info 结构描述,它们被用于创建 GvcMixerEventRole 对象 (通过 gvc_mixer_event_role_new() 函数),创建的对象将被添加到 GvcMixerControl 对象的 all_streams 哈希表中。

gvc 的这些对象具有如下这样的继承层次结构:

GVC Object Hierarchy

gvc 的这些对象具有如下的关系:

GVC Objects Relationship

Linux 系统音频设置相关信息的获取和展示,大体如上所述,即向 pulseaudio 服务订阅各种信息,在 pulseaudio 的回调中,读取它们,并通过 GNOME Settings 应用的 声音 面板各个控件展示出来。

对于音频的设置,同样通过 pulseaudio 的接口实现。音频相关的设置,主要包括如下几项:

  • 音量
  • 默认输出设备
  • 默认输入设备
  • 配置

对于音量设置,当音量没有调到最小时,设置音量值,当音量调到了最小,则设置流静音。针对不同类型的流,调用不同的 pulseaudio 接口来执行。

gvc/gvc-mixer-source.c 中设置 source 的音量和静音的方法如下:

static gboolean
gvc_mixer_source_push_volume (GvcMixerStream *stream, gpointer *op)
{pa_operation        *o;guint                index;const GvcChannelMap *map;pa_context          *context;const pa_cvolume    *cv;index = gvc_mixer_stream_get_index (stream);map = gvc_mixer_stream_get_channel_map (stream);/* set the volume */cv = gvc_channel_map_get_cvolume (map);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_source_volume_by_index (context,index,cv,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_source_volume_by_index() failed: %s", pa_strerror(pa_context_errno(context)));return FALSE;}*op = o;return TRUE;
}static gboolean
gvc_mixer_source_change_is_muted (GvcMixerStream *stream,gboolean        is_muted)
{pa_operation *o;guint         index;pa_context   *context;index = gvc_mixer_stream_get_index (stream);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_source_mute_by_index (context,index,is_muted,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_source_mute_by_index() failed: %s", pa_strerror(pa_context_errno(context)));return FALSE;}pa_operation_unref(o);return TRUE;
}

gvc/gvc-mixer-sink.c 中设置 sink 的音量和静音的方法如下:

static gboolean
gvc_mixer_sink_push_volume (GvcMixerStream *stream, gpointer *op)
{pa_operation        *o;guint                index;const GvcChannelMap *map;pa_context          *context;const pa_cvolume    *cv;index = gvc_mixer_stream_get_index (stream);map = gvc_mixer_stream_get_channel_map (stream);/* set the volume */cv = gvc_channel_map_get_cvolume(map);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_sink_volume_by_index (context,index,cv,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_sink_volume_by_index() failed: %s", pa_strerror(pa_context_errno(context)));return FALSE;}*op = o;return TRUE;
}static gboolean
gvc_mixer_sink_change_is_muted (GvcMixerStream *stream,gboolean        is_muted)
{pa_operation *o;guint         index;pa_context   *context;index = gvc_mixer_stream_get_index (stream);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_sink_mute_by_index (context,index,is_muted,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_sink_mute_by_index() failed: %s", pa_strerror(pa_context_errno(context)));return FALSE;}pa_operation_unref(o);return TRUE;
}

gvc/gvc-mixer-sink-input.c 中设置 sink_input 的音量和静音的方法如下:

static gboolean
gvc_mixer_sink_input_push_volume (GvcMixerStream *stream, gpointer *op)
{pa_operation        *o;guint                index;const GvcChannelMap *map;pa_context          *context;const pa_cvolume    *cv;index = gvc_mixer_stream_get_index (stream);map = gvc_mixer_stream_get_channel_map (stream);cv = gvc_channel_map_get_cvolume(map);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_sink_input_volume (context,index,cv,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_sink_input_volume() failed");return FALSE;}*op = o;return TRUE;
}static gboolean
gvc_mixer_sink_input_change_is_muted (GvcMixerStream *stream,gboolean        is_muted)
{pa_operation *o;guint         index;pa_context   *context;index = gvc_mixer_stream_get_index (stream);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_sink_input_mute (context,index,is_muted,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_sink_input_mute_by_index() failed");return FALSE;}pa_operation_unref(o);return TRUE;
}

gvc/gvc-mixer-source-output.c 中设置 source_output 的音量和静音的方法如下:

static gboolean
gvc_mixer_source_output_push_volume (GvcMixerStream *stream, gpointer *op)
{pa_operation        *o;guint                index;const GvcChannelMap *map;pa_context          *context;const pa_cvolume    *cv;index = gvc_mixer_stream_get_index (stream);map = gvc_mixer_stream_get_channel_map (stream);cv = gvc_channel_map_get_cvolume(map);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_source_output_volume (context,index,cv,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_source_output_volume() failed");return FALSE;}*op = o;return TRUE;
}static gboolean
gvc_mixer_source_output_change_is_muted (GvcMixerStream *stream,gboolean        is_muted)
{pa_operation *o;guint         index;pa_context   *context;index = gvc_mixer_stream_get_index (stream);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_source_output_mute (context,index,is_muted,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_source_output_mute_by_index() failed");return FALSE;}pa_operation_unref(o);return TRUE;
}

gvc/gvc-mixer-event-role.c 中设置 ext stream restore 的音量和静音的方法如下:

static gboolean
update_settings (GvcMixerEventRole *role,gboolean           is_muted,gpointer          *op)
{pa_operation              *o;const GvcChannelMap       *map;pa_context                *context;pa_ext_stream_restore_info info;map = gvc_mixer_stream_get_channel_map (GVC_MIXER_STREAM(role));info.volume = *gvc_channel_map_get_cvolume(map);info.name = "sink-input-by-media-role:event";info.channel_map = *gvc_channel_map_get_pa_channel_map(map);info.device = role->priv->device;info.mute = is_muted;context = gvc_mixer_stream_get_pa_context (GVC_MIXER_STREAM (role));o = pa_ext_stream_restore_write (context,PA_UPDATE_REPLACE,&info,1,TRUE,NULL,NULL);if (o == NULL) {g_warning ("pa_ext_stream_restore_write() failed");return FALSE;}if (op != NULL)*op = o;return TRUE;
}static gboolean
gvc_mixer_event_role_push_volume (GvcMixerStream *stream, gpointer *op)
{return update_settings (GVC_MIXER_EVENT_ROLE (stream),gvc_mixer_stream_get_is_muted (stream), op);
}static gboolean
gvc_mixer_event_role_change_is_muted (GvcMixerStream *stream,gboolean        is_muted)
{/* Apply change straight away so that we don't get a race with* gvc_mixer_event_role_push_volume().* See https://bugs.freedesktop.org/show_bug.cgi?id=51413 */gvc_mixer_stream_set_is_muted (stream, is_muted);return update_settings (GVC_MIXER_EVENT_ROLE (stream),is_muted, NULL);
}

GNOME Settings 应用的 声音 面板中,看到的最上面的 系统音量 用于设置输出音量,它的调整通过 GvcMixerSink 的函数实现;看到的 输入 中的 音量,用于设置录音音量,它的调整通过 GvcMixerSource 的函数实现。

GNOME Settings 应用的 声音 面板中,看到的 输出 中的 均衡淡出重低音 设置,它们的调整也通过 GvcMixerSink 的音量调节函数实现。

GNOME Settings 应用的 声音 面板中,看到的 音量级别 中的 系统声音 设置,它们的调整通过 GvcMixerEventRole 的音量调节函数实现。

设置默认输出设备的调用过程可能像下面这样:

#0  gvc_mixer_sink_change_portPython Exception <class 'ValueError'> Variable 'static_fundamental_type_nodes' not found.: (stream=, port=0x555556b7a570 "\005") at ../subprojects/gvc/gvc-mixer-sink.c:109
#1  0x000055555568293d in gvc_mixer_stream_change_port (stream=0x5555568285a0, port=0x555556b83730 "analog-output;output-amplifier-off")at ../subprojects/gvc/gvc-mixer-stream.c:573
#2  0x0000555555677196 in gvc_mixer_control_change_output (control=0x555556468e60, output=0x555556b81780) at ../subprojects/gvc/gvc-mixer-control.c:638
#3  0x000055555566bfac in output_device_changed_cb (self=0x555555af6360) at ../panels/sound/cc-sound-panel.c:131

UI 层调用 GvcMixerControl 对象的 gvc_mixer_control_change_output() 函数执行操作,这个函数定义如下:

void
gvc_mixer_control_change_output (GvcMixerControl *control,GvcMixerUIDevice* output)
{GvcMixerStream           *stream;GvcMixerStream           *default_stream;const GvcMixerStreamPort *active_port;const gchar              *output_port;g_return_if_fail (GVC_IS_MIXER_CONTROL (control));g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (output));g_debug ("control change output");stream = gvc_mixer_control_get_stream_from_device (control, output);if (stream == NULL) {gvc_mixer_control_change_profile_on_selected_device (control,output, NULL);return;}if (!gvc_mixer_ui_device_has_ports (output)) {g_debug ("Did we try to move to a software/bluetooth sink ?");if (gvc_mixer_control_set_default_sink (control, stream)) {/* sink change was successful,  update the UI.*/g_signal_emit (G_OBJECT (control),signals[ACTIVE_OUTPUT_UPDATE],0,gvc_mixer_ui_device_get_id (output));}else {g_warning ("Failed to set default sink with stream from output %s",gvc_mixer_ui_device_get_description (output));}return;}active_port = gvc_mixer_stream_get_port (stream);output_port = gvc_mixer_ui_device_get_port (output);/* First ensure the correct port is active on the sink */if (g_strcmp0 (active_port->port, output_port) != 0) {g_debug ("Port change, switch to = %s", output_port);if (gvc_mixer_stream_change_port (stream, output_port) == FALSE) {g_warning ("Could not change port !");return;}}default_stream = gvc_mixer_control_get_default_sink (control);/* Finally if we are not on the correct stream, swap over. */if (stream != default_stream) {GvcMixerUIDevice* device;g_debug ("Attempting to swap over to stream %s ",gvc_mixer_stream_get_description (stream));if (gvc_mixer_control_set_default_sink (control, stream)) {device = gvc_mixer_control_lookup_device_from_stream (control, stream);g_signal_emit (G_OBJECT (control),signals[ACTIVE_OUTPUT_UPDATE],0,gvc_mixer_ui_device_get_id (device));} else {/* If the move failed for some reason reset the UI. */device = gvc_mixer_control_lookup_device_from_stream (control, default_stream);g_signal_emit (G_OBJECT (control),signals[ACTIVE_OUTPUT_UPDATE],0,gvc_mixer_ui_device_get_id (device));}}
}

前面我们看到,GvcMixerUIDevice 对象有 stream 型和 port 型之分。如果设置的新的默认输出设备为 stream 型,则调用 gvc_mixer_control_set_default_sink() 函数切换默认的输出 stream,如:

static void
gvc_mixer_control_stream_restore_cb (pa_context *c,GvcMixerStream *new_stream,const pa_ext_stream_restore_info *info,GvcMixerControl *control)
{pa_operation *o;pa_ext_stream_restore_info new_info;if (new_stream == NULL)return;new_info.name = info->name;new_info.channel_map = info->channel_map;new_info.volume = info->volume;new_info.mute = info->mute;new_info.device = gvc_mixer_stream_get_name (new_stream);o = pa_ext_stream_restore_write (control->priv->pa_context,PA_UPDATE_REPLACE,&new_info, 1,TRUE, NULL, NULL);if (o == NULL) {g_warning ("pa_ext_stream_restore_write() failed: %s",pa_strerror (pa_context_errno (control->priv->pa_context)));return;}g_debug ("Changed default device for %s to %s", info->name, new_info.device);pa_operation_unref (o);
}static void
gvc_mixer_control_stream_restore_sink_cb (pa_context *c,const pa_ext_stream_restore_info *info,int eol,void *userdata)
{GvcMixerControl *control = (GvcMixerControl *) userdata;if (eol || info == NULL || !g_str_has_prefix(info->name, "sink-input-by"))return;gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_sink_stream, info, control);
}. . . . . .
gboolean
gvc_mixer_control_set_default_sink (GvcMixerControl *control,GvcMixerStream  *stream)
{pa_operation *o;g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);g_debug ("about to set default sink on server");o = pa_context_set_default_sink (control->priv->pa_context,gvc_mixer_stream_get_name (stream),NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_default_sink() failed: %s",pa_strerror (pa_context_errno (control->priv->pa_context)));return FALSE;}pa_operation_unref (o);control->priv->new_default_sink_stream = stream;g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_sink_stream);o = pa_ext_stream_restore_read (control->priv->pa_context,gvc_mixer_control_stream_restore_sink_cb,control);if (o == NULL) {g_warning ("pa_ext_stream_restore_read() failed: %s",pa_strerror (pa_context_errno (control->priv->pa_context)));return FALSE;}pa_operation_unref (o);return TRUE;
}

如果设置的新的默认输出设备为 port 型,则首先通过 GvcMixerStream 对象的 gvc_mixer_stream_change_port() 函数设置 sink stream 的 port,如在 GvcMixerSink 中的 gvc_mixer_sink_change_port() 函数:

static gboolean
gvc_mixer_sink_change_port (GvcMixerStream *stream,const char     *port)
{pa_operation *o;guint         index;pa_context   *context;index = gvc_mixer_stream_get_index (stream);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_sink_port_by_index (context,index,port,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_sink_port_by_index() failed: %s", pa_strerror(pa_context_errno(context)));return FALSE;}pa_operation_unref(o);return TRUE;
}

随后,如果新设置的输出设备的 stream 与之前的不同,则也会调用 gvc_mixer_control_set_default_sink() 函数切换默认的输出 stream。

设置默认输入设备的过程与设置默认输出设备的过程类似,主要通过 GvcMixerControl 对象的 gvc_mixer_control_change_input() 函数执行,这个函数定义如下:

void
gvc_mixer_control_change_input (GvcMixerControl *control,GvcMixerUIDevice* input)
{GvcMixerStream           *stream;GvcMixerStream           *default_stream;const GvcMixerStreamPort *active_port;const gchar              *input_port;g_return_if_fail (GVC_IS_MIXER_CONTROL (control));g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (input));stream = gvc_mixer_control_get_stream_from_device (control, input);if (stream == NULL) {gvc_mixer_control_change_profile_on_selected_device (control,input, NULL);return;}if (!gvc_mixer_ui_device_has_ports (input)) {g_debug ("Did we try to move to a software/bluetooth source ?");if (! gvc_mixer_control_set_default_source (control, stream)) {g_warning ("Failed to set default source with stream from input %s",gvc_mixer_ui_device_get_description (input));}return;}active_port = gvc_mixer_stream_get_port (stream);input_port = gvc_mixer_ui_device_get_port (input);/* First ensure the correct port is active on the sink */if (g_strcmp0 (active_port->port, input_port) != 0) {g_debug ("Port change, switch to = %s", input_port);if (gvc_mixer_stream_change_port (stream, input_port) == FALSE) {g_warning ("Could not change port!");return;}}default_stream = gvc_mixer_control_get_default_source (control);/* Finally if we are not on the correct stream, swap over. */if (stream != default_stream) {g_debug ("change-input - attempting to swap over to stream %s",gvc_mixer_stream_get_description (stream));gvc_mixer_control_set_default_source (control, stream);}
}

对于新的默认输入设备为 stream 型的,调用 gvc_mixer_control_set_default_source() 函数切换默认的输入 stream,如:

static void
gvc_mixer_control_stream_restore_cb (pa_context *c,GvcMixerStream *new_stream,const pa_ext_stream_restore_info *info,GvcMixerControl *control)
{pa_operation *o;pa_ext_stream_restore_info new_info;if (new_stream == NULL)return;new_info.name = info->name;new_info.channel_map = info->channel_map;new_info.volume = info->volume;new_info.mute = info->mute;new_info.device = gvc_mixer_stream_get_name (new_stream);o = pa_ext_stream_restore_write (control->priv->pa_context,PA_UPDATE_REPLACE,&new_info, 1,TRUE, NULL, NULL);if (o == NULL) {g_warning ("pa_ext_stream_restore_write() failed: %s",pa_strerror (pa_context_errno (control->priv->pa_context)));return;}g_debug ("Changed default device for %s to %s", info->name, new_info.device);pa_operation_unref (o);
}. . . . . .
static void
gvc_mixer_control_stream_restore_source_cb (pa_context *c,const pa_ext_stream_restore_info *info,int eol,void *userdata)
{GvcMixerControl *control = (GvcMixerControl *) userdata;if (eol || info == NULL || !g_str_has_prefix(info->name, "source-output-by"))return;gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_source_stream, info, control);
}. . . . . .
gboolean
gvc_mixer_control_set_default_source (GvcMixerControl *control,GvcMixerStream  *stream)
{GvcMixerUIDevice* input;pa_operation *o;g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);o = pa_context_set_default_source (control->priv->pa_context,gvc_mixer_stream_get_name (stream),NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_default_source() failed");return FALSE;}pa_operation_unref (o);control->priv->new_default_source_stream = stream;g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_source_stream);o = pa_ext_stream_restore_read (control->priv->pa_context,gvc_mixer_control_stream_restore_source_cb,control);if (o == NULL) {g_warning ("pa_ext_stream_restore_read() failed: %s",pa_strerror (pa_context_errno (control->priv->pa_context)));return FALSE;}pa_operation_unref (o);/* source change successful, update the UI. */input = gvc_mixer_control_lookup_device_from_stream (control, stream);g_signal_emit (G_OBJECT (control),signals[ACTIVE_INPUT_UPDATE],0,gvc_mixer_ui_device_get_id (input));return TRUE;
}

如果设置的新的默认输入设备为 port 型,则首先通过 GvcMixerStream 对象的 gvc_mixer_stream_change_port() 函数设置 source stream 的 port,如在 GvcMixerSource 中的 gvc_mixer_source_change_port() 函数:

static gboolean
gvc_mixer_source_change_port (GvcMixerStream *stream,const char     *port)
{pa_operation *o;guint         index;pa_context   *context;index = gvc_mixer_stream_get_index (stream);context = gvc_mixer_stream_get_pa_context (stream);o = pa_context_set_source_port_by_index (context,index,port,NULL,NULL);if (o == NULL) {g_warning ("pa_context_set_source_port_by_index() failed: %s", pa_strerror(pa_context_errno(context)));return FALSE;}pa_operation_unref(o);return TRUE;
}

随后,如果新设置的输入设备的 stream 与之前的不同,则也会调用 gvc_mixer_control_set_default_source() 函数切换默认的输入 stream。

对于设置 配置,其调用过程如下:

#0  gvc_mixer_card_change_profile (card=0x555555685ac6, profile=0x7fffffffca70 "") at ../subprojects/gvc/gvc-mixer-card.c:240
#1  0x0000555555676eba in gvc_mixer_control_change_profile_on_selected_device (control=0x555556468e60, device=0x555556b81780, profile=0x5555568ea940 "output:analog-surround-50")at ../subprojects/gvc/gvc-mixer-control.c:572
#2  0x00005555556731ef in profile_changed_cb (self=0x5555567aa470) at ../panels/sound/cc-profile-combo-box.c:47

gvc_mixer_card_change_profile() 函数中调用 pulseaudio 的接口实现,如:

gboolean
gvc_mixer_card_change_profile (GvcMixerCard *card,const char *profile)
{g_return_val_if_fail (GVC_IS_MIXER_CARD (card), FALSE);g_return_val_if_fail (card->priv->profiles != NULL, FALSE);g_warning("mixer card change profile to '%s'", profile);/* Same profile, or already requested? */if (g_strcmp0 (card->priv->profile, profile) == 0)return TRUE;if (g_strcmp0 (profile, card->priv->target_profile) == 0)return TRUE;if (card->priv->profile_op != NULL) {pa_operation_cancel (card->priv->profile_op);pa_operation_unref (card->priv->profile_op);card->priv->profile_op = NULL;}if (card->priv->profile != NULL) {g_free (card->priv->target_profile);card->priv->target_profile = g_strdup (profile);card->priv->profile_op = pa_context_set_card_profile_by_index (card->priv->pa_context,card->priv->index,card->priv->target_profile,_pa_context_set_card_profile_by_index_cb,card);if (card->priv->profile_op == NULL) {g_warning ("pa_context_set_card_profile_by_index() failed");return FALSE;}} else {g_assert (card->priv->human_profile == NULL);card->priv->profile = g_strdup (profile);}return TRUE;
}

更多 UI 行为处理的细节,这里不再赘述,具体可以参考 GNOME Settings 及其依赖的 gvc 的源码。

Linux GNOME 桌面系统音频设置项的信息及设置操作,通过 pulseaudio 的接口实现。

Done.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/24913.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java异常机制

1.异常概述和异常处理机制 异常(exception)概述 异常就是程序在运行时出现的意外的&#xff0c;不正常的情况。 若异常产生后没有正确的处理&#xff0c;会导致程序的中断&#xff0c;程序不继续执行,以致造成损失。 2.2 异常处理机制 所以我们在开发中要一套机制来处理各种可能…

计算机网络 —— 网络层(子网掩码和子网划分)

计算机网络 —— 网络层&#xff08;子网掩码和子网划分&#xff09; 网络地址转换NAT子网掩码和子网划分举个例子第一步&#xff1a;看类型第二步&#xff1a;从主机号开始比对第三步&#xff1a;去头去尾 我们今天来看子网掩码和子网划分&#xff1a; 网络地址转换NAT 网络…

用于认知负荷评估的集成时空深度聚类(ISTDC)

Integrated Spatio-Temporal Deep Clustering (ISTDC) for cognitive workload assessment 摘要&#xff1a; 本文提出了一种新型的集成时空深度聚类&#xff08;ISTDC&#xff09;模型&#xff0c;用于评估认知负荷。该模型首先利用深度表示学习&#xff08;DRL&#xff09;…

人工智能在【肿瘤生物标志物】领域的最新研究进展|顶刊速递·24-06-08

小罗碎碎念 本期文献速递的主题是——人工智能在“肿瘤生物标志物”领域的最新研究进展。 重点关注 今天推荐的6篇文献中&#xff0c;第二篇和第三篇是小罗最喜欢的&#xff0c;因为对于临床来说&#xff0c;比较具有实际意义&#xff0c;也和自己的想法很契合。 尤其是第三篇…

每日一题——Python实现PAT甲级1015 Reversible Primes(举一反三+思想解读+逐步优化)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 is_prime函数分析&#xff1a; decimal_to_base函数分析&#xff1a; 主循…

Shell脚本学习_环境变量深入

目录 1.Shell环境变量深入&#xff1a;自定义系统环境变量 2.Shell环境变量深入&#xff1a;加载流程原理介绍 3.Shell环境变量深入&#xff1a;加载流程测试 4.Shell环境变量深入&#xff1a;识别与切换Shell环境类型 1.Shell环境变量深入&#xff1a;自定义系统环境变量 …

旧衣回收小程序开发,轻松回收旧衣物

随着环保理念的增强&#xff0c;回收市场得到了快速发展&#xff0c;吸引了不少年轻人进入到市场中创业。除了传统的废品回收外&#xff0c;旧衣回收也受到了大众的重视&#xff0c;市场规模迅速扩大&#xff0c;大众浪费的衣物也获得了归处。 目前旧衣回收的方式主要是线上与…

makefile与进度条

Linux项目自动化构建工具-make/makefile make是一个命令&#xff0c; makefile是一个文件&#xff0c;保存依赖关系和依赖方法。‘ touch Makefile/makefile mybin:mytest.c//依赖关系 目标文件&#xff1a;依赖文件列表 文件列表的文件之间以空格分隔 gcc -o mybin mytest.…

Shell脚本学习_字符串变量

目录 1.Shell字符串变量&#xff1a;格式介绍 2.Shell字符串变量&#xff1a;拼接 3.Shell字符串变量&#xff1a;字符串截取 4.Shell索引数组变量&#xff1a;定义-获取-拼接-删除 1.Shell字符串变量&#xff1a;格式介绍 1、目标&#xff1a; 能够使用字符串的三种方式 …

linux系统——ping命令

ping命令可以用来判断对远端ip的连通性&#xff0c;可以加域名也可以加公共ip地址 这里发送出56字节&#xff0c;返回64字节

ctfshow-web入门-命令执行(web41_exp与分析)

过滤不严&#xff0c;命令执行 preg_match(/[0-9]|[a-z]|\^|\|\~|\$|\[|\]|\{|\}|\&|\-/i, $c) 过滤掉了数字、字母以及一些符号&#xff0c;之前接触过的无字母 rce 是取反编码再取反&#xff0c;采用不可见字符去绕过正则&#xff0c;但是这里取反符号被过滤掉了&#x…

Kali linux学习入门

Kali linux学习入门 文章目录 Kali linux学习入门Kali Linux简介Kali Linux工具篇Kali Docker安装Docker 更换国内镜像源Kali 安装 docker compose Kali Linux文档篇Kali Linux 社区篇 Kali Linux简介 Kali Linux是专门用于渗透测试linux操作系统&#xff0c;它由BackTrack发展…

软件游戏找不到d3dx9_43.dll怎么办,三分钟教你解决此问题

在现代科技发展的时代&#xff0c;电脑已经成为我们生活中不可或缺的一部分。然而&#xff0c;在使用电脑的过程中&#xff0c;我们可能会遇到一些问题&#xff0c;其中之一就是电脑缺失d3dx943.dll文件。这个问题可能会影响到我们的正常使用&#xff0c;因此了解其原因和解决方…

接口(API)开发,测试工具-apifox

前言 为什么需要接口&#xff08;API&#xff09;? 因为不同的平台或系统可能使用不同的技术栈、编程语言或数据格式。API提供了一个标准化的方式&#xff0c;使得这些不同的系统可以相互交换数据和功能调用&#xff0c;实现互操作性 在开发日常的项目交互中&#xff0c;不…

PyCharm中 Fitten Code插件的使用说明一

一. 简介 Fitten Code插件是是一款由非十大模型驱动的 AI 编程助手&#xff0c;它可以自动生成代码&#xff0c;提升开发效率&#xff0c;帮您调试 Bug&#xff0c;节省您的时间&#xff0c;另外还可以对话聊天&#xff0c;解决您编程碰到的问题。 前一篇文章学习了 PyCharm…

小白教程--- kali(po解)WIFI密码 (图文教程)

kali学得好&#xff0c;牢饭少不了&#xff01;&#xff01;&#xff01; 原理&#xff1a; 模拟WiFi的已连接设备&#xff0c;强制让其下线重连&#xff0c;获取其握手包&#xff0c;使用密码字典&#xff08;宝丽&#xff09;婆洁。 环境&#xff08;准备工作&#xff09;&a…

深度解析:ChatGPT全面测评——功能、性能与用户体验全景剖析

从去年底至今&#xff0c;由 OpenAI 发布的大规模语言模型 ChatGPT 引发了几乎所有科技领域从业者的高度关注。据瑞银集团的报告显示&#xff0c;自 2023 年 1 月起&#xff0c;仅两个月内&#xff0c;ChatGPT 的月活用户数便超过了 1 亿。 ChatGPT 被誉为“最强 AI”&#xff…

操作系统总结

进程和线程的区别 本质区别&#xff1a; 进程是资源调度以及分配的基本单位。线程是 CPU 调度的基本单位。 所属关系&#xff1a;一个线程属于一个进程&#xff0c;一个进程可以拥有多个线程。地址空间&#xff1a; 进程有独立的虚拟地址空间。线程没有独立的虚拟地址空间&…

Day53 动态规划part12

LC309买卖股票的最佳时机含冷冻期 与LC122类似&#xff0c;都是可无限次购买股票&#xff0c;只不过引入了冷冻期的概念dp[i][0] 第i天持有股票收益&#xff1b;dp[i][1] 第i天不持有股票收益;情况一&#xff1a;第i天是冷静期&#xff0c;不能以dp[i-1][1]购买股票,所以以dp[…

性能测试 —— Jmeter对数据库压力测试

Jmeter先要和数据库建立连接&#xff0c;sql语句是在Jmeter中写的&#xff0c;但是语句的执行是在数据库里执行的&#xff0c;数据库再将执行结果返回给Jmeter。 在做jmeter数据库压力测试之前&#xff0c;要先检查是否有mysql-connector-java-5.1.39-bin.jar的这个包&#xf…