Gridth

Cross-Compile Packages for Raspbian from x86_64

By Kridsada Thanabulpong

I have been building a DVB streaming server with Raspberry Pi 412 but need to apply a patch to ffmpeg in order to fix aspect ratio when using Raspberry Pi 4's OpenMAX hardware encoder/decoder (h264_omx).

This is my notes on cross-building .deb for Raspbian armhf from Debian on x86_64 machine.3 Instructions are mostly taken from the following blog entries:

Setting up Pbuilder on Debian

pbuilder is an automatic Debian package building, which build packages inside a clean-room environment based on debootstrap. Start by installing pbuilder, its prequisites and keyrings:

$ sudo apt install pbuilder qemu-user-static curl
$ sudo apt install ubuntu-keyring debian-archive-keyring
$ curl -O http://archive.raspbian.org/raspbian/pool/main/r/raspbian-archive-keyring/raspbian-archive-keyring_20120528.2_all.deb
$ dpkg -i raspbian-archive-keyring_20120528.2_all.deb

Create /root/.pbuilderrc with the following content. This is taken from Stein Magnus Jodal's article (see link above), but with PBUILDERSATISFYDEPENDSCMD set so statisfydepends works under armhf:

#!/bin/bash
set -e

if [ "$OS" == "debian" ]; then
    MIRRORSITE="http://ftp.jp.debian.org/debian/"
    COMPONENTS="main contrib non-free"
    DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
        "--keyring=/usr/share/keyrings/debian-archive-keyring.gpg")
    : ${DIST:="buster"}
    : ${ARCH:="amd64"}
elif [ "$OS" == "raspbian" ]; then
    MIRRORSITE="https://archive.raspbian.org/raspbian/"
    COMPONENTS="main contrib non-free rpi"
    DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
        "--keyring=/usr/share/keyrings/raspbian-archive-keyring.gpg")
    PBUILDERSATISFYDEPENDSCMD="/usr/lib/pbuilder/pbuilder-satisfydepends-apt"
    : ${DIST:="buster"}
    : ${ARCH:="armhf"}
elif [ "$OS" == "ubuntu" ]; then
    MIRRORSITE="http://jp.archive.ubuntu.com/ubuntu/"
    COMPONENTS="main restricted universe multiverse"
    DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
        "--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg")
else
    echo "Unknown OS: $OS"
    exit 1
fi

if [ "$DIST" == "" ]; then
    echo "DIST is not set"
    exit 1
fi

if [ "$ARCH" == "" ]; then
    echo "ARCH is not set"
    exit 1
fi

NAME="$OS-$DIST-$ARCH"

if [ "$ARCH" == "armel" ] && [ "$(dpkg --print-architecture)" != "armel" ]; then
    DEBOOTSTRAP="qemu-debootstrap"
fi
if [ "$ARCH" == "armhf" ] && [ "$(dpkg --print-architecture)" != "armhf" ]; then
    DEBOOTSTRAP="qemu-debootstrap"
fi

DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}" "--arch=$ARCH")
BASETGZ="/var/cache/pbuilder/$NAME-base.tgz"
DISTRIBUTION="$DIST"
BUILDRESULT="/var/cache/pbuilder/$NAME/result/"
APTCACHE="/var/cache/pbuilder/$NAME/aptcache/"
BUILDPLACE="/var/cache/pbuilder/build"
HOOKDIR="/var/cache/pbuilder/hook.d/"

Create pbuilder base image for Raspbian:

$ sudo mkdir -p /var/cache/pbuilder/raspbian-buster-armhf/aptcache/
$ sudo OS=raspbian DIST=buster ARCH=armhf pbuilder --create

The repository we specified in pbuilderrc doesn't include some Raspberry Pi's overriden packages, we have to enter the chroot environment and add them manually:

$ sudo OS=raspbian DIST=buster ARCH=armhf pbuilder login --save-after-login

Inside chroot:

# apt update
# apt install curl
# curl http://archive.raspberrypi.org/debian/raspberrypi.gpg.key | apt-key add -
# cat <<EOF > /etc/apt/sources.list.d/raspi.list
deb http://archive.raspberrypi.org/debian/ buster main
EOF
# apt update

Check if repository were successfully added. The result should looks similar to the following:

# apt-cache policy
Package files:
 100 /var/lib/dpkg/status
     release a=now
 500 http://archive.raspberrypi.org/debian buster/main armhf Packages
     release o=Raspberry Pi Foundation,a=testing,n=buster,l=Raspberry Pi Foundation,c=main,b=armhf
     origin archive.raspberrypi.org
 500 http://raspbian.raspberrypi.org/raspbian buster/rpi armhf Packages
     release o=Raspbian,a=stable,n=buster,l=Raspbian,c=rpi,b=armhf
     origin raspbian.raspberrypi.org
 500 http://raspbian.raspberrypi.org/raspbian buster/non-free armhf Packages
     release o=Raspbian,a=stable,n=buster,l=Raspbian,c=non-free,b=armhf
     origin raspbian.raspberrypi.org
 500 http://raspbian.raspberrypi.org/raspbian buster/contrib armhf Packages
     release o=Raspbian,a=stable,n=buster,l=Raspbian,c=contrib,b=armhf
     origin raspbian.raspberrypi.org
 500 http://raspbian.raspberrypi.org/raspbian buster/main armhf Packages
     release o=Raspbian,a=stable,n=buster,l=Raspbian,c=main,b=armhf
     origin raspbian.raspberrypi.org
Pinned packages:

We're done with the base image. Exit chroot with exit.

Fetching Source & Patching

The easiest way to fetch Debian source is to use dget which is part of devscripts. dsc file dget expects can be found in repository archive alongside with .deb files:

$ sudo apt install devscripts
$ mkdir src/ && cd src/
$ dget -u http://archive.raspberrypi.org/debian/pool/main/f/ffmpeg/ffmpeg_4.1.6-1~deb10u1+rpt1.dsc

Now we have source code extracted, we can start applying patches. In Debian it is recommened to use quilt to manage patches:

$ sudo apt install quilt

Setup quilt for project:

$ cd ffmpeg-4.1.6
$ export QUILT_PATCHES=debian/patches

Push patch to latest. It should says something among the line of patch is already latest.

$ quilt push -a

Create a new patch and tell quilt to track the file we want to change (e.g. in this case libavcodec/omx.c). quilt add need to be done before making any changes to source code, once per each file:

$ quilt new fix-omx-aspect-ratio.patch
$ quilt add libavcodec/omx.c

Apply and save patch:

$ curl -sSL https://github.com/gameboym/FFmpeg/commit/7e728faef468cc7330fec1ee259d53147602c8af.patch | patch -n1
$ quilt refresh

Building

Now we have pbuilder setup and source code patched, we can proceed to build the package. Stay in the source directory (where a debian/ directory is present) then run:

$ sudo OS=raspbian DIST=buster ARCH=armhf DEB_BUILD_OPTIONS="parallel=$(nproc)" pdebuild

# Alternatively, without running `make check` afterward:
$ sudo OS=raspbian DIST=buster ARCH=armhf DEB_BUILD_OPTIONS="parallel=$(nproc) nocheck" pdebuild

After a while pdebuild should build a .deb for us in /var/cache/pbuilder/raspbian-buster-armhf/result/, ready to be installed on other machine.

Creating Local Repo & Installation

Copy the files in /var/cache/pbuilder/raspbian-buster-armhf/result to Raspberry Pi 4, e.g. /opt/apt/ then run the following command to create package index (Package.gz):

$ cd /opt/apt
$ sudo sh -c 'dpkg-scanpackages -m . | gzip -c > Packages.gz'

Add the repository to apt sources list:

$ cat <<EOF | sudo tee /etc/apt/sources.list.d/local.list
deb [trusted=yes] file:///opt/apt ./
EOF

Optionally, make local repo highest priority:

$ cat <<EOF | sudo tee /etc/apt/preferences.d/local
Package: *
Pin: origin ""
Pin-Priority: 1001
EOF

Update the repository and install our package:

$ sudo apt update
$ sudo apt install ffmpeg

And that's it!

Footnote

  1. So I can watch TV on my computer/phone rather than in front of a TV; also doubling as a DIY recorder. My setup mostly followed the article in Chinachu's Medium (in Japanese).
  2. Works surprisingly well! Raspberry Pi 4 is capable of rendering 720p in real-time with hardware encoding. Streaming MPEG2TS is also possible, but may require Ethernet.
  3. Although I prefer Void Linux or Alpine Linux, h264_omx support currently only available on 32-bit armhf Raspbian. Using h264_omx is crucial in getting a real-time encoding on Raspberry Pi 4.