Browse Source

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

pull/1636/head
Alessandro Ros 2 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. @@ -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)

60
internal/core/rpicamera_source.go

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

121
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) {
free(params->exposure);
free(params->awb);
free(params->denoise);
free(params->metering);
free(params->tuning_file);
free(params->af_mode);
free(params->af_range);
free(params->af_speed);
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);
}
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

60
internal/rpicamera/params.go

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