@@ -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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -250,6 +250,10 @@ components:
|
||||
type: number
|
||||
rpiCameraAfWindow:
|
||||
type: string
|
||||
rpiCameraTextOverlayEnable:
|
||||
type: boolean
|
||||
rpiCameraTextOverlay:
|
||||
type: string
|
||||
|
||||
# authentication
|
||||
publishUser:
|
||||
|
||||
@@ -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 {
|
||||
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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = \
|
||||
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
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef __BASE64_H__
|
||||
#define __BASE64_H__
|
||||
|
||||
char* base64_decode(const char *data);
|
||||
|
||||
#endif
|
||||
@@ -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 {
|
||||
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) {
|
||||
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_
|
||||
|
||||
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_
|
||||
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) {
|
||||
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);
|
||||
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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) {
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 int pipe_video_fd;
|
||||
static pthread_mutex_t pipe_video_mutex;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -53,6 +62,12 @@ int main() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
ok = text_create(¶ms, &text);
|
||||
if (!ok) {
|
||||
pipe_write_error(pipe_video_fd, "text_create(): %s", text_get_error());
|
||||
return 5;
|
||||
}
|
||||
|
||||
ok = encoder_create(
|
||||
¶ms,
|
||||
camera_get_mode_stride(cam),
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
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
|
||||
} 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
|
||||
} 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
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,6 +5,7 @@ package rpicamera
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -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 {
|
||||
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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
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" ]
|
||||
|
||||
Reference in New Issue
Block a user