Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 110 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,111 @@ cargo build --release --no-default-features --features eim
cargo cache -a
```

#### Building Multiple Plugin Variants

The plugin supports building multiple variants that can coexist in the same GStreamer installation. This is useful when you need to run different models or configurations in the same pipeline.

**Why PLUGIN_VARIANT?**

GStreamer identifies plugins by three key attributes:
1. **Library filename**: The shared library file that contains the plugin
2. **Plugin name**: The internal plugin identifier registered with GStreamer
3. **Element names**: The names of individual elements (e.g., `edgeimpulsevideoinfer`)

To allow multiple plugin builds to coexist, each variant must have unique values for all three. The `PLUGIN_VARIANT` environment variable automatically handles this by:

- **Library naming**: After building, use the `rename-library.sh` script to rename the output library from `libgstedgeimpulse.{dylib,so,dll}` to `libgstedgeimpulse_{variant}.{dylib,so,dll}`
- **Plugin naming**: The plugin name becomes `gst-plugins-edgeimpulse_{variant}` instead of just `gst-plugins-edgeimpulse`
- **Element naming**: All elements are automatically suffixed with `_{variant}` (e.g., `edgeimpulsevideoinfer_variantX`, `edgeimpulseaudioinfer_variantX`, etc.)

**Usage:**

1. **Build with a variant:**
```bash
# Build variant "variantX"
PLUGIN_VARIANT=variantX cargo build --release

# After build completes, rename the library
PLUGIN_VARIANT=variantX ./rename-library.sh
```

2. **Build multiple variants:**
```bash
# Build first variant
PLUGIN_VARIANT=variantX \
EI_MODEL=~/Downloads/model-a \
EI_ENGINE=tflite \
USE_FULL_TFLITE=1 \
cargo build --release
PLUGIN_VARIANT=variantX ./rename-library.sh

# Build second variant (with different model or configuration)
PLUGIN_VARIANT=variantY \
EI_MODEL=~/Downloads/model-b \
EI_ENGINE=tflite \
USE_FULL_TFLITE=1 \
cargo build --release
PLUGIN_VARIANT=variantY ./rename-library.sh
```

3. **Use both variants in the same pipeline:**
```bash
# Make sure both libraries are in GST_PLUGIN_PATH
export GST_PLUGIN_PATH="$(pwd)/target/release"

# Use elements from both variants
gst-launch-1.0 \
videotestsrc ! \
edgeimpulsevideoinfer_variantX ! \
edgeimpulseoverlay_variantX ! \
queue ! \
edgeimpulsevideoinfer_variantY ! \
edgeimpulseoverlay_variantY ! \
autovideosink
```

**Technical Details:**

- The `PLUGIN_VARIANT` environment variable must be set during both the build and rename steps
- The `rename-library.sh` script renames the output library from `libgstedgeimpulse.{dylib,so,dll}` to `libgstedgeimpulse_{variant}.{dylib,so,dll}`
- Each variant produces a uniquely named library file, allowing GStreamer to load multiple variants simultaneously
- Element names include the variant suffix, preventing naming conflicts when multiple variants are loaded

**Example Workflow:**

```bash
# Build variant for model A
PLUGIN_VARIANT=person-detection \
EI_MODEL=~/Downloads/person-detection-v140 \
EI_ENGINE=tflite \
USE_FULL_TFLITE=1 \
cargo build --release
PLUGIN_VARIANT=person-detection ./rename-library.sh

# Build variant for model B
PLUGIN_VARIANT=anomaly-detection \
EI_MODEL=~/Downloads/anomaly-detection-v50 \
EI_ENGINE=tflite \
USE_FULL_TFLITE=1 \
cargo build --release
PLUGIN_VARIANT=anomaly-detection ./rename-library.sh

# Both libraries will be in target/release:
# - libgstedgeimpulse_person-detection.dylib
# - libgstedgeimpulse_anomaly-detection.dylib

# Use both in a pipeline
export GST_PLUGIN_PATH="$(pwd)/target/release"
gst-launch-1.0 \
videotestsrc ! \
edgeimpulsevideoinfer_person-detection ! \
edgeimpulseoverlay_person-detection ! \
queue ! \
edgeimpulsevideoinfer_anomaly-detection ! \
edgeimpulseoverlay_anomaly-detection ! \
autovideosink
```

#### Environment Variables

**Required for FFI Mode:**
Expand All @@ -272,11 +377,11 @@ cargo build --release --no-default-features --features eim

**Platform-Specific Variables:**
- `TARGET`: Standard Rust target triple (e.g., `aarch64-unknown-linux-gnu`, `x86_64-apple-darwin`)
- `TARGET_MAC_ARM64=1`: Build for Apple Silicon (M1/M2/M3) - legacy
- `TARGET_MAC_X86_64=1`: Build for Intel Mac - legacy
- `TARGET_LINUX_X86=1`: Build for Linux x86_64 - legacy
- `TARGET_LINUX_AARCH64=1`: Build for Linux ARM64 - legacy
- `TARGET_LINUX_ARMV7=1`: Build for Linux ARMv7 - legacy
- `TARGET_MAC_ARM64=1`: Build for Apple Silicon (M1/M2/M3)
- `TARGET_MAC_X86_64=1`: Build for Intel Mac
- `TARGET_LINUX_X86=1`: Build for Linux x86_64
- `TARGET_LINUX_AARCH64=1`: Build for Linux ARM64
- `TARGET_LINUX_ARMV7=1`: Build for Linux ARMv7

**Example:**
```bash
Expand Down
114 changes: 112 additions & 2 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::env;

fn main() {
// Set PKG_CONFIG_PATH to include all necessary directories for macOS
if cfg!(target_os = "macos") {
Expand All @@ -8,17 +10,125 @@ fn main() {
format!("{}/share/pkgconfig", homebrew_prefix),
];

let existing_path = std::env::var("PKG_CONFIG_PATH").unwrap_or_default();
let existing_path = env::var("PKG_CONFIG_PATH").unwrap_or_default();
let new_path = if existing_path.is_empty() {
pkg_config_paths.join(":")
} else {
format!("{}:{}", pkg_config_paths.join(":"), existing_path)
};

println!("cargo:warning=Setting PKG_CONFIG_PATH to: {}", new_path);
std::env::set_var("PKG_CONFIG_PATH", &new_path);
env::set_var("PKG_CONFIG_PATH", &new_path);
println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH");
}

// Set PLUGIN_VARIANT for plugin/element name suffixing
let plugin_variant = env::var("PLUGIN_VARIANT").unwrap_or_else(|_| "".to_string());
println!("cargo:rustc-env=PLUGIN_VARIANT={}", plugin_variant);

// Generate the full plugin name
// GStreamer plugin names should use hyphens, not underscores
let pkg_name = env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME should be set by Cargo");
let plugin_name = if plugin_variant.is_empty() {
pkg_name
} else {
format!("{}-{}", pkg_name, plugin_variant)
};
println!("cargo:rustc-env=PLUGIN_NAME={}", plugin_name);

// Generate the plugin identifier for gst::plugin_define!
// GStreamer derives the registration function name from the library filename, not the plugin name
// For libgstedgeimpulse_banana.dylib, it expects gst_plugin_edgeimpulse_banana_register
// So we strip "libgst" and ".dylib" from the library name to get the identifier
let lib_name = if plugin_variant.is_empty() {
"edgeimpulse".to_string()
} else {
// Convert variant to a valid identifier (replace any non-alphanumeric with underscore)
let variant_ident = plugin_variant
.chars()
.map(|c| {
if c.is_alphanumeric() || c == '_' {
c
} else {
'_'
}
})
.collect::<String>();
format!("edgeimpulse_{}", variant_ident)
};
let plugin_identifier = lib_name;

// Generate type names for GObject types to make them unique per variant
// This prevents type registration conflicts when multiple variants are loaded
let type_suffix = if plugin_variant.is_empty() {
"".to_string()
} else {
// Convert variant to a valid identifier for type names (capitalize first letter)
let variant_capitalized = plugin_variant
.chars()
.enumerate()
.map(|(i, c)| {
if i == 0 {
c.to_uppercase().collect::<String>()
} else if c.is_alphanumeric() || c == '_' {
c.to_string()
} else {
"_".to_string()
}
})
.collect::<String>();
variant_capitalized
};

// Generate the plugin definition code with the correct identifier
// This allows the registration function name to match what GStreamer expects
let out_dir = env::var("OUT_DIR").unwrap();
let plugin_def_path = std::path::Path::new(&out_dir).join("plugin_define.rs");
let plugin_def_code = format!(
r#"gst::plugin_define!(
{},
env!("CARGO_PKG_DESCRIPTION"),
plugin_init,
env!("CARGO_PKG_VERSION"),
"MIT/X11",
env!("PLUGIN_NAME"),
env!("PLUGIN_NAME"),
"https://github.com/edgeimpulse/gst-plugins-edgeimpulse",
env!("BUILD_REL_DATE")
);
"#,
plugin_identifier
);
std::fs::write(&plugin_def_path, plugin_def_code)
.expect("Failed to write plugin definition file");

// Generate type name constants for each element type
// This prevents type registration conflicts when multiple variants are loaded
let type_names_path = std::path::Path::new(&out_dir).join("type_names.rs");
let type_names_code = format!(
r#"// Auto-generated type names for variant: {}
#[allow(dead_code)]
pub const VIDEO_INFER_TYPE_NAME: &str = "EdgeImpulseVideoInfer{}";
#[allow(dead_code)]
pub const AUDIO_INFER_TYPE_NAME: &str = "EdgeImpulseAudioInfer{}";
#[allow(dead_code)]
pub const OVERLAY_TYPE_NAME: &str = "EdgeImpulseOverlay{}";
#[allow(dead_code)]
pub const SINK_TYPE_NAME: &str = "GstEdgeImpulseSink{}";
"#,
plugin_variant, type_suffix, type_suffix, type_suffix, type_suffix
);
std::fs::write(&type_names_path, type_names_code).expect("Failed to write type names file");

if !plugin_variant.is_empty() {
println!("cargo:warning=PLUGIN_VARIANT is set to: {}", plugin_variant);
println!("cargo:warning=Plugin name: {}", plugin_name);
println!("cargo:warning=Plugin identifier: {}", plugin_identifier);
println!(
"cargo:warning=After build completes, run: PLUGIN_VARIANT={} ./rename-library.sh",
plugin_variant
);
}

gst_plugin_version_helper::info()
}
51 changes: 51 additions & 0 deletions rename-library.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
# Rename the output library to include PLUGIN_VARIANT suffix
# This allows multiple plugin variants to coexist in the same GStreamer installation
#
# Usage:
# PLUGIN_VARIANT=variantX ./rename-library.sh
# Or after building: cargo build --release && PLUGIN_VARIANT=variantX ./rename-library.sh

set -e

if [ -z "$PLUGIN_VARIANT" ]; then
echo "Error: PLUGIN_VARIANT environment variable is not set"
echo "Usage: PLUGIN_VARIANT=variantX ./rename-library.sh"
exit 1
fi

# Determine the library extension based on the platform
if [[ "$OSTYPE" == "darwin"* ]]; then
LIB_EXT="dylib"
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
LIB_EXT="so"
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
LIB_EXT="dll"
else
LIB_EXT="so" # Default fallback
fi

# Try both debug and release directories
for PROFILE in debug release; do
BUILD_DIR="target/$PROFILE"
ORIGINAL_LIB="libgstedgeimpulse.${LIB_EXT}"
NEW_LIB="libgstedgeimpulse_${PLUGIN_VARIANT}.${LIB_EXT}"
ORIGINAL_D="libgstedgeimpulse.d"
NEW_D="libgstedgeimpulse_${PLUGIN_VARIANT}.d"

if [ -f "$BUILD_DIR/$ORIGINAL_LIB" ]; then
echo "Renaming $ORIGINAL_LIB to $NEW_LIB in $BUILD_DIR"
mv "$BUILD_DIR/$ORIGINAL_LIB" "$BUILD_DIR/$NEW_LIB"

if [ -f "$BUILD_DIR/$ORIGINAL_D" ]; then
mv "$BUILD_DIR/$ORIGINAL_D" "$BUILD_DIR/$NEW_D"
fi
echo "Successfully renamed library to $NEW_LIB"
exit 0
fi
done

echo "Error: Library libgstedgeimpulse.${LIB_EXT} not found in target/debug or target/release"
echo "Please run 'cargo build' or 'cargo build --release' first"
exit 1

13 changes: 11 additions & 2 deletions src/audio/imp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,18 @@ use once_cell::sync::Lazy;
use std::collections::VecDeque;
use std::sync::Mutex;

// Include generated type names for variant-specific builds
include!(concat!(env!("OUT_DIR"), "/type_names.rs"));

static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
let variant = env!("PLUGIN_VARIANT");
let name = if variant.is_empty() {
"edgeimpulseaudioinfer".to_string()
} else {
format!("edgeimpulseaudioinfer_{}", variant)
};
gst::DebugCategory::new(
"edgeimpulseaudioinfer",
&name,
gst::DebugColorFlags::empty(),
Some("Edge Impulse Audio Inference"),
)
Expand Down Expand Up @@ -170,7 +179,7 @@ pub struct EdgeImpulseAudioInfer {

#[glib::object_subclass]
impl ObjectSubclass for EdgeImpulseAudioInfer {
const NAME: &'static str = "EdgeImpulseAudioInfer";
const NAME: &'static str = AUDIO_INFER_TYPE_NAME;
type Type = super::EdgeImpulseAudioInfer;
type ParentType = gstreamer_base::BaseTransform;
}
Expand Down
8 changes: 7 additions & 1 deletion src/audio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ unsafe impl Sync for EdgeImpulseAudioInfer {}

// Register the type for our element
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
let variant = env!("PLUGIN_VARIANT");
let name = if variant.is_empty() {
"edgeimpulseaudioinfer".to_string()
} else {
format!("edgeimpulseaudioinfer_{}", variant)
};
gst::Element::register(
Some(plugin),
"edgeimpulseaudioinfer",
&name,
gst::Rank::NONE,
EdgeImpulseAudioInfer::static_type(),
)
Expand Down
14 changes: 3 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
Ok(())
}

gst::plugin_define!(
edgeimpulse,
env!("CARGO_PKG_DESCRIPTION"),
plugin_init,
concat!(env!("CARGO_PKG_VERSION")),
"MIT/X11",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_NAME"),
"https://github.com/edgeimpulse/gst-plugins-edgeimpulse",
env!("BUILD_REL_DATE")
);
// Include the dynamically generated plugin definition from build.rs
// This ensures the registration function name matches what GStreamer expects
include!(concat!(env!("OUT_DIR"), "/plugin_define.rs"));
Loading