Add basic tutorial 13 from C gstreamer examples

This commit is contained in:
Guillaume Gomez 2021-01-07 22:49:18 +01:00
parent c4a06e515b
commit 2fcaaa53ce
2 changed files with 213 additions and 0 deletions

View file

@ -16,8 +16,13 @@ gst-app = { package = "gstreamer-app", path = "../gstreamer-app" }
gst-pbutils = { package = "gstreamer-pbutils", path = "../gstreamer-pbutils" }
byte-slice-cast = "1"
anyhow = "1"
termion = { version = "1.5", optional = true }
[features]
tutorial5 = ["gtk", "gdk", "gst-video"]
tutorial5-x11 = ["tutorial5"]
tutorial5-quartz = ["tutorial5"]
[[bin]]
name = "basic-tutorial-13"
required-features = ["termion"]

View file

@ -0,0 +1,208 @@
use gst::event::{Seek, Step};
use gst::prelude::*;
use gst::{Element, SeekFlags, SeekType, State};
use anyhow::Error;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use std::{io, thread, time};
#[path = "../tutorials-common.rs"]
mod tutorials_common;
// Commands that we get from the terminal and we send to the main thread.
#[derive(Clone, Copy, PartialEq)]
enum Command {
PlayPause,
DataRateUp,
DataRateDown,
ReverseRate,
NextFrame,
Quit,
}
fn send_seek_event(pipeline: &Element, rate: f64) -> bool {
// Obtain the current position, needed for the seek event
let position = match pipeline.query_position::<gst::ClockTime>() {
Some(pos) => pos,
None => {
eprintln!("Unable to retrieve current position...");
return false;
}
};
// Create the seek event
let seek_event = if rate > 0. {
Seek::new(
rate,
SeekFlags::FLUSH | SeekFlags::ACCURATE,
SeekType::Set,
position,
SeekType::End,
gst::ClockTime::from(0),
)
} else {
Seek::new(
rate,
SeekFlags::FLUSH | SeekFlags::ACCURATE,
SeekType::Set,
position,
SeekType::Set,
position,
)
};
// If we have not done so, obtain the sink through which we will send the seek events
if let Ok(Some(video_sink)) = pipeline
.get_property("video-sink")
.unwrap()
.get::<Element>()
{
println!("Current rate: {}", rate);
// Send the event
video_sink.send_event(seek_event)
} else {
eprintln!("Failed to update rate...");
false
}
}
// This is where we get the user input from the terminal.
fn handle_keyboard(ready_tx: glib::Sender<Command>) {
// We set the terminal in "raw mode" so that we can get the keys without waiting for the user
// to press return.
let _stdout = io::stdout().into_raw_mode().unwrap();
let mut stdin = termion::async_stdin().keys();
loop {
if let Some(Ok(input)) = stdin.next() {
let command = match input {
Key::Char('p') | Key::Char('P') => Command::PlayPause,
Key::Char('s') => Command::DataRateDown,
Key::Char('S') => Command::DataRateUp,
Key::Char('d') | Key::Char('D') => Command::ReverseRate,
Key::Char('n') | Key::Char('N') => Command::NextFrame,
Key::Char('q') | Key::Char('Q') => Command::Quit,
Key::Ctrl('c') | Key::Ctrl('C') => Command::Quit,
_ => continue,
};
ready_tx
.send(command)
.expect("failed to send data through channel");
if command == Command::Quit {
break;
}
}
thread::sleep(time::Duration::from_millis(50));
}
}
fn tutorial_main() -> Result<(), Error> {
// Initialize GStreamer.
gst::init()?;
// Print usage map.
println!(
"\
USAGE: Choose one of the following options, then press enter:
'P' to toggle between PAUSE and PLAY
'S' to increase playback speed, 's' to decrease playback speed
'D' to toggle playback direction
'N' to move to next frame (in the current direction, better in PAUSE)
'Q' to quit"
);
// Get a main context...
let main_context = glib::MainContext::default();
// ... and make it the main context by default so that we can then have a channel to send the
// commands we received from the terminal.
main_context.acquire();
// Build the channel to get the terminal inputs from a different thread.
let (ready_tx, ready_rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
thread::spawn(move || handle_keyboard(ready_tx));
// Build the pipeline.
let uri =
"https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm";
let pipeline = gst::parse_launch(&format!("playbin uri={}", uri))?;
// Start playing.
let _ = pipeline.set_state(State::Playing)?;
let main_loop = glib::MainLoop::new(Some(&main_context), false);
let main_loop_clone = main_loop.clone();
let pipeline_weak = pipeline.downgrade();
// Setting up "play" information.
let mut playing = true;
let mut rate = 1.;
ready_rx.attach(Some(&main_loop.get_context()), move |command: Command| {
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return glib::Continue(true),
};
match command {
Command::PlayPause => {
let status = if playing {
let _ = pipeline.set_state(State::Paused);
"PAUSE"
} else {
let _ = pipeline.set_state(State::Playing);
"PLAYING"
};
playing = !playing;
println!("Setting state to {}", status);
}
Command::DataRateUp => {
if send_seek_event(&pipeline, rate * 2.) {
rate *= 2.;
}
}
Command::DataRateDown => {
if send_seek_event(&pipeline, rate / 2.) {
rate /= 2.;
}
}
Command::ReverseRate => {
if send_seek_event(&pipeline, rate * -1.) {
rate *= -1.;
}
}
Command::NextFrame => {
if let Ok(Some(video_sink)) = pipeline
.get_property("video-sink")
.unwrap()
.get::<Element>()
{
// Send the event
let step = Step::new(gst::format::Buffers(Some(1)), rate.abs(), true, false);
video_sink.send_event(step);
println!("Stepping one frame");
}
}
Command::Quit => {
main_loop_clone.quit();
}
}
glib::Continue(true)
});
main_loop.run();
pipeline.set_state(State::Null)?;
Ok(())
}
fn main() {
match tutorials_common::run(tutorial_main) {
Ok(_) => {}
Err(err) => eprintln!("Failed: {}", err),
}
}