Browse Source

rpicamera: add text overlay with current time (#1288) (#1604)

pull/1636/head
Alessandro Ros 3 years ago committed by GitHub
parent
commit
3c9eed5fae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      README.md
  2. 4
      apidocs/openapi.yaml
  3. 5
      internal/conf/path.go
  4. 60
      internal/core/rpicamera_source.go
  5. 10
      internal/rpicamera/exe/Makefile
  6. 87
      internal/rpicamera/exe/base64.c
  7. 6
      internal/rpicamera/exe/base64.h
  8. 45
      internal/rpicamera/exe/camera.cpp
  9. 10
      internal/rpicamera/exe/camera.h
  10. 65
      internal/rpicamera/exe/encoder.c
  11. 2
      internal/rpicamera/exe/encoder.h
  12. 25
      internal/rpicamera/exe/main.c
  13. 121
      internal/rpicamera/exe/parameters.c
  14. 2
      internal/rpicamera/exe/parameters.h
  15. 171
      internal/rpicamera/exe/text.c
  16. 15
      internal/rpicamera/exe/text.h
  17. 8432
      internal/rpicamera/exe/text_font.h
  18. 60
      internal/rpicamera/params.go
  19. 5
      internal/rpicamera/rpicamera.go
  20. 5
      rtsp-simple-server.yml
  21. 4
      scripts/binaries.mk
  22. 4
      scripts/dockerhub.mk

14
README.md

@ -573,7 +573,14 @@ The command will produce the `rtsp-simple-server` binary.
#### Raspberry Pi #### Raspberry Pi
In case of a Raspberry Pi, the server can be compiled with native support for the Raspberry Pi Camera. Install Go ≥ 1.20, download the repository, open a terminal in it and run: The server can be compiled with native support for the Raspberry Pi Camera. Compilation must happen on a Raspberry Pi Device, with the following dependencies:
* Go ≥ 1.20
* `libcamera-dev`
* `libfreetype-dev`
* `patchelf`
Download the repository, open a terminal in it and run:
```sh ```sh
cd internal/rpicamera/exe cd internal/rpicamera/exe
@ -634,7 +641,10 @@ _rtsp-simple-server_ natively support the Raspberry Pi Camera, enabling high-qua
If you want to run the standard (non-containerized) version of the server: If you want to run the standard (non-containerized) version of the server:
1. Make sure that the `libcamera0` package version is at least `0.0.2`, otherwise upgrade it with `sudo apt update && sudo apt install libcamera0`. 1. Make sure that the following packages are installed:
* `libcamera0` (at least version 0.0.2)
* `libfreetype6`
2. download the server executable. If you're using 64-bit version of the operative system, make sure to pick the `arm64` variant. 2. download the server executable. If you're using 64-bit version of the operative system, make sure to pick the `arm64` variant.

4
apidocs/openapi.yaml

@ -250,6 +250,10 @@ components:
type: number type: number
rpiCameraAfWindow: rpiCameraAfWindow:
type: string type: string
rpiCameraTextOverlayEnable:
type: boolean
rpiCameraTextOverlay:
type: string
# authentication # authentication
publishUser: publishUser:

5
internal/conf/path.go

@ -80,6 +80,8 @@ type PathConf struct {
RPICameraAfSpeed string `json:"rpiCameraAfSpeed"` RPICameraAfSpeed string `json:"rpiCameraAfSpeed"`
RPICameraLensPosition float64 `json:"rpiCameraLensPosition"` RPICameraLensPosition float64 `json:"rpiCameraLensPosition"`
RPICameraAfWindow string `json:"rpiCameraAfWindow"` RPICameraAfWindow string `json:"rpiCameraAfWindow"`
RPICameraTextOverlayEnable bool `json:"rpiCameraTextOverlayEnable"`
RPICameraTextOverlay string `json:"rpiCameraTextOverlay"`
// authentication // authentication
PublishUser Credential `json:"publishUser"` PublishUser Credential `json:"publishUser"`
@ -249,6 +251,9 @@ func (pconf *PathConf) checkAndFillMissing(conf *Conf, name string) error {
if pconf.RPICameraLevel == "" { if pconf.RPICameraLevel == "" {
pconf.RPICameraLevel = "4.1" pconf.RPICameraLevel = "4.1"
} }
if pconf.RPICameraTextOverlay == "" {
pconf.RPICameraTextOverlay = "%Y-%m-%d %H:%M:%S - MediaMTX"
}
default: default:
return fmt.Errorf("invalid source: '%s'", pconf.Source) return fmt.Errorf("invalid source: '%s'", pconf.Source)

60
internal/core/rpicamera_source.go

@ -15,35 +15,37 @@ import (
func paramsFromConf(cnf *conf.PathConf) rpicamera.Params { func paramsFromConf(cnf *conf.PathConf) rpicamera.Params {
return rpicamera.Params{ return rpicamera.Params{
CameraID: cnf.RPICameraCamID, CameraID: cnf.RPICameraCamID,
Width: cnf.RPICameraWidth, Width: cnf.RPICameraWidth,
Height: cnf.RPICameraHeight, Height: cnf.RPICameraHeight,
HFlip: cnf.RPICameraHFlip, HFlip: cnf.RPICameraHFlip,
VFlip: cnf.RPICameraVFlip, VFlip: cnf.RPICameraVFlip,
Brightness: cnf.RPICameraBrightness, Brightness: cnf.RPICameraBrightness,
Contrast: cnf.RPICameraContrast, Contrast: cnf.RPICameraContrast,
Saturation: cnf.RPICameraSaturation, Saturation: cnf.RPICameraSaturation,
Sharpness: cnf.RPICameraSharpness, Sharpness: cnf.RPICameraSharpness,
Exposure: cnf.RPICameraExposure, Exposure: cnf.RPICameraExposure,
AWB: cnf.RPICameraAWB, AWB: cnf.RPICameraAWB,
Denoise: cnf.RPICameraDenoise, Denoise: cnf.RPICameraDenoise,
Shutter: cnf.RPICameraShutter, Shutter: cnf.RPICameraShutter,
Metering: cnf.RPICameraMetering, Metering: cnf.RPICameraMetering,
Gain: cnf.RPICameraGain, Gain: cnf.RPICameraGain,
EV: cnf.RPICameraEV, EV: cnf.RPICameraEV,
ROI: cnf.RPICameraROI, ROI: cnf.RPICameraROI,
TuningFile: cnf.RPICameraTuningFile, TuningFile: cnf.RPICameraTuningFile,
Mode: cnf.RPICameraMode, Mode: cnf.RPICameraMode,
FPS: cnf.RPICameraFPS, FPS: cnf.RPICameraFPS,
IDRPeriod: cnf.RPICameraIDRPeriod, IDRPeriod: cnf.RPICameraIDRPeriod,
Bitrate: cnf.RPICameraBitrate, Bitrate: cnf.RPICameraBitrate,
Profile: cnf.RPICameraProfile, Profile: cnf.RPICameraProfile,
Level: cnf.RPICameraLevel, Level: cnf.RPICameraLevel,
AfMode: cnf.RPICameraAfMode, AfMode: cnf.RPICameraAfMode,
AfRange: cnf.RPICameraAfRange, AfRange: cnf.RPICameraAfRange,
AfSpeed: cnf.RPICameraAfSpeed, AfSpeed: cnf.RPICameraAfSpeed,
LensPosition: cnf.RPICameraLensPosition, LensPosition: cnf.RPICameraLensPosition,
AfWindow: cnf.RPICameraAfWindow, AfWindow: cnf.RPICameraAfWindow,
TextOverlayEnable: cnf.RPICameraTextOverlayEnable,
TextOverlay: cnf.RPICameraTextOverlay,
} }
} }

10
internal/rpicamera/exe/Makefile

@ -4,7 +4,8 @@ CFLAGS = \
-Wall \ -Wall \
-Wextra \ -Wextra \
-Wno-unused-parameter \ -Wno-unused-parameter \
-Wno-unused-result -Wno-unused-result \
$$(pkg-config --cflags freetype2)
CXXFLAGS = \ CXXFLAGS = \
-Ofast \ -Ofast \
@ -19,16 +20,19 @@ CXXFLAGS = \
LDFLAGS = \ LDFLAGS = \
-s \ -s \
-pthread \ -pthread \
$$(pkg-config --libs freetype2) \
$$(pkg-config --libs libcamera) $$(pkg-config --libs libcamera)
OBJS = \ OBJS = \
base64.o \
camera.o \ camera.o \
encoder.o \ encoder.o \
main.o \ main.o \
parameters.o \ parameters.o \
pipe.o \ pipe.o \
window.o \ sensor_mode.o \
sensor_mode.o text.o \
window.o
all: exe all: exe

87
internal/rpicamera/exe/base64.c

@ -0,0 +1,87 @@
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "base64.h"
static const unsigned char decoding_table[256] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
0x3c, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
char* base64_decode(const char *data) {
size_t input_length = strlen(data);
if (input_length % 4 != 0) {
return NULL;
}
size_t output_length = input_length / 4 * 3;
if (data[input_length - 1] == '=') {
(output_length)--;
}
if (data[input_length - 2] == '=') {
(output_length)--;
}
unsigned char* output = (unsigned char *)malloc(output_length + 1);
if (output == NULL) {
return NULL;
}
for (int i = 0, j = 0; i < (int)input_length;) {
uint32_t sextet_a = (data[i] == '=') ? (0 & i++) : decoding_table[(unsigned char)(data[i++])];
uint32_t sextet_b = (data[i] == '=') ? (0 & i++) : decoding_table[(unsigned char)(data[i++])];
uint32_t sextet_c = (data[i] == '=') ? (0 & i++) : decoding_table[(unsigned char)(data[i++])];
uint32_t sextet_d = (data[i] == '=') ? (0 & i++) : decoding_table[(unsigned char)(data[i++])];
uint32_t triple = (sextet_a << 3 * 6)
+ (sextet_b << 2 * 6)
+ (sextet_c << 1 * 6)
+ (sextet_d << 0 * 6);
if (j < (int)output_length) {
output[j++] = (triple >> 2 * 8) & 0xFF;
}
if (j < (int)output_length) {
output[j++] = (triple >> 1 * 8) & 0xFF;
}
if (j < (int)output_length) {
output[j++] = (triple >> 0 * 8) & 0xFF;
}
}
output[output_length] = 0x00;
return (char *)output;
};

6
internal/rpicamera/exe/base64.h

@ -0,0 +1,6 @@
#ifndef __BASE64_H__
#define __BASE64_H__
char* base64_decode(const char *data);
#endif

45
internal/rpicamera/exe/camera.cpp

@ -14,7 +14,6 @@
#include <libcamera/property_ids.h> #include <libcamera/property_ids.h>
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include "parameters.h"
#include "camera.h" #include "camera.h"
using libcamera::CameraManager; using libcamera::CameraManager;
@ -81,6 +80,7 @@ struct CameraPriv {
std::vector<std::unique_ptr<Request>> requests; std::vector<std::unique_ptr<Request>> requests;
std::mutex ctrls_mutex; std::mutex ctrls_mutex;
std::unique_ptr<ControlList> ctrls; std::unique_ptr<ControlList> ctrls;
std::map<FrameBuffer *, uint8_t *> mapped_buffers;
}; };
static int get_v4l2_colorspace(std::optional<ColorSpace> const &cs) { static int get_v4l2_colorspace(std::optional<ColorSpace> const &cs) {
@ -90,6 +90,22 @@ static int get_v4l2_colorspace(std::optional<ColorSpace> const &cs) {
return V4L2_COLORSPACE_SMPTE170M; return V4L2_COLORSPACE_SMPTE170M;
} }
// https://github.com/raspberrypi/libcamera-apps/blob/a5b5506a132056ac48ba22bc581cc394456da339/core/libcamera_app.cpp#L824
static uint8_t *map_buffer(FrameBuffer *buffer) {
size_t buffer_size = 0;
for (unsigned i = 0; i < buffer->planes().size(); i++) {
const FrameBuffer::Plane &plane = buffer->planes()[i];
buffer_size += plane.length;
if (i == buffer->planes().size() - 1 || plane.fd.get() != buffer->planes()[i + 1].fd.get()) {
return (uint8_t *)mmap(NULL, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, plane.fd.get(), 0);
}
}
return NULL;
}
bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_t **cam) { bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_t **cam) {
// We make sure to set the environment variable before libcamera init // We make sure to set the environment variable before libcamera init
setenv("LIBCAMERA_RPI_TUNING_FILE", params->tuning_file, 1); setenv("LIBCAMERA_RPI_TUNING_FILE", params->tuning_file, 1);
@ -195,6 +211,11 @@ bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_
int i = 0; int i = 0;
for (const std::unique_ptr<FrameBuffer> &buffer : camp->allocator->buffers(stream)) { for (const std::unique_ptr<FrameBuffer> &buffer : camp->allocator->buffers(stream)) {
// map buffer of the video stream only
if (stream == video_stream_conf.stream()) {
camp->mapped_buffers[buffer.get()] = map_buffer(buffer.get());
}
res = camp->requests.at(i++)->addBuffer(stream, buffer.get()); res = camp->requests.at(i++)->addBuffer(stream, buffer.get());
if (res != 0) { if (res != 0) {
set_error("addBuffer() failed"); set_error("addBuffer() failed");
@ -210,6 +231,14 @@ bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_
return true; return true;
} }
static int buffer_size(const std::vector<FrameBuffer::Plane> &planes) {
int size = 0;
for (const FrameBuffer::Plane &plane : planes) {
size += plane.length;
}
return size;
}
static void on_request_complete(Request *request) { static void on_request_complete(Request *request) {
if (request->status() == Request::RequestCancelled) { if (request->status() == Request::RequestCancelled) {
return; return;
@ -218,12 +247,14 @@ static void on_request_complete(Request *request) {
CameraPriv *camp = (CameraPriv *)request->cookie(); CameraPriv *camp = (CameraPriv *)request->cookie();
FrameBuffer *buffer = request->buffers().at(camp->video_stream); FrameBuffer *buffer = request->buffers().at(camp->video_stream);
int size = 0;
for (const FrameBuffer::Plane &plane : buffer->planes()) { camp->frame_cb(
size += plane.length; camp->mapped_buffers.at(buffer),
} camp->video_stream->configuration().stride,
uint64_t ts = buffer->metadata().timestamp / 1000; camp->video_stream->configuration().size.height,
camp->frame_cb(buffer->planes()[0].fd.get(), size, ts); buffer->planes()[0].fd.get(),
buffer_size(buffer->planes()),
buffer->metadata().timestamp / 1000);
request->reuse(Request::ReuseFlag::ReuseBuffers); request->reuse(Request::ReuseFlag::ReuseBuffers);

10
internal/rpicamera/exe/camera.h

@ -1,9 +1,17 @@
#ifndef __CAMERA_H__ #ifndef __CAMERA_H__
#define __CAMERA_H__ #define __CAMERA_H__
#include "parameters.h"
typedef void camera_t; typedef void camera_t;
typedef void (*camera_frame_cb)(int buffer_fd, uint64_t size, uint64_t timestamp); typedef void (*camera_frame_cb)(
uint8_t *mapped_buffer,
int stride,
int height,
int buffer_fd,
uint64_t size,
uint64_t timestamp);
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {

65
internal/rpicamera/exe/encoder.c

@ -14,7 +14,6 @@
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include "parameters.h"
#include "encoder.h" #include "encoder.h"
#define DEVICE "/dev/video11" #define DEVICE "/dev/video11"
@ -115,11 +114,12 @@ static void *output_thread(void *userdata) {
bool encoder_create(const parameters_t *params, int stride, int colorspace, encoder_output_cb output_cb, encoder_t **enc) { bool encoder_create(const parameters_t *params, int stride, int colorspace, encoder_output_cb output_cb, encoder_t **enc) {
*enc = malloc(sizeof(encoder_priv_t)); *enc = malloc(sizeof(encoder_priv_t));
encoder_priv_t *encp = (encoder_priv_t *)(*enc); encoder_priv_t *encp = (encoder_priv_t *)(*enc);
memset(encp, 0, sizeof(encoder_priv_t));
encp->fd = open(DEVICE, O_RDWR, 0); encp->fd = open(DEVICE, O_RDWR, 0);
if (encp->fd < 0) { if (encp->fd < 0) {
set_error("unable to open device"); set_error("unable to open device");
return false; goto failed;
} }
struct v4l2_control ctrl = {0}; struct v4l2_control ctrl = {0};
@ -128,8 +128,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
int res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); int res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl);
if (res != 0) { if (res != 0) {
set_error("unable to set bitrate"); set_error("unable to set bitrate");
close(encp->fd); goto failed;
return false;
} }
ctrl.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE; ctrl.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE;
@ -137,8 +136,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl);
if (res != 0) { if (res != 0) {
set_error("unable to set profile"); set_error("unable to set profile");
close(encp->fd); goto failed;
return false;
} }
ctrl.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL; ctrl.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL;
@ -146,8 +144,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl);
if (res != 0) { if (res != 0) {
set_error("unable to set level"); set_error("unable to set level");
close(encp->fd); goto failed;
return false;
} }
ctrl.id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD; ctrl.id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD;
@ -155,8 +152,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl);
if (res != 0) { if (res != 0) {
set_error("unable to set IDR period"); set_error("unable to set IDR period");
close(encp->fd); goto failed;
return false;
} }
ctrl.id = V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER; ctrl.id = V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER;
@ -164,8 +160,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl);
if (res != 0) { if (res != 0) {
set_error("unable to set REPEAT_SEQ_HEADER"); set_error("unable to set REPEAT_SEQ_HEADER");
close(encp->fd); goto failed;
return false;
} }
struct v4l2_format fmt = {0}; struct v4l2_format fmt = {0};
@ -180,8 +175,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt); res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt);
if (res != 0) { if (res != 0) {
set_error("unable to set output format"); set_error("unable to set output format");
close(encp->fd); goto failed;
return false;
} }
memset(&fmt, 0, sizeof(fmt)); memset(&fmt, 0, sizeof(fmt));
@ -197,8 +191,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt); res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt);
if (res != 0) { if (res != 0) {
set_error("unable to set capture format"); set_error("unable to set capture format");
close(encp->fd); goto failed;
return false;
} }
struct v4l2_streamparm parm = {0}; struct v4l2_streamparm parm = {0};
@ -208,8 +201,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_S_PARM, &parm); res = ioctl(encp->fd, VIDIOC_S_PARM, &parm);
if (res != 0) { if (res != 0) {
set_error("unable to set fps"); set_error("unable to set fps");
close(encp->fd); goto failed;
return false;
} }
struct v4l2_requestbuffers reqbufs = {0}; struct v4l2_requestbuffers reqbufs = {0};
@ -219,8 +211,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs); res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs);
if (res != 0) { if (res != 0) {
set_error("unable to set output buffers"); set_error("unable to set output buffers");
close(encp->fd); goto failed;
return false;
} }
memset(&reqbufs, 0, sizeof(reqbufs)); memset(&reqbufs, 0, sizeof(reqbufs));
@ -230,8 +221,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs); res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs);
if (res != 0) { if (res != 0) {
set_error("unable to set capture buffers"); set_error("unable to set capture buffers");
close(encp->fd); goto failed;
return false;
} }
encp->capture_buffers = malloc(sizeof(void *) * reqbufs.count); encp->capture_buffers = malloc(sizeof(void *) * reqbufs.count);
@ -248,9 +238,7 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
int res = ioctl(encp->fd, VIDIOC_QUERYBUF, &buffer); int res = ioctl(encp->fd, VIDIOC_QUERYBUF, &buffer);
if (res != 0) { if (res != 0) {
set_error("unable to query buffer"); set_error("unable to query buffer");
free(encp->capture_buffers); goto failed;
close(encp->fd);
return false;
} }
encp->capture_buffers[i] = mmap( encp->capture_buffers[i] = mmap(
@ -261,17 +249,13 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
buffer.m.planes[0].m.mem_offset); buffer.m.planes[0].m.mem_offset);
if (encp->capture_buffers[i] == MAP_FAILED) { if (encp->capture_buffers[i] == MAP_FAILED) {
set_error("mmap() failed"); set_error("mmap() failed");
free(encp->capture_buffers); goto failed;
close(encp->fd);
return false;
} }
res = ioctl(encp->fd, VIDIOC_QBUF, &buffer); res = ioctl(encp->fd, VIDIOC_QBUF, &buffer);
if (res != 0) { if (res != 0) {
set_error("ioctl(VIDIOC_QBUF) failed"); set_error("ioctl(VIDIOC_QBUF) failed");
free(encp->capture_buffers); goto failed;
close(encp->fd);
return false;
} }
} }
@ -279,18 +263,13 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
res = ioctl(encp->fd, VIDIOC_STREAMON, &type); res = ioctl(encp->fd, VIDIOC_STREAMON, &type);
if (res != 0) { if (res != 0) {
set_error("unable to activate output stream"); set_error("unable to activate output stream");
free(encp->capture_buffers); goto failed;
close(encp->fd);
return false;
} }
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
res = ioctl(encp->fd, VIDIOC_STREAMON, &type); res = ioctl(encp->fd, VIDIOC_STREAMON, &type);
if (res != 0) { if (res != 0) {
set_error("unable to activate capture stream"); set_error("unable to activate capture stream");
free(encp->capture_buffers);
close(encp->fd);
return false;
} }
encp->params = params; encp->params = params;
@ -301,6 +280,18 @@ bool encoder_create(const parameters_t *params, int stride, int colorspace, enco
pthread_create(&encp->output_thread, NULL, output_thread, encp); pthread_create(&encp->output_thread, NULL, output_thread, encp);
return true; return true;
failed:
if (encp->capture_buffers != NULL) {
free(encp->capture_buffers);
}
if (encp->fd >= 0) {
close(encp->fd);
}
free(encp);
return false;
} }
void encoder_encode(encoder_t *enc, int buffer_fd, size_t size, int64_t timestamp_us) { void encoder_encode(encoder_t *enc, int buffer_fd, size_t size, int64_t timestamp_us) {

2
internal/rpicamera/exe/encoder.h

@ -1,6 +1,8 @@
#ifndef __ENCODER_H__ #ifndef __ENCODER_H__
#define __ENCODER_H__ #define __ENCODER_H__
#include "parameters.h"
typedef void encoder_t; typedef void encoder_t;
typedef void (*encoder_output_cb)(uint64_t ts, const uint8_t *buf, uint64_t size); typedef void (*encoder_output_cb)(uint64_t ts, const uint8_t *buf, uint64_t size);

25
internal/rpicamera/exe/main.c

@ -9,13 +9,22 @@
#include "parameters.h" #include "parameters.h"
#include "pipe.h" #include "pipe.h"
#include "camera.h" #include "camera.h"
#include "text.h"
#include "encoder.h" #include "encoder.h"
int pipe_video_fd; static int pipe_video_fd;
pthread_mutex_t pipe_video_mutex; static pthread_mutex_t pipe_video_mutex;
encoder_t *enc; static text_t *text;
static encoder_t *enc;
static void on_frame(int buffer_fd, uint64_t size, uint64_t timestamp) {
static void on_frame(
uint8_t *mapped_buffer,
int stride,
int height,
int buffer_fd,
uint64_t size,
uint64_t timestamp) {
text_draw(text, mapped_buffer, stride, height);
encoder_encode(enc, buffer_fd, size, timestamp); encoder_encode(enc, buffer_fd, size, timestamp);
} }
@ -53,6 +62,12 @@ int main() {
return 5; return 5;
} }
ok = text_create(&params, &text);
if (!ok) {
pipe_write_error(pipe_video_fd, "text_create(): %s", text_get_error());
return 5;
}
ok = encoder_create( ok = encoder_create(
&params, &params,
camera_get_mode_stride(cam), camera_get_mode_stride(cam),

121
internal/rpicamera/exe/parameters.c

@ -6,6 +6,7 @@
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include "base64.h"
#include "parameters.h" #include "parameters.h"
static char errbuf[256]; static char errbuf[256];
@ -21,15 +22,20 @@ const char *parameters_get_error() {
} }
bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf_size) { bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf_size) {
memset(params, 0, sizeof(parameters_t));
char *tmp = malloc(buf_size + 1); char *tmp = malloc(buf_size + 1);
memcpy(tmp, buf, buf_size); memcpy(tmp, buf, buf_size);
tmp[buf_size] = 0x00; tmp[buf_size] = 0x00;
char *entry; while (true) {
char *entry = strsep(&tmp, " ");
if (entry == NULL) {
break;
}
while ((entry = strsep(&tmp, " ")) != NULL) { char *key = strsep(&entry, ":");
char *key = strsep(&entry, "="); char *val = strsep(&entry, ":");
char *val = strsep(&entry, "=");
if (strcmp(key, "CameraID") == 0) { if (strcmp(key, "CameraID") == 0) {
params->camera_id = atoi(val); params->camera_id = atoi(val);
@ -50,43 +56,45 @@ bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf
} else if (strcmp(key, "Sharpness") == 0) { } else if (strcmp(key, "Sharpness") == 0) {
params->sharpness = atof(val); params->sharpness = atof(val);
} else if (strcmp(key, "Exposure") == 0) { } else if (strcmp(key, "Exposure") == 0) {
params->exposure = strdup(val); params->exposure = base64_decode(val);
} else if (strcmp(key, "AWB") == 0) { } else if (strcmp(key, "AWB") == 0) {
params->awb = strdup(val); params->awb = base64_decode(val);
} else if (strcmp(key, "Denoise") == 0) { } else if (strcmp(key, "Denoise") == 0) {
params->denoise = strdup(val); params->denoise = base64_decode(val);
} else if (strcmp(key, "Shutter") == 0) { } else if (strcmp(key, "Shutter") == 0) {
params->shutter = atoi(val); params->shutter = atoi(val);
} else if (strcmp(key, "Metering") == 0) { } else if (strcmp(key, "Metering") == 0) {
params->metering = strdup(val); params->metering = base64_decode(val);
} else if (strcmp(key, "Gain") == 0) { } else if (strcmp(key, "Gain") == 0) {
params->gain = atof(val); params->gain = atof(val);
} else if (strcmp(key, "EV") == 0) { } else if (strcmp(key, "EV") == 0) {
params->ev = atof(val); params->ev = atof(val);
} else if (strcmp(key, "ROI") == 0) { } else if (strcmp(key, "ROI") == 0) {
if (strlen(val) != 0) { char *decoded_val = base64_decode(val);
if (strlen(decoded_val) != 0) {
params->roi = malloc(sizeof(window_t)); params->roi = malloc(sizeof(window_t));
bool ok = window_load(val, params->roi); bool ok = window_load(decoded_val, params->roi);
if (!ok) { if (!ok) {
set_error("invalid ROI"); set_error("invalid ROI");
return false; free(decoded_val);
goto failed;
} }
} else {
params->roi = NULL;
} }
free(decoded_val);
} else if (strcmp(key, "TuningFile") == 0) { } else if (strcmp(key, "TuningFile") == 0) {
params->tuning_file = strdup(val); params->tuning_file = base64_decode(val);
} else if (strcmp(key, "Mode") == 0) { } else if (strcmp(key, "Mode") == 0) {
if (strlen(val) != 0) { char *decoded_val = base64_decode(val);
if (strlen(decoded_val) != 0) {
params->mode = malloc(sizeof(sensor_mode_t)); params->mode = malloc(sizeof(sensor_mode_t));
bool ok = sensor_mode_load(val, params->mode); bool ok = sensor_mode_load(decoded_val, params->mode);
if (!ok) { if (!ok) {
set_error("invalid sensor mode"); set_error("invalid sensor mode");
return false; free(decoded_val);
goto failed;
} }
} else {
params->mode = NULL;
} }
free(decoded_val);
} else if (strcmp(key, "FPS") == 0) { } else if (strcmp(key, "FPS") == 0) {
params->fps = atoi(val); params->fps = atoi(val);
} else if (strcmp(key, "IDRPeriod") == 0) { } else if (strcmp(key, "IDRPeriod") == 0) {
@ -94,40 +102,49 @@ bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf
} else if (strcmp(key, "Bitrate") == 0) { } else if (strcmp(key, "Bitrate") == 0) {
params->bitrate = atoi(val); params->bitrate = atoi(val);
} else if (strcmp(key, "Profile") == 0) { } else if (strcmp(key, "Profile") == 0) {
if (strcmp(val, "baseline") == 0) { char *decoded_val = base64_decode(val);
if (strcmp(decoded_val, "baseline") == 0) {
params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE;
} else if (strcmp(val, "main") == 0) { } else if (strcmp(decoded_val, "main") == 0) {
params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN; params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN;
} else { } else {
params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH; params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH;
} }
free(decoded_val);
} else if (strcmp(key, "Level") == 0) { } else if (strcmp(key, "Level") == 0) {
if (strcmp(val, "4.0") == 0) { char *decoded_val = base64_decode(val);
if (strcmp(decoded_val, "4.0") == 0) {
params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_0; params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_0;
} else if (strcmp(val, "4.1") == 0) { } else if (strcmp(decoded_val, "4.1") == 0) {
params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_1; params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_1;
} else { } else {
params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_2; params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_2;
} }
free(decoded_val);
} else if (strcmp(key, "AfMode") == 0) { } else if (strcmp(key, "AfMode") == 0) {
params->af_mode = strdup(val); params->af_mode = base64_decode(val);
} else if (strcmp(key, "AfRange") == 0) { } else if (strcmp(key, "AfRange") == 0) {
params->af_range = strdup(val); params->af_range = base64_decode(val);
} else if (strcmp(key, "AfSpeed") == 0) { } else if (strcmp(key, "AfSpeed") == 0) {
params->af_speed = strdup(val); params->af_speed = base64_decode(val);
} else if (strcmp(key, "LensPosition") == 0) { } else if (strcmp(key, "LensPosition") == 0) {
params->lens_position = atof(val); params->lens_position = atof(val);
} else if (strcmp(key, "AfWindow") == 0) { } else if (strcmp(key, "AfWindow") == 0) {
if (strlen(val) != 0) { char *decoded_val = base64_decode(val);
if (strlen(decoded_val) != 0) {
params->af_window = malloc(sizeof(window_t)); params->af_window = malloc(sizeof(window_t));
bool ok = window_load(val, params->af_window); bool ok = window_load(decoded_val, params->af_window);
if (!ok) { if (!ok) {
set_error("invalid AfWindow"); set_error("invalid AfWindow");
return false; free(decoded_val);
goto failed;
} }
} else {
params->af_window = NULL;
} }
free(decoded_val);
} else if (strcmp(key, "TextOverlayEnable") == 0) {
params->text_overlay_enable = (strcmp(val, "1") == 0);
} else if (strcmp(key, "TextOverlay") == 0) {
params->text_overlay = base64_decode(val);
} }
} }
@ -137,25 +154,49 @@ bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf
params->capture_buffer_count = params->buffer_count * 2; params->capture_buffer_count = params->buffer_count * 2;
return true; return true;
failed:
free(tmp);
parameters_destroy(params);
return false;
} }
void parameters_destroy(parameters_t *params) { void parameters_destroy(parameters_t *params) {
free(params->exposure); if (params->exposure != NULL) {
free(params->awb); free(params->exposure);
free(params->denoise); }
free(params->metering); if (params->awb != NULL) {
free(params->tuning_file); free(params->awb);
free(params->af_mode); }
free(params->af_range); if (params->denoise != NULL) {
free(params->af_speed); free(params->denoise);
}
if (params->metering != NULL) {
free(params->metering);
}
if (params->roi != NULL) { if (params->roi != NULL) {
free(params->roi); free(params->roi);
} }
if (params->tuning_file != NULL) {
free(params->tuning_file);
}
if (params->mode != NULL) { if (params->mode != NULL) {
free(params->mode); free(params->mode);
} }
if (params->af_mode != NULL) {
free(params->af_mode);
}
if (params->af_range != NULL) {
free(params->af_range);
}
if (params->af_speed != NULL) {
free(params->af_speed);
}
if (params->af_window != NULL) { if (params->af_window != NULL) {
free(params->af_window); free(params->af_window);
} }
if (params->text_overlay != NULL) {
free(params->text_overlay);
}
} }

2
internal/rpicamera/exe/parameters.h

@ -37,6 +37,8 @@ typedef struct {
char *af_speed; char *af_speed;
float lens_position; float lens_position;
window_t *af_window; window_t *af_window;
bool text_overlay_enable;
char *text_overlay;
// private // private
unsigned int buffer_count; unsigned int buffer_count;

171
internal/rpicamera/exe/text.c

@ -0,0 +1,171 @@
#include <time.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include "text_font.h"
#include "text.h"
static char errbuf[256];
static void set_error(const char *format, ...) {
va_list args;
va_start(args, format);
vsnprintf(errbuf, 256, format, args);
}
const char *text_get_error() {
return errbuf;
}
typedef struct {
bool enabled;
char *text_overlay;
FT_Library library;
FT_Face face;
} text_priv_t;
bool text_create(const parameters_t *params, text_t **text) {
*text = malloc(sizeof(text_priv_t));
text_priv_t *textp = (text_priv_t *)(*text);
memset(textp, 0, sizeof(text_priv_t));
textp->enabled = params->text_overlay_enable;
textp->text_overlay = strdup(params->text_overlay);
if (textp->enabled) {
int error = FT_Init_FreeType(&textp->library);
if (error) {
set_error("FT_Init_FreeType() failed");
goto failed;
}
error = FT_New_Memory_Face(
textp->library,
IBMPlexMono_Medium,
sizeof(IBMPlexMono_Medium),
0,
&textp->face);
if (error) {
set_error("FT_New_Memory_Face() failed");
goto failed;
}
error = FT_Set_Pixel_Sizes(
textp->face,
25,
25);
if (error) {
set_error("FT_Set_Pixel_Sizes() failed");
goto failed;
}
}
return true;
failed:
free(textp);
return false;
}
static void draw_rect(uint8_t *buf, int stride, int height, int x, int y, unsigned int rect_width, unsigned int rect_height) {
uint8_t *Y = buf;
uint8_t *U = Y + stride * height;
uint8_t *V = U + (stride / 2) * (height / 2);
const uint8_t color[3] = {0, 128, 128};
uint32_t opacity = 45;
for (unsigned int src_y = 0; src_y < rect_height; src_y++) {
for (unsigned int src_x = 0; src_x < rect_width; src_x++) {
unsigned int dest_x = x + src_x;
unsigned int dest_y = y + src_y;
int i1 = dest_y*stride + dest_x;
int i2 = dest_y/2*stride/2 + dest_x/2;
Y[i1] = ((color[0] * opacity) + (uint32_t)Y[i1] * (255 - opacity)) / 255;
U[i2] = ((color[1] * opacity) + (uint32_t)U[i2] * (255 - opacity)) / 255;
V[i2] = ((color[2] * opacity) + (uint32_t)V[i2] * (255 - opacity)) / 255;
}
}
}
static void draw_bitmap(uint8_t *buf, int stride, int height, const FT_Bitmap *bitmap, int x, int y) {
uint8_t *Y = buf;
uint8_t *U = Y + stride * height;
uint8_t *V = U + (stride / 2) * (height / 2);
for (unsigned int src_y = 0; src_y < bitmap->rows; src_y++) {
for (unsigned int src_x = 0; src_x < bitmap->width; src_x++) {
uint8_t v = bitmap->buffer[src_y*bitmap->pitch + src_x];
if (v != 0) {
unsigned int dest_x = x + src_x;
unsigned int dest_y = y + src_y;
int i1 = dest_y*stride + dest_x;
int i2 = dest_y/2*stride/2 + dest_x/2;
uint32_t opacity = (uint32_t)v;
Y[i1] = (uint8_t)(((uint32_t)v * opacity + (uint32_t)Y[i1] * (255 - opacity)) / 255);
U[i2] = (uint8_t)((128 * opacity + (uint32_t)U[i2] * (255 - opacity)) / 255);
V[i2] = (uint8_t)((128 * opacity + (uint32_t)V[i2] * (255 - opacity)) / 255);
}
}
}
}
static int get_text_width(FT_Face face, const char *text) {
int ret = 0;
for (const char *ptr = text; *ptr != 0x00; ptr++) {
int error = FT_Load_Char(face, *ptr, FT_LOAD_RENDER);
if (error) {
continue;
}
ret += face->glyph->advance.x >> 6;
}
return ret;
}
void text_draw(text_t *text, uint8_t *buf, int stride, int height) {
text_priv_t *textp = (text_priv_t *)text;
if (textp->enabled) {
time_t timer = time(NULL);
struct tm *tm_info = localtime(&timer);
char buffer[255];
memset(buffer, 0, sizeof(buffer));
strftime(buffer, 255, textp->text_overlay, tm_info);
draw_rect(
buf,
stride,
height,
7,
7,
get_text_width(textp->face, buffer) + 10,
34);
int x = 12;
int y = 33;
for (const char *ptr = buffer; *ptr != 0x00; ptr++) {
int error = FT_Load_Char(textp->face, *ptr, FT_LOAD_RENDER);
if (error) {
continue;
}
draw_bitmap(
buf,
stride,
height,
&textp->face->glyph->bitmap,
x + textp->face->glyph->bitmap_left,
y - textp->face->glyph->bitmap_top);
x += textp->face->glyph->advance.x >> 6;
}
}
}

15
internal/rpicamera/exe/text.h

@ -0,0 +1,15 @@
#ifndef __TEXT_H__
#define __TEXT_H__
#include <stdint.h>
#include <stdbool.h>
#include "parameters.h"
typedef void text_t;
const char *text_get_error();
bool text_create(const parameters_t *params, text_t **text);
void text_draw(text_t *text, uint8_t *buf, int stride, int height);
#endif

8432
internal/rpicamera/exe/text_font.h

File diff suppressed because it is too large Load Diff

60
internal/rpicamera/params.go

@ -2,33 +2,35 @@ package rpicamera
// Params is a set of camera parameters. // Params is a set of camera parameters.
type Params struct { type Params struct {
CameraID int CameraID int
Width int Width int
Height int Height int
HFlip bool HFlip bool
VFlip bool VFlip bool
Brightness float64 Brightness float64
Contrast float64 Contrast float64
Saturation float64 Saturation float64
Sharpness float64 Sharpness float64
Exposure string Exposure string
AWB string AWB string
Denoise string Denoise string
Shutter int Shutter int
Metering string Metering string
Gain float64 Gain float64
EV float64 EV float64
ROI string ROI string
TuningFile string TuningFile string
Mode string Mode string
FPS int FPS int
IDRPeriod int IDRPeriod int
Bitrate int Bitrate int
Profile string Profile string
Level string Level string
AfMode string AfMode string
AfRange string AfRange string
AfSpeed string AfSpeed string
LensPosition float64 LensPosition float64
AfWindow string AfWindow string
TextOverlayEnable bool
TextOverlay string
} }

5
internal/rpicamera/rpicamera.go

@ -5,6 +5,7 @@ package rpicamera
import ( import (
_ "embed" _ "embed"
"encoding/base64"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
@ -66,7 +67,7 @@ func serializeParams(p Params) []byte {
ret := make([]string, nf) ret := make([]string, nf)
for i := 0; i < nf; i++ { for i := 0; i < nf; i++ {
entry := rt.Field(i).Name + "=" entry := rt.Field(i).Name + ":"
f := rv.Field(i) f := rv.Field(i)
switch f.Kind() { switch f.Kind() {
@ -77,7 +78,7 @@ func serializeParams(p Params) []byte {
entry += strconv.FormatFloat(f.Float(), 'f', -1, 64) entry += strconv.FormatFloat(f.Float(), 'f', -1, 64)
case reflect.String: case reflect.String:
entry += f.String() entry += base64.StdEncoding.EncodeToString([]byte(f.String()))
case reflect.Bool: case reflect.Bool:
if f.Bool() { if f.Bool() {

5
rtsp-simple-server.yml

@ -353,6 +353,11 @@ paths:
# Specifies the autofocus window, in the form x,y,width,height where the coordinates # Specifies the autofocus window, in the form x,y,width,height where the coordinates
# are given as a proportion of the entire image. # are given as a proportion of the entire image.
rpiCameraAfWindow: rpiCameraAfWindow:
# enables printing text on each frame.
rpiCameraTextOverlayEnable: false
# text that is printed on each frame.
# format is the one of the strftime() function.
rpiCameraTextOverlay: '%Y-%m-%d %H:%M:%S - MediaMTX'
# Username required to publish. # Username required to publish.
# SHA256-hashed values can be inserted with the "sha256:" prefix. # SHA256-hashed values can be inserted with the "sha256:" prefix.

4
scripts/binaries.mk

@ -1,14 +1,14 @@
define DOCKERFILE_BINARIES define DOCKERFILE_BINARIES
FROM $(RPI32_IMAGE) AS rpicamera32 FROM $(RPI32_IMAGE) AS rpicamera32
RUN ["cross-build-start"] RUN ["cross-build-start"]
RUN apt update && apt install -y --no-install-recommends g++ pkg-config make libcamera-dev patchelf RUN apt update && apt install -y --no-install-recommends g++ pkg-config make libcamera-dev libfreetype-dev patchelf
WORKDIR /s/internal/rpicamera WORKDIR /s/internal/rpicamera
COPY internal/rpicamera . COPY internal/rpicamera .
RUN cd exe && make -j$$(nproc) RUN cd exe && make -j$$(nproc)
FROM $(RPI64_IMAGE) AS rpicamera64 FROM $(RPI64_IMAGE) AS rpicamera64
RUN ["cross-build-start"] RUN ["cross-build-start"]
RUN apt update && apt install -y --no-install-recommends g++ pkg-config make libcamera-dev patchelf RUN apt update && apt install -y --no-install-recommends g++ pkg-config make libcamera-dev libfreetype-dev patchelf
WORKDIR /s/internal/rpicamera WORKDIR /s/internal/rpicamera
COPY internal/rpicamera . COPY internal/rpicamera .
RUN cd exe && make -j$$(nproc) RUN cd exe && make -j$$(nproc)

4
scripts/dockerhub.mk

@ -8,7 +8,7 @@ export DOCKERFILE_DOCKERHUB
define DOCKERFILE_DOCKERHUB_RPI_32 define DOCKERFILE_DOCKERHUB_RPI_32
FROM $(RPI32_IMAGE) AS base FROM $(RPI32_IMAGE) AS base
RUN apt update && apt install -y --no-install-recommends libcamera0 RUN apt update && apt install -y --no-install-recommends libcamera0 libfreetype6
ARG BINARY ARG BINARY
ADD $$BINARY / ADD $$BINARY /
ENTRYPOINT [ "/rtsp-simple-server" ] ENTRYPOINT [ "/rtsp-simple-server" ]
@ -17,7 +17,7 @@ export DOCKERFILE_DOCKERHUB_RPI_32
define DOCKERFILE_DOCKERHUB_RPI_64 define DOCKERFILE_DOCKERHUB_RPI_64
FROM $(RPI64_IMAGE) FROM $(RPI64_IMAGE)
RUN apt update && apt install -y --no-install-recommends libcamera0 RUN apt update && apt install -y --no-install-recommends libcamera0 libfreetype6
ARG BINARY ARG BINARY
ADD $$BINARY / ADD $$BINARY /
ENTRYPOINT [ "/rtsp-simple-server" ] ENTRYPOINT [ "/rtsp-simple-server" ]

Loading…
Cancel
Save