grid.in.th

Cross-Compile Packages for Raspbian from x86_64

by Sirn

I have been building a DVB streaming server with Raspberry Pi 4 in the past couple of weeks.1 The setup turns out to work pretty well as Raspberry Pi 4 can render 720p in realtime with hardware encoding. Streaming MPEG2TS is also possible with Ethernet. However, FFmpeg version shipped with Raspbian do not retain a metadata required for maintaining aspect ratio when using OpenMAX hardware encoder/decoder (h264_omx). This can be fixed by patching FFmpeg and recompile from source.

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

Setting up Pbuilder on Debian

pbuilder is an automatic Debian package builder, which build packages inside a clean-room environment based on debootstrap. To get started, install pbuilder and its dependencies:

$ 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

pbuilder is using .pbuilderrc to bootstrap the environment and require root to run in its default operation mode. Create /root/.pbuilderrc with the following content. This is taken from Stein Magnus Jodal's article (see link above), but with changes such as PBUILDERSATISFYDEPENDSCMD 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 does not include some Raspberry Pi's override packages. We must enter the chroot environment to add them:

$ 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 added:

# 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. get expects dsc file as an input which 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

Make sure the patch is 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 for every 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 start building the package. Stay in the source directory (same as debian/ directory) and 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/.

Creating Local Repo & Installation

Copy the files in /var/cache/pbuilder/raspbian-buster-armhf/result to Raspberry Pi 4 (for example, using rsync) and 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!

Changes

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. Although I prefer Void Linux or Alpine Linux, h264_omx is only available on 32-bit armhf Raspbian. Using h264_omx is crucial in getting a realtime encoding on Raspberry Pi 4.