2022-10-12 13:35:11 +00:00
|
|
|
// Copyright (C) 2022 LTN Global Communications, Inc.
|
|
|
|
// Contact: Jan Alexander Steffens (heftig) <jan.steffens@ltnglobal.com>
|
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
|
|
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
|
|
|
// <https://mozilla.org/MPL/2.0/>.
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
|
|
use gio::prelude::*;
|
|
|
|
use gst::{glib, prelude::*};
|
|
|
|
use gtk::prelude::*;
|
2023-04-14 09:46:43 +00:00
|
|
|
use std::cell::{Cell, RefCell};
|
2022-10-12 13:35:11 +00:00
|
|
|
|
|
|
|
struct DroppingProbe(glib::WeakRef<gst::Pad>, Option<gst::PadProbeId>);
|
|
|
|
|
|
|
|
impl DroppingProbe {
|
|
|
|
fn install(pad: &gst::Pad) -> Self {
|
|
|
|
let probe_id = pad
|
|
|
|
.add_probe(gst::PadProbeType::BUFFER, |_, _| gst::PadProbeReturn::Drop)
|
|
|
|
.unwrap();
|
|
|
|
Self(pad.downgrade(), Some(probe_id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for DroppingProbe {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if let Some((pad, probe_id)) = self.0.upgrade().zip(self.1.take()) {
|
|
|
|
pad.remove_probe(probe_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_pipeline() -> gst::Pipeline {
|
2023-12-04 14:54:00 +00:00
|
|
|
gst::parse::launch(
|
2022-10-12 13:35:11 +00:00
|
|
|
r#"videotestsrc name=vsrc is-live=1
|
|
|
|
! video/x-raw,framerate=60/1,width=800,height=600
|
2023-10-25 10:32:30 +00:00
|
|
|
! identity single-segment=1
|
2022-10-12 13:35:11 +00:00
|
|
|
! timeoverlay text="Pre:"
|
|
|
|
! queue
|
|
|
|
! livesync latency=50000000
|
|
|
|
! videorate
|
|
|
|
! timeoverlay text="Post:" halignment=right
|
|
|
|
! queue
|
|
|
|
! gtk4paintablesink name=vsink
|
|
|
|
audiotestsrc name=asrc is-live=1
|
|
|
|
! audio/x-raw,channels=2
|
2023-10-25 10:32:30 +00:00
|
|
|
! identity single-segment=1
|
2022-10-12 13:35:11 +00:00
|
|
|
! queue
|
|
|
|
! livesync latency=50000000
|
|
|
|
! audiorate
|
|
|
|
! queue
|
|
|
|
! autoaudiosink
|
|
|
|
"#,
|
|
|
|
)
|
|
|
|
.expect("Failed to create pipeline")
|
|
|
|
.downcast()
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_window(app: >k::Application) {
|
|
|
|
let pipeline = create_pipeline();
|
|
|
|
let video_src_pad = pipeline.by_name("vsrc").unwrap().static_pad("src").unwrap();
|
|
|
|
let audio_src_pad = pipeline.by_name("asrc").unwrap().static_pad("src").unwrap();
|
|
|
|
|
|
|
|
let window = gtk::ApplicationWindow::new(app);
|
|
|
|
window.set_default_size(800, 684);
|
|
|
|
|
|
|
|
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
|
|
|
|
|
|
|
let picture = gtk::Picture::new();
|
|
|
|
let paintable = pipeline
|
|
|
|
.by_name("vsink")
|
|
|
|
.unwrap()
|
|
|
|
.property::<gtk::gdk::Paintable>("paintable");
|
|
|
|
picture.set_paintable(Some(&paintable));
|
|
|
|
vbox.append(&picture);
|
|
|
|
|
|
|
|
let action_bar = gtk::ActionBar::new();
|
|
|
|
vbox.append(&action_bar);
|
|
|
|
|
|
|
|
let offset_spin = gtk::SpinButton::with_range(0.0, 500.0, 100.0);
|
|
|
|
action_bar.pack_start(&offset_spin);
|
|
|
|
|
|
|
|
{
|
|
|
|
let video_src_pad = video_src_pad.clone();
|
|
|
|
let audio_src_pad = audio_src_pad.clone();
|
|
|
|
offset_spin.connect_value_notify(move |offset_spin| {
|
|
|
|
const MSECOND: f64 = gst::ClockTime::MSECOND.nseconds() as _;
|
|
|
|
|
|
|
|
let offset = (offset_spin.value() * -MSECOND) as i64;
|
|
|
|
video_src_pad.set_offset(offset);
|
|
|
|
audio_src_pad.set_offset(offset);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
let drop_button = gtk::ToggleButton::with_label("Drop Signal");
|
|
|
|
action_bar.pack_end(&drop_button);
|
|
|
|
|
|
|
|
let drop_ids = Cell::new(None);
|
|
|
|
drop_button.connect_toggled(move |drop_button| {
|
|
|
|
if drop_button.is_active() {
|
|
|
|
let video_probe = DroppingProbe::install(&video_src_pad);
|
|
|
|
let audio_probe = DroppingProbe::install(&audio_src_pad);
|
|
|
|
drop_ids.set(Some((video_probe, audio_probe)));
|
|
|
|
} else {
|
|
|
|
drop_ids.set(None);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-04-14 09:46:43 +00:00
|
|
|
let bus_watch = {
|
2022-10-12 13:35:11 +00:00
|
|
|
let bus = pipeline.bus().unwrap();
|
|
|
|
let window = window.downgrade();
|
|
|
|
bus.add_watch_local(move |_, msg| {
|
|
|
|
use gst::MessageView;
|
|
|
|
|
|
|
|
match msg.view() {
|
|
|
|
MessageView::Eos(..) => {
|
|
|
|
if let Some(window) = window.upgrade() {
|
|
|
|
window.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageView::Error(err) => {
|
|
|
|
eprintln!(
|
|
|
|
"Error from {}: {} ({:?})",
|
|
|
|
msg.src().map(|s| s.path_string()).as_deref().unwrap_or(""),
|
|
|
|
err.error(),
|
|
|
|
err.debug(),
|
|
|
|
);
|
|
|
|
if let Some(window) = window.upgrade() {
|
|
|
|
window.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => (),
|
|
|
|
};
|
|
|
|
|
2023-07-06 13:27:28 +00:00
|
|
|
glib::ControlFlow::Continue
|
2022-10-12 13:35:11 +00:00
|
|
|
})
|
2023-04-14 09:46:43 +00:00
|
|
|
.unwrap()
|
|
|
|
};
|
2022-10-12 13:35:11 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
let pipeline = pipeline.clone();
|
|
|
|
window.connect_realize(move |_| {
|
|
|
|
pipeline
|
|
|
|
.set_state(gst::State::Playing)
|
|
|
|
.expect("Failed to start pipeline");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-04-14 09:46:43 +00:00
|
|
|
let bus_watch = RefCell::new(Some(bus_watch));
|
2022-10-12 13:35:11 +00:00
|
|
|
window.connect_unrealize(move |_| {
|
2023-04-14 09:46:43 +00:00
|
|
|
drop(bus_watch.borrow_mut().take());
|
2022-10-12 13:35:11 +00:00
|
|
|
pipeline
|
|
|
|
.set_state(gst::State::Null)
|
|
|
|
.expect("Failed to stop pipeline");
|
|
|
|
});
|
|
|
|
|
|
|
|
window.set_child(Some(&vbox));
|
|
|
|
window.show();
|
|
|
|
}
|
|
|
|
|
2023-01-27 16:58:59 +00:00
|
|
|
fn main() -> glib::ExitCode {
|
2022-10-12 13:35:11 +00:00
|
|
|
let app = gtk::Application::new(
|
|
|
|
Some("gtk-plugins-rs.gtk-livesync"),
|
|
|
|
gio::ApplicationFlags::FLAGS_NONE,
|
|
|
|
);
|
|
|
|
|
|
|
|
app.connect_startup(move |_app| {
|
|
|
|
gst::init().expect("Failed to initialize GStreamer");
|
|
|
|
gstlivesync::plugin_register_static().expect("Failed to register livesync plugin");
|
|
|
|
gstgtk4::plugin_register_static().expect("Failed to register gstgtk4 plugin");
|
|
|
|
});
|
|
|
|
|
|
|
|
app.connect_activate(create_window);
|
2023-01-27 16:58:59 +00:00
|
|
|
app.run()
|
2022-10-12 13:35:11 +00:00
|
|
|
}
|