GCC Code Coverage Report


Directory: ./
File: panels/sound/cc-level-bar.c
Date: 2024-05-04 07:58:27
Exec Total Coverage
Lines: 0 82 0.0%
Functions: 0 11 0.0%
Branches: 0 39 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (C) 2018 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "cc-level-bar.h"
19 #include "cc-sound-enums.h"
20 #include "gvc-mixer-stream-private.h"
21
22 struct _CcLevelBar
23 {
24 GtkWidget parent_instance;
25
26 GtkLevelBar *level_bar;
27 pa_stream *level_stream;
28 };
29
30 G_DEFINE_TYPE (CcLevelBar, cc_level_bar, GTK_TYPE_WIDGET)
31
32 #define SMOOTHING 0.3
33
34 static void
35 update_level (CcLevelBar *self,
36 gdouble value)
37 {
38 /* Use Exponential Moving Average (EMA) to smooth out value changes and
39 * reduce fluctuation and jitter.
40 */
41 double prev_ema = gtk_level_bar_get_value (self->level_bar);
42 double ema = (value * SMOOTHING) + (prev_ema * (1.0 - SMOOTHING));
43
44 ema = CLAMP (ema, 0.0, 1.0);
45
46 gtk_level_bar_set_value (self->level_bar, ema);
47 }
48
49 static void
50 read_cb (pa_stream *stream,
51 size_t length,
52 void *userdata)
53 {
54 CcLevelBar *self = userdata;
55 const void *data;
56 gdouble value;
57
58 if (pa_stream_peek (stream, &data, &length) < 0)
59 {
60 g_warning ("Failed to read data from stream");
61 return;
62 }
63
64 if (!data)
65 {
66 pa_stream_drop (stream);
67 return;
68 }
69
70 assert (length > 0);
71 assert (length % sizeof (float) == 0);
72
73 value = ((const float *) data)[length / sizeof (float) -1];
74
75 pa_stream_drop (stream);
76
77 update_level (self, value);
78 }
79
80 static void
81 suspended_cb (pa_stream *stream,
82 void *userdata)
83 {
84 CcLevelBar *self = userdata;
85
86 if (pa_stream_is_suspended (stream))
87 {
88 g_debug ("Stream suspended");
89 gtk_level_bar_set_value (self->level_bar, 0.0);
90 }
91 }
92
93 static void
94 close_stream (pa_stream *stream)
95 {
96 if (stream == NULL)
97 return;
98
99 /* Stop receiving data */
100 pa_stream_set_read_callback (stream, NULL, NULL);
101 pa_stream_set_suspended_callback (stream, NULL, NULL);
102
103 /* Disconnect from the stream */
104 pa_stream_disconnect (stream);
105 }
106
107 static void
108 cc_level_bar_dispose (GObject *object)
109 {
110 CcLevelBar *self = CC_LEVEL_BAR (object);
111
112 close_stream (self->level_stream);
113 g_clear_pointer (&self->level_stream, pa_stream_unref);
114
115 gtk_widget_unparent (GTK_WIDGET (self->level_bar));
116
117 G_OBJECT_CLASS (cc_level_bar_parent_class)->dispose (object);
118 }
119
120 void
121 cc_level_bar_class_init (CcLevelBarClass *klass)
122 {
123 GObjectClass *object_class = G_OBJECT_CLASS (klass);
124 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
125
126 object_class->dispose = cc_level_bar_dispose;
127
128 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
129 }
130
131 void
132 cc_level_bar_init (CcLevelBar *self)
133 {
134 self->level_bar = GTK_LEVEL_BAR (gtk_level_bar_new ());
135
136 // Make the level bar all the same color by removing all pre-existing offsets
137 gtk_level_bar_remove_offset_value (self->level_bar, GTK_LEVEL_BAR_OFFSET_LOW);
138 gtk_level_bar_remove_offset_value (self->level_bar, GTK_LEVEL_BAR_OFFSET_HIGH);
139 gtk_level_bar_remove_offset_value (self->level_bar, GTK_LEVEL_BAR_OFFSET_FULL);
140
141 gtk_widget_set_parent (GTK_WIDGET (self->level_bar), GTK_WIDGET (self));
142 }
143
144 void
145 cc_level_bar_set_stream (CcLevelBar *self,
146 GvcMixerStream *stream)
147 {
148 pa_context *context;
149 pa_sample_spec sample_spec;
150 pa_proplist *proplist;
151 pa_buffer_attr attr;
152 g_autofree gchar *device = NULL;
153
154 g_return_if_fail (CC_IS_LEVEL_BAR (self));
155
156 close_stream (self->level_stream);
157 g_clear_pointer (&self->level_stream, pa_stream_unref);
158
159 if (stream == NULL)
160 {
161 gtk_level_bar_set_value (self->level_bar, 0.0);
162 return;
163 }
164
165 context = gvc_mixer_stream_get_pa_context (stream);
166
167 if (pa_context_get_server_protocol_version (context) < 13)
168 {
169 g_warning ("Unsupported version of PulseAudio");
170 return;
171 }
172
173 sample_spec.channels = 1;
174 sample_spec.format = PA_SAMPLE_FLOAT32;
175 sample_spec.rate = 25;
176
177 proplist = pa_proplist_new ();
178 pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "org.gnome.VolumeControl");
179 self->level_stream = pa_stream_new_with_proplist (context, "Peak detect", &sample_spec, NULL, proplist);
180 pa_proplist_free (proplist);
181 if (self->level_stream == NULL)
182 {
183 g_warning ("Failed to create monitoring stream");
184 return;
185 }
186
187 pa_stream_set_read_callback (self->level_stream, read_cb, self);
188 pa_stream_set_suspended_callback (self->level_stream, suspended_cb, self);
189
190 memset (&attr, 0, sizeof (attr));
191 attr.fragsize = sizeof (float);
192 attr.maxlength = (uint32_t) -1;
193 device = g_strdup_printf ("%u", gvc_mixer_stream_get_index (stream));
194 if (pa_stream_connect_record (self->level_stream,
195 device,
196 &attr,
197 (pa_stream_flags_t) (PA_STREAM_DONT_MOVE |
198 PA_STREAM_PEAK_DETECT |
199 PA_STREAM_ADJUST_LATENCY)) < 0)
200 {
201 g_warning ("Failed to connect monitoring stream");
202 }
203 }
204