We're hiring!

Cross building Rust GStreamer plugins for the Raspberry Pi

Guillaume Desmottes avatar

Guillaume Desmottes
June 23, 2020

Share this post:

In our previous post we discussed about how Rust can be a great language for embedded programming. In this article, we'll explain an easy setup to cross build Rust code depending on system libraries, a common requirement when working on embedded systems.

Original photo by Stefan Cosma on Unsplash

Cross compilation

Embedded systems are generally based on architectures different than the one used on the computers that developers are using to develop their software. A typical embedded device may use an ARM CPU while our laptops and desktops are based on x86-64 platforms. As a result it is important to be able to generate binaries compatible with the embedded platform (the target) directly from the developer system (the host). This requires a cross compiling environment on the host system configured according to the target settings. Such environment is generally the aggregation of two parts:

  • the building tools (compiler, linker, etc) able to run on the host but producing code for the target platform, which is called the toolchain;
  • the set of libraries and headers for the target system which are needed to build the code, which is called the sysroot.

Seting up such environnement can be a tedious process. It's often very tricky to find the right combination of toolchains and sysroots and they are generally duct-taped together using various environment variables and hacky scripts.

Cross compiling Rust code

Fortunately, cross-building pure Rust code is generally much easier thanks to rustup and cargo.

Let's have a look at this example from rust-cross demonstrating how to setup a cross-build environment for ARMv7 on an Ubuntu system with three simple steps:

  • Install the C cross toolchain: sudo apt-get install -qq gcc-arm-linux-gnueabihf
  • Install the cross compiled standard crates: rustup target add armv7-unknown-linux-gnueabihf
  • Configure cargo for cross compilation by creating a ~/.cargo/config file with the following content:
linker = "arm-linux-gnueabihf-gcc"

We can now very easily cross-build our Rust code using:

$ cargo build --target=armv7-unknown-linux-gnueabihf

This works well enough for simple applications relying solely on Rust code. But everything falls apart as soon as one crate wraps a C or C++ system library, such as openssl or GStreamer. Such crates will need to link to the system library when building and so require to have not only the library built for target platform but also extra metadata such as a pkg-config file used by the build system to retrieve the proper compiler and linker flags.


In this article, we'll build the master branch of gst-plugins-rs, the set of GStreamer plugins written in Rust, for the very popular Raspberry Pi board.

The Pi is running a Raspbian 10 based on Debian Buster and we will be building on a Fedora 32 using Rust 1.43.1, the latest stable version.

For convenience we'll target ARMv7 while the Pi is actually running an ARMv6 CPU. This won't be a problem as long as the code is running on a Pi version 2, 3 or 4, as those are able to run ARMv7 code. We'll also install GStreamer from Debian armhf which is using a slightly different instructions set from Raspbian. In our case this won't be a problem as Debian Buster armfh and Raspbian are close enough.

Building with Cross

Cross is a "zero setup" cross compilation tool maintained by the Rust tools team. It's designed to make cross building Rust projects as easy as possible by providing Docker images containing the environment and toolchain needed to build for a specific target.

It's really easy to install:

$ cargo install cross

Cross building a Rust project can now simply be done by using:

$ cross build --target armv7-unknown-linux-gnueabihf

Cross will take care of picking the right image for the target, download it and do all the setup for the project to be transparently built inside the container.

So all we need to build our GStreamer plugins now is a Cross image with the required system dependencies installed. Unfortunately the default images used by Cross for building are based on Ubuntu 16.04 and the GStreamer version shipped with it is too old for our needs. When doing cross compilation is important to ensure that the sysroot used for building uses the same versions of the system libraries than the ones installed on the target. Not doing so may result in linking problem when trying to execute the binaries on the embedded device.

Furthermore we are going to use Debian multiarch support to install the arm versions of our dependencies on the image. Multiarch has been greatly improved in Buster, the latest Debian version, so best to use it in our setup. This will also reduce the risk of incompatibility with the Pi as Raspbian is based on Debian Buster as well.

As a result we'll have to generate our own Buster-based image that Cross will use for cross building our GStreamer plugins.

As said above, Cross images are generated using Docker. As we are running Fedora we are actually going to use podman instead, a drop-in replacement for Docker properly integrated into Fedora. Podman implements the exact same command line interface as Docker so you should be able to reproduce the described steps by simply using the docker command instead of podman.

Let's use Cross's image Dockerfile as a template for our image:

$ git clone https://github.com/rust-embedded/cross.git
$ cd cross
$ cp docker/Dockerfile.armv7-unknown-linux-gnueabihf docker/Dockerfile.buster-gst-armv7-unknown-linux-gnueabihf

The file looks like this:

FROM ubuntu:16.04

COPY common.sh /
RUN /common.sh

COPY cmake.sh /
RUN /cmake.sh

COPY xargo.sh /
RUN /xargo.sh

RUN apt-get install --assume-yes --no-install-recommends \
    g++-arm-linux-gnueabihf \

COPY qemu.sh /
RUN /qemu.sh arm softmmu

COPY dropbear.sh /
RUN /dropbear.sh

COPY linux-image.sh /
RUN /linux-image.sh armv7

COPY linux-runner /

    CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc \
    CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++ \
    QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf \

First we want our image to be based on buster instead of Ubuntu 16.04 so we need to change the first line defining the base image:

FROM debian:buster

QEMU can be used by CI systems to cross run tests. We actually don't need it on modern hosts so we removed it as well as the dropbear and linux-runner integration.

In order to be able to build the gstreamer-rs crate, we enable Debian multiarch support and install the armhf version of the GStreamer packages. We also need openssl as it is another build dependency of the Rust plugins.

RUN dpkg --add-architecture armhf && \
    apt-get update && \
    apt-get install -y libgstreamer1.0-dev:armhf libgstreamer-plugins-base1.0-dev:armhf libssl-dev:armhf

Finally we need to tweak PKG_CONFIG_PATH so the build system will pick-up the right lib when building:


Here is the final version of our Dockerfile, you can also find it in this branch:

FROM debian:buster

COPY common.sh /
RUN /common.sh

COPY cmake.sh /
RUN /cmake.sh

COPY xargo.sh /
RUN /xargo.sh

RUN apt-get install --assume-yes --no-install-recommends \
    g++-arm-linux-gnueabihf \

RUN dpkg --add-architecture armhf && \
    apt-get update && \
    apt-get install -y libgstreamer1.0-dev:armhf libgstreamer-plugins-base1.0-dev:armhf libssl-dev:armhf

    CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc \
    CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++ \
    QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf \

We can now generate the image, we'll label it my/buster-gst-armv7-unknown-linux-gnueabihf:

Cross building gst-plugins-rs

Now that our image is ready we can build gst-plugins-rs. We start by retrieving it from git:

$ git clone https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
$ cd gst-plugins-rs

We have to edit Cargo.toml in order to disable some plugins:

We also need to install Cross from git master as one of my patch has not been released yet.

$ cargo install --git https://github.com/rust-embedded/cross.git

Finally we need to create a Cross.toml file at the root of the project so cross will use our image when building instead of the default one:

image = "localhost/my/buster-gst-armv7-unknown-linux-gnueabihf"

We can now cross-compile gst-plugins-rs:

$ cross build --target armv7-unknown-linux-gnueabihf

Once the build is done, all the .so files for our plugins have been generated and can be safely copied to the Pi.

$ scp target/armv7-unknown-linux-gnueabihf/debug/libgst*.so pi:gst-rs

We can now ssh to the Pi and try one of the plugins, for example using:

$ GST_PLUGIN_PATH=gst-rs gst-inspect-1.0 rsflvdemux


Thanks to Cross it's now easier than ever to setup cross compiling toolchains. Docker/Podman images and Debian multiarch allow us to cross build projects relying on system libraries in a well containerized setup.

One minor downside is the amount of boilerplate required to build custom Cross images. Some improvements are being discussed to simplify this process, including providing updated images. Building an image would then be really easy as we would just have to derive one of those images and install the extra system libraries we need.

The steps we followed in this tutorial should be easily adaptable for any other target supported by Cross, just use the corresponding Dockerfile as template for your image. And of course you can use the same tools to cross build any project relying on other system libraries than GStreamer, just adapt the apt-get install command in the Dockerfile with your actual dependencies.

The combined convenience of Cross with the power and safety of Rust make them both great tools for embedded development. By saving developers from fighting toolchains setups or tracking memory corruption bugs, they can focus on writing robust and maintainable code. This is also what we do best at Collabora so please don't hesitate to contact us if you need any help with your Rust or embedded projects.

Comments (0)

Add a Comment

Allowed tags: <b><i><br>Add a new comment:

Search the newsroom

Latest Blog Posts

Using syzkaller, part 4: Driver fuzzing


Syzkaller is much needed tool for Linux kernel testing and debugging. With some work, it can also be enhanced to find bugs in specific drivers,…

Cross building Rust GStreamer plugins for the Raspberry Pi


Previously, we discussed about how Rust can be a great language for embedded programming. In this article, we'll explain an easy setup to…

Generating MPEG-DASH streams for Open Source adaptive streaming with GStreamer


Adaptive streaming is a technique to provide flexibility and scalability by offering variable bit-rate streams to the client. Here's a quick…

Bifrost meets GNOME: Onward & upward to zero graphics blobs


With only free software, a Mali G31 chip can now run Wayland compositors with zero-copy graphics, including GNOME 3. We can run every scene…

Using regmaps to make Linux drivers more generic


Device drivers can support more revisions and SoC platforms by abstracting away specific hardware interface layouts. Let's examine a specific…

Cross-compiling with gst-build and GStreamer


gst-build is one of the main build systems used by the community to develop the GStreamer platform. In my last blog post, I presented gst-build…

Open Since 2005 logo

We use cookies on this website to ensure that you get the best experience. By continuing to use this website you are consenting to the use of these cookies. To find out more please follow this link.

Collabora Ltd © 2005-2020. All rights reserved. Privacy Notice. Sitemap.