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 |
|
|
|