mirror of
https://github.com/bluenviron/mediamtx.git
synced 2025-12-20 02:00:05 -08:00
rpi: embed libcamera and libfreetype into the server (#2581) (#3665)
Some checks are pending
code_lint / golangci_lint (push) Waiting to run
code_lint / mod_tidy (push) Waiting to run
code_lint / api_docs (push) Waiting to run
code_test / test_64 (push) Waiting to run
code_test / test_32 (push) Waiting to run
code_test / test_highlevel (push) Waiting to run
Some checks are pending
code_lint / golangci_lint (push) Waiting to run
code_lint / mod_tidy (push) Waiting to run
code_lint / api_docs (push) Waiting to run
code_test / test_64 (push) Waiting to run
code_test / test_32 (push) Waiting to run
code_test / test_highlevel (push) Waiting to run
This commit is contained in:
parent
c5059fa7a0
commit
6256d0b893
9 changed files with 196 additions and 129 deletions
22
README.md
22
README.md
|
|
@ -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
|
||||
|
||||
Both 32 bit and 64 bit architectures are supported.
|
||||
|
||||
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.
|
||||
|
||||
If you want to run the standard (non-Docker) version of the server:
|
||||
|
||||
1. Make sure that the following packages are installed:
|
||||
1. Download the server executable. If you're using 64-bit version of the operative system, make sure to pick the `arm64` variant.
|
||||
|
||||
* `libcamera0` (≥ 0.0.5)
|
||||
* `libfreetype6`
|
||||
|
||||
2. 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:
|
||||
|
|
@ -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 \
|
||||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -164,11 +78,12 @@ 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 {
|
||||
|
|
@ -176,6 +91,7 @@ func (c *camera) initialize() error {
|
|||
<-c.waitDone
|
||||
c.pipeConf.close()
|
||||
c.pipeVideo.close()
|
||||
freeComponent()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -194,6 +110,7 @@ func (c *camera) close() {
|
|||
c.pipeConf.close()
|
||||
c.pipeVideo.close()
|
||||
<-c.readerDone
|
||||
freeComponent()
|
||||
}
|
||||
|
||||
func (c *camera) reloadParams(params params) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
package rpicamera
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"embed"
|
||||
)
|
||||
|
||||
//go:embed mtxrpicam_32
|
||||
var component []byte
|
||||
//go:embed mtxrpicam_32/*
|
||||
var component embed.FS
|
||||
|
|
|
|||
|
|
@ -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
internal/staticsources/rpicamera/component_dl.go
Normal file
3
internal/staticsources/rpicamera/component_dl.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package rpicamera
|
||||
|
||||
//go:generate go run ./mtxrpicamdownloader
|
||||
|
|
@ -1 +1 @@
|
|||
v1.0.0
|
||||
v2.0.0
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
|
|
@ -10,6 +13,47 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func dumpTar(src io.Reader) error {
|
||||
uncompressed, err := gzip.NewReader(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tr := tar.NewReader(uncompressed)
|
||||
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
err = os.Mkdir(header.Name, header.FileInfo().Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case tar.TypeReg:
|
||||
f, err := os.OpenFile(header.Name, os.O_WRONLY|os.O_CREATE, header.FileInfo().Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func do() error {
|
||||
buf, err := os.ReadFile("./mtxrpicamdownloader/VERSION")
|
||||
if err != nil {
|
||||
|
|
@ -19,7 +63,12 @@ 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"} {
|
||||
err = os.RemoveAll(strings.TrimSuffix(f, ".tar.gz"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := http.Get("https://github.com/bluenviron/mediamtx-rpicamera/releases/download/" + version + "/" + f)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -30,14 +79,10 @@ func do() error {
|
|||
return fmt.Errorf("bad status code: %v", res.StatusCode)
|
||||
}
|
||||
|
||||
buf, err := io.ReadAll(res.Body)
|
||||
err = dumpTar(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.WriteFile(f, buf, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("ok")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue