Skip to content

Commit

Permalink
rpi: embed libcamera and libfreetype into the server (#2581)
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed Aug 18, 2024
1 parent c5059fa commit 518da7a
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 123 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,20 +471,20 @@ The resulting stream will be available in path `/cam`.
_MediaMTX_ natively supports the Raspberry Pi Camera, enabling high-quality and low-latency video streaming from the camera to any user, for any purpose. There are a couple of requirements:
1. The server must run on a Raspberry Pi, with Raspberry Pi OS Bullseye as operative system. Both 32 bit and 64 bit architectures are supported.
1. The server must run on a Raspberry Pi, with one of the following operating systems:
2. Make sure that the legacy camera stack is disabled. Type `sudo raspi-config`, then go to `Interfacing options`, `enable/disable legacy camera support`, choose `no`. Reboot the system.
* Raspberry Pi OS Bookworm
* Raspberry Pi OS Bullseye
If you want to run the standard (non-Docker) version of the server:
Both 32 bit and 64 bit architectures are supported.
1. Make sure that the following packages are installed:
2. If you are using Raspberry Pi OS Bullseye, make sure that the legacy camera stack is disabled. Type `sudo raspi-config`, then go to `Interfacing options`, `enable/disable legacy camera support`, choose `no`. Reboot the system.
* `libcamera0` (≥ 0.0.5)
* `libfreetype6`
If you want to run the standard (non-Docker) version of the server:
2. download the server executable. If you're using 64-bit version of the operative system, make sure to pick the `arm64` variant.
1. Download the server executable. If you're using 64-bit version of the operative system, make sure to pick the `arm64` variant.
3. edit `mediamtx.yml` and replace everything inside section `paths` with the following content:
2. Edit `mediamtx.yml` and replace everything inside section `paths` with the following content:
```yml
paths:
Expand All @@ -494,7 +494,7 @@ If you want to run the standard (non-Docker) version of the server:
The resulting stream will be available in path `/cam`.
If you want to run the server inside Docker, you need to use the `latest-rpi` image (that already contains required libraries) and launch the container with some additional flags:
If you want to run the server inside Docker, you need to use the `latest-rpi` image and launch the container with some additional flags:
```sh
docker run --rm -it \
Expand All @@ -506,7 +506,7 @@ docker run --rm -it \
bluenviron/mediamtx:latest-rpi
```
Be aware that the Docker image is not compatible with cameras that requires a custom `libcamera` (like some ArduCam products), since it comes with a standard `libcamera` included.
Be aware that the server is not compatible with cameras that requires a custom `libcamera` (like some ArduCam products), since it comes with a bundled `libcamera`. If you want to use a custom one, you can [compile from source](#compile-from-source).
Camera settings can be changed by using the `rpiCamera*` parameters:
Expand Down
123 changes: 20 additions & 103 deletions internal/staticsources/rpicamera/camera.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,109 +4,16 @@
package rpicamera

import (
"debug/elf"
"fmt"
"os"
"os/exec"
"runtime"
"path/filepath"
"strconv"
"strings"
"sync"
"time"

"github.com/bluenviron/mediacommon/pkg/codecs/h264"
)

const (
tempPathPrefix = "/dev/shm/mediamtx-rpicamera-"
)

func startEmbeddedExe(content []byte, env []string) (*exec.Cmd, error) {
tempPath := tempPathPrefix + strconv.FormatInt(time.Now().UnixNano(), 10)

err := os.WriteFile(tempPath, content, 0o755)
if err != nil {
return nil, err
}

cmd := exec.Command(tempPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = env

err = cmd.Start()
os.Remove(tempPath)

if err != nil {
return nil, err
}

return cmd, nil
}

func findLibrary(name string) (string, error) {
byts, err := exec.Command("ldconfig", "-p").Output()
if err == nil {
for _, line := range strings.Split(string(byts), "\n") {
f := strings.Split(line, " => ")
if len(f) == 2 && strings.Contains(f[1], name+".so") {
return f[1], nil
}
}
}

return "", fmt.Errorf("library '%s' not found", name)
}

func check64bit(fpath string) error {
f, err := os.Open(fpath)
if err != nil {
return err
}
defer f.Close()

ef, err := elf.NewFile(f)
if err != nil {
return err
}
defer ef.Close()

if ef.FileHeader.Class == elf.ELFCLASS64 {
return fmt.Errorf("libcamera is 64-bit, you need the 64-bit server version")
}

return nil
}

var (
mutex sync.Mutex
checked bool
)

func checkLibraries64Bit() error {
mutex.Lock()
defer mutex.Unlock()

if checked {
return nil
}

for _, name := range []string{"libcamera", "libcamera-base"} {
lib, err := findLibrary(name)
if err != nil {
return err
}

err = check64bit(lib)
if err != nil {
return err
}
}

checked = true
return nil
}

type camera struct {
Params params
OnData func(time.Duration, [][]byte)
Expand All @@ -120,34 +27,41 @@ type camera struct {
}

func (c *camera) initialize() error {
if runtime.GOARCH == "arm" {
err := checkLibraries64Bit()
if err != nil {
return err
}
err := dumpComponent()
if err != nil {
return err
}

var err error
c.pipeConf, err = newPipe()
if err != nil {
freeComponent()
return err
}

c.pipeVideo, err = newPipe()
if err != nil {
c.pipeConf.close()
freeComponent()
return err
}

env := []string{
"PIPE_CONF_FD=" + strconv.FormatInt(int64(c.pipeConf.readFD), 10),
"PIPE_VIDEO_FD=" + strconv.FormatInt(int64(c.pipeVideo.writeFD), 10),
"LD_LIBRARY_PATH=" + dumpPath,
}

c.cmd, err = startEmbeddedExe(component, env)
c.cmd = exec.Command(filepath.Join(dumpPath, "exe"))
c.cmd.Stdout = os.Stdout
c.cmd.Stderr = os.Stderr
c.cmd.Env = env
c.cmd.Dir = dumpPath

err = c.cmd.Start()
if err != nil {
c.pipeConf.close()
c.pipeVideo.close()
freeComponent()
return err
}

Expand All @@ -164,18 +78,20 @@ func (c *camera) initialize() error {
}()

select {
case <-c.waitDone:
case err := <-c.waitDone:
c.pipeConf.close()
c.pipeVideo.close()
<-c.readerDone
return fmt.Errorf("process exited unexpectedly")
freeComponent()
return fmt.Errorf("process exited unexpectedly: %v", err)

case err := <-c.readerDone:
if err != nil {
c.pipeConf.write([]byte{'e'})
<-c.waitDone
c.pipeConf.close()
c.pipeVideo.close()
freeComponent()
return err
}
}
Expand All @@ -194,6 +110,7 @@ func (c *camera) close() {
c.pipeConf.close()
c.pipeVideo.close()
<-c.readerDone
freeComponent()
}

func (c *camera) reloadParams(params params) {
Expand Down
104 changes: 104 additions & 0 deletions internal/staticsources/rpicamera/component.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,107 @@
//go:build (linux && arm) || (linux && arm64)
// +build linux,arm linux,arm64

package rpicamera

import (
"os"
"path/filepath"
"strconv"
"sync"
"time"
)

//go:generate go run ./mtxrpicamdownloader

const (
dumpPrefix = "/dev/shm/mediamtx-rpicamera-"
)

var (
dumpMutex sync.Mutex
dumpCount = 0
dumpPath = ""
)

func dumpEmbedFSRecursive(src string, dest string) error {
files, err := component.ReadDir(src)
if err != nil {
return err
}

for _, f := range files {
if f.IsDir() {
err = os.Mkdir(filepath.Join(dest, f.Name()), 0o755)
if err != nil {
return err
}

err = dumpEmbedFSRecursive(filepath.Join(src, f.Name()), filepath.Join(dest, f.Name()))
if err != nil {
return err
}
} else {
buf, err := component.ReadFile(filepath.Join(src, f.Name()))
if err != nil {
return err
}

err = os.WriteFile(filepath.Join(dest, f.Name()), buf, 0o644)
if err != nil {
return err
}
}
}

return nil
}

func dumpComponent() error {
dumpMutex.Lock()
defer dumpMutex.Unlock()

if dumpCount > 0 {
dumpCount++
return nil
}

dumpPath = dumpPrefix + strconv.FormatInt(time.Now().UnixNano(), 10)

err := os.Mkdir(dumpPath, 0o755)
if err != nil {
return err
}

files, err := component.ReadDir(".")
if err != nil {
os.RemoveAll(dumpPath)
return err
}

err = dumpEmbedFSRecursive(files[0].Name(), dumpPath)
if err != nil {
os.RemoveAll(dumpPath)
return err
}

err = os.Chmod(filepath.Join(dumpPath, "exe"), 0o755)
if err != nil {
os.RemoveAll(dumpPath)
return err
}

dumpCount++

return nil
}

func freeComponent() {
dumpMutex.Lock()
defer dumpMutex.Unlock()

dumpCount--

if dumpCount == 0 {
os.RemoveAll(dumpPath)
}
}
6 changes: 3 additions & 3 deletions internal/staticsources/rpicamera/component_32.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
package rpicamera

import (
_ "embed"
"embed"
)

//go:embed mtxrpicam_32
var component []byte
//go:embed mtxrpicam_32/*
var component embed.FS
6 changes: 3 additions & 3 deletions internal/staticsources/rpicamera/component_64.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
package rpicamera

import (
_ "embed"
"embed"
)

//go:embed mtxrpicam_64
var component []byte
//go:embed mtxrpicam_64/*
var component embed.FS
3 changes: 3 additions & 0 deletions internal/staticsources/rpicamera/component_dl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package rpicamera

//go:generate go run ./mtxrpicamdownloader
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.0.0
v2.0.0
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func do() error {

log.Printf("downloading mediamtx-rpicamera version %s...", version)

for _, f := range []string{"mtxrpicam_32", "mtxrpicam_64"} {
for _, f := range []string{"mtxrpicam_32.tar.gz", "mtxrpicam_64.tar.gz"} {
res, err := http.Get("https://github.com/bluenviron/mediamtx-rpicamera/releases/download/" + version + "/" + f)
if err != nil {
return err
Expand Down
2 changes: 0 additions & 2 deletions scripts/dockerhub.mk
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@ export DOCKERFILE_DOCKERHUB_FFMPEG

define DOCKERFILE_DOCKERHUB_RPI_BASE_32
FROM $(RPI32_IMAGE)
RUN apt update && apt install -y --no-install-recommends libcamera0 libfreetype6 && rm -rf /var/lib/apt/lists/*
endef
export DOCKERFILE_DOCKERHUB_RPI_BASE_32

define DOCKERFILE_DOCKERHUB_RPI_BASE_64
FROM $(RPI64_IMAGE)
RUN apt update && apt install -y --no-install-recommends libcamera0 libfreetype6 && rm -rf /var/lib/apt/lists/*
endef
export DOCKERFILE_DOCKERHUB_RPI_BASE_64

Expand Down

0 comments on commit 518da7a

Please sign in to comment.