FICUSONLINE F9E
Build Linphone with Yocto on Raspberry Pi (I2S)
I will create a Yocto Project-based image specialized for Linphone, a SIP VoIP application, targeting the Raspberry Pi 2. The setup includes the Raspberry Pi 2 itself, a MEMS microphone and DAC speaker connected via GPIO I²S, and a camera. The network connection will be wired for now, but I will later build an image that supports a USB Wi-Fi dongle as well.
Takanobu FuseAdministrator

3 weeks ago

Linux

Overview

The purpose of this project is to use a Raspberry Pi as a SIP phone or an intercom system entry panel (doorphone) for testing purposes.

With SIP-VoIP functionality, when used as a SIP phone, it can serve as an indoor monitor or a communication tool for family members. When used as a doorphone, it allows remote visitor verification, conversation with visitors, and door lock control.

Using Yocto, which enables the creation of embedded Linux distributions, I will build a custom image tailored for the Raspberry Pi 2. The mobile application used will be Linhome.

Components

Raspberry Pi 2

  • Computer board: Raspberry Pi 2 (with camera, button, door switch, microphone, and speaker)

  • Mobile app: Android Linhome (for door control, visitor verification via video, and communication)

For microphone and speaker connections, the I²S (Inter-IC Sound) interface will be used. The googlevoicehat-soundcard has been selected as the sound device because it supports I²S audio input and output. ( hifiberry-dac was not selected as it does not support I²S audio input.)

Note : The button and door switch will not be physically connected. Instead, GPIO pin status checks and operations will be performed remotely via SSH connection.


Yocto Project

Use Version 5.0.4 Scarthgap

Yocto Overview

The Yocto Project ® 5.0.4 documentation

10 Images — The Yocto Project ® 5.0.4 documentation

11 Features — The Yocto Project ® 5.0.4 documentation

For the build process, a Docker container will be used, incorporating the build tool Poky and the necessary layers (meta-xxx) for the build.

In Yocto, a Recipe is a set of files that define build conditions and procedures, categorized by functionality or target device. A collection of these recipes forms a Layer.


Creating the Docker Image

A Dockerfile will be created to bundle the necessary recipes and build environment for compiling Linphone into a single container.

All work will be done within the custom directory scarthgap-raspberrypi2/.

scarthgap-raspberrypi2/dockerfile

FROM debian:11

ARG YOCTO_VERSION=scarthgap
ARG BITBAKE_TARGET=core-image-sato

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get -y upgrade

# Required Packages for the Host Development System
# https://docs.yoctoproject.org/ref-manual/system-requirements.html#required-packages-for-the-build-host
RUN apt-get update && \ 
    apt-get install -y gawk wget git diffstat unzip texinfo gcc build-essential chrpath socat \
    cpio python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping python3-git \
    python3-jinja2 libegl1-mesa libsdl1.2-dev pylint3 xterm python3-subunit mesa-common-dev \
    zstd liblz4-tool screen nano  && \
    apt-get clean

# Additional host packages needed for build
RUN apt-get update && \
    apt-get install -y file && \
    apt-get clean
    
# Additional host packages needed for qemu
RUN apt-get update && \
    apt-get install -y iproute2 && \
    apt-get clean
    
# Additional host packages required by poky/scripts/wic
RUN apt-get update && \
    apt-get install -y curl dosfstools mtools parted syslinux tree zip && \
    apt-get clean

# Create a non-root user that will perform the actual build
RUN id build 2>/dev/null || useradd --uid 30000 --create-home build
RUN apt-get install -y sudo
RUN echo "build ALL=(ALL) NOPASSWD: ALL" | tee -a /etc/sudoers

# Fix error "Please use a locale setting which supports utf-8."
# See https://wiki.yoctoproject.org/wiki/TipsAndTricks/ResolvingLocaleIssues
RUN apt-get install -y locales
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
        echo 'LANG="en_US.UTF-8"'>/etc/default/locale && \
        dpkg-reconfigure --frontend=noninteractive locales && \
        update-locale LANG=en_US.UTF-8

ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US.UTF-8

USER build
WORKDIR /home/build

# Clone yocto
RUN git clone -b "${YOCTO_VERSION}" git://git.yoctoproject.org/poky
WORKDIR poky
RUN git clone -b "${YOCTO_VERSION}" git://git.openembedded.org/meta-openembedded
RUN git clone -b "${YOCTO_VERSION}" git://git.yoctoproject.org/meta-raspberrypi

WORKDIR /home/build/poky

#RUN bash -c 'source oe-init-build-env && bitbake ${BITBAKE_TARGET}'
CMD ["bash"]

Create the image using the Dockerfile:

$ docker build scarthgap-raspberrypi2 -t raspberrypi2

Then, start a container named raspi2 from the above image, specifying build as the shared folder between the host and the container, and modify the access permissions of the build directory:

$ docker run --name raspi2 -v ./build:/home/build/poky/build -ti raspberrypi2 bash
# sudo chown -R build:build build

Download the meta-bc Yocto branch for linphone-sdk into /home/build/poky:

# git clone https://gitlab.linphone.org/BC/public/meta-bc.git -b feature/yocto-kirkstone

Then, modify meta-bc/conf/layer.conf and set:

meta-bc/conf/layer.conf

LAYERSERIES_COMPAT_bc = "scarthgap"

Execute the initial command for build environment:

# source oe-init-build-env 
You had no conf/local.conf file. This configuration file has therefore been
created for you from /home/build/poky/meta-poky/conf/templates/default/local.conf.sample
You may wish to edit it to, for example, select a different MACHINE (target
hardware).

You had no conf/bblayers.conf file. This configuration file has therefore been
created for you from /home/build/poky/meta-poky/conf/templates/default/bblayers.conf.sample
To add additional metadata layers into your configuration please add entries
to conf/bblayers.conf.

The Yocto Project has extensive documentation about OE including a reference
manual which can be found at:
    https://docs.yoctoproject.org

For more information about OpenEmbedded see the website:
    https://www.openembedded.org/

This is the default build configuration for the Poky reference distribution.

### Shell environment set up for builds. ###

You can now run 'bitbake <target>'

Common targets are:
    core-image-minimal
    core-image-full-cmdline
    core-image-sato
    core-image-weston
    meta-toolchain
    meta-ide-support

You can also run generated qemu images with a command like 'runqemu qemux86-64'.

Other commonly useful commands are:
 - 'devtool' and 'recipetool' handle common recipe tasks
 - 'bitbake-layers' handles common layer tasks
 - 'oe-pkgdata-util' handles common target package tasks

Note : Running # source oe-init-build-env will automatically change the working directory to /home/build/poky/build and generate configuration files in the build/conf directory.


Editing Build Configuration Files

Layer List(Scarthgap Ver5.0)

Layer “meta-raspberrypi” Recipe (Machine) List(Scarthgap Ver5.0)

Edit the bblayers.conf, local.conf files in the build/conf directory shared with the host.

bblayers.conf : Specify the necessary meta-layers for the image build.

Add the following entries to include the required layers:

build/conf/bblayers.conf

# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
POKY_BBLAYERS_CONF_VERSION = "2"

BSPDIR := "${@os.path.abspath(os.path.dirname(d.getVar('FILE', True)) + '/../..')}"

BBPATH = "${TOPDIR}"
BBFILES ?= ""

BBLAYERS ?= " \
  ${BSPDIR}/meta \
  ${BSPDIR}/meta-poky \
  ${BSPDIR}/meta-openembedded/meta-oe \
  ${BSPDIR}/meta-openembedded/meta-networking \
  ${BSPDIR}/meta-openembedded/meta-python \
  ${BSPDIR}/meta-openembedded/meta-multimedia \
  ${BSPDIR}/meta-yocto-bsp \
  ${BSPDIR}/meta-raspberrypi \
  ${BSPDIR}/meta-bc \
  "

local.conf : Configure additional recipes, target machine, and build conditions for the image.

Additional Recipes:

IMAGE_INSTALL:append = …

Here, options to build the linphone-sdk from meta-bc will also be added. See below.

Linphone with Yocto

Target machine

MACHINE = “raspberrypi2”

Video extension feature

VIDEO_CAMERA = “1”

If other features are required, please refer to the meta-raspberrypi documentation and enable them.

Optional build configuration

build/conf/local.conf


MACHINE = "raspberrypi2"

VIDEO_CAMERA = "1"

DISTRO ?= "poky"

PACKAGE_CLASSES ?= "package_ipk"

EXTRA_IMAGE_FEATURES ?= "debug-tweaks ssh-server-openssh"

USER_CLASSES ?= "buildstats"

PATCHRESOLVE = "noop"

BB_DISKMON_DIRS ??= "\
    STOPTASKS,${TMPDIR},1G,100K \
    STOPTASKS,${DL_DIR},1G,100K \
    STOPTASKS,${SSTATE_DIR},1G,100K \
    STOPTASKS,/tmp,100M,100K \
    HALT,${TMPDIR},100M,1K \
    HALT,${DL_DIR},100M,1K \
    HALT,${SSTATE_DIR},100M,1K \
    HALT,/tmp,10M,1K"

CONF_VERSION = "2"


# Additional configuration, added recipes of Branch: kirkstone https://layers.openembedded.org/layerindex/branch/kirkstone/recipes
IMAGE_INSTALL:append = " alsa-plugins alsa-utils alsa-lib boost gettext glew libopus libsrtp libvpx pulseaudio v4l-utils raspi-gpio linphone-sdk openssl mpg123"

LICENSE_FLAGS_ACCEPTED = "commercial"
PACKAGE_CLASSES ?= "package_ipk"
PACKAGECONFIG:append:pn-linphone-sdk = " h264 mdns"
PACKAGECONFIG:append:pn-avahi = " libdns_sd"

BB_NUMBER_THREADS ?= "8"
CORE_IMAGE_EXTRA_INSTALL += "dhcpcd"
CORE_IMAGE_EXTRA_INSTALL += "init-ifupdown"

INHERIT += "rm_work"

Image Build: Bitbake

Run the bitbake command to build linphone (-sdk) (this may take several hours depending on the host machine’s specifications). Even if this standalone build is skipped, linphone-sdk will be included during the final image build, as it is specified in IMAGE_INSTALL:append in local.conf. Performing this standalone build in advance can minimize recovery costs in case of errors.

BitBake User Manual

# bitbake linphone-sdk

Build the final image by specifying core-image-sato:

# bitbake core-image-sato

The build image will be output to the following folder. Extract the compressed file below.

build/tmp/deploy/images/raspberrypi2/core-image-sato-raspberrypi2-2024xxxxxxxxxx.rootfs.wic.bz2

$ bzip2 -d core-image-sato-raspberrypi2-2024xxxxxxxxxx.rootfs.wic.bz2

Write image to SD

$ sudo dd if=core-image-sato-raspberrypi2-2024xxxxxxxxxx.rootfs.wic of=/dev/sda bs=1024

Since the image file size is between 1-2GB, to make efficient use of the SD card’s capacity, please expand the root directory to the maximum capacity of the SD card using a disk utility.


Creating the googlevoicehat Overlay

The googlevoicehat-soundcard.dtbo is a device tree overlay used to enable the microphone and DAC via the GPIO I2S interface. However, it is not compiled by default, so it must be manually compiled from the device tree source file.

Manually compile the overlay file from the googlevoicehat dts file (run this inside the raspi2 container).

# find . -name "*.dts" | grep googlevoicehat
./build/tmp/work-shared/raspberrypi2/kernel-source/arch/arm/boot/dts/overlays/googlevoicehat-soundcard-overlay.dts
# sudo apt install device-tree-compiler
# dtc -@ -I dts -O dtb -o googlevoicehat-soundcard.dtbo build/tmp/work-shared/raspberrypi2/kernel-source/arch/arm/boot/dts/overlays/googlevoicehat-soundcard-overlay.dts

Note : dtc (Device Tree Compiler) is a utility available on many Linux systems that compiles device tree source (DTS) files into binary device tree blob (DTB/DTBO) files.

After compiling, copy the generated device tree overlay googlevoicehat-soundcard-overlay.dtbo to the SD card’s /boot/overlays directory.


Boot Conditions via config.txt

Additional boot conditions can be set in /boot/config.txt.

https://www.raspberrypi.com/documentation/computers/config_txt.html

Specify Video Memory:

gpu_mem=128

Add Overlays:

dtoverlay=googlevoicehat-soundcard


Boot and Functionality Check (Video and Sound Output)

Insert the SD card with the image written to it into the Raspberry Pi and boot it up.

Video and Camera Functionality

Verify the camera connection:

# v4l2-ctl --all
Driver Info:
	Driver name      : bcm2835 mmal
	Card type        : mmal service 16.1
	Bus info         : platform:bcm2835_v4l2-0
	Driver version   : 6.6.22
	Capabilities     : 0x85200005
		Video Capture
		Video Overlay
		Read/Write
		Streaming
		Extended Pix Format
		Device Capabilities
.......
.......

Test One Shot:

# v4l2-ctl --stream-mmap --stream-count=1 --stream-to=capture.jpeg

Confirm Video Format:

# v4l2-ctl --list-formats
ioctl: VIDIOC_ENUM_FMT
	Type: Video Capture

	[0]: 'YU12' (Planar YUV 4:2:0)
	[1]: 'YUYV' (YUYV 4:2:2)
	[2]: 'RGB3' (24-bit RGB 8-8-8)
	[3]: 'JPEG' (JFIF JPEG, compressed)
	[4]: 'H264' (H.264, compressed)
	[5]: 'MJPG' (Motion-JPEG, compressed)
	[6]: 'YVYU' (YVYU 4:2:2)
	[7]: 'VYUY' (VYUY 4:2:2)
	[8]: 'UYVY' (UYVY 4:2:2)
	[9]: 'NV12' (Y/UV 4:2:0)
	[10]: 'BGR3' (24-bit BGR 8-8-8)
	[11]: 'YV12' (Planar YVU 4:2:0)
	[12]: 'NV21' (Y/VU 4:2:0)
	[13]: 'RX24' (32-bit XBGR 8-8-8-8)

Streaming Test:

# v4l2-ctl --stream-mmap --stream-count=10
<<<<<<< 5.55 fps
<<<

Sound-related

Check the Playback and Capture devices:

# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: vc4hdmi [vc4-hdmi], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: sndrpigooglevoi [snd_rpi_googlevoicehat_soundcar], device 0: Google voiceHAT SoundCard HiFi voicehat-hifi-0 [Google voiceHAT SoundCard HiFi voicehat-hifi-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

# arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: sndrpigooglevoi [snd_rpi_googlevoicehat_soundcar], device 0: Google voiceHAT SoundCard HiFi voicehat-hifi-0 [Google voiceHAT SoundCard HiFi voicehat-hifi-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

Based on the above output, create the ALSA configuration file (.asoundrc) with dmixer support here.

.asoundrc

pcm.!default {
    type asym
    playback.pcm "plug:softvol"
    capture.pcm "plug:capture_level"
}

ctl.!default {
    type hw
    card 1
}

pcm.voicehat_playback {
    type hw
    card 1
    device 0
    format S32_LE
}

pcm.voicehat_capture {
    type hw
    card 1
    device 0
    format S32_LE
}

pcm.softvol {
    type softvol
    slave.pcm "dmixed_playback"
    control.name "Softvol Playback Volume"
    control.card 1
    min_dB -3.0
    max_dB 30.0
}

pcm.dmixed_playback {
    type dmix
    ipc_key 1024
    slave {
        pcm "voicehat_playback"
        period_time 0
        period_size 1024
        buffer_size 8192
        rate 48000
        format S32_LE
    }
    bindings {
        0 0
        1 1
    }
}

pcm.capture_level {
    type softvol
    slave.pcm "voicehat_capture"
    control.name "Level Capture"
    control.card 1
    min_dB -3.0
    max_dB 30.0
}

Test Speaker:

# speaker-test -D plughw:1,0 -c2
# speaker-test -D plughw:1,0 -c2 --test=wav -w /usr/share/sounds/alsa/Front_Center.wav

Note : plughw:1,0 means card 1, device 0. In ~/.asound.conf : specify card 1

Test Record and Play

# arecord -d 5 -f cd -V mono test.wav
# aplay test.wav

Adjust volume and mic sensitive by alsamixer:

# alsamixer

Alsamixer


Starting the Linphone Daemon

Make sure to create the necessary database files (directories) beforehand during the initial startup.

# cd /opt/belledonne-communications/bin/
# mkdir -p /home/root/.local/share/linphone
# touch /home/root/.local/share/linphone/x3dh.c25519.sqlite3
# touch /home/root/.local/share/linphone/linphone.db

Run Command:

# /opt/belledonne-communications/bin/linphone-daemon
daemon-linphone>

Command Options:

# /opt/belledonne-communications/bin/linphone-daemon --help
daemon-linphone [<options>]
Licence: Commercial
where options are :
	--help                     Print this notice.
	--dump-commands-help       Dump the help of every available commands.
	--dump-commands-html-help  Dump the help of every available commands.
	--pipe <pipepath>          Create an unix server socket in the specified path to receive commands from. For Windows just use a name instead of a path.
	--log <path>               Supply a file where the log will be saved.
	--factory-config <path>    Supply a readonly linphonerc style config file to start with.
	--config <path>            Supply a linphonerc style config file to start with.
	--disable-stats-events     Do not automatically raise RTP statistics events.
	--enable-lsd               Use the linphone sound daemon.
	-C                         Enable video capture.
	-D                         Enable video display.
	--auto-answer              Automatically answer incoming calls.

Start the Linphone daemon with video capture, auto-answer enabled, and logging options activated.

# linphone-daemon --config linphonerc -C --auto-answer --log ./linphone_log

Linhone Daemon Help:

daemon-linphone>help
Status: Ok

adaptive-jitter-compensation [audio|video] [enable|disable]
answer <call_id>
audio-codec-disable <payload_type_number>|<mime_type>|ALL
audio-codec-enable <payload_type_number>|<mime_type>|ALL
audio-codec-get <payload_type_number>|<mime_type>
audio-codec-move <payload_type_number>|<mime_type> <index>
......
......

User Registration and Confirmation:

daemon-linphone>register sip:[email protected] sip.linphone.org password
Status: Ok

Id: 1

daemon-linphone>register-status ALL
Status: Ok

Id: 1
State: LinphoneRegistrationOk

Call:

daemon-linphone>call [email protected]

If the caller is within the same domain, the part after the “@” can be omitted.

daemon-linphone>call capitalfuse

Verify the send and receive functionality with the Linhome client.

The settings of Linhome are fixed through provisioning to the Linhome Free SIP Service . To modify the settings, you need to prepare a custom account manager, generate a QR code containing a custom URL, and scan it.

Assistant

QR

Device List

Connection