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. 2
      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. 113
      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. 2
      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. @@ -573,7 +573,14 @@ The command will produce the `rtsp-simple-server` binary.
#### 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
cd internal/rpicamera/exe
@ -634,7 +641,10 @@ _rtsp-simple-server_ natively support the Raspberry Pi Camera, enabling high-qua @@ -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:
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.

4
apidocs/openapi.yaml

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

5
internal/conf/path.go

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

2
internal/core/rpicamera_source.go

@ -44,6 +44,8 @@ func paramsFromConf(cnf *conf.PathConf) rpicamera.Params { @@ -44,6 +44,8 @@ func paramsFromConf(cnf *conf.PathConf) rpicamera.Params {
AfSpeed: cnf.RPICameraAfSpeed,
LensPosition: cnf.RPICameraLensPosition,
AfWindow: cnf.RPICameraAfWindow,
TextOverlayEnable: cnf.RPICameraTextOverlayEnable,
TextOverlay: cnf.RPICameraTextOverlay,
}
}

10
internal/rpicamera/exe/Makefile

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

87
internal/rpicamera/exe/base64.c

@ -0,0 +1,87 @@ @@ -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 @@ @@ -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 @@ @@ -14,7 +14,6 @@
#include <libcamera/property_ids.h>
#include <linux/videodev2.h>
#include "parameters.h"
#include "camera.h"
using libcamera::CameraManager;
@ -81,6 +80,7 @@ struct CameraPriv { @@ -81,6 +80,7 @@ struct CameraPriv {
std::vector<std::unique_ptr<Request>> requests;
std::mutex ctrls_mutex;
std::unique_ptr<ControlList> ctrls;
std::map<FrameBuffer *, uint8_t *> mapped_buffers;
};
static int get_v4l2_colorspace(std::optional<ColorSpace> const &cs) {
@ -90,6 +90,22 @@ 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;
}
// 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) {
// We make sure to set the environment variable before libcamera init
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_ @@ -195,6 +211,11 @@ bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_
int i = 0;
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());
if (res != 0) {
set_error("addBuffer() failed");
@ -210,6 +231,14 @@ bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_ @@ -210,6 +231,14 @@ bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_
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) {
if (request->status() == Request::RequestCancelled) {
return;
@ -218,12 +247,14 @@ static void on_request_complete(Request *request) { @@ -218,12 +247,14 @@ static void on_request_complete(Request *request) {
CameraPriv *camp = (CameraPriv *)request->cookie();
FrameBuffer *buffer = request->buffers().at(camp->video_stream);
int size = 0;
for (const FrameBuffer::Plane &plane : buffer->planes()) {
size += plane.length;
}
uint64_t ts = buffer->metadata().timestamp / 1000;
camp->frame_cb(buffer->planes()[0].fd.get(), size, ts);
camp->frame_cb(
camp->mapped_buffers.at(buffer),
camp->video_stream->configuration().stride,
camp->video_stream->configuration().size.height,
buffer->planes()[0].fd.get(),
buffer_size(buffer->planes()),
buffer->metadata().timestamp / 1000);
request->reuse(Request::ReuseFlag::ReuseBuffers);

10
internal/rpicamera/exe/camera.h

@ -1,9 +1,17 @@ @@ -1,9 +1,17 @@
#ifndef __CAMERA_H__
#define __CAMERA_H__
#include "parameters.h"
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
extern "C" {

65
internal/rpicamera/exe/encoder.c

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

2
internal/rpicamera/exe/encoder.h

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
#ifndef __ENCODER_H__
#define __ENCODER_H__
#include "parameters.h"
typedef void encoder_t;
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 @@ @@ -9,13 +9,22 @@
#include "parameters.h"
#include "pipe.h"
#include "camera.h"
#include "text.h"
#include "encoder.h"
int pipe_video_fd;
pthread_mutex_t pipe_video_mutex;
encoder_t *enc;
static void on_frame(int buffer_fd, uint64_t size, uint64_t timestamp) {
static int pipe_video_fd;
static pthread_mutex_t pipe_video_mutex;
static text_t *text;
static encoder_t *enc;
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);
}
@ -53,6 +62,12 @@ int main() { @@ -53,6 +62,12 @@ int main() {
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(
&params,
camera_get_mode_stride(cam),

113
internal/rpicamera/exe/parameters.c

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
#include <linux/videodev2.h>
#include "base64.h"
#include "parameters.h"
static char errbuf[256];
@ -21,15 +22,20 @@ const char *parameters_get_error() { @@ -21,15 +22,20 @@ const char *parameters_get_error() {
}
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);
memcpy(tmp, buf, buf_size);
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 *val = strsep(&entry, "=");
char *key = strsep(&entry, ":");
char *val = strsep(&entry, ":");
if (strcmp(key, "CameraID") == 0) {
params->camera_id = atoi(val);
@ -50,43 +56,45 @@ bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf @@ -50,43 +56,45 @@ bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf
} else if (strcmp(key, "Sharpness") == 0) {
params->sharpness = atof(val);
} else if (strcmp(key, "Exposure") == 0) {
params->exposure = strdup(val);
params->exposure = base64_decode(val);
} else if (strcmp(key, "AWB") == 0) {
params->awb = strdup(val);
params->awb = base64_decode(val);
} else if (strcmp(key, "Denoise") == 0) {
params->denoise = strdup(val);
params->denoise = base64_decode(val);
} else if (strcmp(key, "Shutter") == 0) {
params->shutter = atoi(val);
} else if (strcmp(key, "Metering") == 0) {
params->metering = strdup(val);
params->metering = base64_decode(val);
} else if (strcmp(key, "Gain") == 0) {
params->gain = atof(val);
} else if (strcmp(key, "EV") == 0) {
params->ev = atof(val);
} 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));
bool ok = window_load(val, params->roi);
bool ok = window_load(decoded_val, params->roi);
if (!ok) {
set_error("invalid ROI");
return false;
free(decoded_val);
goto failed;
}
} else {
params->roi = NULL;
}
free(decoded_val);
} else if (strcmp(key, "TuningFile") == 0) {
params->tuning_file = strdup(val);
params->tuning_file = base64_decode(val);
} 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));
bool ok = sensor_mode_load(val, params->mode);
bool ok = sensor_mode_load(decoded_val, params->mode);
if (!ok) {
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) {
params->fps = atoi(val);
} else if (strcmp(key, "IDRPeriod") == 0) {
@ -94,40 +102,49 @@ bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf @@ -94,40 +102,49 @@ bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf
} else if (strcmp(key, "Bitrate") == 0) {
params->bitrate = atoi(val);
} 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;
} else if (strcmp(val, "main") == 0) {
} else if (strcmp(decoded_val, "main") == 0) {
params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN;
} else {
params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH;
}
free(decoded_val);
} 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;
} else if (strcmp(val, "4.1") == 0) {
} else if (strcmp(decoded_val, "4.1") == 0) {
params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_1;
} else {
params->level = V4L2_MPEG_VIDEO_H264_LEVEL_4_2;
}
free(decoded_val);
} else if (strcmp(key, "AfMode") == 0) {
params->af_mode = strdup(val);
params->af_mode = base64_decode(val);
} else if (strcmp(key, "AfRange") == 0) {
params->af_range = strdup(val);
params->af_range = base64_decode(val);
} else if (strcmp(key, "AfSpeed") == 0) {
params->af_speed = strdup(val);
params->af_speed = base64_decode(val);
} else if (strcmp(key, "LensPosition") == 0) {
params->lens_position = atof(val);
} 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));
bool ok = window_load(val, params->af_window);
bool ok = window_load(decoded_val, params->af_window);
if (!ok) {
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 @@ -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;
return true;
failed:
free(tmp);
parameters_destroy(params);
return false;
}
void parameters_destroy(parameters_t *params) {
if (params->exposure != NULL) {
free(params->exposure);
}
if (params->awb != NULL) {
free(params->awb);
}
if (params->denoise != NULL) {
free(params->denoise);
}
if (params->metering != NULL) {
free(params->metering);
free(params->tuning_file);
free(params->af_mode);
free(params->af_range);
free(params->af_speed);
}
if (params->roi != NULL) {
free(params->roi);
}
if (params->tuning_file != NULL) {
free(params->tuning_file);
}
if (params->mode != NULL) {
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) {
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 { @@ -37,6 +37,8 @@ typedef struct {
char *af_speed;
float lens_position;
window_t *af_window;
bool text_overlay_enable;
char *text_overlay;
// private
unsigned int buffer_count;

171
internal/rpicamera/exe/text.c

@ -0,0 +1,171 @@ @@ -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 @@ @@ -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

2
internal/rpicamera/params.go

@ -31,4 +31,6 @@ type Params struct { @@ -31,4 +31,6 @@ type Params struct {
AfSpeed string
LensPosition float64
AfWindow string
TextOverlayEnable bool
TextOverlay string
}

5
internal/rpicamera/rpicamera.go

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

5
rtsp-simple-server.yml

@ -353,6 +353,11 @@ paths: @@ -353,6 +353,11 @@ paths:
# Specifies the autofocus window, in the form x,y,width,height where the coordinates
# are given as a proportion of the entire image.
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.
# SHA256-hashed values can be inserted with the "sha256:" prefix.

4
scripts/binaries.mk

@ -1,14 +1,14 @@ @@ -1,14 +1,14 @@
define DOCKERFILE_BINARIES
FROM $(RPI32_IMAGE) AS rpicamera32
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
COPY internal/rpicamera .
RUN cd exe && make -j$$(nproc)
FROM $(RPI64_IMAGE) AS rpicamera64
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
COPY internal/rpicamera .
RUN cd exe && make -j$$(nproc)

4
scripts/dockerhub.mk

@ -8,7 +8,7 @@ export DOCKERFILE_DOCKERHUB @@ -8,7 +8,7 @@ export DOCKERFILE_DOCKERHUB
define DOCKERFILE_DOCKERHUB_RPI_32
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
ADD $$BINARY /
ENTRYPOINT [ "/rtsp-simple-server" ]
@ -17,7 +17,7 @@ export DOCKERFILE_DOCKERHUB_RPI_32 @@ -17,7 +17,7 @@ export DOCKERFILE_DOCKERHUB_RPI_32
define DOCKERFILE_DOCKERHUB_RPI_64
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
ADD $$BINARY /
ENTRYPOINT [ "/rtsp-simple-server" ]

Loading…
Cancel
Save