diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 24dbbaf25..be0d96617 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,7 @@ test_crates: - export RUSTFLAGS="-D warnings" - - DEBIAN_FRONTEND=noninteractive apt-get install -y libgstreamer-plugins-base1.0-dev ffmpeg + - DEBIAN_FRONTEND=noninteractive apt-get install -y ffmpeg - rustup target add thumbv7em-none-eabihf # Test ffmpeg-writer @@ -49,11 +49,6 @@ test_crates: - cargo test --release - cd ../.. - # Test gstreamer apriltag detector - - cd gst-plugin-apriltag - - cargo test --release - - cd .. - # Test braid-april-cal, which requires opencv - cd braid-april-cal # run test in release mode, otherwise slow @@ -105,10 +100,6 @@ test_crates: - cargo build --release --features backend_pyloncxx - cd .. - - cd gst-plugin-apriltag - - cargo check --all-targets - - cd .. - - cd led-box-comms - cargo test - cd .. diff --git a/Cargo.toml b/Cargo.toml index 2267059d1..d49412984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,6 @@ members = [ "freemovr-calibration/freemovr-calibration-webapp", "freemovr-calibration/ncollide-geom", "groupby", - "gst-plugin-apriltag", "http-video-streaming", "http-video-streaming/http-video-streaming-types", "imops", diff --git a/gst-plugin-apriltag/.gitignore b/gst-plugin-apriltag/.gitignore deleted file mode 100644 index c97b89ff1..000000000 --- a/gst-plugin-apriltag/.gitignore +++ /dev/null @@ -1 +0,0 @@ -movie-standard41h12.mkv diff --git a/gst-plugin-apriltag/Cargo.toml b/gst-plugin-apriltag/Cargo.toml deleted file mode 100644 index 37f945164..000000000 --- a/gst-plugin-apriltag/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "gst-plugin-apriltag" -description = "support for April Tag fiducial markers" -version = "0.1.0" -license = "BSD-2-Clause" -authors = ["Andrew Straw "] -edition = "2021" -rust-version = "1.76" -repository = "https://github.com/strawlab/strand-braid" - -[lib] -name = "gstrsapriltag" -crate-type = ["cdylib", "rlib", "staticlib"] -path = "src/lib.rs" - -[dependencies] -glib = "0.10" -gobject-sys = "0.10" -gstreamer = "0.16" -gstreamer-base = "0.16" -gstreamer-video = "0.16" -lazy_static.workspace = true -csv.workspace = true -serde.workspace = true -bytes.workspace = true - -ads-apriltag.workspace = true - -[build-dependencies] -gst-plugin-version-helper = "0.7.5" - -[dev-dependencies] -download-verify.workspace = true diff --git a/gst-plugin-apriltag/LICENSE.txt b/gst-plugin-apriltag/LICENSE.txt deleted file mode 100644 index f7eb17ab9..000000000 --- a/gst-plugin-apriltag/LICENSE.txt +++ /dev/null @@ -1,24 +0,0 @@ -BSD 2-Clause License - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/gst-plugin-apriltag/README.md b/gst-plugin-apriltag/README.md deleted file mode 100644 index 1f10b8751..000000000 --- a/gst-plugin-apriltag/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# gst-plugin-apriltag - -## Prerequisites: - -This crate requires gstreamer (with the video plugin) libraries. - -On Debian/Ubuntu linux, you can install the prerequisites to build like this: - - sudo apt-get install libgstreamer-plugins-base1.0-dev - -On Debian/Ubuntu linux, you can install `gst-inspect-1.0` like this: - - sudo apt-get install gstreamer1.0-tools - -## Build and run - -Build and run like this: - - cargo build --release - export GST_PLUGIN_PATH=`pwd`/../target/release - - # to detect 36h11 tags (the default): - - gst-launch-1.0 filesrc location=movie-36h11.m4v ! decodebin ! videoconvert ! apriltagdetector ! filesink location=movie-36h11.csv - - # or use the 'family' property to use 'standard_41h12' tags: - - gst-launch-1.0 filesrc location=movie-standard41h12.m4v ! decodebin ! videoconvert ! apriltagdetector family=standard-41h12 ! filesink location=movie-standard41h12.csv - - # to record live on the Jetson Nano: - - gst-launch-1.0 nvarguscamerasrc ! capsfilter caps='video/x-raw(memory:NVMM),width=3820,height=2464,framerate=21/1,format=NV12' ! nvvidconv flip-method=2 ! apriltagdetector ! filesink location=april-out.csv - - # To show webcam live (with v4l2src) on-screen and also save april tag output to file. Note that this does not buffer the writes to disk - # so they can be seen with "tail -f movie-standard41h12.csv". To buffer, change buffer-mode to "full" or remove the buffer-mode property - # to accept the default): - - gst-launch-1.0 -v v4l2src ! tee name=t ! queue ! xvimagesink t. ! queue ! videoconvert ! apriltagdetector family=standard-41h12 ! filesink buffer-mode=unbuffered location=movie-standard41h12.csv - -Inspect the plugin like this: - - gst-inspect-1.0 apriltagdetector - -## Debug - -You can also use the environment variables `GST_DEBUG=2,apriltagdetector:6` to -perform more debugging. - -For example: - - GST_DEBUG=2,apriltagdetector:6 gst-launch-1.0 videotestsrc num-buffers=5 is-live=1 ! apriltagdetector family=16h5 ! filesink location=trash.csv - -## License - -Like apriltag itself, gst-plugin-apriltag is licensed under the BSD-2-Clause license. - -Portions of the code derive from the gst-plugin tutorial (C) 2018 Sebastian -Dröge, licensed under the Apache License, Version 2.0 or the MIT license. diff --git a/gst-plugin-apriltag/build.rs b/gst-plugin-apriltag/build.rs deleted file mode 100644 index 72a8a02c7..000000000 --- a/gst-plugin-apriltag/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate gst_plugin_version_helper; - -fn main() { - gst_plugin_version_helper::info() -} diff --git a/gst-plugin-apriltag/src/apriltagdetector.rs b/gst-plugin-apriltag/src/apriltagdetector.rs deleted file mode 100644 index c886f3dc6..000000000 --- a/gst-plugin-apriltag/src/apriltagdetector.rs +++ /dev/null @@ -1,723 +0,0 @@ -// Copyright (C) 2020 Andrew Straw -// -// Licensed under the BSD 2 Clause License. See LICENSE.txt. -// -// Copyright (C) 2018 Sebastian Dröge -// -// Licensed under the Apache License, Version 2.0 or the MIT license , at your option. This file may not be -// copied, modified, or distributed except according to those terms. - -use glib::prelude::*; -use glib::subclass; -use glib::subclass::prelude::*; -use gst::prelude::*; -use gst::subclass::prelude::*; - -use crate::TagFamily; -use std::i32; -use std::sync::Mutex; - -use ads_apriltag as apriltag; - -const SRC_CAPS: &'static str = "text/x-csv"; - -// Property value storage -#[derive(Debug, Clone)] -struct Settings { - family: TagFamily, - maxhamming: i32, - decimate: f32, - blur: f32, - refine_edges: bool, -} - -const DEFAULT_FAMILY: TagFamily = TagFamily::Family36h11; -const DEFAULT_MAXHAMMING: i32 = 1; -const DEFAULT_DECIMATE: f32 = 2.0; -const DEFAULT_BLUR: f32 = 0.0; -const DEFAULT_REFINE_EDGES: bool = true; - -impl Default for Settings { - fn default() -> Self { - Settings { - family: DEFAULT_FAMILY, - maxhamming: DEFAULT_MAXHAMMING, - decimate: DEFAULT_DECIMATE, - blur: DEFAULT_BLUR, - refine_edges: DEFAULT_REFINE_EDGES, - } - } -} - -// Metadata for the properties -static PROPERTIES: [subclass::Property; 5] = [ - subclass::Property("family", |name| { - glib::ParamSpec::enum_( - name, - "Family", - "Tag family to use", - TagFamily::static_type(), - DEFAULT_FAMILY as i32, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("maxhamming", |name| { - glib::ParamSpec::int( - name, - "maxhamming", - "Detect tags with up to this many bit errors", - 0, - std::i32::MAX, - DEFAULT_MAXHAMMING, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("decimate", |name| { - glib::ParamSpec::float( - name, - "Decimate", - "Decimate input image by this factor", - 0.0, - std::f32::MAX, - DEFAULT_DECIMATE, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("blur", |name| { - glib::ParamSpec::float( - name, - "Blur", - "Apply low-pass blur to input; negative sharpens", - std::f32::MIN, - std::f32::MAX, - DEFAULT_BLUR, - glib::ParamFlags::READWRITE, - ) - }), - subclass::Property("refine-edges", |name| { - glib::ParamSpec::boolean( - name, - "refine-edges", - "Spend more time trying to align edges of tags", - DEFAULT_REFINE_EDGES, - glib::ParamFlags::READWRITE, - ) - }), - // TODO add threads, debug as properties? -]; - -// Stream-specific state, i.e. video format configuration -#[derive(Debug)] -struct State { - write_headers: bool, - video_info: gst_video::VideoInfo, - inner: apriltag::Detector, -} - -// Struct containing all the element data -struct AprilTagDetector { - settings: Mutex, - srcpad: gst::Pad, - sinkpad: gst::Pad, - state: Mutex>, -} - -lazy_static! { - static ref CAT: gst::DebugCategory = gst::DebugCategory::new( - "apriltagdetector", - gst::DebugColorFlags::empty(), - Some("AprilTagDetector Element"), - ); -} - -fn add_family(td: &mut apriltag::Detector, settings: &Settings) { - use TagFamily::*; - let tf = match settings.family { - Family36h11 => apriltag::Family::new_tag_36h11(), - FamilyStandard41h12 => apriltag::Family::new_tag_standard_41h12(), - Family16h5 => apriltag::Family::new_tag_16h5(), - Family25h9 => apriltag::Family::new_tag_25h9(), - FamilyCircle21h7 => apriltag::Family::new_tag_circle_21h7(), - FamilyCircle49h12 => apriltag::Family::new_tag_circle_49h12(), - FamilyCustom48h12 => apriltag::Family::new_tag_custom_48h12(), - FamilyStandard52h13 => apriltag::Family::new_tag_standard_52h13(), - }; - td.add_family_bits(tf, settings.maxhamming); - - gst_debug!( - CAT, - "set april tag detector family {}, maxhamming {}", - settings.family, - settings.maxhamming - ); -} - -fn make_detector(settings: &Settings) -> apriltag::Detector { - let mut td = apriltag::Detector::new(); - add_family(&mut td, &settings); - - let raw_td = td.as_mut(); - raw_td.quad_decimate = settings.decimate; - raw_td.quad_sigma = settings.blur; - raw_td.refine_edges = if settings.refine_edges { 1 } else { 0 }; - raw_td.decode_sharpening = 0.25; - td -} - -fn do_detections( - in_frame: gst_video::VideoFrameRef<&gst::BufferRef>, - td: &apriltag::Detector, -) -> apriltag::Zarray { - use apriltag::ImageU8; - - // 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().try_into().unwrap(); - let height = in_frame.height().try_into().unwrap(); - let stride = in_frame.plane_stride()[0].try_into().unwrap(); - let data = in_frame.plane_data(0).unwrap(); - - gst_debug!( - CAT, - "detecting with width {}, height {}, stride {}, data {:?}", - width, - height, - stride, - &data[..3] - ); - - let im = apriltag::ImageU8Borrowed::new(width, height, stride, data).unwrap(); - - // given our caps, this is a video/x-raw Gray8 video frame - let result = td.detect(&im.inner()); - - gst_debug!(CAT, "found {} point(s)", result.as_slice().len()); - - result -} - -use serde::Serialize; - -// The center pixel of the detection is (h02,h12) -#[derive(Serialize)] -struct DetectionSerializer { - // frame: usize, - pts_nanoseconds: Option, - id: i32, - hamming: i32, - decision_margin: f32, - h00: f64, - h01: f64, - h02: f64, - h10: f64, - h11: f64, - h12: f64, - h20: f64, - h21: f64, - // no h22 because it is always 1.0 - family: String, -} - -fn to_serializer(orig: &apriltag::Detection, pts: gst::ClockTime) -> DetectionSerializer { - let h = orig.h(); - // We are not going to save h22, so (in debug builds) let's check it meets - // our expectations. - debug_assert!((h[8] - 1.0).abs() < 1e-16); - DetectionSerializer { - // frame, - pts_nanoseconds: pts.nanoseconds(), - id: orig.id(), - hamming: orig.hamming(), - decision_margin: orig.decision_margin(), - h00: h[0], - h01: h[1], - h02: h[2], - h10: h[3], - h11: h[4], - h12: h[5], - h20: h[6], - h21: h[7], - family: orig.family_type().to_str().to_string(), - } -} - -fn to_csv_lines( - detections: &[apriltag::Detection], - write_headers: bool, - pts: gst::ClockTime, -) -> gst::Buffer { - let mut wtr = csv::WriterBuilder::new() - .has_headers(write_headers) - .from_writer(Vec::new()); - - for d in detections.iter() { - let d2 = to_serializer(&d, pts); - wtr.serialize(d2).expect("serialize"); - } - let my_bytes = wtr.into_inner().expect("into inner buffer"); - - let mut buffer = gst::Buffer::with_size(my_bytes.len()).unwrap(); - { - let buffer = buffer.get_mut().unwrap(); - let mut data = buffer.map_writable().unwrap(); - let mut dslice = data.as_mut_slice(); - - // TODO: This makes a copy. Can we eliminate the copy? - use bytes::BufMut; - dslice.put(my_bytes.as_slice()); - } - - buffer -} - -impl AprilTagDetector { - // After creating of our two pads set all the functions on them - // - // Each function is wrapped in catch_panic_pad_function(), which will - // - Catch panics from the pad functions and instead of aborting the process - // it will simply convert them into an error message and poison the element - // instance - // - Extract our AprilTagDetector struct from the object instance and pass it to us - // - // Details about what each function is good for is next to each function definition - fn set_pad_functions(sinkpad: &gst::Pad, srcpad: &gst::Pad) { - unsafe { - sinkpad.set_chain_function(|pad, parent, buffer| { - AprilTagDetector::catch_panic_pad_function( - parent, - || Err(gst::FlowError::Error), - |april_tag, element| april_tag.sink_chain(pad, element, buffer), - ) - }); - sinkpad.set_event_function(|pad, parent, event| { - AprilTagDetector::catch_panic_pad_function( - parent, - || false, - |april_tag, element| april_tag.sink_event(pad, element, event), - ) - }); - // sinkpad.set_query_function(|pad, parent, query| { - // AprilTagDetector::catch_panic_pad_function( - // parent, - // || false, - // |april_tag, element| april_tag.sink_query(pad, element, query), - // ) - // }); - - srcpad.set_event_function(|pad, parent, event| { - AprilTagDetector::catch_panic_pad_function( - parent, - || false, - |april_tag, element| april_tag.src_event(pad, element, event), - ) - }); - srcpad.set_query_function(|pad, parent, query| { - AprilTagDetector::catch_panic_pad_function( - parent, - || false, - |april_tag, element| april_tag.src_query(pad, element, query), - ) - }); - } - } - - // Called whenever a new buffer is passed to our sink pad. Here buffers should be processed and - // whenever some output buffer is available have to push it out of the source pad. - // Here we just pass through all buffers directly - // - // See the documentation of gst::Buffer and gst::BufferRef to see what can be done with - // buffers. - fn sink_chain( - &self, - pad: &gst::Pad, - element: &gst::Element, - inbuf: gst::Buffer, - ) -> Result { - gst_debug!(CAT, obj: pad, "Handling buffer {:?}", inbuf); - let pts = inbuf.get_pts(); - - let (detections, write_headers) = { - let mut state_guard = self.state.lock().unwrap(); - let state = state_guard.as_mut().unwrap(); - - // 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 - let in_frame = gst_video::VideoFrameRef::from_buffer_ref_readable( - inbuf.as_ref(), - &state.video_info, - ) - .map_err(|_| { - gst_element_error!( - element, - gst::CoreError::Failed, - ["Failed to map input buffer readable"] - ); - gst::FlowError::Error - })?; - - let write_headers = state.write_headers; - - let detections = do_detections(in_frame, &state.inner); - if write_headers && detections.len() > 0 { - // The headers will get written below, so we do not have to keep - // writing them. - state.write_headers = false; - } - - (detections, write_headers) - // drop state_guard here - }; - - let buffer = to_csv_lines(detections.as_slice(), write_headers, pts); - - self.srcpad.push(buffer).map_err(|err| { - gst_error!(CAT, obj: element, "Failed to push buffer {:?}", err); - err - })?; - - Ok(gst::FlowSuccess::Ok) - } - - // Called whenever an event arrives on the sink pad. It has to be handled accordingly and in - // most cases has to be either passed to Pad::event_default() on this pad for default handling, - // or Pad::push_event() on all pads with the opposite direction for direct forwarding. - // Here we just pass through all events directly to the source pad. - // - // See the documentation of gst::Event and gst::EventRef to see what can be done with - // events, and especially the gst::EventView type for inspecting events. - fn sink_event(&self, pad: &gst::Pad, element: &gst::Element, event: gst::Event) -> bool { - use gst::EventView; - - gst_log!(CAT, obj: pad, "Handling event {:?}", event); - - match event.view() { - EventView::Caps(ev) => { - let incaps = ev.get_caps(); - - let video_info = match gst_video::VideoInfo::from_caps(incaps) { - Err(_) => { - // return Err(gst_loggable_error!(CAT, "Failed to parse input caps")) - panic!("Failed to parse input caps"); - } - Ok(info) => info, - }; - gst_log!(CAT, obj: pad, "Got video_info {:?}", video_info); - - { - let settings_guard = self.settings.lock().unwrap(); - - let mut state_guard = self.state.lock().unwrap(); - *state_guard = Some(State { - write_headers: true, - video_info, - inner: make_detector(&settings_guard), - }); - } - - // let s = caps.get_structure(0).unwrap(); - // let framerate = match s.get_some::("framerate") { - - // We send our own caps downstream - let caps = gst::Caps::builder(SRC_CAPS).build(); - self.srcpad.push_event(gst::event::Caps::new(&caps)) - } - _ => pad.event_default(Some(element), event), - } - } - - // // Called whenever a query is sent to the sink pad. It has to be answered if the element can - // // handle it, potentially by forwarding the query first to the peer pads of the pads with the - // // opposite direction, or false has to be returned. Default handling can be achieved with - // // Pad::query_default() on this pad and forwarding with Pad::peer_query() on the pads with the - // // opposite direction. - // // Here we just forward all queries directly to the source pad's peers. - // // - // // See the documentation of gst::Query and gst::QueryRef to see what can be done with - // // queries, and especially the gst::QueryView type for inspecting and modifying queries. - // fn sink_query( - // &self, - // pad: &gst::Pad, - // _element: &gst::Element, - // query: &mut gst::QueryRef, - // ) -> bool { - // gst_log!(CAT, obj: pad, "Handling query {:?}", query); - // self.srcpad.peer_query(query) - // } - - // Called whenever an event arrives on the source pad. It has to be handled accordingly and in - // most cases has to be either passed to Pad::event_default() on the same pad for default - // handling, or Pad::push_event() on all pads with the opposite direction for direct - // forwarding. - // Here we just pass through all events directly to the sink pad. - // - // See the documentation of gst::Event and gst::EventRef to see what can be done with - // events, and especially the gst::EventView type for inspecting events. - fn src_event(&self, pad: &gst::Pad, _element: &gst::Element, event: gst::Event) -> bool { - gst_log!(CAT, obj: pad, "Handling event {:?}", event); - self.sinkpad.push_event(event) - } - - // Called whenever a query is sent to the source pad. It has to be answered if the element can - // handle it, potentially by forwarding the query first to the peer pads of the pads with the - // opposite direction, or false has to be returned. Default handling can be achieved with - // Pad::query_default() on this pad and forwarding with Pad::peer_query() on the pads with the - // opposite direction. - // Here we just forward all queries directly to the sink pad's peers. - // - // See the documentation of gst::Query and gst::QueryRef to see what can be done with - // queries, and especially the gst::QueryView type for inspecting and modifying queries. - fn src_query( - &self, - pad: &gst::Pad, - _element: &gst::Element, - query: &mut gst::QueryRef, - ) -> bool { - gst_log!(CAT, obj: pad, "Handling query {:?}", query); - // TODO: should we somehow return "unknown" number of bytes here? - self.sinkpad.peer_query(query) - } -} - -// 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 -impl ObjectSubclass for AprilTagDetector { - const NAME: &'static str = "AprilTagDetector"; - type ParentType = gst::Element; - type Instance = gst::subclass::ElementInstanceStruct; - type Class = subclass::simple::ClassStruct; - - // This macro provides some boilerplate. - glib_object_subclass!(); - - // 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. - fn class_init(klass: &mut subclass::simple::ClassStruct) { - // 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. - klass.set_metadata( - "AprilTagDetector", - "Filter/Analyzer/Video", - "Detects and localizes April Tags in video", - "Andrew Straw ", - ); - - // 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. - - // sink - take in gray8 frames - - let caps = gst::Caps::new_simple( - "video/x-raw", - &[ - ("format", &gst_video::VideoFormat::Gray8.to_str()), - ("width", &gst::IntRange::::new(0, i32::MAX)), - ("height", &gst::IntRange::::new(0, i32::MAX)), - ( - "framerate", - &gst::FractionRange::new( - gst::Fraction::new(0, 1), - gst::Fraction::new(i32::MAX, 1), - ), - ), - ], - ); - let sink_pad_template = gst::PadTemplate::new( - "sink", - gst::PadDirection::Sink, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(sink_pad_template); - - // ------ - - let caps = gst::Caps::new_simple(SRC_CAPS, &[]); - let src_pad_template = gst::PadTemplate::new( - "src", - gst::PadDirection::Src, - gst::PadPresence::Always, - &caps, - ) - .unwrap(); - klass.add_pad_template(src_pad_template); - - // ------ - - // Install all our properties - klass.install_properties(&PROPERTIES); - } - - // Called when a new instance is to be created. We need to return an instance - // of our struct here and also get the class struct passed in case it's needed - fn with_class(klass: &subclass::simple::ClassStruct) -> Self { - // Create our two pads from the templates that were registered with - // the class - let templ = klass.get_pad_template("sink").unwrap(); - let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink")).build(); - let templ = klass.get_pad_template("src").unwrap(); - let srcpad = gst::Pad::builder_with_template(&templ, Some("src")).build(); - - // And then set all our pad functions for handling anything that happens - // on these pads - AprilTagDetector::set_pad_functions(&sinkpad, &srcpad); - - // Return an instance of our struct and also include our debug category here. - // The debug category will be used later whenever we need to put something - // into the debug logs - Self { - settings: Mutex::new(Default::default()), - srcpad, - sinkpad, - state: Mutex::new(None), - } - } -} - -// Implementation of glib::Object virtual methods -impl ObjectImpl for AprilTagDetector { - // This macro provides some boilerplate - glib_object_impl!(); - - // Called whenever a value of a property is changed. It can be called - // at any time from any thread. - fn set_property(&self, obj: &glib::Object, id: usize, value: &glib::Value) { - let prop = &PROPERTIES[id]; - - let element = match obj.downcast_ref::() { - Some(e) => e, - None => { - return; - } - }; - - let mut settings = self.settings.lock().unwrap(); - - match *prop { - subclass::Property("family", ..) => { - settings.family = value.get_some().unwrap(); - } - subclass::Property("maxhamming", ..) => { - settings.maxhamming = value.get_some().unwrap(); - } - subclass::Property("decimate", ..) => { - settings.decimate = value.get_some().unwrap(); - } - subclass::Property("blur", ..) => { - settings.blur = value.get_some().unwrap(); - } - subclass::Property("refine-edges", ..) => { - settings.refine_edges = value.get_some().unwrap(); - } - _ => unimplemented!(), - } - - gst_debug!(CAT, obj: element, "Changing settings to {:?}", settings,); - - let mut state_guard = self.state.lock().unwrap(); - if let Some(state) = state_guard.as_mut() { - state.inner = make_detector(&settings); - } - } - - // Called whenever a value of a property is read. It can be called - // at any time from any thread. - fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { - let prop = &PROPERTIES[id]; - - match *prop { - subclass::Property("family", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.family.to_value()) - } - subclass::Property("maxhamming", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.maxhamming.to_value()) - } - subclass::Property("decimate", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.decimate.to_value()) - } - subclass::Property("blur", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.blur.to_value()) - } - subclass::Property("refine-edges", ..) => { - let settings = self.settings.lock().unwrap(); - Ok(settings.refine_edges.to_value()) - } - _ => unimplemented!(), - } - } - - // Called right after construction of a new instance - fn constructed(&self, obj: &glib::Object) { - // Call the parent class' ::constructed() implementation first - self.parent_constructed(obj); - - // Here we actually add the pads we created in AprilTagDetector::new() to the - // element so that GStreamer is aware of their existence. - let element = obj.downcast_ref::().unwrap(); - element.add_pad(&self.sinkpad).unwrap(); - element.add_pad(&self.srcpad).unwrap(); - } -} - -// Implementation of gst::Element virtual methods -impl ElementImpl for AprilTagDetector { - // Called whenever the state of the element should be changed. This allows for - // starting up the element, allocating/deallocating resources or shutting down - // the element again. - fn change_state( - &self, - element: &gst::Element, - transition: gst::StateChange, - ) -> Result { - gst_trace!(CAT, obj: element, "Changing state {:?}", transition); - - // match transition { - // gst::StateChange::ReadyToPaused | gst::StateChange::PausedToReady => { - // // Reset the whole state - // let mut state = self.state.lock().unwrap(); - // *state = State::default(); - // } - // _ => (), - // } - - if let gst::StateChange::ReadyToNull = transition { - *self.state.lock().unwrap() = None; - } - - // Call the parent class' implementation of ::change_state() - self.parent_change_state(element, transition) - } -} - -// Registers the type for our element, and then registers in GStreamer under -// the name "apriltagdetector" for being able to instantiate it via e.g. -// gst::ElementFactory::make(). -pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - gst::Element::register( - Some(plugin), - "apriltagdetector", - gst::Rank::None, - AprilTagDetector::get_type(), - ) -} diff --git a/gst-plugin-apriltag/src/lib.rs b/gst-plugin-apriltag/src/lib.rs deleted file mode 100644 index 134e7d3c2..000000000 --- a/gst-plugin-apriltag/src/lib.rs +++ /dev/null @@ -1,181 +0,0 @@ -#[macro_use] -extern crate glib; -use glib::prelude::*; -#[macro_use] -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; - -mod apriltagdetector; - -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] -#[repr(u32)] -pub enum TagFamily { - Family36h11 = 0, - FamilyStandard41h12 = 1, - Family16h5 = 2, - Family25h9 = 3, - FamilyCircle21h7 = 4, - FamilyCircle49h12 = 5, - FamilyCustom48h12 = 6, - FamilyStandard52h13 = 7, -} - -impl std::fmt::Display for TagFamily { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - use TagFamily::*; - let fam = match self { - Family36h11 => "36h11".to_string(), - FamilyStandard41h12 => "standard-41h12".to_string(), - Family16h5 => "16h5".to_string(), - Family25h9 => "25h9".to_string(), - FamilyCircle21h7 => "circle-21h7".to_string(), - FamilyCircle49h12 => "circle-49h12".to_string(), - FamilyCustom48h12 => "custom-48h12".to_string(), - FamilyStandard52h13 => "standard-52h13".to_string(), - }; - - write!(f, "{}", fam) - } -} - -impl glib::translate::ToGlib for TagFamily { - type GlibType = i32; - - fn to_glib(&self) -> i32 { - *self as i32 - } -} - -impl glib::translate::FromGlib for TagFamily { - fn from_glib(value: i32) -> Self { - use TagFamily::*; - match value { - 0 => Family36h11, - 1 => FamilyStandard41h12, - 2 => Family16h5, - 3 => Family25h9, - 4 => FamilyCircle21h7, - 5 => FamilyCircle49h12, - 6 => FamilyCustom48h12, - 7 => FamilyStandard52h13, - _ => unreachable!(), - } - } -} - -impl StaticType for TagFamily { - fn static_type() -> glib::Type { - tag_family_get_type() - } -} - -impl<'a> glib::value::FromValueOptional<'a> for TagFamily { - unsafe fn from_value_optional(value: &glib::Value) -> Option { - Some(glib::value::FromValue::from_value(value)) - } -} - -impl<'a> glib::value::FromValue<'a> for TagFamily { - unsafe fn from_value(value: &glib::Value) -> Self { - use glib::translate::ToGlibPtr; - - glib::translate::from_glib(gobject_sys::g_value_get_enum(value.to_glib_none().0)) - } -} - -impl glib::value::SetValue for TagFamily { - unsafe fn set_value(value: &mut glib::Value, this: &Self) { - use glib::translate::{ToGlib, ToGlibPtrMut}; - - gobject_sys::g_value_set_enum(value.to_glib_none_mut().0, this.to_glib()) - } -} - -fn tag_family_get_type() -> glib::Type { - use std::sync::Once; - static ONCE: Once = Once::new(); - static mut TYPE: glib::Type = glib::Type::Invalid; - - ONCE.call_once(|| { - use std::ffi; - use std::ptr; - - static mut VALUES: [gobject_sys::GEnumValue; 9] = [ - gobject_sys::GEnumValue { - value: TagFamily::Family36h11 as i32, - value_name: b"36H11\0" as *const _ as *const _, - value_nick: b"36h11\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: TagFamily::FamilyStandard41h12 as i32, - value_name: b"Standard 41H12\0" as *const _ as *const _, - value_nick: b"standard-41h12\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: TagFamily::Family16h5 as i32, - value_name: b"16H5\0" as *const _ as *const _, - value_nick: b"16h5\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: TagFamily::Family25h9 as i32, - value_name: b"25H9\0" as *const _ as *const _, - value_nick: b"25h9\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: TagFamily::FamilyCircle21h7 as i32, - value_name: b"Circle 21hH7\0" as *const _ as *const _, - value_nick: b"circle-21h7\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: TagFamily::FamilyCircle49h12 as i32, - value_name: b"Circle 49H12\0" as *const _ as *const _, - value_nick: b"circle-49h12\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: TagFamily::FamilyCustom48h12 as i32, - value_name: b"Custom 48H12\0" as *const _ as *const _, - value_nick: b"custom-48h12\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: TagFamily::FamilyStandard52h13 as i32, - value_name: b"Standard 52H13\0" as *const _ as *const _, - value_nick: b"standard-52h13\0" as *const _ as *const _, - }, - gobject_sys::GEnumValue { - value: 0, - value_name: ptr::null(), - value_nick: ptr::null(), - }, - ]; - - let name = ffi::CString::new("GstApriltagTagFamily").unwrap(); - #[allow(static_mut_refs)] - unsafe { - let type_ = gobject_sys::g_enum_register_static(name.as_ptr(), VALUES.as_ptr()); - TYPE = glib::translate::from_glib(type_); - } - }); - - unsafe { TYPE } -} - -gst_plugin_define!( - apriltag, - env!("CARGO_PKG_DESCRIPTION"), - plugin_init, - concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")), - "BSD", - env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_NAME"), - env!("CARGO_PKG_REPOSITORY"), - env!("BUILD_REL_DATE") -); - -fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { - apriltagdetector::register(plugin)?; - Ok(()) -} diff --git a/gst-plugin-apriltag/tests/test.rs b/gst-plugin-apriltag/tests/test.rs deleted file mode 100644 index 0aaa7ec03..000000000 --- a/gst-plugin-apriltag/tests/test.rs +++ /dev/null @@ -1,101 +0,0 @@ -use gstreamer as gst; -use gstreamer::prelude::*; - -const FNAME: &str = "movie-standard41h12.mkv"; -const URL_BASE: &str = "https://strawlab-cdn.com/assets"; -const SHA256SUM: &str = "ddd2932d74139cd6ab5500b40c5f0482d5036df2f766be3a5f28ae2345e23aed"; - -fn init() { - use std::sync::Once; - static INIT: Once = Once::new(); - - INIT.call_once(|| { - gst::init().unwrap(); - gstrsapriltag::plugin_register_static().expect("gstrsapriltag tests"); - }); -} - -#[test] -fn test_create() { - init(); - assert!(gst::ElementFactory::make("apriltagdetector", None).is_ok()); -} - -#[test] -fn test_runs() { - download_verify::download_verify( - format!("{}/{}", URL_BASE, FNAME).as_str(), - FNAME, - &download_verify::Hash::Sha256(SHA256SUM.into()), - ) - .unwrap(); - - init(); - - let pipeline = gst::Pipeline::new(None); - let filesrc = gst::ElementFactory::make("filesrc", None).unwrap(); - filesrc.set_property_from_str("location", FNAME); - - let decodebin = gst::ElementFactory::make("decodebin", None).unwrap(); - - pipeline.add_many(&[&filesrc, &decodebin]).unwrap(); - gst::Element::link_many(&[&filesrc, &decodebin]).unwrap(); - - let pipeline_weak = pipeline.downgrade(); - - // see https://github.com/snapview/gstreamer-rs/blob/master/examples/src/bin/decodebin.rs - - decodebin.connect_pad_added(move |_dbin, src_pad| { - let pipeline = match pipeline_weak.upgrade() { - Some(pipeline) => pipeline, - None => return, - }; - - let videoconvert = gst::ElementFactory::make("videoconvert", None).unwrap(); - - let apriltagdetector = gst::ElementFactory::make("apriltagdetector", None).unwrap(); - apriltagdetector.set_property_from_str("family", "standard-41h12"); - - let filesink = gst::ElementFactory::make("filesink", None).unwrap(); - // TODO: save data to something we then double check for correctness. - - let elements = &[&videoconvert, &apriltagdetector, &filesink]; - pipeline.add_many(elements).unwrap(); - gst::Element::link_many(elements).unwrap(); - - // According to https://github.com/snapview/gstreamer-rs/blob/master/examples/src/bin/decodebin.rs - // This should be done, but it fails for me: - // for e in elements { - // e.sync_state_with_parent().unwrap(); - // } - - let sink_pad = videoconvert - .get_static_pad("sink") - .expect("videoconvert has no sinkpad"); - src_pad.link(&sink_pad).unwrap(); - }); - - let bus = pipeline.get_bus().unwrap(); - - pipeline.set_state(gst::State::Playing).unwrap(); - - for msg in bus.iter_timed(gst::CLOCK_TIME_NONE) { - use gst::MessageView; - - match msg.view() { - MessageView::Eos(..) => break, - MessageView::Error(err) => { - println!( - "Error from {:?}: {} ({:?})", - err.get_src().map(|s| s.get_path_string()), - err.get_error(), - err.get_debug() - ); - break; - } - _ => (), - } - } - - pipeline.set_state(gst::State::Null).unwrap(); -}