2019-09-18 10:12:11 +00:00
|
|
|
// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
|
|
|
|
//
|
2022-01-15 18:40:12 +00:00
|
|
|
// 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/>.
|
2019-09-18 10:12:11 +00:00
|
|
|
//
|
2022-01-15 18:40:12 +00:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
2019-09-18 10:12:11 +00:00
|
|
|
|
|
|
|
use gio::prelude::*;
|
|
|
|
|
2021-06-03 18:20:54 +00:00
|
|
|
use gst::glib;
|
2019-09-18 10:12:11 +00:00
|
|
|
use gst::prelude::*;
|
|
|
|
|
|
|
|
use gtk::prelude::*;
|
2022-08-26 16:24:59 +00:00
|
|
|
use gtk::Inhibit;
|
2020-04-24 11:33:21 +00:00
|
|
|
|
2019-09-18 10:12:11 +00:00
|
|
|
use std::cell::RefCell;
|
|
|
|
|
2019-09-18 11:33:53 +00:00
|
|
|
const MAIN_PIPELINE: &str = "videotestsrc is-live=true pattern=ball";
|
|
|
|
const FALLBACK_PIPELINE: &str = "videotestsrc is-live=true pattern=snow";
|
|
|
|
|
|
|
|
//const MAIN_PIPELINE: &str = "videotestsrc is-live=true pattern=ball ! x264enc tune=zerolatency";
|
|
|
|
//const FALLBACK_PIPELINE: &str = "videotestsrc is-live=true pattern=snow ! x264enc tune=zerolatency";
|
|
|
|
|
2022-08-26 16:24:59 +00:00
|
|
|
fn create_pipeline() -> (gst::Pipeline, gst::Pad, gst::Element) {
|
2019-09-18 10:12:11 +00:00
|
|
|
let pipeline = gst::Pipeline::new(None);
|
|
|
|
|
2019-09-18 11:33:53 +00:00
|
|
|
let video_src = gst::parse_bin_from_description(MAIN_PIPELINE, true)
|
|
|
|
.unwrap()
|
|
|
|
.upcast();
|
2021-07-30 10:53:35 +00:00
|
|
|
let fallback_video_src = gst::parse_bin_from_description(FALLBACK_PIPELINE, true)
|
2019-09-18 11:33:53 +00:00
|
|
|
.unwrap()
|
|
|
|
.upcast();
|
2019-09-18 10:12:11 +00:00
|
|
|
|
|
|
|
let fallbackswitch = gst::ElementFactory::make("fallbackswitch", None).unwrap();
|
2021-11-08 09:55:40 +00:00
|
|
|
fallbackswitch.set_property("timeout", gst::ClockTime::SECOND);
|
2019-09-18 10:12:11 +00:00
|
|
|
|
2019-09-18 11:33:53 +00:00
|
|
|
let decodebin = gst::ElementFactory::make("decodebin", None).unwrap();
|
2019-09-18 10:12:11 +00:00
|
|
|
let videoconvert = gst::ElementFactory::make("videoconvert", None).unwrap();
|
|
|
|
|
2019-09-18 11:33:53 +00:00
|
|
|
let videoconvert_clone = videoconvert.clone();
|
|
|
|
decodebin.connect_pad_added(move |_, pad| {
|
2021-04-12 12:49:54 +00:00
|
|
|
let caps = pad.current_caps().unwrap();
|
2021-04-20 12:58:11 +00:00
|
|
|
let s = caps.structure(0).unwrap();
|
2019-09-18 11:33:53 +00:00
|
|
|
|
2021-04-20 12:58:11 +00:00
|
|
|
let sinkpad = videoconvert_clone.static_pad("sink").unwrap();
|
2019-09-18 11:33:53 +00:00
|
|
|
|
2021-04-12 12:49:54 +00:00
|
|
|
if s.name() == "video/x-raw" && !sinkpad.is_linked() {
|
2019-09-18 11:33:53 +00:00
|
|
|
pad.link(&sinkpad).unwrap();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-08-26 16:24:59 +00:00
|
|
|
let video_sink = gst::ElementFactory::make("gtk4paintablesink", None).unwrap();
|
2019-09-18 10:12:11 +00:00
|
|
|
|
|
|
|
pipeline
|
|
|
|
.add_many(&[
|
|
|
|
&video_src,
|
|
|
|
&fallback_video_src,
|
|
|
|
&fallbackswitch,
|
2019-09-18 11:33:53 +00:00
|
|
|
&decodebin,
|
2019-09-18 10:12:11 +00:00
|
|
|
&videoconvert,
|
|
|
|
&video_sink,
|
|
|
|
])
|
|
|
|
.unwrap();
|
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
/* The first pad requested will be automatically preferred */
|
2019-09-18 10:12:11 +00:00
|
|
|
video_src
|
2021-09-13 15:08:45 +00:00
|
|
|
.link_pads(Some("src"), &fallbackswitch, Some("sink_%u"))
|
2019-09-18 10:12:11 +00:00
|
|
|
.unwrap();
|
|
|
|
fallback_video_src
|
2021-09-13 15:08:45 +00:00
|
|
|
.link_pads(Some("src"), &fallbackswitch, Some("sink_%u"))
|
2019-09-18 10:12:11 +00:00
|
|
|
.unwrap();
|
|
|
|
fallbackswitch
|
2019-09-18 11:33:53 +00:00
|
|
|
.link_pads(Some("src"), &decodebin, Some("sink"))
|
2019-09-18 10:12:11 +00:00
|
|
|
.unwrap();
|
|
|
|
videoconvert
|
|
|
|
.link_pads(Some("src"), &video_sink, Some("sink"))
|
|
|
|
.unwrap();
|
|
|
|
|
2022-08-26 16:24:59 +00:00
|
|
|
(pipeline, video_src.static_pad("src").unwrap(), video_sink)
|
2019-09-18 10:12:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn create_ui(app: >k::Application) {
|
2022-08-26 16:24:59 +00:00
|
|
|
let (pipeline, video_src_pad, video_sink) = create_pipeline();
|
2019-09-18 10:12:11 +00:00
|
|
|
|
2022-08-26 16:24:59 +00:00
|
|
|
let window = gtk::ApplicationWindow::new(app);
|
2019-09-18 10:12:11 +00:00
|
|
|
window.set_default_size(320, 240);
|
|
|
|
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
2022-08-26 16:24:59 +00:00
|
|
|
let picture = gtk::Picture::new();
|
|
|
|
let paintable = video_sink.property::<gtk::gdk::Paintable>("paintable");
|
|
|
|
picture.set_paintable(Some(&paintable));
|
|
|
|
vbox.append(&picture);
|
2019-09-18 10:12:11 +00:00
|
|
|
|
|
|
|
let position_label = gtk::Label::new(Some("Position: 00:00:00"));
|
2022-08-26 16:24:59 +00:00
|
|
|
vbox.append(&position_label);
|
2019-09-18 10:12:11 +00:00
|
|
|
|
2020-06-16 08:56:48 +00:00
|
|
|
let drop_button = gtk::ToggleButton::with_label("Drop Signal");
|
2022-08-26 16:24:59 +00:00
|
|
|
vbox.append(&drop_button);
|
2019-09-18 10:12:11 +00:00
|
|
|
|
2022-08-26 16:24:59 +00:00
|
|
|
window.set_child(Some(&vbox));
|
|
|
|
window.show();
|
2019-09-18 10:12:11 +00:00
|
|
|
|
|
|
|
app.add_window(&window);
|
|
|
|
|
|
|
|
let video_sink_weak = video_sink.downgrade();
|
2020-09-14 13:47:27 +00:00
|
|
|
let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
|
2019-09-18 10:12:11 +00:00
|
|
|
let video_sink = match video_sink_weak.upgrade() {
|
|
|
|
Some(video_sink) => video_sink,
|
|
|
|
None => return glib::Continue(true),
|
|
|
|
};
|
|
|
|
|
|
|
|
let position = video_sink
|
|
|
|
.query_position::<gst::ClockTime>()
|
2021-05-28 16:35:28 +00:00
|
|
|
.unwrap_or(gst::ClockTime::ZERO);
|
2019-09-18 10:12:11 +00:00
|
|
|
position_label.set_text(&format!("Position: {:.1}", position));
|
|
|
|
|
|
|
|
glib::Continue(true)
|
|
|
|
});
|
|
|
|
|
|
|
|
let video_src_pad_weak = video_src_pad.downgrade();
|
|
|
|
let drop_id = RefCell::new(None);
|
|
|
|
drop_button.connect_toggled(move |drop_button| {
|
|
|
|
let video_src_pad = match video_src_pad_weak.upgrade() {
|
|
|
|
Some(video_src_pad) => video_src_pad,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
2021-04-12 16:12:05 +00:00
|
|
|
let drop = drop_button.is_active();
|
2019-09-18 10:12:11 +00:00
|
|
|
if drop {
|
|
|
|
let mut drop_id = drop_id.borrow_mut();
|
|
|
|
if drop_id.is_none() {
|
|
|
|
*drop_id = video_src_pad
|
|
|
|
.add_probe(gst::PadProbeType::BUFFER, |_, _| gst::PadProbeReturn::Drop);
|
|
|
|
}
|
2019-12-22 09:18:30 +00:00
|
|
|
} else if let Some(drop_id) = drop_id.borrow_mut().take() {
|
|
|
|
video_src_pad.remove_probe(drop_id);
|
2019-09-18 10:12:11 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let app_weak = app.downgrade();
|
2022-08-26 16:24:59 +00:00
|
|
|
window.connect_close_request(move |_| {
|
2019-09-18 10:12:11 +00:00
|
|
|
let app = match app_weak.upgrade() {
|
|
|
|
Some(app) => app,
|
|
|
|
None => return Inhibit(false),
|
|
|
|
};
|
|
|
|
|
|
|
|
app.quit();
|
|
|
|
Inhibit(false)
|
|
|
|
});
|
|
|
|
|
2021-04-12 12:49:54 +00:00
|
|
|
let bus = pipeline.bus().unwrap();
|
2019-09-18 10:12:11 +00:00
|
|
|
let app_weak = app.downgrade();
|
|
|
|
bus.add_watch_local(move |_, msg| {
|
|
|
|
use gst::MessageView;
|
|
|
|
|
|
|
|
let app = match app_weak.upgrade() {
|
|
|
|
Some(app) => app,
|
|
|
|
None => return glib::Continue(false),
|
|
|
|
};
|
|
|
|
|
|
|
|
match msg.view() {
|
|
|
|
MessageView::Eos(..) => app.quit(),
|
|
|
|
MessageView::Error(err) => {
|
|
|
|
println!(
|
|
|
|
"Error from {:?}: {} ({:?})",
|
2021-04-12 12:49:54 +00:00
|
|
|
msg.src().map(|s| s.path_string()),
|
|
|
|
err.error(),
|
|
|
|
err.debug()
|
2019-09-18 10:12:11 +00:00
|
|
|
);
|
|
|
|
app.quit();
|
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
};
|
|
|
|
|
|
|
|
glib::Continue(true)
|
2019-12-18 05:50:10 +00:00
|
|
|
})
|
|
|
|
.expect("Failed to add bus watch");
|
2019-09-18 10:12:11 +00:00
|
|
|
|
|
|
|
pipeline.set_state(gst::State::Playing).unwrap();
|
|
|
|
|
|
|
|
// Pipeline reference is owned by the closure below, so will be
|
|
|
|
// destroyed once the app is destroyed
|
|
|
|
let timeout_id = RefCell::new(Some(timeout_id));
|
|
|
|
app.connect_shutdown(move |_| {
|
|
|
|
pipeline.set_state(gst::State::Null).unwrap();
|
|
|
|
|
|
|
|
bus.remove_watch().unwrap();
|
|
|
|
|
|
|
|
if let Some(timeout_id) = timeout_id.borrow_mut().take() {
|
2021-10-12 06:31:25 +00:00
|
|
|
timeout_id.remove();
|
2019-09-18 10:12:11 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
gst::init().unwrap();
|
|
|
|
gtk::init().unwrap();
|
|
|
|
|
|
|
|
gstfallbackswitch::plugin_register_static().expect("Failed to register fallbackswitch plugin");
|
2022-08-26 16:24:59 +00:00
|
|
|
gstgtk4::plugin_register_static().expect("Failed to register gtk4paintablesink plugin");
|
2019-09-18 10:12:11 +00:00
|
|
|
|
2021-04-12 08:53:40 +00:00
|
|
|
let app = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE);
|
2019-09-18 10:12:11 +00:00
|
|
|
|
|
|
|
app.connect_activate(create_ui);
|
2021-04-08 07:36:58 +00:00
|
|
|
app.run();
|
2019-09-18 10:12:11 +00:00
|
|
|
}
|