Backend-agnostic async library for streaming audio and video in Rust.
Defines a clean trait layer over media backends so application code stays independent of any particular codec or hardware vendor. GStreamer is the first implementation; FFmpeg and V4L2/VAAPI are planned.
| Feature | Description | Status |
|---|---|---|
gstreamer |
GStreamer backend (capture, encode, decode, pipeline) | ✅ Available |
ffmpeg |
FFmpeg backend | 🗓 Planned |
vaapi |
V4L2 / VAAPI hardware acceleration | 🗓 Planned |
The gstreamer feature is enabled by default.
The gstreamer feature requires GStreamer development libraries:
# Debian / Ubuntu
sudo apt install \
libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-libav \
gstreamer1.0-plugins-ugly
# macOS
brew install gstreamer gst-plugins-base gst-plugins-goodTo build without any system dependencies:
cargo build --no-default-features[dependencies]
streamer-rs = "0.1"
# Without GStreamer (core traits only):
streamer-rs = { version = "0.1", default-features = false }use streamer_rs::{GstVideoSource, VideoSource};
#[tokio::main]
async fn main() -> streamer_rs::Result<()> {
let mut source = GstVideoSource::from_rtsp("rtsp://localhost:8554/live")?;
source.start().await?;
while let Some(frame) = source.next_frame().await? {
let bytes = frame.data.to_bytes().await?;
println!("frame {}x{} — {} bytes", frame.width, frame.height, bytes.len());
}
source.stop().await
}use streamer::{GstVideoSource, VideoSource};
#[tokio::main]
async fn main() -> streamer::Result<()> {
let mut source = GstVideoSource::new("/dev/video0")?;
source.start().await?;
while let Some(frame) = source.next_frame().await? {
// frame.data is a GstFrameData — no CPU copy until you call:
let bytes = frame.data.to_bytes().await?;
println!("frame {}x{} — {} bytes", frame.width, frame.height, bytes.len());
}
source.stop().await
}use streamer::{
Codec, EncoderConfig, Framerate, GstVideoEncoder, GstVideoSource, Resolution,
VideoEncoder, VideoSource,
};
#[tokio::main]
async fn main() -> streamer::Result<()> {
let mut source = GstVideoSource::new("/dev/video0")?;
let mut encoder = GstVideoEncoder::new(EncoderConfig {
codec: Codec::H264,
resolution: Resolution::new(1920, 1080),
framerate: Framerate::new(30, 1),
bitrate_kbps: 4000,
})?;
source.start().await?;
encoder.start().await?;
while let Some(frame) = source.next_frame().await? {
// GstFrameData flows into GstVideoEncoder with no CPU copy
let packets = encoder.encode(frame).await?;
for pkt in packets {
println!("keyframe={} size={}", pkt.is_keyframe, pkt.data.len());
}
}
let remaining = encoder.flush().await?;
encoder.stop().await?;
source.stop().await
}use streamer::{GstPipeline, Pipeline};
#[tokio::main]
async fn main() -> streamer::Result<()> {
let mut pipeline = GstPipeline::build(
"videotestsrc ! videoconvert ! autovideosink"
)?;
pipeline.play().await?;
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
pipeline.stop().await
}For pipelines that don't go through GStreamer, use BytesFrameData:
use bytes::Bytes;
use streamer::{BytesFrameData, FrameData, VideoFrame, PixelFormat};
let data = BytesFrameData(Bytes::from(vec![0u8; 1920 * 1080 * 3 / 2]));
let frame = VideoFrame {
width: 1920,
height: 1080,
format: PixelFormat::I420,
pts: std::time::Duration::ZERO,
dts: None,
data,
};
let bytes = frame.data.to_bytes().await?;VideoFrame<D: FrameData> is generic over its pixel data. The FrameData trait has a single required method:
async fn to_bytes(&self) -> Result<Bytes>;GStreamer decodes directly on the GPU when hardware acceleration is enabled. GstFrameData holds the native gst::Buffer without copying it to CPU memory until to_bytes() is called. When a GstFrameData frame is passed to GstVideoEncoder or GstVideoSink, the buffer is forwarded to GStreamer's appsrc natively — no roundtrip through CPU memory.
| Type | Where data lives | Copy on to_bytes() |
|---|---|---|
BytesFrameData |
CPU (Bytes) |
None (clone is cheap) |
GstFrameData |
Where GStreamer left it (GPU / CPU) | Only if not already mapped |
- Add a feature flag in
Cargo.toml - Create
src/backends/<name>/and implement the relevant traits:VideoSource— produces framesVideoSink— consumes framesVideoEncoder— raw frames → compressed packetsVideoDecoder— compressed packets → raw framesVideoTransform— frame-to-frame transformPipeline— full pipeline lifecycle
- Define your
FrameDatatype (or reuseBytesFrameData) - Gate the module in
src/backends/mod.rswith#[cfg(feature = "<name>")] - Re-export from
src/lib.rsunder the same gate
No changes to core traits are required.
Copyright © SpinorML Ltd. Licensed under the GNU Affero General Public License v3.0.