gst-plugin-tutorial: Update markdown for using lazy_static! to declare the debug category

This commit is contained in:
Sebastian Dröge 2019-12-17 09:35:21 +02:00
parent 5ca81246f1
commit 916c382171
2 changed files with 72 additions and 66 deletions

View file

@ -45,6 +45,7 @@ glib = { git = "https://github.com/gtk-rs/glib" }
gstreamer = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gstreamer-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gstreamer-video = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
lazy_static = "1.0"
[lib]
name = "gstrstutorial"
@ -55,7 +56,7 @@ path = "src/lib.rs"
gst-plugin-version-helper = { git = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs" }
```
We depend on the `gstreamer`, `gstreamer-base` and `gstreamer-video` crates for various GStreamer APIs that will be used later, and the `glib` crate to be able to use some GLib API that well need. GStreamer is building upon GLib, and this leaks through in various places. We also have one build dependency on the `gst-plugin-version-helper` crate, which helps to get some information about the plugin for the `gst_plugin_define!` macro automatically.
We depend on the `gstreamer`, `gstreamer-base` and `gstreamer-video` crates for various GStreamer APIs that will be used later, and the `glib` crate to be able to use some GLib API that well need. GStreamer is building upon GLib, and this leaks through in various places. We also have one build dependency on the `gst-plugin-version-helper` crate, which helps to get some information about the plugin for the `gst_plugin_define!` macro automatically, and the `lazy_static` crate, which allows to declare lazily initialized global variables.
With the basic project structure being set-up, we should be able to compile the project with `cargo build` now, which will download and build all dependencies and then creates a file called `target/debug/libgstrstutorial.so` (or .dll on Windows, .dylib on macOS). This is going to be our GStreamer plugin.
@ -78,6 +79,8 @@ extern crate glib;
extern crate gstreamer as gst;
extern crate gstreamer_base as gst_base;
extern crate gstreamer_video as gst_video;
#[macro_use]
extern crate lazy_static;
```
Next we make use of the `gst_plugin_define!` `macro` from the `gstreamer` crate to set-up the static metadata of the plugin (and make the shared library recognizeable by GStreamer to be a valid plugin), and to define the name of our entry point function (`plugin_init`) where we will register all the elements that this plugin provides.
@ -192,7 +195,7 @@ impl ObjectSubclass for Rgb2Gray {
}
fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
}
}
@ -211,15 +214,14 @@ This defines a struct `Rgb2Gray` which is empty for now and an empty implementat
`ObjectSubclass` has an associated constant which contains the name of the type, some associated types, and functions for initializing/returning a new instance of our element (`new`) and for initializing the class metadata (`class_init`, more on that later). We simply let those functions proxy to associated functions on the `Rgb2Gray` struct that were going to define at a later time.
In addition, we also define a `register` function (the one that is already called from our `plugin_init` function). When `register` function is called it registers the element factory with GStreamer based on the type ID, to be able to create new instances of it with the name “rsrgb2gray” (e.g. when using [`gst::ElementFactory::make`](https://slomo.pages.freedesktop.org/rustdocs/gstreamer/gstreamer/struct.ElementFactory.html#method.make)). The `get_type` function will register the type with the GObject type system on the first call and the next time it's called (or on all the following calls) it will return the type ID.
In addition, we also define a `register` function (the one that is already called from our `plugin_init` function). When `register` function is called it registers the element factory with GStreamer based on the type ID, to be able to create new instances of it with the name “rsrgb2gray” (e.g. when using [`gst::ElementFactory::make`](https://slomo.pages.freedesktop.org/rustdocs/gstreamer/gstreamer/struct.ElementFactory.html#method.make)). The `get_type` function will register the type with the GObject type system on the first call and the next time it's called (or on all the following calls) it will return the type ID.
## Type Class & Instance Initialization
As a next step we implement the `new` funtion and `class_init` functions. In the first version, this struct is almost empty but we will later use it to store all state of our element.
As a next step we implement the `new` funtion and `class_init` functions. In the first version, this struct is empty for now but we will later use it to store all state of our element.
```rust
struct Rgb2Gray {
cat: gst::DebugCategory,
}
impl Rgb2Gray{}
@ -229,11 +231,6 @@ impl ObjectSubclass for Rgb2Gray {
fn new() -> Self {
Self {
cat: gst::DebugCategory::new(
"rsrgb2gray",
gst::DebugColorFlags::empty(),
Some("Rust RGB-GRAY converter"),
),
}
}
@ -255,11 +252,11 @@ impl ObjectSubclass for Rgb2Gray {
```
In the `new` function we return our struct, containing a newly created GStreamer debug category of name “rsrgb2gray”. Were going to use this debug category later for making use of GStreamers debug logging system for logging the state and changes of our element.
In the `new` function we return our empty struct.
In the `class_init` function we, again, set up some metadata for our new element. In this case these are a description, a classification of our element, a longer description and the author. The metadata can later be retrieved and made use of via the [`Registry`](https://slomo.pages.freedesktop.org/rustdocs/gstreamer/gstreamer/struct.Registry.html) and [`PluginFeature`](https://slomo.pages.freedesktop.org/rustdocs/gstreamer/gstreamer/struct.PluginFeature.html)/[`ElementFactory`](https://slomo.pages.freedesktop.org/rustdocs/gstreamer/gstreamer/struct.ElementFactory.html) API. We also configure the `BaseTransform` class and define that we will never operate in-place (producing our output in the input buffer), and that we dont want to work in passthrough mode if the input/output formats are the same.
Additionally we need to implement various traits on the Rgb2Gray struct, which will later be used to override virtual methods of the various parent classes of our element. For now we can keep the trait implementations empty, except for `ObjectImpl` trait which should simply call the `glib_object_impl!()` macro for some boilerplate code. There is one trait implementation required per parent class.
Additionally we need to implement various traits on the Rgb2Gray struct, which will later be used to override virtual methods of the various parent classes of our element. For now we can keep the trait implementations empty, except for `ObjectImpl` trait which should simply call the `glib_object_impl!()` macro for some boilerplate code. There is one trait implementation required per parent class.
```rust
impl ObjectImpl for Rgb2Gray {
@ -307,6 +304,27 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
}
```
## Debug Category
To be able to later have a debug category available for our new element that
can be used for logging, we make use of the `lazy_static!` macro from the
crate with the same name. This crate allows to declare lazily initialized
global variables, i.e. on the very first use the provided code will be
executed and the result will be stored for all later uses.
```rust
lazy_static! {
static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
"rsrgb2gray",
gst::DebugColorFlags::empty(),
Some("Rust RGB-GRAY converter"),
);
}
```
We give the debug category the same name as our element, which generally is
the convention with GStreamer elements.
## Caps & Pad Templates
Data flow of GStreamer elements is happening via pads, which are the input(s) and output(s) (or sinks and sources in GStreamer terminology) of an element. Via the pads, buffers containing actual media data, events or queries are transferred. An element can have any number of sink and source pads, but our new element will only have one of each.
@ -387,7 +405,6 @@ struct State {
}
struct Rgb2Gray {
cat: gst::DebugCategory,
state: Mutex<Option<State>>
}
@ -398,11 +415,6 @@ impl ObjectSubclass for Rgb2Gray {
fn new() -> Self {
Self {
cat: gst::DebugCategory::new(
"rsrgb2gray",
gst::DebugColorFlags::empty(),
"Rust RGB-GRAY converter",
),
state: Mutex::new(None),
}
}
@ -431,7 +443,7 @@ impl BaseTransformImpl for Rgb2Gray {
};
gst_debug!(
self.cat,
CAT,
obj: element,
"Configured for caps {} to {}",
incaps,
@ -447,7 +459,7 @@ impl BaseTransformImpl for Rgb2Gray {
// Drop state
let _ = self.state.lock().unwrap().take();
gst_info!(self.cat, obj: element, "Stopped");
gst_info!(CAT, obj: element, "Stopped");
Ok(())
}
@ -511,7 +523,7 @@ impl BaseTransformImpl for Rgb2Gray {
};
gst_debug!(
self.cat,
CAT,
obj: element,
"Transformed caps from {} to {} in direction {:?}",
caps,
@ -554,8 +566,8 @@ impl Rgb2Gray {
let r = u32::from(in_p[2]);
let gray = ((r * R_Y) + (g * G_Y) + (b * B_Y)) / 65536;
(gray as u8)
gray as u8
}
}
```
@ -574,7 +586,6 @@ impl BaseTransformImpl for Rgb2Gray {
inbuf: &gst::Buffer,
outbuf: &mut gst::BufferRef,
) -> Result<gst::FlowSuccess, gst::FlowError> {
let mut state_guard = self.state.lock().unwrap();
let state = state_guard.as_mut().ok_or_else(|| {
gst_element_error!(element, gst::CoreError::Negotiation, ["Have no state yet"]);
@ -745,7 +756,6 @@ static PROPERTIES: [subclass::Property; 2] = [
];
struct Rgb2Gray {
cat: gst::DebugCategory,
settings: Mutex<Settings>,
state: Mutex<Option<State>>,
}
@ -757,11 +767,6 @@ impl ObjectSubclass for Rgb2Gray {
fn new() -> Self {
Self {
cat: gst::DebugCategory::new(
"rsrgb2gray",
gst::DebugColorFlags::empty(),
"Rust RGB-GRAY converter",
),
settings: Mutex::new(Default::default()),
state: Mutex::new(None),
}
@ -794,7 +799,7 @@ impl ObjectImpl for Rgb2Gray {
let mut settings = self.settings.lock().unwrap();
let invert = value.get_some().expect("type checked upstream");
gst_info!(
self.cat,
CAT,
obj: element,
"Changing invert from {} to {}",
settings.invert,
@ -806,7 +811,7 @@ impl ObjectImpl for Rgb2Gray {
let mut settings = self.settings.lock().unwrap();
let shift = value.get_some().expect("type checked upstream");
gst_info!(
self.cat,
CAT,
obj: element,
"Changing shift from {} to {}",
settings.shift,

View file

@ -134,11 +134,18 @@ impl Default for State {
// Struct containing all the element data
struct SineSrc {
cat: gst::DebugCategory,
settings: Mutex<Settings>,
state: Mutex<State>,
}
lazy_static! {
static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
"rssinesrc",
gst::DebugColorFlags::empty(),
Some("Rust Sine Wave Source"),
);
}
impl SineSrc {
// Called when a new instance is to be created
fn new(element: &BaseSrc) -> Box<BaseSrcImpl<BaseSrc>> {
@ -148,11 +155,6 @@ impl SineSrc {
element.set_format(gst::Format::Time);
Box::new(Self {
cat: gst::DebugCategory::new(
"rssinesrc",
gst::DebugColorFlags::empty(),
Some("Rust Sine Wave Source"),
),
settings: Mutex::new(Default::default()),
state: Mutex::new(Default::default()),
})
@ -220,7 +222,7 @@ impl ObjectImpl<BaseSrc> for SineSrc {
let mut settings = self.settings.lock().unwrap();
let samples_per_buffer = value.get_some().expect("type checked upstream");
gst_info!(
self.cat,
CAT,
obj: &element,
"Changing samples-per-buffer from {} to {}",
settings.samples_per_buffer,
@ -236,7 +238,7 @@ impl ObjectImpl<BaseSrc> for SineSrc {
let mut settings = self.settings.lock().unwrap();
let freq = value.get_some().expect("type checked upstream");
gst_info!(
self.cat,
CAT,
obj: &element,
"Changing freq from {} to {}",
settings.freq,
@ -248,7 +250,7 @@ impl ObjectImpl<BaseSrc> for SineSrc {
let mut settings = self.settings.lock().unwrap();
let volume = value.get_some().expect("type checked upstream");
gst_info!(
self.cat,
CAT,
obj: &element,
"Changing volume from {} to {}",
settings.volume,
@ -260,7 +262,7 @@ impl ObjectImpl<BaseSrc> for SineSrc {
let mut settings = self.settings.lock().unwrap();
let mute = value.get_some().expect("type checked upstream");
gst_info!(
self.cat,
CAT,
obj: &element,
"Changing mute from {} to {}",
settings.mute,
@ -272,7 +274,7 @@ impl ObjectImpl<BaseSrc> for SineSrc {
let mut settings = self.settings.lock().unwrap();
let is_live = value.get_some().expect("type checked upstream");
gst_info!(
self.cat,
CAT,
obj: &element,
"Changing is-live from {} to {}",
settings.is_live,
@ -324,7 +326,7 @@ impl BaseSrcImpl<BaseSrc> for SineSrc {
// Reset state
*self.state.lock().unwrap() = Default::default();
gst_info!(self.cat, obj: element, "Started");
gst_info!(CAT, obj: element, "Started");
true
}
@ -334,7 +336,7 @@ impl BaseSrcImpl<BaseSrc> for SineSrc {
// Reset state
*self.state.lock().unwrap() = Default::default();
gst_info!(self.cat, obj: element, "Stopped");
gst_info!(CAT, obj: element, "Stopped");
true
}
@ -385,7 +387,7 @@ First of all, we need to get notified whenever the caps that our source is confi
Some(info) => info,
};
gst_debug!(self.cat, obj: element, "Configuring for caps {}", caps);
gst_debug!(CAT, obj: element, "Configuring for caps {}", caps);
element.set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer);
@ -581,7 +583,7 @@ Now that this is done, we need to implement the `BaseSrc::create` virtual meth
// point but at most samples_per_buffer samples per buffer
let n_samples = if let Some(sample_stop) = state.sample_stop {
if sample_stop <= state.sample_offset {
gst_log!(self.cat, obj: element, "At EOS");
gst_log!(CAT, obj: element, "At EOS");
return Err(gst::FlowReturn::Eos);
}
@ -640,7 +642,7 @@ Now that this is done, we need to implement the `BaseSrc::create` virtual meth
state.sample_offset += n_samples;
drop(state);
gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer);
gst_debug!(CAT, obj: element, "Produced buffer {:?}", buffer);
Ok(buffer)
}
@ -707,7 +709,7 @@ For working in live mode, we have to add a few different parts in various places
let id = clock.new_single_shot_id(wait_until).unwrap();
gst_log!(
self.cat,
CAT,
obj: element,
"Waiting until {}, now {}",
wait_until,
@ -715,7 +717,7 @@ For working in live mode, we have to add a few different parts in various places
);
let (res, jitter) = id.wait();
gst_log!(
self.cat,
CAT,
obj: element,
"Waited res {:?} jitter {}",
res,
@ -723,7 +725,7 @@ For working in live mode, we have to add a few different parts in various places
);
}
gst_debug!(self.cat, obj: element, "Produced buffer {:?}", buffer);
gst_debug!(CAT, obj: element, "Produced buffer {:?}", buffer);
Ok(buffer)
}
@ -777,12 +779,12 @@ This querying is done with the `LATENCY` query, which we will now also have to
QueryView::Latency(ref mut q) => {
let settings = *self.settings.lock().unwrap();
let state = self.state.lock().unwrap();
if let Some(ref info) = state.info {
let latency = gst::SECOND
.mul_div_floor(settings.samples_per_buffer as u64, info.rate() as u64)
.unwrap();
gst_debug!(self.cat, obj: element, "Returning latency {}", latency);
gst_debug!(CAT, obj: element, "Returning latency {}", latency);
q.set(settings.is_live, latency, gst::CLOCK_TIME_NONE);
true
} else {
@ -791,7 +793,7 @@ This querying is done with the `LATENCY` query, which we will now also have to
}
_ => BaseSrcImplExt::parent_query(self, element, query),
}
}
```
@ -816,7 +818,6 @@ struct ClockWait {
}
struct SineSrc {
cat: gst::DebugCategory,
settings: Mutex<Settings>,
state: Mutex<State>,
clock_wait: Mutex<ClockWait>,
@ -827,7 +828,7 @@ struct SineSrc {
fn unlock(&self, element: &BaseSrc) -> bool {
// This should unblock the create() function ASAP, so we
// just unschedule the clock it here, if any.
gst_debug!(self.cat, obj: element, "Unlocking");
gst_debug!(CAT, obj: element, "Unlocking");
let mut clock_wait = self.clock_wait.lock().unwrap();
if let Some(clock_id) = clock_wait.clock_id.take() {
clock_id.unschedule();
@ -846,7 +847,7 @@ Once everything is unlocked, we need to reset things again so that data flow can
fn unlock_stop(&self, element: &BaseSrc) -> bool {
// This signals that unlocking is done, so we can reset
// all values again.
gst_debug!(self.cat, obj: element, "Unlock stop");
gst_debug!(CAT, obj: element, "Unlock stop");
let mut clock_wait = self.clock_wait.lock().unwrap();
clock_wait.flushing = false;
@ -864,7 +865,7 @@ Now as a last step, we need to actually make use of the new struct we added arou
// so that we immediately stop waiting on e.g. shutdown.
let mut clock_wait = self.clock_wait.lock().unwrap();
if clock_wait.flushing {
gst_debug!(self.cat, obj: element, "Flushing");
gst_debug!(CAT, obj: element, "Flushing");
return Err(gst::FlowReturn::Flushing);
}
@ -873,7 +874,7 @@ Now as a last step, we need to actually make use of the new struct we added arou
drop(clock_wait);
gst_log!(
self.cat,
CAT,
obj: element,
"Waiting until {}, now {}",
wait_until,
@ -881,7 +882,7 @@ Now as a last step, we need to actually make use of the new struct we added arou
);
let (res, jitter) = id.wait();
gst_log!(
self.cat,
CAT,
obj: element,
"Waited res {:?} jitter {}",
res,
@ -892,7 +893,7 @@ Now as a last step, we need to actually make use of the new struct we added arou
// If the clock ID was unscheduled, unlock() was called
// and we should return Flushing immediately.
if res == gst::ClockReturn::Unscheduled {
gst_debug!(self.cat, obj: element, "Flushing");
gst_debug!(CAT, obj: element, "Flushing");
return Err(gst::FlowReturn::Flushing);
}
```
@ -921,7 +922,7 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
// and for calculating the timestamps, etc.
if segment.get_rate() < 0.0 {
gst_error!(self.cat, obj: element, "Reverse playback not supported");
gst_error!(CAT, obj: element, "Reverse playback not supported");
return false;
}
@ -953,7 +954,7 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
(sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64));
gst_debug!(
self.cat,
CAT,
obj: element,
"Seeked to {}-{:?} (accum: {}) for segment {:?}",
sample_offset,
@ -975,7 +976,7 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
if state.info.is_none() {
gst_error!(
self.cat,
CAT,
obj: element,
"Can only seek in Default format if sample rate is known"
);
@ -989,7 +990,7 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
(sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64));
gst_debug!(
self.cat,
CAT,
obj: element,
"Seeked to {}-{:?} (accum: {}) for segment {:?}",
sample_offset,
@ -1008,7 +1009,7 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
true
} else {
gst_error!(
self.cat,
CAT,
obj: element,
"Can't seek in format {:?}",
segment.get_format()