mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer-rs.git
synced 2024-11-25 19:11:06 +00:00
Add basic tutorial 13 from C gstreamer examples
This commit is contained in:
parent
c4a06e515b
commit
2fcaaa53ce
2 changed files with 213 additions and 0 deletions
|
@ -16,8 +16,13 @@ gst-app = { package = "gstreamer-app", path = "../gstreamer-app" }
|
||||||
gst-pbutils = { package = "gstreamer-pbutils", path = "../gstreamer-pbutils" }
|
gst-pbutils = { package = "gstreamer-pbutils", path = "../gstreamer-pbutils" }
|
||||||
byte-slice-cast = "1"
|
byte-slice-cast = "1"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
termion = { version = "1.5", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tutorial5 = ["gtk", "gdk", "gst-video"]
|
tutorial5 = ["gtk", "gdk", "gst-video"]
|
||||||
tutorial5-x11 = ["tutorial5"]
|
tutorial5-x11 = ["tutorial5"]
|
||||||
tutorial5-quartz = ["tutorial5"]
|
tutorial5-quartz = ["tutorial5"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "basic-tutorial-13"
|
||||||
|
required-features = ["termion"]
|
||||||
|
|
208
tutorials/src/bin/basic-tutorial-13.rs
Normal file
208
tutorials/src/bin/basic-tutorial-13.rs
Normal 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),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue