Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add recording indication #193

Merged
merged 12 commits into from
Aug 12, 2023
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ You'll need the following dependencies to build:
* libgstreamer1.0-dev (>= 1.20)
* libpulse-dev
* libpulse-mainloop-glib0
* [livechart](https://github.com/lcallarec/live-chart) (commit `d364df43bd8336bf7ed461c57a28e97efa7115b1` or later)
* meson (>= 0.57.0)
* valac

Expand Down
8 changes: 8 additions & 0 deletions com.github.ryonakano.reco.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ finish-args:
- '--env=GST_PLUGIN_PATH_1_0=/app/lib/gstreamer-1.0'
- '--metadata=X-DConf=migrate-path=/com/github/ryonakano/reco/'
modules:
- name: live-chart
buildsystem: meson
sources:
# TODO: Replace with tarball when new release is published
- type: git
url: https://github.com/lcallarec/live-chart.git
branch: master
commit: d364df43bd8336bf7ed461c57a28e97efa7115b1
- name: gst-libav
buildsystem: meson
config_opts:
Expand Down
4 changes: 4 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ sources = files(
'src' / 'Views' / 'CountDownView.vala',
'src' / 'Views' / 'WelcomeView.vala',
'src' / 'Views' / 'RecordView.vala',
'src' / 'Widgets' / 'LevelBar.vala',
'src' / 'Application.vala',
'src' / 'MainWindow.vala',
'src' / 'StyleSwitcher.vala'
Expand All @@ -63,8 +64,11 @@ executable(
dependency('gstreamer-1.0', version: '>= 1.20'),
dependency('libpulse'),
dependency('libpulse-mainloop-glib'),
# TODO: Add limitation for GTK4 supported version
dependency('livechart'),
dependency('pango')
],
link_args: ['-X', '-lm'],
install: true
)

Expand Down
55 changes: 50 additions & 5 deletions src/Services/Recorder.vala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2018-2023 Ryo Nakano <ryonakaknock3@gmail.com>
*
* GStreamer related codes are inspired from artemanufrij/screencast, src/MainWindow.vala
* GStreamer related codes are inspired from:
* * artemanufrij/screencast, src/MainWindow.vala
* * GNOME/gnome-sound-recorder (gnome-3-38), src/recorder.js
*/

public class Recorder : Object {
Expand All @@ -11,6 +13,28 @@ public class Recorder : Object {

public bool is_recording { get; private set; }

// current sound level, taking value from 0 to 1
public double current_peak {
get {
return _current_peak;
}
set {
double decibel = value;
if (decibel > 0) {
decibel = 0;
}

double p = Math.pow (10, decibel / 20);
if (p == _current_peak) {
// No need to renew value
return;
}

_current_peak = p;
}
}
private double _current_peak = 0;

private PulseAudioManager pam;
private string tmp_full_path;
private string suffix;
Expand Down Expand Up @@ -71,6 +95,11 @@ public class Recorder : Object {
throw new Gst.ParseError.NO_SUCH_ELEMENT ("Failed to create element \"pipeline\"");
}

var level = Gst.ElementFactory.make ("level", "level");
if (level == null) {
throw new Gst.ParseError.NO_SUCH_ELEMENT ("Failed to create element \"level\"");
}

var sink = Gst.ElementFactory.make ("filesink", "sink");
if (sink == null) {
throw new Gst.ParseError.NO_SUCH_ELEMENT ("Failed to create element \"filesink\"");
Expand Down Expand Up @@ -130,23 +159,23 @@ public class Recorder : Object {
"audio/x-raw", "channels", Type.INT,
(ChannelID) Application.settings.get_enum ("channel")
));
pipeline.add_many (caps_filter, encoder, sink);
pipeline.add_many (caps_filter, level, encoder, sink);

switch (source) {
case SourceID.MIC:
pipeline.add_many (mic_sound);
mic_sound.link_many (caps_filter, encoder);
mic_sound.link_many (caps_filter, level, encoder);
break;
case SourceID.SYSTEM:
pipeline.add_many (sys_sound);
sys_sound.link_many (caps_filter, encoder);
sys_sound.link_many (caps_filter, level, encoder);
break;
case SourceID.BOTH:
var mixer = Gst.ElementFactory.make ("audiomixer", "mixer");
pipeline.add_many (mic_sound, sys_sound, mixer);
mic_sound.get_static_pad ("src").link (mixer.request_pad_simple ("sink_%u"));
sys_sound.get_static_pad ("src").link (mixer.request_pad_simple ("sink_%u"));
mixer.link_many (caps_filter, encoder);
mixer.link_many (caps_filter, level, encoder);
break;
default:
assert_not_reached ();
Expand Down Expand Up @@ -181,6 +210,22 @@ public class Recorder : Object {
pipeline.dispose ();

save_file (tmp_full_path, suffix);
break;
case Gst.MessageType.ELEMENT:
unowned Gst.Structure? structure = msg.get_structure ();
if (!structure.has_name ("level")) {
break;
}

// FIXME: GLib.ValueArray is deprecated but used as an I/F structure in the GStreamer side:
// https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/1.20.5/subprojects/gst-plugins-good/gst/level/gstlevel.c#L579
// We would need a patch for GStreamer to replace ValueArray with Array
// when it's removed before GStreamer resolves
unowned var peak_arr = (GLib.ValueArray) structure.get_value ("peak").get_boxed ();
if (peak_arr != null) {
current_peak = peak_arr.get_nth (0).get_double ();
}

break;
default:
break;
Expand Down
6 changes: 4 additions & 2 deletions src/Views/RecordView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,13 @@ public class RecordView : Gtk.Box {
var label_grid = new Gtk.Grid () {
column_spacing = 6,
row_spacing = 6,
halign = Gtk.Align.CENTER,
vexpand = true
halign = Gtk.Align.CENTER
};
label_grid.attach (time_label, 0, 1, 1, 1);
label_grid.attach (remaining_time_label, 0, 2, 1, 1);

var levelbar = new LevelBar ();

var cancel_button = new Gtk.Button () {
icon_name = "user-trash-symbolic",
tooltip_text = _("Cancel recording"),
Expand Down Expand Up @@ -87,6 +88,7 @@ public class RecordView : Gtk.Box {
buttons_grid.attach (pause_button, 2, 0, 1, 1);

append (label_grid);
append (levelbar);
append (buttons_grid);

cancel_button.clicked.connect (() => {
Expand Down
64 changes: 64 additions & 0 deletions src/Widgets/LevelBar.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2023 Ryo Nakano <ryonakaknock3@gmail.com>
*/

public class LevelBar : Gtk.Box {
private const double PEAK_PERCENTAGE = 100.0;

private LiveChart.Serie serie;
private uint update_graph_timeout;

public LevelBar () {
}

construct {
orientation = Gtk.Orientation.VERTICAL;
spacing = 0;

var recorder = Recorder.get_default ();

serie = new LiveChart.Serie ("peak-value", new LiveChart.Bar ());
serie.line.color = { 0.7f, 0.1f, 0.2f, 1.0f };
serie.line.width = 1.0;

var config = new LiveChart.Config ();
config.x_axis.tick_interval = 1;
config.y_axis.fixed_max = PEAK_PERCENTAGE;
config.padding = LiveChart.Padding () {
smart = LiveChart.AutoPadding.NONE,
top = 0,
right = 0,
bottom = 12,
left = 0
};

var chart = new LiveChart.Chart (config) {
hexpand = true,
vexpand = true
};
// Hide all axis lines, legend, and background; just show the graph
chart.grid.visible = false;
chart.legend.visible = false;
chart.background.color = { 0.0f, 0.0f, 0.0f, 0.0f };

chart.add_serie (serie);

append (chart);

recorder.notify["is-recording"].connect (() => {
if (recorder.is_recording) {
// Start updating the graph when recording started
update_graph_timeout = Timeout.add (100, () => {
int current = (int) (recorder.current_peak * PEAK_PERCENTAGE);
serie.add (current);
return GLib.Source.CONTINUE;
});
} else {
// Stop updating the graph when recording stopped
GLib.Source.remove (update_graph_timeout);
serie.clear ();
}
});
}
}