2022-08-15 15:37:49 +00:00
|
|
|
//go:build rpicamera
|
|
|
|
// +build rpicamera
|
|
|
|
|
|
|
|
package rpicamera
|
|
|
|
|
|
|
|
import (
|
|
|
|
_ "embed"
|
|
|
|
"fmt"
|
2022-12-08 22:49:53 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"runtime"
|
2022-08-15 15:37:49 +00:00
|
|
|
"strconv"
|
2022-12-08 22:49:53 +00:00
|
|
|
"strings"
|
2022-10-25 10:14:37 +00:00
|
|
|
"time"
|
2022-08-15 15:37:49 +00:00
|
|
|
|
|
|
|
"github.com/aler9/gortsplib/pkg/h264"
|
|
|
|
)
|
|
|
|
|
|
|
|
//go:embed exe/exe
|
|
|
|
var exeContent []byte
|
|
|
|
|
Add additional Raspberry Pi Camera parameters (#1198)
* rpicamera: add rpiCameraHFlip and rpiCameraVFlip parameters
* rpicamera: add rpiCameraBrightness, rpiCameraContrast,
rpiCameraSaturation, rpiCameraSharpness, rpiCameraExposure,
rpiCameraAWB, rpiCameraDenoise, rpiCameraShutter, rpiCameraMetering,
rpiCameraGain, rpiCameraEV, rpiCameraROI, rpiCameraTuningFile
* support float values in config file
2022-10-24 13:08:30 +00:00
|
|
|
func bool2env(v bool) string {
|
|
|
|
if v {
|
|
|
|
return "1"
|
|
|
|
}
|
|
|
|
return "0"
|
|
|
|
}
|
|
|
|
|
2022-12-08 22:49:53 +00:00
|
|
|
func getKernelArch() (string, error) {
|
|
|
|
cmd := exec.Command("uname", "-m")
|
|
|
|
|
|
|
|
byts, err := cmd.Output()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(byts[:len(byts)-1]), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 32-bit embedded executables can't run on 64-bit.
|
|
|
|
func checkArch() error {
|
|
|
|
if runtime.GOARCH != "arm" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
arch, err := getKernelArch()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if arch == "aarch64" {
|
|
|
|
return fmt.Errorf("OS is 64-bit, you need the arm64 server version")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupSymlink(name string) error {
|
|
|
|
cmd := exec.Command("sh", "-c", "ldconfig -p | grep "+name+".so | awk '{ print $4 }'")
|
|
|
|
byts, err := cmd.Output()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
lib := strings.TrimSpace(string(byts))
|
|
|
|
if lib == "" {
|
|
|
|
return fmt.Errorf(name + " not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Remove("/dev/shm/" + name + ".so.x.x.x")
|
|
|
|
return os.Symlink(lib, "/dev/shm/"+name+".so.x.x.x")
|
|
|
|
}
|
|
|
|
|
|
|
|
// create libcamera simlinks that are version agnostic.
|
|
|
|
func setupSymlinks() error {
|
|
|
|
err := setupSymlink("libcamera")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return setupSymlink("libcamera-base")
|
|
|
|
}
|
|
|
|
|
2022-08-15 15:37:49 +00:00
|
|
|
type RPICamera struct {
|
2022-10-25 10:14:37 +00:00
|
|
|
onData func(time.Duration, [][]byte)
|
2022-08-15 15:37:49 +00:00
|
|
|
|
|
|
|
exe *embeddedExe
|
|
|
|
pipe *pipe
|
|
|
|
|
|
|
|
waitDone chan error
|
|
|
|
readerDone chan error
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(
|
|
|
|
params Params,
|
2022-10-25 10:14:37 +00:00
|
|
|
onData func(time.Duration, [][]byte),
|
2022-08-15 15:37:49 +00:00
|
|
|
) (*RPICamera, error) {
|
2022-12-08 22:49:53 +00:00
|
|
|
err := checkArch()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = setupSymlinks()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-08-15 15:37:49 +00:00
|
|
|
pipe, err := newPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
env := []string{
|
2022-12-08 22:49:53 +00:00
|
|
|
"LD_LIBRARY_PATH=/dev/shm",
|
2022-08-15 15:37:49 +00:00
|
|
|
"PIPE_FD=" + strconv.FormatInt(int64(pipe.writeFD), 10),
|
|
|
|
"CAMERA_ID=" + strconv.FormatInt(int64(params.CameraID), 10),
|
|
|
|
"WIDTH=" + strconv.FormatInt(int64(params.Width), 10),
|
|
|
|
"HEIGHT=" + strconv.FormatInt(int64(params.Height), 10),
|
Add additional Raspberry Pi Camera parameters (#1198)
* rpicamera: add rpiCameraHFlip and rpiCameraVFlip parameters
* rpicamera: add rpiCameraBrightness, rpiCameraContrast,
rpiCameraSaturation, rpiCameraSharpness, rpiCameraExposure,
rpiCameraAWB, rpiCameraDenoise, rpiCameraShutter, rpiCameraMetering,
rpiCameraGain, rpiCameraEV, rpiCameraROI, rpiCameraTuningFile
* support float values in config file
2022-10-24 13:08:30 +00:00
|
|
|
"H_FLIP=" + bool2env(params.HFlip),
|
|
|
|
"V_FLIP=" + bool2env(params.VFlip),
|
|
|
|
"BRIGHTNESS=" + strconv.FormatFloat(params.Brightness, 'f', -1, 64),
|
|
|
|
"CONTRAST=" + strconv.FormatFloat(params.Contrast, 'f', -1, 64),
|
|
|
|
"SATURATION=" + strconv.FormatFloat(params.Saturation, 'f', -1, 64),
|
|
|
|
"SHARPNESS=" + strconv.FormatFloat(params.Sharpness, 'f', -1, 64),
|
|
|
|
"EXPOSURE=" + params.Exposure,
|
|
|
|
"AWB=" + params.AWB,
|
|
|
|
"DENOISE=" + params.Denoise,
|
|
|
|
"SHUTTER=" + strconv.FormatInt(int64(params.Shutter), 10),
|
|
|
|
"METERING=" + params.Metering,
|
|
|
|
"GAIN=" + strconv.FormatFloat(params.Gain, 'f', -1, 64),
|
|
|
|
"EV=" + strconv.FormatFloat(params.EV, 'f', -1, 64),
|
|
|
|
"ROI=" + params.ROI,
|
|
|
|
"TUNING_FILE=" + params.TuningFile,
|
2022-10-25 09:23:44 +00:00
|
|
|
"MODE=" + params.Mode,
|
2022-08-15 15:37:49 +00:00
|
|
|
"FPS=" + strconv.FormatInt(int64(params.FPS), 10),
|
|
|
|
"IDR_PERIOD=" + strconv.FormatInt(int64(params.IDRPeriod), 10),
|
|
|
|
"BITRATE=" + strconv.FormatInt(int64(params.Bitrate), 10),
|
|
|
|
"PROFILE=" + params.Profile,
|
|
|
|
"LEVEL=" + params.Level,
|
|
|
|
}
|
|
|
|
|
|
|
|
exe, err := newEmbeddedExe(exeContent, env)
|
|
|
|
if err != nil {
|
|
|
|
pipe.close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
waitDone := make(chan error)
|
|
|
|
go func() {
|
|
|
|
waitDone <- exe.cmd.Wait()
|
|
|
|
}()
|
|
|
|
|
|
|
|
readerDone := make(chan error)
|
|
|
|
go func() {
|
|
|
|
readerDone <- func() error {
|
|
|
|
buf, err := pipe.read()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch buf[0] {
|
|
|
|
case 'e':
|
|
|
|
return fmt.Errorf(string(buf[1:]))
|
|
|
|
|
|
|
|
case 'r':
|
|
|
|
return nil
|
|
|
|
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unexpected output from pipe (%c)", buf[0])
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-waitDone:
|
|
|
|
exe.close()
|
|
|
|
pipe.close()
|
|
|
|
<-readerDone
|
|
|
|
return nil, fmt.Errorf("process exited unexpectedly")
|
|
|
|
|
|
|
|
case err := <-readerDone:
|
|
|
|
if err != nil {
|
|
|
|
exe.close()
|
|
|
|
<-waitDone
|
|
|
|
pipe.close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
readerDone = make(chan error)
|
|
|
|
go func() {
|
|
|
|
readerDone <- func() error {
|
|
|
|
for {
|
|
|
|
buf, err := pipe.read()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if buf[0] != 'b' {
|
|
|
|
return fmt.Errorf("unexpected output from pipe (%c)", buf[0])
|
|
|
|
}
|
|
|
|
|
2022-10-25 10:14:37 +00:00
|
|
|
tmp := uint64(buf[8])<<56 | uint64(buf[7])<<48 | uint64(buf[6])<<40 | uint64(buf[5])<<32 |
|
|
|
|
uint64(buf[4])<<24 | uint64(buf[3])<<16 | uint64(buf[2])<<8 | uint64(buf[1])
|
|
|
|
dts := time.Duration(tmp) * time.Microsecond
|
|
|
|
|
|
|
|
nalus, err := h264.AnnexBUnmarshal(buf[9:])
|
2022-08-15 15:37:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-10-25 10:14:37 +00:00
|
|
|
onData(dts, nalus)
|
2022-08-15 15:37:49 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}()
|
|
|
|
|
|
|
|
return &RPICamera{
|
|
|
|
onData: onData,
|
|
|
|
exe: exe,
|
|
|
|
pipe: pipe,
|
|
|
|
waitDone: waitDone,
|
|
|
|
readerDone: readerDone,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *RPICamera) Close() {
|
|
|
|
c.exe.close()
|
|
|
|
<-c.waitDone
|
|
|
|
c.pipe.close()
|
|
|
|
<-c.readerDone
|
|
|
|
}
|