2018-11-24 09:31:58 +00:00
|
|
|
// Copyright (C) 2017,2018 Sebastian Dröge <sebastian@centricular.com>
|
2018-01-13 18:05:35 +00:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
use glib;
|
2018-11-24 09:31:58 +00:00
|
|
|
use glib::subclass;
|
|
|
|
use glib::subclass::prelude::*;
|
2018-01-13 18:05:35 +00:00
|
|
|
use gst;
|
|
|
|
use gst::prelude::*;
|
2018-11-24 09:31:58 +00:00
|
|
|
use gst::subclass::prelude::*;
|
|
|
|
use gst_base;
|
|
|
|
use gst_base::subclass::prelude::*;
|
2018-01-13 18:05:35 +00:00
|
|
|
use gst_video;
|
|
|
|
|
|
|
|
use std::i32;
|
|
|
|
use std::sync::Mutex;
|
|
|
|
|
|
|
|
// Default values of properties
|
|
|
|
const DEFAULT_INVERT: bool = false;
|
|
|
|
const DEFAULT_SHIFT: u32 = 0;
|
|
|
|
|
|
|
|
// Property value storage
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
struct Settings {
|
|
|
|
invert: bool,
|
|
|
|
shift: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Settings {
|
|
|
|
fn default() -> Self {
|
|
|
|
Settings {
|
|
|
|
invert: DEFAULT_INVERT,
|
|
|
|
shift: DEFAULT_SHIFT,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Metadata for the properties
|
2018-11-24 09:31:58 +00:00
|
|
|
static PROPERTIES: [subclass::Property; 2] = [
|
2018-12-18 09:23:45 +00:00
|
|
|
subclass::Property("invert", |name| {
|
2018-11-24 09:31:58 +00:00
|
|
|
glib::ParamSpec::boolean(
|
2018-12-18 09:23:45 +00:00
|
|
|
name,
|
2018-11-24 09:31:58 +00:00
|
|
|
"Invert",
|
|
|
|
"Invert grayscale output",
|
|
|
|
DEFAULT_INVERT,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
2018-12-18 09:23:45 +00:00
|
|
|
subclass::Property("shift", |name| {
|
2018-11-24 09:31:58 +00:00
|
|
|
glib::ParamSpec::uint(
|
2018-12-18 09:23:45 +00:00
|
|
|
name,
|
2018-11-24 09:31:58 +00:00
|
|
|
"Shift",
|
|
|
|
"Shift grayscale output (wrapping around)",
|
|
|
|
0,
|
|
|
|
255,
|
|
|
|
DEFAULT_SHIFT,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
2018-01-13 18:05:35 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
// Stream-specific state, i.e. video format configuration
|
|
|
|
struct State {
|
|
|
|
in_info: gst_video::VideoInfo,
|
|
|
|
out_info: gst_video::VideoInfo,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Struct containing all the element data
|
|
|
|
struct Rgb2Gray {
|
|
|
|
settings: Mutex<Settings>,
|
|
|
|
state: Mutex<Option<State>>,
|
|
|
|
}
|
|
|
|
|
2019-10-31 22:34:21 +00:00
|
|
|
lazy_static! {
|
|
|
|
static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
|
|
|
|
"rsrgb2gray",
|
|
|
|
gst::DebugColorFlags::empty(),
|
|
|
|
Some("Rust RGB-GRAY converter"),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-01-13 18:05:35 +00:00
|
|
|
impl Rgb2Gray {
|
2018-11-24 09:31:58 +00:00
|
|
|
// Converts one pixel of BGRx to a grayscale value, shifting and/or
|
|
|
|
// inverting it as configured
|
|
|
|
#[inline]
|
|
|
|
fn bgrx_to_gray(in_p: &[u8], shift: u8, invert: bool) -> u8 {
|
|
|
|
// See https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601
|
|
|
|
const R_Y: u32 = 19595; // 0.299 * 65536
|
|
|
|
const G_Y: u32 = 38470; // 0.587 * 65536
|
|
|
|
const B_Y: u32 = 7471; // 0.114 * 65536
|
|
|
|
|
|
|
|
assert_eq!(in_p.len(), 4);
|
|
|
|
|
|
|
|
let b = u32::from(in_p[0]);
|
|
|
|
let g = u32::from(in_p[1]);
|
|
|
|
let r = u32::from(in_p[2]);
|
|
|
|
|
|
|
|
let gray = ((r * R_Y) + (g * G_Y) + (b * B_Y)) / 65536;
|
|
|
|
let gray = (gray as u8).wrapping_add(shift);
|
|
|
|
|
|
|
|
if invert {
|
|
|
|
255 - gray
|
|
|
|
} else {
|
|
|
|
gray
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-13 10:01:36 +00:00
|
|
|
// This trait registers our type with the GObject object system and
|
|
|
|
// provides the entry points for creating a new instance and setting
|
|
|
|
// up the class data
|
2018-11-24 09:31:58 +00:00
|
|
|
impl ObjectSubclass for Rgb2Gray {
|
|
|
|
const NAME: &'static str = "RsRgb2Gray";
|
|
|
|
type ParentType = gst_base::BaseTransform;
|
|
|
|
type Instance = gst::subclass::ElementInstanceStruct<Self>;
|
|
|
|
type Class = subclass::simple::ClassStruct<Self>;
|
|
|
|
|
2018-12-13 10:01:36 +00:00
|
|
|
// This macro provides some boilerplate
|
2018-11-24 09:31:58 +00:00
|
|
|
glib_object_subclass!();
|
|
|
|
|
2018-12-13 10:01:36 +00:00
|
|
|
// Called when a new instance is to be created. We need to return an instance
|
|
|
|
// of our struct here.
|
2018-11-24 09:31:58 +00:00
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
2018-01-13 18:05:35 +00:00
|
|
|
settings: Mutex::new(Default::default()),
|
|
|
|
state: Mutex::new(None),
|
2018-11-24 09:31:58 +00:00
|
|
|
}
|
2018-01-13 18:05:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Called exactly once when registering the type. Used for
|
|
|
|
// setting up metadata for all instances, e.g. the name and
|
|
|
|
// classification and the pad templates with their caps.
|
|
|
|
//
|
|
|
|
// Actual instances can create pads based on those pad templates
|
|
|
|
// with a subset of the caps given here. In case of basetransform,
|
|
|
|
// a "src" and "sink" pad template are required here and the base class
|
|
|
|
// will automatically instantiate pads for them.
|
|
|
|
//
|
|
|
|
// Our element here can convert BGRx to BGRx or GRAY8, both being grayscale.
|
2018-11-24 09:31:58 +00:00
|
|
|
fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
|
2018-12-13 10:01:36 +00:00
|
|
|
// Set the element specific metadata. This information is what
|
|
|
|
// is visible from gst-inspect-1.0 and can also be programatically
|
|
|
|
// retrieved from the gst::Registry after initial registration
|
|
|
|
// without having to load the plugin in memory.
|
2018-01-13 18:05:35 +00:00
|
|
|
klass.set_metadata(
|
|
|
|
"RGB-GRAY Converter",
|
|
|
|
"Filter/Effect/Converter/Video",
|
|
|
|
"Converts RGB to GRAY or grayscale RGB",
|
|
|
|
"Sebastian Dröge <sebastian@centricular.com>",
|
|
|
|
);
|
|
|
|
|
2018-12-13 10:01:36 +00:00
|
|
|
// Create and add pad templates for our sink and source pad. These
|
|
|
|
// are later used for actually creating the pads and beforehand
|
|
|
|
// already provide information to GStreamer about all possible
|
|
|
|
// pads that could exist for this type.
|
|
|
|
|
2018-01-13 18:05:35 +00:00
|
|
|
// On the src pad, we can produce BGRx and GRAY8 of any
|
|
|
|
// width/height and with any framerate
|
|
|
|
let caps = gst::Caps::new_simple(
|
|
|
|
"video/x-raw",
|
|
|
|
&[
|
|
|
|
(
|
|
|
|
"format",
|
|
|
|
&gst::List::new(&[
|
2019-10-04 08:47:50 +00:00
|
|
|
&gst_video::VideoFormat::Bgrx.to_str(),
|
|
|
|
&gst_video::VideoFormat::Gray8.to_str(),
|
2018-01-13 18:05:35 +00:00
|
|
|
]),
|
|
|
|
),
|
|
|
|
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
|
|
|
("height", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
|
|
|
(
|
|
|
|
"framerate",
|
|
|
|
&gst::FractionRange::new(
|
|
|
|
gst::Fraction::new(0, 1),
|
|
|
|
gst::Fraction::new(i32::MAX, 1),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
// The src pad template must be named "src" for basetransform
|
|
|
|
// and specific a pad that is always there
|
|
|
|
let src_pad_template = gst::PadTemplate::new(
|
|
|
|
"src",
|
|
|
|
gst::PadDirection::Src,
|
|
|
|
gst::PadPresence::Always,
|
|
|
|
&caps,
|
2019-01-29 15:26:40 +00:00
|
|
|
)
|
|
|
|
.unwrap();
|
2018-01-13 18:05:35 +00:00
|
|
|
klass.add_pad_template(src_pad_template);
|
|
|
|
|
|
|
|
// On the sink pad, we can accept BGRx of any
|
|
|
|
// width/height and with any framerate
|
|
|
|
let caps = gst::Caps::new_simple(
|
|
|
|
"video/x-raw",
|
|
|
|
&[
|
2019-10-04 08:47:50 +00:00
|
|
|
("format", &gst_video::VideoFormat::Bgrx.to_str()),
|
2018-01-13 18:05:35 +00:00
|
|
|
("width", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
|
|
|
("height", &gst::IntRange::<i32>::new(0, i32::MAX)),
|
|
|
|
(
|
|
|
|
"framerate",
|
|
|
|
&gst::FractionRange::new(
|
|
|
|
gst::Fraction::new(0, 1),
|
|
|
|
gst::Fraction::new(i32::MAX, 1),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
// The sink pad template must be named "sink" for basetransform
|
|
|
|
// and specific a pad that is always there
|
|
|
|
let sink_pad_template = gst::PadTemplate::new(
|
|
|
|
"sink",
|
|
|
|
gst::PadDirection::Sink,
|
|
|
|
gst::PadPresence::Always,
|
|
|
|
&caps,
|
2019-01-29 15:26:40 +00:00
|
|
|
)
|
|
|
|
.unwrap();
|
2018-01-13 18:05:35 +00:00
|
|
|
klass.add_pad_template(sink_pad_template);
|
|
|
|
|
|
|
|
// Install all our properties
|
|
|
|
klass.install_properties(&PROPERTIES);
|
|
|
|
|
|
|
|
// Configure basetransform so that we are never running in-place,
|
|
|
|
// don't passthrough on same caps and also never call transform_ip
|
|
|
|
// in passthrough mode (which does not matter for us here).
|
|
|
|
//
|
|
|
|
// We could work in-place for BGRx->BGRx but don't do here for simplicity
|
|
|
|
// for now.
|
2018-11-24 09:31:58 +00:00
|
|
|
klass.configure(
|
|
|
|
gst_base::subclass::BaseTransformMode::NeverInPlace,
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
);
|
2018-01-13 18:05:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-13 10:01:36 +00:00
|
|
|
// Implementation of glib::Object virtual methods
|
2018-11-24 09:31:58 +00:00
|
|
|
impl ObjectImpl for Rgb2Gray {
|
2018-12-13 10:01:36 +00:00
|
|
|
// This macro provides some boilerplate.
|
2018-11-24 09:31:58 +00:00
|
|
|
glib_object_impl!();
|
|
|
|
|
2018-01-13 18:05:35 +00:00
|
|
|
// Called whenever a value of a property is changed. It can be called
|
|
|
|
// at any time from any thread.
|
2018-11-24 09:31:58 +00:00
|
|
|
fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) {
|
|
|
|
let prop = &PROPERTIES[id];
|
|
|
|
let element = obj.downcast_ref::<gst_base::BaseTransform>().unwrap();
|
2018-01-13 18:05:35 +00:00
|
|
|
|
|
|
|
match *prop {
|
2018-11-24 09:31:58 +00:00
|
|
|
subclass::Property("invert", ..) => {
|
2018-01-13 18:05:35 +00:00
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-12 22:45:36 +00:00
|
|
|
let invert = value.get_some().expect("type checked upstream");
|
2018-01-13 18:05:35 +00:00
|
|
|
gst_info!(
|
2019-10-31 22:34:21 +00:00
|
|
|
CAT,
|
2018-07-12 05:31:58 +00:00
|
|
|
obj: element,
|
2018-01-13 18:05:35 +00:00
|
|
|
"Changing invert from {} to {}",
|
|
|
|
settings.invert,
|
|
|
|
invert
|
|
|
|
);
|
|
|
|
settings.invert = invert;
|
|
|
|
}
|
2018-11-24 09:31:58 +00:00
|
|
|
subclass::Property("shift", ..) => {
|
2018-01-13 18:05:35 +00:00
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-12 22:45:36 +00:00
|
|
|
let shift = value.get_some().expect("type checked upstream");
|
2018-01-13 18:05:35 +00:00
|
|
|
gst_info!(
|
2019-10-31 22:34:21 +00:00
|
|
|
CAT,
|
2018-07-12 05:31:58 +00:00
|
|
|
obj: element,
|
2018-01-13 18:05:35 +00:00
|
|
|
"Changing shift from {} to {}",
|
|
|
|
settings.shift,
|
|
|
|
shift
|
|
|
|
);
|
|
|
|
settings.shift = shift;
|
|
|
|
}
|
|
|
|
_ => unimplemented!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called whenever a value of a property is read. It can be called
|
|
|
|
// at any time from any thread.
|
2018-11-24 09:31:58 +00:00
|
|
|
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
|
|
|
|
let prop = &PROPERTIES[id];
|
2018-01-13 18:05:35 +00:00
|
|
|
|
|
|
|
match *prop {
|
2018-11-24 09:31:58 +00:00
|
|
|
subclass::Property("invert", ..) => {
|
2018-01-13 18:05:35 +00:00
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok(settings.invert.to_value())
|
|
|
|
}
|
2018-11-24 09:31:58 +00:00
|
|
|
subclass::Property("shift", ..) => {
|
2018-01-13 18:05:35 +00:00
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok(settings.shift.to_value())
|
|
|
|
}
|
|
|
|
_ => unimplemented!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-13 10:01:36 +00:00
|
|
|
// Implementation of gst::Element virtual methods
|
2018-11-24 09:31:58 +00:00
|
|
|
impl ElementImpl for Rgb2Gray {}
|
2018-01-13 18:05:35 +00:00
|
|
|
|
2018-12-13 10:01:36 +00:00
|
|
|
// Implementation of gst_base::BaseTransform virtual methods
|
2018-11-24 09:31:58 +00:00
|
|
|
impl BaseTransformImpl for Rgb2Gray {
|
2018-01-13 18:05:35 +00:00
|
|
|
// Called for converting caps from one pad to another to account for any
|
|
|
|
// changes in the media format this element is performing.
|
|
|
|
//
|
|
|
|
// In our case that means that:
|
|
|
|
fn transform_caps(
|
|
|
|
&self,
|
2018-11-24 09:31:58 +00:00
|
|
|
element: &gst_base::BaseTransform,
|
2018-01-13 18:05:35 +00:00
|
|
|
direction: gst::PadDirection,
|
|
|
|
caps: &gst::Caps,
|
|
|
|
filter: Option<&gst::Caps>,
|
2019-02-01 14:18:50 +00:00
|
|
|
) -> Option<gst::Caps> {
|
2018-01-13 18:05:35 +00:00
|
|
|
let other_caps = if direction == gst::PadDirection::Src {
|
|
|
|
// For src to sink, no matter if we get asked for BGRx or GRAY8 caps, we can only
|
|
|
|
// accept corresponding BGRx caps on the sinkpad. We will only ever get BGRx and GRAY8
|
|
|
|
// caps here as input.
|
|
|
|
let mut caps = caps.clone();
|
|
|
|
|
|
|
|
for s in caps.make_mut().iter_mut() {
|
2019-10-04 08:47:50 +00:00
|
|
|
s.set("format", &gst_video::VideoFormat::Bgrx.to_str());
|
2018-01-13 18:05:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
caps
|
|
|
|
} else {
|
|
|
|
// For the sink to src case, we will only get BGRx caps and for each of them we could
|
|
|
|
// output the same caps or the same caps as GRAY8. We prefer GRAY8 (put it first), and
|
|
|
|
// at a later point the caps negotiation mechanism of GStreamer will decide on which
|
|
|
|
// one to actually produce.
|
|
|
|
let mut gray_caps = gst::Caps::new_empty();
|
|
|
|
|
|
|
|
{
|
|
|
|
let gray_caps = gray_caps.get_mut().unwrap();
|
|
|
|
|
|
|
|
for s in caps.iter() {
|
|
|
|
let mut s_gray = s.to_owned();
|
2019-10-04 08:47:50 +00:00
|
|
|
s_gray.set("format", &gst_video::VideoFormat::Gray8.to_str());
|
2018-01-13 18:05:35 +00:00
|
|
|
gray_caps.append_structure(s_gray);
|
|
|
|
}
|
|
|
|
gray_caps.append(caps.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
gray_caps
|
|
|
|
};
|
|
|
|
|
|
|
|
gst_debug!(
|
2019-10-31 22:34:21 +00:00
|
|
|
CAT,
|
2018-01-13 18:05:35 +00:00
|
|
|
obj: element,
|
|
|
|
"Transformed caps from {} to {} in direction {:?}",
|
|
|
|
caps,
|
|
|
|
other_caps,
|
|
|
|
direction
|
|
|
|
);
|
|
|
|
|
|
|
|
// In the end we need to filter the caps through an optional filter caps to get rid of any
|
|
|
|
// unwanted caps.
|
|
|
|
if let Some(filter) = filter {
|
2019-02-01 14:18:50 +00:00
|
|
|
Some(filter.intersect_with_mode(&other_caps, gst::CapsIntersectMode::First))
|
2018-01-13 18:05:35 +00:00
|
|
|
} else {
|
2019-02-01 14:18:50 +00:00
|
|
|
Some(other_caps)
|
2018-01-13 18:05:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the size of one processing unit (i.e. a frame in our case) corresponding
|
|
|
|
// to the given caps. This is used for allocating a big enough output buffer and
|
|
|
|
// sanity checking the input buffer size, among other things.
|
2018-11-24 09:31:58 +00:00
|
|
|
fn get_unit_size(&self, _element: &gst_base::BaseTransform, caps: &gst::Caps) -> Option<usize> {
|
2019-12-15 08:51:12 +00:00
|
|
|
gst_video::VideoInfo::from_caps(caps)
|
|
|
|
.map(|info| info.size())
|
|
|
|
.ok()
|
2018-01-13 18:05:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Called whenever the input/output caps are changing, i.e. in the very beginning before data
|
|
|
|
// flow happens and whenever the situation in the pipeline is changing. All buffers after this
|
|
|
|
// call have the caps given here.
|
|
|
|
//
|
|
|
|
// We simply remember the resulting VideoInfo from the caps to be able to use this for knowing
|
|
|
|
// the width, stride, etc when transforming buffers
|
2018-11-24 09:31:58 +00:00
|
|
|
fn set_caps(
|
|
|
|
&self,
|
|
|
|
element: &gst_base::BaseTransform,
|
|
|
|
incaps: &gst::Caps,
|
|
|
|
outcaps: &gst::Caps,
|
2019-11-20 22:06:16 +00:00
|
|
|
) -> Result<(), gst::LoggableError> {
|
2018-01-13 18:05:35 +00:00
|
|
|
let in_info = match gst_video::VideoInfo::from_caps(incaps) {
|
2019-12-15 08:51:12 +00:00
|
|
|
Err(_) => return Err(gst_loggable_error!(CAT, "Failed to parse input caps")),
|
|
|
|
Ok(info) => info,
|
2018-01-13 18:05:35 +00:00
|
|
|
};
|
|
|
|
let out_info = match gst_video::VideoInfo::from_caps(outcaps) {
|
2019-12-15 08:51:12 +00:00
|
|
|
Err(_) => return Err(gst_loggable_error!(CAT, "Failed to parse output caps")),
|
|
|
|
Ok(info) => info,
|
2018-01-13 18:05:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
gst_debug!(
|
2019-10-31 22:34:21 +00:00
|
|
|
CAT,
|
2018-01-13 18:05:35 +00:00
|
|
|
obj: element,
|
|
|
|
"Configured for caps {} to {}",
|
|
|
|
incaps,
|
|
|
|
outcaps
|
|
|
|
);
|
|
|
|
|
2018-10-11 10:49:48 +00:00
|
|
|
*self.state.lock().unwrap() = Some(State { in_info, out_info });
|
2018-01-13 18:05:35 +00:00
|
|
|
|
2019-11-20 22:06:16 +00:00
|
|
|
Ok(())
|
2018-01-13 18:05:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Called when shutting down the element so we can release all stream-related state
|
|
|
|
// There's also start(), which is called whenever starting the element again
|
2019-01-26 15:07:51 +00:00
|
|
|
fn stop(&self, element: &gst_base::BaseTransform) -> Result<(), gst::ErrorMessage> {
|
2018-01-13 18:05:35 +00:00
|
|
|
// Drop state
|
|
|
|
let _ = self.state.lock().unwrap().take();
|
|
|
|
|
2019-10-31 22:34:21 +00:00
|
|
|
gst_info!(CAT, obj: element, "Stopped");
|
2018-01-13 18:05:35 +00:00
|
|
|
|
2019-01-26 15:07:51 +00:00
|
|
|
Ok(())
|
2018-01-13 18:05:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Does the actual transformation of the input buffer to the output buffer
|
|
|
|
fn transform(
|
|
|
|
&self,
|
2018-11-24 09:31:58 +00:00
|
|
|
element: &gst_base::BaseTransform,
|
2018-01-13 18:05:35 +00:00
|
|
|
inbuf: &gst::Buffer,
|
|
|
|
outbuf: &mut gst::BufferRef,
|
2019-01-11 23:45:05 +00:00
|
|
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
2018-01-13 18:05:35 +00:00
|
|
|
// Keep a local copy of the values of all our properties at this very moment. This
|
|
|
|
// ensures that the mutex is never locked for long and the application wouldn't
|
|
|
|
// have to block until this function returns when getting/setting property values
|
|
|
|
let settings = *self.settings.lock().unwrap();
|
|
|
|
|
|
|
|
// Get a locked reference to our state, i.e. the input and output VideoInfo
|
|
|
|
let mut state_guard = self.state.lock().unwrap();
|
2019-01-11 23:45:05 +00:00
|
|
|
let state = state_guard.as_mut().ok_or_else(|| {
|
|
|
|
gst_element_error!(element, gst::CoreError::Negotiation, ["Have no state yet"]);
|
|
|
|
gst::FlowError::NotNegotiated
|
|
|
|
})?;
|
2018-01-13 18:05:35 +00:00
|
|
|
|
|
|
|
// Map the input buffer as a VideoFrameRef. This is similar to directly mapping
|
|
|
|
// the buffer with inbuf.map_readable() but in addition extracts various video
|
|
|
|
// specific metadata and sets up a convenient data structure that directly gives
|
|
|
|
// pointers to the different planes and has all the information about the raw
|
|
|
|
// video frame, like width, height, stride, video format, etc.
|
|
|
|
//
|
|
|
|
// This fails if the buffer can't be read or is invalid in relation to the video
|
|
|
|
// info that is passed here
|
2019-01-11 23:45:05 +00:00
|
|
|
let in_frame =
|
|
|
|
gst_video::VideoFrameRef::from_buffer_ref_readable(inbuf.as_ref(), &state.in_info)
|
2019-12-18 05:50:10 +00:00
|
|
|
.map_err(|_| {
|
2019-01-11 23:45:05 +00:00
|
|
|
gst_element_error!(
|
|
|
|
element,
|
|
|
|
gst::CoreError::Failed,
|
|
|
|
["Failed to map input buffer readable"]
|
|
|
|
);
|
|
|
|
gst::FlowError::Error
|
|
|
|
})?;
|
2018-01-13 18:05:35 +00:00
|
|
|
|
|
|
|
// And now map the output buffer writable, so we can fill it.
|
|
|
|
let mut out_frame =
|
2019-12-18 05:50:10 +00:00
|
|
|
gst_video::VideoFrameRef::from_buffer_ref_writable(outbuf, &state.out_info).map_err(
|
|
|
|
|_| {
|
2018-01-13 18:05:35 +00:00
|
|
|
gst_element_error!(
|
|
|
|
element,
|
|
|
|
gst::CoreError::Failed,
|
|
|
|
["Failed to map output buffer writable"]
|
|
|
|
);
|
2019-01-11 23:45:05 +00:00
|
|
|
gst::FlowError::Error
|
2019-12-18 05:50:10 +00:00
|
|
|
},
|
|
|
|
)?;
|
2018-01-13 18:05:35 +00:00
|
|
|
|
|
|
|
// Keep the various metadata we need for working with the video frames in
|
|
|
|
// local variables. This saves some typing below.
|
|
|
|
let width = in_frame.width() as usize;
|
|
|
|
let in_stride = in_frame.plane_stride()[0] as usize;
|
|
|
|
let in_data = in_frame.plane_data(0).unwrap();
|
|
|
|
let out_stride = out_frame.plane_stride()[0] as usize;
|
|
|
|
let out_format = out_frame.format();
|
|
|
|
let out_data = out_frame.plane_data_mut(0).unwrap();
|
|
|
|
|
|
|
|
// First check the output format. Our input format is always BGRx but the output might
|
|
|
|
// be BGRx or GRAY8. Based on what it is we need to do processing slightly differently.
|
|
|
|
if out_format == gst_video::VideoFormat::Bgrx {
|
|
|
|
// Some assertions about our assumptions how the data looks like. This is only there
|
|
|
|
// to give some further information to the compiler, in case these can be used for
|
|
|
|
// better optimizations of the resulting code.
|
|
|
|
//
|
|
|
|
// If any of the assertions were not true, the code below would fail cleanly.
|
|
|
|
assert_eq!(in_data.len() % 4, 0);
|
|
|
|
assert_eq!(out_data.len() % 4, 0);
|
|
|
|
assert_eq!(out_data.len() / out_stride, in_data.len() / in_stride);
|
|
|
|
|
|
|
|
let in_line_bytes = width * 4;
|
|
|
|
let out_line_bytes = width * 4;
|
|
|
|
|
|
|
|
assert!(in_line_bytes <= in_stride);
|
|
|
|
assert!(out_line_bytes <= out_stride);
|
|
|
|
|
|
|
|
// Iterate over each line of the input and output frame, mutable for the output frame.
|
|
|
|
// Each input line has in_stride bytes, each output line out_stride. We use the
|
2019-01-25 14:42:27 +00:00
|
|
|
// chunks_exact/chunks_exact_mut iterators here for getting a chunks of that many bytes per
|
2018-01-13 18:05:35 +00:00
|
|
|
// iteration and zip them together to have access to both at the same time.
|
|
|
|
for (in_line, out_line) in in_data
|
2019-01-25 14:42:27 +00:00
|
|
|
.chunks_exact(in_stride)
|
|
|
|
.zip(out_data.chunks_exact_mut(out_stride))
|
2018-01-13 18:05:35 +00:00
|
|
|
{
|
|
|
|
// Next iterate the same way over each actual pixel in each line. Every pixel is 4
|
2019-01-25 14:42:27 +00:00
|
|
|
// bytes in the input and output, so we again use the chunks_exact/chunks_exact_mut iterators
|
2018-01-13 18:05:35 +00:00
|
|
|
// to give us each pixel individually and zip them together.
|
|
|
|
//
|
|
|
|
// Note that we take a sub-slice of the whole lines: each line can contain an
|
|
|
|
// arbitrary amount of padding at the end (e.g. for alignment purposes) and we
|
|
|
|
// don't want to process that padding.
|
|
|
|
for (in_p, out_p) in in_line[..in_line_bytes]
|
2019-01-25 14:42:27 +00:00
|
|
|
.chunks_exact(4)
|
|
|
|
.zip(out_line[..out_line_bytes].chunks_exact_mut(4))
|
2018-01-13 18:05:35 +00:00
|
|
|
{
|
|
|
|
assert_eq!(out_p.len(), 4);
|
|
|
|
|
|
|
|
// Use our above-defined function to convert a BGRx pixel with the settings to
|
|
|
|
// a grayscale value. Then store the same value in the red/green/blue component
|
|
|
|
// of the pixel.
|
|
|
|
let gray = Rgb2Gray::bgrx_to_gray(in_p, settings.shift as u8, settings.invert);
|
|
|
|
out_p[0] = gray;
|
|
|
|
out_p[1] = gray;
|
|
|
|
out_p[2] = gray;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if out_format == gst_video::VideoFormat::Gray8 {
|
|
|
|
assert_eq!(in_data.len() % 4, 0);
|
|
|
|
assert_eq!(out_data.len() / out_stride, in_data.len() / in_stride);
|
|
|
|
|
|
|
|
let in_line_bytes = width * 4;
|
|
|
|
let out_line_bytes = width;
|
|
|
|
|
|
|
|
assert!(in_line_bytes <= in_stride);
|
|
|
|
assert!(out_line_bytes <= out_stride);
|
|
|
|
|
|
|
|
// Iterate over each line of the input and output frame, mutable for the output frame.
|
|
|
|
// Each input line has in_stride bytes, each output line out_stride. We use the
|
2019-01-25 14:42:27 +00:00
|
|
|
// chunks_exact/chunks_exact_mut iterators here for getting a chunks of that many bytes per
|
2018-01-13 18:05:35 +00:00
|
|
|
// iteration and zip them together to have access to both at the same time.
|
|
|
|
for (in_line, out_line) in in_data
|
2019-01-25 14:42:27 +00:00
|
|
|
.chunks_exact(in_stride)
|
|
|
|
.zip(out_data.chunks_exact_mut(out_stride))
|
2018-01-13 18:05:35 +00:00
|
|
|
{
|
|
|
|
// Next iterate the same way over each actual pixel in each line. Every pixel is 4
|
|
|
|
// bytes in the input and 1 byte in the output, so we again use the
|
2019-01-25 14:42:27 +00:00
|
|
|
// chunks_exact/chunks_exact_mut iterators to give us each pixel individually and zip them
|
2018-01-13 18:05:35 +00:00
|
|
|
// together.
|
|
|
|
//
|
|
|
|
// Note that we take a sub-slice of the whole lines: each line can contain an
|
|
|
|
// arbitrary amount of padding at the end (e.g. for alignment purposes) and we
|
|
|
|
// don't want to process that padding.
|
|
|
|
for (in_p, out_p) in in_line[..in_line_bytes]
|
2019-01-25 14:42:27 +00:00
|
|
|
.chunks_exact(4)
|
2018-01-13 18:05:35 +00:00
|
|
|
.zip(out_line[..out_line_bytes].iter_mut())
|
|
|
|
{
|
|
|
|
// Use our above-defined function to convert a BGRx pixel with the settings to
|
|
|
|
// a grayscale value. Then store the value in the grayscale output directly.
|
|
|
|
let gray = Rgb2Gray::bgrx_to_gray(in_p, settings.shift as u8, settings.invert);
|
|
|
|
*out_p = gray;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
|
2019-01-11 23:45:05 +00:00
|
|
|
Ok(gst::FlowSuccess::Ok)
|
2018-01-13 18:05:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Registers the type for our element, and then registers in GStreamer under
|
|
|
|
// the name "rsrgb2gray" for being able to instantiate it via e.g.
|
|
|
|
// gst::ElementFactory::make().
|
2018-11-04 18:46:07 +00:00
|
|
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
2019-06-04 09:04:06 +00:00
|
|
|
gst::Element::register(
|
|
|
|
Some(plugin),
|
|
|
|
"rsrgb2gray",
|
|
|
|
gst::Rank::None,
|
|
|
|
Rgb2Gray::get_type(),
|
|
|
|
)
|
2018-01-13 18:05:35 +00:00
|
|
|
}
|