gst-plugins-rs/generic/inter/examples/plug-and-play.rs

325 lines
13 KiB
Rust

use anyhow::Error;
use futures::prelude::*;
use gst::prelude::*;
use std::collections::HashMap;
use std::io::prelude::*;
use tokio::task;
struct Producer {
pipeline: gst::Pipeline,
sink: gst::Element,
overlay: gst::Element,
}
struct Consumer {
pipeline: gst::Pipeline,
src: gst::Element,
}
fn create_sink_pipeline(producer_name: &str) -> Result<Producer, Error> {
let pipeline = gst::Pipeline::builder()
.name(format!("producer-{producer_name}"))
.build();
let videotestsrc = gst::ElementFactory::make("videotestsrc")
.property_from_str("pattern", "ball")
.property("is-live", true)
.build()?;
let capsfilter = gst::ElementFactory::make("capsfilter")
.property(
"caps",
gst::Caps::builder("video/x-raw")
.field("framerate", gst::Fraction::new(50, 1))
.build(),
)
.build()?;
let queue = gst::ElementFactory::make("queue").build()?;
let overlay = gst::ElementFactory::make("textoverlay")
.property("font-desc", "Sans 30")
.property("text", format!("Producer: {producer_name}"))
.property_from_str("valignment", "top")
.build()?;
let timeoverlay = gst::ElementFactory::make("timeoverlay")
.property("font-desc", "Sans 30")
.property_from_str("valignment", "center")
.property_from_str("halignment", "center")
.build()?;
let sink = gst::ElementFactory::make("intersink")
.property("producer-name", producer_name)
.build()?;
pipeline.add_many([
&videotestsrc,
&capsfilter,
&queue,
&overlay,
&timeoverlay,
&sink,
])?;
gst::Element::link_many([
&videotestsrc,
&capsfilter,
&queue,
&overlay,
&timeoverlay,
&sink,
])?;
Ok(Producer {
pipeline,
sink,
overlay,
})
}
fn create_src_pipeline(producer_name: &str, consumer_name: &str) -> Result<Consumer, Error> {
let pipeline = gst::Pipeline::builder()
.name(format!("consumer-{consumer_name}"))
.build();
let src = gst::ElementFactory::make("intersrc")
.property("producer-name", producer_name)
.build()?;
let queue = gst::ElementFactory::make("queue").build()?;
let vconv = gst::ElementFactory::make("videoconvert").build()?;
let overlay = gst::ElementFactory::make("textoverlay")
.property("font-desc", "Sans 30")
.property("text", format!("Consumer: {consumer_name}"))
.property_from_str("valignment", "bottom")
.build()?;
let vconv2 = gst::ElementFactory::make("videoconvert").build()?;
let sink = gst::ElementFactory::make("autovideosink").build()?;
pipeline.add_many([&src, &queue, &vconv, &overlay, &vconv2, &sink])?;
gst::Element::link_many([&src, &queue, &vconv, &overlay, &vconv2, &sink])?;
Ok(Consumer { pipeline, src })
}
fn prompt_on() {
print!("$ ");
let _ = std::io::stdout().flush();
}
fn monitor_pipeline(pipeline: &gst::Pipeline, base_time: gst::ClockTime) -> Result<(), Error> {
pipeline.set_clock(Some(&gst::SystemClock::obtain()))?;
pipeline.set_start_time(gst::ClockTime::NONE);
pipeline.set_base_time(base_time);
pipeline.set_state(gst::State::Playing)?;
let mut bus_stream = pipeline.bus().expect("Pipeline should have a bus").stream();
let pipeline_clone = pipeline.downgrade();
task::spawn(async move {
while let Some(msg) = bus_stream.next().await {
use gst::MessageView;
if let Some(pipeline) = pipeline_clone.upgrade() {
match msg.view() {
MessageView::Latency(..) => {
let _ = pipeline.recalculate_latency();
}
MessageView::Eos(..) => {
println!(
"EOS from {}",
msg.src()
.map(|s| String::from(s.path_string()))
.unwrap_or_else(|| "None".into())
);
prompt_on();
break;
}
MessageView::Error(err) => {
let _ = pipeline.set_state(gst::State::Null);
println!(
"Got error from {}: {} ({})",
msg.src()
.map(|s| String::from(s.path_string()))
.unwrap_or_else(|| "None".into()),
err.error(),
err.debug().unwrap_or_else(|| "".into()),
);
prompt_on();
break;
}
MessageView::StateChanged(sc) => {
if msg.src() == Some(pipeline.upcast_ref()) {
pipeline.debug_to_dot_file(
gst::DebugGraphDetails::all(),
format!("{}-{:?}-{:?}", pipeline.name(), sc.old(), sc.current()),
);
}
}
_ => (),
}
} else {
break;
}
}
});
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Error> {
gst::init()?;
println!("h for help");
let base_time = gst::SystemClock::obtain().time().unwrap();
let mut producers: HashMap<String, Producer> = HashMap::new();
let mut consumers: HashMap<String, Consumer> = HashMap::new();
let mut stdin = std::io::stdin().lock();
loop {
let mut buf = String::new();
prompt_on();
match stdin.read_line(&mut buf)? {
0 => {
eprintln!("EOF!");
break;
}
_ => {
let command: Vec<_> = buf.split_whitespace().collect();
match command.first() {
Some(&"ap") => {
if command.len() != 2 {
println!("ap <producer_name>: Add a producer");
} else {
let producer_name = command.get(1).unwrap().to_string();
if producers.contains_key(&producer_name) {
println!("Producer with name {producer_name} already exists!");
continue;
}
let producer = create_sink_pipeline(&producer_name)?;
monitor_pipeline(&producer.pipeline, base_time)?;
println!("Added producer with name {producer_name}");
producers.insert(producer_name, producer);
}
}
Some(&"ac") => {
if command.len() != 3 {
println!("ac <consumer_name> <producer_name>: Add a consumer");
} else {
let consumer_name = command.get(1).unwrap().to_string();
let producer_name = command.get(2).unwrap().to_string();
if consumers.contains_key(&consumer_name) {
println!("Consumer with name {consumer_name} already exists!");
continue;
}
let consumer = create_src_pipeline(&producer_name, &consumer_name)?;
monitor_pipeline(&consumer.pipeline, base_time)?;
println!("Added consumer with name {consumer_name} and producer name {producer_name}");
consumers.insert(consumer_name, consumer);
}
}
Some(&"rp") => {
if command.len() != 2 {
println!("rp <producer_name>: Remove a producer");
} else {
let producer_name = command.get(1).unwrap().to_string();
if let Some(producer) = producers.remove(&producer_name) {
let _ = producer.pipeline.set_state(gst::State::Null);
println!("Removed producer with name {producer_name}");
} else {
println!("No producer with name {producer_name}");
}
}
}
Some(&"rc") => {
if command.len() != 2 {
println!("rc <consumer_name>: Remove a consumer");
} else {
let consumer_name = command.get(1).unwrap().to_string();
if let Some(consumer) = consumers.remove(&consumer_name) {
let _ = consumer.pipeline.set_state(gst::State::Null);
println!("Removed consumer with name {consumer_name}");
} else {
println!("No consumer with name {consumer_name}");
}
}
}
Some(&"cnp") => {
if command.len() != 3 {
println!("cnp <old_producer_name> <new_producer_name>: Change the name of a producer");
} else {
let old_producer_name = command.get(1).unwrap().to_string();
let producer_name = command.get(2).unwrap().to_string();
if producers.contains_key(&producer_name) {
println!("Producer with name {producer_name} already exists!");
continue;
}
if let Some(producer) = producers.remove(&old_producer_name) {
producer.sink.set_property("producer-name", &producer_name);
producer
.overlay
.set_property("text", format!("Producer: {producer_name}"));
println!(
"Changed producer name {old_producer_name} -> {producer_name}"
);
producers.insert(producer_name, producer);
} else {
println!("No producer with name {old_producer_name}");
}
}
}
Some(&"cpn") => {
if command.len() != 3 {
println!("cpn <consumer_name> <new_producer_name>: Change the producer name for a consumer");
} else {
let consumer_name = command.get(1).unwrap().to_string();
let producer_name = command.get(2).unwrap().to_string();
if let Some(consumer) = consumers.get_mut(&consumer_name) {
consumer.src.set_property("producer-name", &producer_name);
println!("Changed producer name for consumer {consumer_name} to {producer_name}");
} else {
println!("No consumer with name {consumer_name}");
}
}
}
Some(&"h") => {
println!("h: show this help");
println!("ap <producer_name>: Add a producer");
println!("ac <consumer_name> <producer_name>: Add a consumer");
println!("rp <producer_name>: Remove a producer");
println!("rc <consumer_name>: Remove a consumer");
println!("cnp <old_producer_name> <new_producer_name>: Change the name of a producer");
println!("cpn <consumer_name> <new_producer_name>: Change the producer name for a consumer");
}
_ => {
println!("Unknown command");
}
}
}
}
buf.clear();
}
for (_, producer) in producers {
let _ = producer.pipeline.set_state(gst::State::Null);
}
for (_, consumer) in consumers {
let _ = consumer.pipeline.set_state(gst::State::Null);
}
Ok(())
}