From c5059fa7a08bd6bb79a27fc60e6b36963a804682 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Wed, 14 Aug 2024 23:24:17 +0200 Subject: [PATCH] move RPI Camera component into dedicated repository (#3656) --- .dockerignore | 3 +- .gitignore | 3 +- README.md | 20 - internal/protocols/rpicamera/exe/Makefile | 57 -- internal/protocols/rpicamera/exe/base64.c | 87 --- internal/protocols/rpicamera/exe/base64.h | 6 - internal/protocols/rpicamera/exe/camera.cpp | 499 ------------------ internal/protocols/rpicamera/exe/camera.h | 31 -- internal/protocols/rpicamera/exe/encoder.c | 337 ------------ internal/protocols/rpicamera/exe/encoder.h | 15 - internal/protocols/rpicamera/exe/main.c | 116 ---- internal/protocols/rpicamera/exe/parameters.c | 210 -------- internal/protocols/rpicamera/exe/parameters.h | 64 --- internal/protocols/rpicamera/exe/pipe.c | 44 -- internal/protocols/rpicamera/exe/pipe.h | 12 - .../protocols/rpicamera/exe/sensor_mode.c | 27 - .../protocols/rpicamera/exe/sensor_mode.h | 15 - internal/protocols/rpicamera/exe/text.c | 171 ------ internal/protocols/rpicamera/exe/text.h | 15 - internal/protocols/rpicamera/exe/window.c | 30 -- internal/protocols/rpicamera/exe/window.h | 15 - .../protocols/rpicamera/rpicamera_disabled.go | 33 -- internal/servers/hls/hlsjsdownloader/main.go | 6 +- .../rpicamera/camera.go} | 28 +- .../rpicamera/camera_disabled.go | 24 + internal/staticsources/rpicamera/component.go | 3 + .../staticsources/rpicamera/component_32.go | 11 + .../staticsources/rpicamera/component_64.go | 11 + .../rpicamera/mtxrpicamdownloader/VERSION | 1 + .../rpicamera/mtxrpicamdownloader/main.go | 53 ++ .../rpicamera/params.go | 47 +- .../rpicamera/params_serialize.go | 48 ++ .../rpicamera/pipe.go | 4 +- internal/staticsources/rpicamera/source.go | 13 +- scripts/binaries.mk | 41 +- 35 files changed, 189 insertions(+), 1911 deletions(-) delete mode 100644 internal/protocols/rpicamera/exe/Makefile delete mode 100644 internal/protocols/rpicamera/exe/base64.c delete mode 100644 internal/protocols/rpicamera/exe/base64.h delete mode 100644 internal/protocols/rpicamera/exe/camera.cpp delete mode 100644 internal/protocols/rpicamera/exe/camera.h delete mode 100644 internal/protocols/rpicamera/exe/encoder.c delete mode 100644 internal/protocols/rpicamera/exe/encoder.h delete mode 100644 internal/protocols/rpicamera/exe/main.c delete mode 100644 internal/protocols/rpicamera/exe/parameters.c delete mode 100644 internal/protocols/rpicamera/exe/parameters.h delete mode 100644 internal/protocols/rpicamera/exe/pipe.c delete mode 100644 internal/protocols/rpicamera/exe/pipe.h delete mode 100644 internal/protocols/rpicamera/exe/sensor_mode.c delete mode 100644 internal/protocols/rpicamera/exe/sensor_mode.h delete mode 100644 internal/protocols/rpicamera/exe/text.c delete mode 100644 internal/protocols/rpicamera/exe/text.h delete mode 100644 internal/protocols/rpicamera/exe/window.c delete mode 100644 internal/protocols/rpicamera/exe/window.h delete mode 100644 internal/protocols/rpicamera/rpicamera_disabled.go rename internal/{protocols/rpicamera/rpicamera.go => staticsources/rpicamera/camera.go} (88%) create mode 100644 internal/staticsources/rpicamera/camera_disabled.go create mode 100644 internal/staticsources/rpicamera/component.go create mode 100644 internal/staticsources/rpicamera/component_32.go create mode 100644 internal/staticsources/rpicamera/component_64.go create mode 100644 internal/staticsources/rpicamera/mtxrpicamdownloader/VERSION create mode 100644 internal/staticsources/rpicamera/mtxrpicamdownloader/main.go rename internal/{protocols => staticsources}/rpicamera/params.go (53%) create mode 100644 internal/staticsources/rpicamera/params_serialize.go rename internal/{protocols => staticsources}/rpicamera/pipe.go (93%) diff --git a/.dockerignore b/.dockerignore index 516b0488..5be57d00 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,5 +4,4 @@ /coverage*.txt /apidocs/*.html /internal/servers/hls/hls.min.js -/internal/protocols/rpicamera/exe/text_font.* -/internal/protocols/rpicamera/exe/exe +/internal/staticsources/rpicamera/mtxrpicam_* diff --git a/.gitignore b/.gitignore index cb096347..eba11185 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ /coverage*.txt /apidocs/*.html /internal/servers/hls/hls.min.js -/internal/protocols/rpicamera/exe/text_font.* -/internal/protocols/rpicamera/exe/exe +/internal/staticsources/rpicamera/mtxrpicam_* diff --git a/README.md b/README.md index 567479ec..c5b86d61 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,6 @@ _rtsp-simple-server_ has been rebranded as _MediaMTX_. The reason is pretty obvi * [Encryption](#encryption-1) * [Compile from source](#compile-from-source) * [Standard](#standard) - * [Raspberry Pi](#raspberry-pi) * [OpenWrt](#openwrt-1) * [Cross compile](#cross-compile) * [Compile for all supported platforms](#compile-for-all-supported-platforms) @@ -2047,25 +2046,6 @@ CGO_ENABLED=0 go build . The command will produce the `mediamtx` binary. -### Raspberry Pi - -The server can be compiled with native support for the Raspberry Pi Camera. Compilation must be performed on a Raspberry Pi, with the following dependencies: - -* Go ≥ 1.22 -* `libcamera-dev` -* `libfreetype-dev` -* `xxd` - -Download the repository, open a terminal in it and run: - -```sh -make -C internal/protocols/rpicamera/exe -j$(nproc) -go generate ./... -go build -tags rpicamera . -``` - -The command will produce the `mediamtx` binary. - ### OpenWrt The compilation procedure is the same as the standard one. On the OpenWrt device, install git and Go: diff --git a/internal/protocols/rpicamera/exe/Makefile b/internal/protocols/rpicamera/exe/Makefile deleted file mode 100644 index 65c5e796..00000000 --- a/internal/protocols/rpicamera/exe/Makefile +++ /dev/null @@ -1,57 +0,0 @@ -CFLAGS = \ - -Ofast \ - -Werror \ - -Wall \ - -Wextra \ - -Wno-unused-parameter \ - -Wno-unused-result \ - $$(pkg-config --cflags freetype2) - -CXXFLAGS = \ - -Ofast \ - -Werror \ - -Wall \ - -Wextra \ - -Wno-unused-parameter \ - -Wno-unused-result \ - -std=c++17 \ - $$(pkg-config --cflags libcamera) - -LDFLAGS = \ - -s \ - -pthread \ - $$(pkg-config --libs freetype2) \ - $$(pkg-config --libs libcamera) - -OBJS = \ - base64.o \ - camera.o \ - encoder.o \ - main.o \ - parameters.o \ - pipe.o \ - sensor_mode.o \ - text.o \ - window.o - -all: exe - -TEXT_FONT_URL = https://github.com/IBM/plex/raw/v6.4.2/IBM-Plex-Mono/fonts/complete/ttf/IBMPlexMono-Medium.ttf -TEXT_FONT_SHA256 = 0bede3debdea8488bbb927f8f0650d915073209734a67fe8cd5a3320b572511c - -text_font.ttf: - wget -O text_font.tmp $(TEXT_FONT_URL) - H=$$(sha256sum text_font.tmp | awk '{ print $$1 }'); [ "$$H" = "$(TEXT_FONT_SHA256)" ] || { echo "hash mismatch; got $$H, expected $(TEXT_FONT_SHA256)"; exit 1; } - mv text_font.tmp $@ - -text_font.h: text_font.ttf - xxd --include $< > text_font.h - -%.o: %.c text_font.h - $(CC) $(CFLAGS) -c $< -o $@ - -%.o: %.cpp - $(CXX) $(CXXFLAGS) -c $< -o $@ - -exe: $(OBJS) - $(CXX) $^ $(LDFLAGS) -o $@ diff --git a/internal/protocols/rpicamera/exe/base64.c b/internal/protocols/rpicamera/exe/base64.c deleted file mode 100644 index 5079bb21..00000000 --- a/internal/protocols/rpicamera/exe/base64.c +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -#include -#include - -#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; -}; diff --git a/internal/protocols/rpicamera/exe/base64.h b/internal/protocols/rpicamera/exe/base64.h deleted file mode 100644 index 5a656aed..00000000 --- a/internal/protocols/rpicamera/exe/base64.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef __BASE64_H__ -#define __BASE64_H__ - -char* base64_decode(const char *data); - -#endif diff --git a/internal/protocols/rpicamera/exe/camera.cpp b/internal/protocols/rpicamera/exe/camera.cpp deleted file mode 100644 index 73701a61..00000000 --- a/internal/protocols/rpicamera/exe/camera.cpp +++ /dev/null @@ -1,499 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "camera.h" - -using libcamera::CameraManager; -using libcamera::CameraConfiguration; -using libcamera::Camera; -using libcamera::ColorSpace; -using libcamera::ControlList; -using libcamera::FrameBufferAllocator; -using libcamera::FrameBuffer; -using libcamera::PixelFormat; -using libcamera::Rectangle; -using libcamera::Request; -using libcamera::Size; -using libcamera::Span; -using libcamera::Stream; -using libcamera::StreamRole; -using libcamera::StreamConfiguration; -using libcamera::Transform; - -namespace controls = libcamera::controls; -namespace formats = libcamera::formats; -namespace properties = libcamera::properties; - -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 *camera_get_error() { - return errbuf; -} - -// https://github.com/raspberrypi/libcamera-apps/blob/dd97618a25523c2c4aa58f87af5f23e49aa6069c/core/libcamera_app.cpp#L42 -static PixelFormat mode_to_pixel_format(sensor_mode_t *mode) { - static std::vector, PixelFormat>> table = { - { {8, false}, formats::SBGGR8 }, - { {8, true}, formats::SBGGR8 }, - { {10, false}, formats::SBGGR10 }, - { {10, true}, formats::SBGGR10_CSI2P }, - { {12, false}, formats::SBGGR12 }, - { {12, true}, formats::SBGGR12_CSI2P }, - }; - - auto it = std::find_if(table.begin(), table.end(), [&mode] (auto &m) { - return mode->bit_depth == m.first.first && mode->packed == m.first.second; }); - if (it != table.end()) { - return it->second; - } - - return formats::SBGGR12_CSI2P; -} - -struct CameraPriv { - const parameters_t *params; - camera_frame_cb frame_cb; - std::unique_ptr camera_manager; - std::shared_ptr camera; - Stream *video_stream; - std::unique_ptr allocator; - std::vector> requests; - std::mutex ctrls_mutex; - std::unique_ptr ctrls; - std::map mapped_buffers; -}; - -static int get_v4l2_colorspace(std::optional const &cs) { - if (cs == ColorSpace::Rec709) { - return V4L2_COLORSPACE_REC709; - } - 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; -} - -// https://github.com/raspberrypi/libcamera-apps/blob/a6267d51949d0602eedf60f3ddf8c6685f652812/core/options.cpp#L101 -static void set_hdr(bool hdr) { - bool ok = false; - for (int i = 0; i < 4 && !ok; i++) - { - std::string dev("/dev/v4l-subdev"); - dev += (char)('0' + i); - int fd = open(dev.c_str(), O_RDWR, 0); - if (fd < 0) - continue; - - v4l2_control ctrl { V4L2_CID_WIDE_DYNAMIC_RANGE, hdr }; - ok = !ioctl(fd, VIDIOC_S_CTRL, &ctrl); - close(fd); - } -} - -bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_t **cam) { - std::unique_ptr camp = std::make_unique(); - - set_hdr(params->hdr); - - if (strcmp(params->log_level, "debug") == 0) { - setenv("LIBCAMERA_LOG_LEVELS", "*:DEBUG", 1); - } else if (strcmp(params->log_level, "info") == 0) { - setenv("LIBCAMERA_LOG_LEVELS", "*:INFO", 1); - } else if (strcmp(params->log_level, "warn") == 0) { - setenv("LIBCAMERA_LOG_LEVELS", "*:WARN", 1); - } else { // error - setenv("LIBCAMERA_LOG_LEVELS", "*:ERROR", 1); - } - - // We make sure to set the environment variable before libcamera init - setenv("LIBCAMERA_RPI_TUNING_FILE", params->tuning_file, 1); - - camp->camera_manager = std::make_unique(); - int ret = camp->camera_manager->start(); - if (ret != 0) { - set_error("CameraManager.start() failed"); - return false; - } - - std::vector> cameras = camp->camera_manager->cameras(); - auto rem = std::remove_if(cameras.begin(), cameras.end(), - [](auto &cam) { return cam->id().find("/usb") != std::string::npos; }); - cameras.erase(rem, cameras.end()); - if (params->camera_id >= cameras.size()){ - set_error("selected camera is not available"); - return false; - } - - camp->camera = camp->camera_manager->get(cameras[params->camera_id]->id()); - if (camp->camera == NULL) { - set_error("CameraManager.get() failed"); - return false; - } - - ret = camp->camera->acquire(); - if (ret != 0) { - set_error("Camera.acquire() failed"); - return false; - } - - std::vector stream_roles = { StreamRole::VideoRecording }; - if (params->mode != NULL) { - stream_roles.push_back(StreamRole::Raw); - } - - std::unique_ptr conf = camp->camera->generateConfiguration(stream_roles); - if (conf == NULL) { - set_error("Camera.generateConfiguration() failed"); - return false; - } - - StreamConfiguration &video_stream_conf = conf->at(0); - video_stream_conf.size = libcamera::Size(params->width, params->height); - video_stream_conf.pixelFormat = formats::YUV420; - video_stream_conf.bufferCount = params->buffer_count; - if (params->width >= 1280 || params->height >= 720) { - video_stream_conf.colorSpace = ColorSpace::Rec709; - } else { - video_stream_conf.colorSpace = ColorSpace::Smpte170m; - } - - if (params->mode != NULL) { - StreamConfiguration &raw_stream_conf = conf->at(1); - raw_stream_conf.size = Size(params->mode->width, params->mode->height); - raw_stream_conf.pixelFormat = mode_to_pixel_format(params->mode); - raw_stream_conf.bufferCount = video_stream_conf.bufferCount; - } - - conf->transform = Transform::Identity; - if (params->h_flip) { - conf->transform = Transform::HFlip * conf->transform; - } - if (params->v_flip) { - conf->transform = Transform::VFlip * conf->transform; - } - - CameraConfiguration::Status vstatus = conf->validate(); - if (vstatus == CameraConfiguration::Invalid) { - set_error("StreamConfiguration.validate() failed"); - return false; - } - - int res = camp->camera->configure(conf.get()); - if (res != 0) { - set_error("Camera.configure() failed"); - return false; - } - - camp->video_stream = video_stream_conf.stream(); - - for (unsigned int i = 0; i < params->buffer_count; i++) { - std::unique_ptr request = camp->camera->createRequest((uint64_t)camp.get()); - if (request == NULL) { - set_error("createRequest() failed"); - return false; - } - camp->requests.push_back(std::move(request)); - } - - camp->allocator = std::make_unique(camp->camera); - for (StreamConfiguration &stream_conf : *conf) { - Stream *stream = stream_conf.stream(); - - res = camp->allocator->allocate(stream); - if (res < 0) { - set_error("allocate() failed"); - return false; - } - - int i = 0; - for (const std::unique_ptr &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"); - return false; - } - } - } - - camp->params = params; - camp->frame_cb = frame_cb; - *cam = camp.release(); - - return true; -} - -static int buffer_size(const std::vector &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; - } - - CameraPriv *camp = (CameraPriv *)request->cookie(); - - FrameBuffer *buffer = request->buffers().at(camp->video_stream); - - 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); - - { - std::lock_guard lock(camp->ctrls_mutex); - request->controls() = *camp->ctrls; - camp->ctrls->clear(); - } - - camp->camera->queueRequest(request); -} - -int camera_get_mode_stride(camera_t *cam) { - CameraPriv *camp = (CameraPriv *)cam; - return camp->video_stream->configuration().stride; -} - -int camera_get_mode_colorspace(camera_t *cam) { - CameraPriv *camp = (CameraPriv *)cam; - return get_v4l2_colorspace(camp->video_stream->configuration().colorSpace); -} - -static void fill_dynamic_controls(ControlList *ctrls, const parameters_t *params) { - ctrls->set(controls::Brightness, params->brightness); - ctrls->set(controls::Contrast, params->contrast); - ctrls->set(controls::Saturation, params->saturation); - ctrls->set(controls::Sharpness, params->sharpness); - - int exposure_mode; - if (strcmp(params->exposure, "short") == 0) { - exposure_mode = controls::ExposureShort; - } else if (strcmp(params->exposure, "long") == 0) { - exposure_mode = controls::ExposureLong; - } else if (strcmp(params->exposure, "custom") == 0) { - exposure_mode = controls::ExposureCustom; - } else { - exposure_mode = controls::ExposureNormal; - } - ctrls->set(controls::AeExposureMode, exposure_mode); - - int awb_mode; - if (strcmp(params->awb, "incandescent") == 0) { - awb_mode = controls::AwbIncandescent; - } else if (strcmp(params->awb, "tungsten") == 0) { - awb_mode = controls::AwbTungsten; - } else if (strcmp(params->awb, "fluorescent") == 0) { - awb_mode = controls::AwbFluorescent; - } else if (strcmp(params->awb, "indoor") == 0) { - awb_mode = controls::AwbIndoor; - } else if (strcmp(params->awb, "daylight") == 0) { - awb_mode = controls::AwbDaylight; - } else if (strcmp(params->awb, "cloudy") == 0) { - awb_mode = controls::AwbCloudy; - } else if (strcmp(params->awb, "custom") == 0) { - awb_mode = controls::AwbCustom; - } else { - awb_mode = controls::AwbAuto; - } - ctrls->set(controls::AwbMode, awb_mode); - - if (params->awb_gain_red > 0 && params->awb_gain_blue > 0) { - ctrls->set(controls::ColourGains, - Span({params->awb_gain_red, params->awb_gain_blue})); - } - - int denoise_mode; - if (strcmp(params->denoise, "cdn_off") == 0) { - denoise_mode = controls::draft::NoiseReductionModeMinimal; - } else if (strcmp(params->denoise, "cdn_hq") == 0) { - denoise_mode = controls::draft::NoiseReductionModeHighQuality; - } else if (strcmp(params->denoise, "cdn_fast") == 0) { - denoise_mode = controls::draft::NoiseReductionModeFast; - } else { - denoise_mode = controls::draft::NoiseReductionModeOff; - } - ctrls->set(controls::draft::NoiseReductionMode, denoise_mode); - - ctrls->set(controls::ExposureTime, params->shutter); - - int metering_mode; - if (strcmp(params->metering, "spot") == 0) { - metering_mode = controls::MeteringSpot; - } else if (strcmp(params->metering, "matrix") == 0) { - metering_mode = controls::MeteringMatrix; - } else if (strcmp(params->metering, "custom") == 0) { - metering_mode = controls::MeteringCustom; - } else { - metering_mode = controls::MeteringCentreWeighted; - } - ctrls->set(controls::AeMeteringMode, metering_mode); - - ctrls->set(controls::AnalogueGain, params->gain); - - ctrls->set(controls::ExposureValue, params->ev); - - int64_t frame_time = (int64_t)(((float)1000000) / params->fps); - ctrls->set(controls::FrameDurationLimits, Span({ frame_time, frame_time })); -} - -bool camera_start(camera_t *cam) { - CameraPriv *camp = (CameraPriv *)cam; - - camp->ctrls = std::make_unique(controls::controls); - - fill_dynamic_controls(camp->ctrls.get(), camp->params); - - if (camp->camera->controls().count(&controls::AfMode) > 0) { - if (camp->params->af_window != NULL) { - std::optional opt = camp->camera->properties().get(properties::ScalerCropMaximum); - Rectangle sensor_area; - try { - sensor_area = opt.value(); - } catch(const std::bad_optional_access& exc) { - set_error("get(ScalerCropMaximum) failed"); - return false; - } - - Rectangle afwindows_rectangle[1]; - - afwindows_rectangle[0] = Rectangle( - camp->params->af_window->x * sensor_area.width, - camp->params->af_window->y * sensor_area.height, - camp->params->af_window->width * sensor_area.width, - camp->params->af_window->height * sensor_area.height); - - afwindows_rectangle[0].translateBy(sensor_area.topLeft()); - camp->ctrls->set(controls::AfMetering, controls::AfMeteringWindows); - camp->ctrls->set(controls::AfWindows, afwindows_rectangle); - } - - int af_mode; - if (strcmp(camp->params->af_mode, "manual") == 0) { - af_mode = controls::AfModeManual; - } else if (strcmp(camp->params->af_mode, "continuous") == 0) { - af_mode = controls::AfModeContinuous; - } else { - af_mode = controls::AfModeAuto; - } - camp->ctrls->set(controls::AfMode, af_mode); - - int af_range; - if (strcmp(camp->params->af_range, "macro") == 0) { - af_range = controls::AfRangeMacro; - } else if (strcmp(camp->params->af_range, "full") == 0) { - af_range = controls::AfRangeFull; - } else { - af_range = controls::AfRangeNormal; - } - camp->ctrls->set(controls::AfRange, af_range); - - int af_speed; - if (strcmp(camp->params->af_range, "fast") == 0) { - af_speed = controls::AfSpeedFast; - } else { - af_speed = controls::AfSpeedNormal; - } - camp->ctrls->set(controls::AfSpeed, af_speed); - - if (strcmp(camp->params->af_mode, "auto") == 0) { - camp->ctrls->set(controls::AfTrigger, controls::AfTriggerStart); - } else if (strcmp(camp->params->af_mode, "manual") == 0) { - camp->ctrls->set(controls::LensPosition, camp->params->lens_position); - } - } - - if (camp->params->roi != NULL) { - std::optional opt = camp->camera->properties().get(properties::ScalerCropMaximum); - Rectangle sensor_area; - try { - sensor_area = opt.value(); - } catch(const std::bad_optional_access& exc) { - set_error("get(ScalerCropMaximum) failed"); - return false; - } - - Rectangle crop( - camp->params->roi->x * sensor_area.width, - camp->params->roi->y * sensor_area.height, - camp->params->roi->width * sensor_area.width, - camp->params->roi->height * sensor_area.height); - crop.translateBy(sensor_area.topLeft()); - camp->ctrls->set(controls::ScalerCrop, crop); - } - - int res = camp->camera->start(camp->ctrls.get()); - if (res != 0) { - set_error("Camera.start() failed"); - return false; - } - - camp->ctrls->clear(); - - camp->camera->requestCompleted.connect(on_request_complete); - - for (std::unique_ptr &request : camp->requests) { - int res = camp->camera->queueRequest(request.get()); - if (res != 0) { - set_error("Camera.queueRequest() failed"); - return false; - } - } - - return true; -} - -void camera_reload_params(camera_t *cam, const parameters_t *params) { - CameraPriv *camp = (CameraPriv *)cam; - - std::lock_guard lock(camp->ctrls_mutex); - fill_dynamic_controls(camp->ctrls.get(), params); -} diff --git a/internal/protocols/rpicamera/exe/camera.h b/internal/protocols/rpicamera/exe/camera.h deleted file mode 100644 index 467a7cc7..00000000 --- a/internal/protocols/rpicamera/exe/camera.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef __CAMERA_H__ -#define __CAMERA_H__ - -#include "parameters.h" - -typedef void camera_t; - -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" { -#endif - -const char *camera_get_error(); -bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_t **cam); -int camera_get_mode_stride(camera_t *cam); -int camera_get_mode_colorspace(camera_t *cam); -bool camera_start(camera_t *cam); -void camera_reload_params(camera_t *cam, const parameters_t *params); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/internal/protocols/rpicamera/exe/encoder.c b/internal/protocols/rpicamera/exe/encoder.c deleted file mode 100644 index 0bfeb7b5..00000000 --- a/internal/protocols/rpicamera/exe/encoder.c +++ /dev/null @@ -1,337 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "encoder.h" - -#define DEVICE "/dev/video11" -#define POLL_TIMEOUT_MS 200 - -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 *encoder_get_error() { - return errbuf; -} - -typedef struct { - const parameters_t *params; - int fd; - void **capture_buffers; - int cur_buffer; - encoder_output_cb output_cb; - pthread_t output_thread; - bool ts_initialized; - uint64_t start_ts; -} encoder_priv_t; - -static void *output_thread(void *userdata) { - encoder_priv_t *encp = (encoder_priv_t *)userdata; - - while (true) { - struct pollfd p = { encp->fd, POLLIN, 0 }; - int res = poll(&p, 1, POLL_TIMEOUT_MS); - if (res == -1) { - fprintf(stderr, "output_thread(): poll() failed\n"); - exit(1); - } - - if (p.revents & POLLIN) { - struct v4l2_buffer buf = {0}; - struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; - buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - buf.memory = V4L2_MEMORY_DMABUF; - buf.length = 1; - buf.m.planes = planes; - int res = ioctl(encp->fd, VIDIOC_DQBUF, &buf); - if (res != 0) { - fprintf(stderr, "output_thread(): ioctl(VIDIOC_DQBUF) failed\n"); - exit(1); - } - - memset(&buf, 0, sizeof(buf)); - memset(planes, 0, sizeof(planes)); - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - buf.memory = V4L2_MEMORY_MMAP; - buf.length = 1; - buf.m.planes = planes; - res = ioctl(encp->fd, VIDIOC_DQBUF, &buf); - if (res == 0) { - uint64_t ts = ((uint64_t)buf.timestamp.tv_sec * (uint64_t)1000000) + (uint64_t)buf.timestamp.tv_usec; - - if (!encp->ts_initialized) { - encp->ts_initialized = true; - encp->start_ts = ts; - } - - ts -= encp->start_ts; - - const uint8_t *bufmem = (const uint8_t *)encp->capture_buffers[buf.index]; - int bufsize = buf.m.planes[0].bytesused; - encp->output_cb(ts, bufmem, bufsize); - - int index = buf.index; - int length = buf.m.planes[0].length; - - struct v4l2_buffer buf = {0}; - struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; - buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - buf.memory = V4L2_MEMORY_MMAP; - buf.index = index; - buf.length = 1; - buf.m.planes = planes; - buf.m.planes[0].bytesused = 0; - buf.m.planes[0].length = length; - int res = ioctl(encp->fd, VIDIOC_QBUF, &buf); - if (res < 0) { - fprintf(stderr, "output_thread(): ioctl(VIDIOC_QBUF) failed\n"); - exit(1); - } - } - } - } - - return NULL; -} - -static bool fill_dynamic_params(int fd, const parameters_t *params) { - struct v4l2_control ctrl = {0}; - ctrl.id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD; - ctrl.value = params->idr_period; - int res = ioctl(fd, VIDIOC_S_CTRL, &ctrl); - if (res != 0) { - set_error("unable to set IDR period"); - return false; - } - - ctrl.id = V4L2_CID_MPEG_VIDEO_BITRATE; - ctrl.value = params->bitrate; - res = ioctl(fd, VIDIOC_S_CTRL, &ctrl); - if (res != 0) { - set_error("unable to set bitrate"); - return false; - } - - return true; -} - -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"); - goto failed; - } - - bool res2 = fill_dynamic_params(encp->fd, params); - if (!res2) { - goto failed; - } - - struct v4l2_control ctrl = {0}; - ctrl.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE; - ctrl.value = params->profile; - int res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); - if (res != 0) { - set_error("unable to set profile"); - goto failed; - } - - ctrl.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL; - ctrl.value = params->level; - res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); - if (res != 0) { - set_error("unable to set level"); - goto failed; - } - - ctrl.id = V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER; - ctrl.value = 0; - res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); - if (res != 0) { - set_error("unable to set REPEAT_SEQ_HEADER"); - goto failed; - } - - struct v4l2_format fmt = {0}; - fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - fmt.fmt.pix_mp.width = params->width; - fmt.fmt.pix_mp.height = params->height; - fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420; - fmt.fmt.pix_mp.plane_fmt[0].bytesperline = stride; - fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; - fmt.fmt.pix_mp.colorspace = colorspace; - fmt.fmt.pix_mp.num_planes = 1; - res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt); - if (res != 0) { - set_error("unable to set output format"); - goto failed; - } - - memset(&fmt, 0, sizeof(fmt)); - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - fmt.fmt.pix_mp.width = params->width; - fmt.fmt.pix_mp.height = params->height; - fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; - fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; - fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT; - fmt.fmt.pix_mp.num_planes = 1; - fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0; - fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10; - res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt); - if (res != 0) { - set_error("unable to set capture format"); - goto failed; - } - - struct v4l2_streamparm parm = {0}; - parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - parm.parm.output.timeperframe.numerator = 1; - parm.parm.output.timeperframe.denominator = params->fps; - res = ioctl(encp->fd, VIDIOC_S_PARM, &parm); - if (res != 0) { - set_error("unable to set fps"); - goto failed; - } - - struct v4l2_requestbuffers reqbufs = {0}; - reqbufs.count = params->buffer_count; - reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - reqbufs.memory = V4L2_MEMORY_DMABUF; - res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs); - if (res != 0) { - set_error("unable to set output buffers"); - goto failed; - } - - memset(&reqbufs, 0, sizeof(reqbufs)); - reqbufs.count = params->capture_buffer_count; - reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - reqbufs.memory = V4L2_MEMORY_MMAP; - res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs); - if (res != 0) { - set_error("unable to set capture buffers"); - goto failed; - } - - encp->capture_buffers = malloc(sizeof(void *) * reqbufs.count); - - for (unsigned int i = 0; i < reqbufs.count; i++) { - struct v4l2_plane planes[VIDEO_MAX_PLANES]; - - struct v4l2_buffer buffer = {0}; - buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - buffer.memory = V4L2_MEMORY_MMAP; - buffer.index = i; - buffer.length = 1; - buffer.m.planes = planes; - int res = ioctl(encp->fd, VIDIOC_QUERYBUF, &buffer); - if (res != 0) { - set_error("unable to query buffer"); - goto failed; - } - - encp->capture_buffers[i] = mmap( - 0, - buffer.m.planes[0].length, - PROT_READ | PROT_WRITE, MAP_SHARED, - encp->fd, - buffer.m.planes[0].m.mem_offset); - if (encp->capture_buffers[i] == MAP_FAILED) { - set_error("mmap() failed"); - goto failed; - } - - res = ioctl(encp->fd, VIDIOC_QBUF, &buffer); - if (res != 0) { - set_error("ioctl(VIDIOC_QBUF) failed"); - goto failed; - } - } - - enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - res = ioctl(encp->fd, VIDIOC_STREAMON, &type); - if (res != 0) { - set_error("unable to activate output stream"); - 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"); - } - - encp->params = params; - encp->cur_buffer = 0; - encp->output_cb = output_cb; - encp->ts_initialized = false; - - 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) { - encoder_priv_t *encp = (encoder_priv_t *)enc; - - int index = encp->cur_buffer++; - encp->cur_buffer %= encp->params->buffer_count; - - struct v4l2_buffer buf = {0}; - struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; - buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - buf.index = index; - buf.field = V4L2_FIELD_NONE; - buf.memory = V4L2_MEMORY_DMABUF; - buf.length = 1; - buf.timestamp.tv_sec = timestamp_us / 1000000; - buf.timestamp.tv_usec = timestamp_us % 1000000; - buf.m.planes = planes; - buf.m.planes[0].m.fd = buffer_fd; - buf.m.planes[0].bytesused = size; - buf.m.planes[0].length = size; - int res = ioctl(encp->fd, VIDIOC_QBUF, &buf); - if (res != 0) { - fprintf(stderr, "encoder_encode(): ioctl(VIDIOC_QBUF) failed\n"); - // it happens when the raspberry is under pressure. do not exit. - } -} - -void encoder_reload_params(encoder_t *enc, const parameters_t *params) { - encoder_priv_t *encp = (encoder_priv_t *)enc; - - fill_dynamic_params(encp->fd, params); -} diff --git a/internal/protocols/rpicamera/exe/encoder.h b/internal/protocols/rpicamera/exe/encoder.h deleted file mode 100644 index eb1e7928..00000000 --- a/internal/protocols/rpicamera/exe/encoder.h +++ /dev/null @@ -1,15 +0,0 @@ -#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); - -const char *encoder_get_error(); -bool encoder_create(const parameters_t *params, int stride, int colorspace, encoder_output_cb output_cb, encoder_t **enc); -void encoder_encode(encoder_t *enc, int buffer_fd, size_t size, int64_t timestamp_us); -void encoder_reload_params(encoder_t *enc, const parameters_t *params); - -#endif diff --git a/internal/protocols/rpicamera/exe/main.c b/internal/protocols/rpicamera/exe/main.c deleted file mode 100644 index bb379215..00000000 --- a/internal/protocols/rpicamera/exe/main.c +++ /dev/null @@ -1,116 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "parameters.h" -#include "pipe.h" -#include "camera.h" -#include "text.h" -#include "encoder.h" - -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); -} - -static void on_encoder_output(uint64_t ts, const uint8_t *buf, uint64_t size) { - pthread_mutex_lock(&pipe_video_mutex); - pipe_write_buf(pipe_video_fd, ts, buf, size); - pthread_mutex_unlock(&pipe_video_mutex); -} - -int main() { - int pipe_conf_fd = atoi(getenv("PIPE_CONF_FD")); - pipe_video_fd = atoi(getenv("PIPE_VIDEO_FD")); - - uint8_t *buf; - uint32_t n = pipe_read(pipe_conf_fd, &buf); - - parameters_t params; - bool ok = parameters_unserialize(¶ms, &buf[1], n-1); - free(buf); - if (!ok) { - pipe_write_error(pipe_video_fd, "parameters_unserialize(): %s", parameters_get_error()); - return 5; - } - - pthread_mutex_init(&pipe_video_mutex, NULL); - pthread_mutex_lock(&pipe_video_mutex); - - camera_t *cam; - ok = camera_create( - ¶ms, - on_frame, - &cam); - if (!ok) { - pipe_write_error(pipe_video_fd, "camera_create(): %s", camera_get_error()); - 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), - camera_get_mode_colorspace(cam), - on_encoder_output, - &enc); - if (!ok) { - pipe_write_error(pipe_video_fd, "encoder_create(): %s", encoder_get_error()); - return 5; - } - - ok = camera_start(cam); - if (!ok) { - pipe_write_error(pipe_video_fd, "camera_start(): %s", camera_get_error()); - return 5; - } - - pipe_write_ready(pipe_video_fd); - pthread_mutex_unlock(&pipe_video_mutex); - - while (true) { - uint8_t *buf; - uint32_t n = pipe_read(pipe_conf_fd, &buf); - - switch (buf[0]) { - case 'e': - return 0; - - case 'c': - { - parameters_t params; - bool ok = parameters_unserialize(¶ms, &buf[1], n-1); - free(buf); - if (!ok) { - printf("skipping reloading parameters since they are invalid: %s\n", parameters_get_error()); - continue; - } - camera_reload_params(cam, ¶ms); - encoder_reload_params(enc, ¶ms); - parameters_destroy(¶ms); - } - } - } - - return 0; -} diff --git a/internal/protocols/rpicamera/exe/parameters.c b/internal/protocols/rpicamera/exe/parameters.c deleted file mode 100644 index 9805d2f3..00000000 --- a/internal/protocols/rpicamera/exe/parameters.c +++ /dev/null @@ -1,210 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -#include "base64.h" -#include "parameters.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 *parameters_get_error() { - return errbuf; -} - -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; - - while (true) { - char *entry = strsep(&tmp, " "); - if (entry == NULL) { - break; - } - - char *key = strsep(&entry, ":"); - char *val = strsep(&entry, ":"); - - if (strcmp(key, "LogLevel") == 0) { - params->log_level = base64_decode(val); - } else if (strcmp(key, "CameraID") == 0) { - params->camera_id = atoi(val); - } else if (strcmp(key, "Width") == 0) { - params->width = atoi(val); - } else if (strcmp(key, "Height") == 0) { - params->height = atoi(val); - } else if (strcmp(key, "HFlip") == 0) { - params->h_flip = (strcmp(val, "1") == 0); - } else if (strcmp(key, "VFlip") == 0) { - params->v_flip = (strcmp(val, "1") == 0); - } else if (strcmp(key, "Brightness") == 0) { - params->brightness = atof(val); - } else if (strcmp(key, "Contrast") == 0) { - params->contrast = atof(val); - } else if (strcmp(key, "Saturation") == 0) { - params->saturation = atof(val); - } else if (strcmp(key, "Sharpness") == 0) { - params->sharpness = atof(val); - } else if (strcmp(key, "Exposure") == 0) { - params->exposure = base64_decode(val); - } else if (strcmp(key, "AWB") == 0) { - params->awb = base64_decode(val); - } else if (strcmp(key, "AWBGainRed") == 0) { - params->awb_gain_red = atof(val); - } else if (strcmp(key, "AWBGainBlue") == 0) { - params->awb_gain_blue = atof(val); - } else if (strcmp(key, "Denoise") == 0) { - params->denoise = base64_decode(val); - } else if (strcmp(key, "Shutter") == 0) { - params->shutter = atoi(val); - } else if (strcmp(key, "Metering") == 0) { - 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) { - char *decoded_val = base64_decode(val); - if (strlen(decoded_val) != 0) { - params->roi = malloc(sizeof(window_t)); - bool ok = window_load(decoded_val, params->roi); - if (!ok) { - set_error("invalid ROI"); - free(decoded_val); - goto failed; - } - } - free(decoded_val); - } else if (strcmp(key, "HDR") == 0) { - params->hdr = (strcmp(val, "1") == 0); - } else if (strcmp(key, "TuningFile") == 0) { - params->tuning_file = base64_decode(val); - } else if (strcmp(key, "Mode") == 0) { - char *decoded_val = base64_decode(val); - if (strlen(decoded_val) != 0) { - params->mode = malloc(sizeof(sensor_mode_t)); - bool ok = sensor_mode_load(decoded_val, params->mode); - if (!ok) { - set_error("invalid sensor mode"); - free(decoded_val); - goto failed; - } - } - free(decoded_val); - } else if (strcmp(key, "FPS") == 0) { - params->fps = atof(val); - } else if (strcmp(key, "IDRPeriod") == 0) { - params->idr_period = atoi(val); - } else if (strcmp(key, "Bitrate") == 0) { - params->bitrate = atoi(val); - } else if (strcmp(key, "Profile") == 0) { - char *decoded_val = base64_decode(val); - if (strcmp(decoded_val, "baseline") == 0) { - params->profile = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; - } 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) { - 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(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 = base64_decode(val); - } else if (strcmp(key, "AfRange") == 0) { - params->af_range = base64_decode(val); - } else if (strcmp(key, "AfSpeed") == 0) { - params->af_speed = base64_decode(val); - } else if (strcmp(key, "LensPosition") == 0) { - params->lens_position = atof(val); - } else if (strcmp(key, "AfWindow") == 0) { - char *decoded_val = base64_decode(val); - if (strlen(decoded_val) != 0) { - params->af_window = malloc(sizeof(window_t)); - bool ok = window_load(decoded_val, params->af_window); - if (!ok) { - set_error("invalid AfWindow"); - free(decoded_val); - goto failed; - } - } - 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); - } - } - - free(tmp); - - params->buffer_count = 6; - 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); - } - 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); - } -} diff --git a/internal/protocols/rpicamera/exe/parameters.h b/internal/protocols/rpicamera/exe/parameters.h deleted file mode 100644 index 36ced18b..00000000 --- a/internal/protocols/rpicamera/exe/parameters.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef __PARAMETERS_H__ -#define __PARAMETERS_H__ - -#include -#include - -#include "window.h" -#include "sensor_mode.h" - -typedef struct { - char *log_level; - unsigned int camera_id; - unsigned int width; - unsigned int height; - bool h_flip; - bool v_flip; - float brightness; - float contrast; - float saturation; - float sharpness; - char *exposure; - char *awb; - float awb_gain_red; - float awb_gain_blue; - char *denoise; - unsigned int shutter; - char *metering; - float gain; - float ev; - window_t *roi; - bool hdr; - char *tuning_file; - sensor_mode_t *mode; - float fps; - unsigned int idr_period; - unsigned int bitrate; - unsigned int profile; - unsigned int level; - char *af_mode; - char *af_range; - char *af_speed; - float lens_position; - window_t *af_window; - bool text_overlay_enable; - char *text_overlay; - - // private - unsigned int buffer_count; - unsigned int capture_buffer_count; -} parameters_t; - -#ifdef __cplusplus -extern "C" { -#endif - -const char *parameters_get_error(); -bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf_size); -void parameters_destroy(parameters_t *params); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/internal/protocols/rpicamera/exe/pipe.c b/internal/protocols/rpicamera/exe/pipe.c deleted file mode 100644 index 38d2437f..00000000 --- a/internal/protocols/rpicamera/exe/pipe.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "pipe.h" - -void pipe_write_error(int fd, const char *format, ...) { - char buf[256]; - buf[0] = 'e'; - va_list args; - va_start(args, format); - vsnprintf(&buf[1], 255, format, args); - uint32_t n = strlen(buf); - write(fd, &n, 4); - write(fd, buf, n); -} - -void pipe_write_ready(int fd) { - char buf[] = {'r'}; - uint32_t n = 1; - write(fd, &n, 4); - write(fd, buf, n); -} - -void pipe_write_buf(int fd, uint64_t ts, const uint8_t *buf, uint32_t n) { - char head[] = {'b'}; - n += 1 + sizeof(uint64_t); - write(fd, &n, 4); - write(fd, head, 1); - write(fd, &ts, sizeof(uint64_t)); - write(fd, buf, n - 1 - sizeof(uint64_t)); -} - -uint32_t pipe_read(int fd, uint8_t **pbuf) { - uint32_t n; - read(fd, &n, 4); - - *pbuf = malloc(n); - read(fd, *pbuf, n); - return n; -} diff --git a/internal/protocols/rpicamera/exe/pipe.h b/internal/protocols/rpicamera/exe/pipe.h deleted file mode 100644 index 26630452..00000000 --- a/internal/protocols/rpicamera/exe/pipe.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef __PIPE_H__ -#define __PIPE_H__ - -#include -#include - -void pipe_write_error(int fd, const char *format, ...); -void pipe_write_ready(int fd); -void pipe_write_buf(int fd, uint64_t ts, const uint8_t *buf, uint32_t n); -uint32_t pipe_read(int fd, uint8_t **pbuf); - -#endif diff --git a/internal/protocols/rpicamera/exe/sensor_mode.c b/internal/protocols/rpicamera/exe/sensor_mode.c deleted file mode 100644 index 55932e21..00000000 --- a/internal/protocols/rpicamera/exe/sensor_mode.c +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include - -#include "sensor_mode.h" - -bool sensor_mode_load(const char *encoded, sensor_mode_t *mode) { - char p; - int n = sscanf(encoded, "%u:%u:%u:%c", &(mode->width), &(mode->height), &(mode->bit_depth), &p); - if (n < 2) { - return false; - } - - if (n < 4) { - mode->packed = true; - } else if (toupper(p) == 'P') { - mode->packed = true; - } else if (toupper(p) == 'U') { - mode->packed = false; - } - - if (n < 3) { - mode->bit_depth = 12; - } - - return true; -} diff --git a/internal/protocols/rpicamera/exe/sensor_mode.h b/internal/protocols/rpicamera/exe/sensor_mode.h deleted file mode 100644 index 6306ee87..00000000 --- a/internal/protocols/rpicamera/exe/sensor_mode.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef __SENSOR_MODE_H__ -#define __SENSOR_MODE_H__ - -#include - -typedef struct { - int width; - int height; - int bit_depth; - bool packed; -} sensor_mode_t; - -bool sensor_mode_load(const char *encoded, sensor_mode_t *mode); - -#endif diff --git a/internal/protocols/rpicamera/exe/text.c b/internal/protocols/rpicamera/exe/text.c deleted file mode 100644 index 4ec8593d..00000000 --- a/internal/protocols/rpicamera/exe/text.c +++ /dev/null @@ -1,171 +0,0 @@ -#include - -#include -#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, - text_font_ttf, - sizeof(text_font_ttf), - 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; - } - } -} diff --git a/internal/protocols/rpicamera/exe/text.h b/internal/protocols/rpicamera/exe/text.h deleted file mode 100644 index 235bf7a0..00000000 --- a/internal/protocols/rpicamera/exe/text.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef __TEXT_H__ -#define __TEXT_H__ - -#include -#include - -#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 diff --git a/internal/protocols/rpicamera/exe/window.c b/internal/protocols/rpicamera/exe/window.c deleted file mode 100644 index 83e65358..00000000 --- a/internal/protocols/rpicamera/exe/window.c +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include - -#include "window.h" - -bool window_load(const char *encoded, window_t *window) { - float vals[4]; - int i = 0; - char *token = strtok((char *)encoded, ","); - while (token != NULL) { - vals[i] = atof(token); - if (vals[i] < 0 || vals[i] > 1) { - return false; - } - - i++; - token = strtok(NULL, ","); - } - - if (i != 4) { - return false; - } - - window->x = vals[0]; - window->y = vals[1]; - window->width = vals[2]; - window->height = vals[3]; - - return true; -} diff --git a/internal/protocols/rpicamera/exe/window.h b/internal/protocols/rpicamera/exe/window.h deleted file mode 100644 index f27e470a..00000000 --- a/internal/protocols/rpicamera/exe/window.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef __WINDOW_H__ -#define __WINDOW_H__ - -#include - -typedef struct { - float x; - float y; - float width; - float height; -} window_t; - -bool window_load(const char *encoded, window_t *window); - -#endif diff --git a/internal/protocols/rpicamera/rpicamera_disabled.go b/internal/protocols/rpicamera/rpicamera_disabled.go deleted file mode 100644 index 3bf10ce5..00000000 --- a/internal/protocols/rpicamera/rpicamera_disabled.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !rpicamera -// +build !rpicamera - -// Package rpicamera allows to interact with a Raspberry Pi Camera. -package rpicamera - -import ( - "fmt" - "time" -) - -// Cleanup cleanups files created by the camera implementation. -func Cleanup() { -} - -// RPICamera is a RPI Camera reader. -type RPICamera struct { - Params Params - OnData func(time.Duration, [][]byte) -} - -// Initialize initializes a RPICamera. -func (c *RPICamera) Initialize() error { - return fmt.Errorf("server was compiled without support for the Raspberry Pi Camera") -} - -// Close closes a RPICamera. -func (c *RPICamera) Close() { -} - -// ReloadParams reloads the camera parameters. -func (c *RPICamera) ReloadParams(_ Params) { -} diff --git a/internal/servers/hls/hlsjsdownloader/main.go b/internal/servers/hls/hlsjsdownloader/main.go index 2f1fb557..3dcf3df5 100644 --- a/internal/servers/hls/hlsjsdownloader/main.go +++ b/internal/servers/hls/hlsjsdownloader/main.go @@ -20,7 +20,6 @@ func do() error { if err != nil { return err } - version := strings.TrimSpace(string(buf)) log.Printf("downloading hls.js version %s...", version) @@ -40,12 +39,11 @@ func do() error { return err } - hashBuf, err := os.ReadFile("./hlsjsdownloader/HASH") + buf, err = os.ReadFile("./hlsjsdownloader/HASH") if err != nil { return err } - - str := strings.TrimSpace(string(hashBuf)) + str := strings.TrimSpace(string(buf)) hash, err := hex.DecodeString(str) if err != nil { diff --git a/internal/protocols/rpicamera/rpicamera.go b/internal/staticsources/rpicamera/camera.go similarity index 88% rename from internal/protocols/rpicamera/rpicamera.go rename to internal/staticsources/rpicamera/camera.go index d99e305b..46bddb86 100644 --- a/internal/protocols/rpicamera/rpicamera.go +++ b/internal/staticsources/rpicamera/camera.go @@ -1,11 +1,10 @@ -//go:build rpicamera -// +build rpicamera +//go:build (linux && arm) || (linux && arm64) +// +build linux,arm linux,arm64 package rpicamera import ( "debug/elf" - _ "embed" "fmt" "os" "os/exec" @@ -19,12 +18,9 @@ import ( ) const ( - tempPathPrefix = "/dev/shm/rtspss-embeddedexe-" + tempPathPrefix = "/dev/shm/mediamtx-rpicamera-" ) -//go:embed exe/exe -var exeContent []byte - func startEmbeddedExe(content []byte, env []string) (*exec.Cmd, error) { tempPath := tempPathPrefix + strconv.FormatInt(time.Now().UnixNano(), 10) @@ -111,9 +107,8 @@ func checkLibraries64Bit() error { return nil } -// RPICamera is a RPI Camera reader. -type RPICamera struct { - Params Params +type camera struct { + Params params OnData func(time.Duration, [][]byte) cmd *exec.Cmd @@ -124,8 +119,7 @@ type RPICamera struct { readerDone chan error } -// Initialize initializes a RPICamera. -func (c *RPICamera) Initialize() error { +func (c *camera) initialize() error { if runtime.GOARCH == "arm" { err := checkLibraries64Bit() if err != nil { @@ -150,7 +144,7 @@ func (c *RPICamera) Initialize() error { "PIPE_VIDEO_FD=" + strconv.FormatInt(int64(c.pipeVideo.writeFD), 10), } - c.cmd, err = startEmbeddedExe(exeContent, env) + c.cmd, err = startEmbeddedExe(component, env) if err != nil { c.pipeConf.close() c.pipeVideo.close() @@ -194,7 +188,7 @@ func (c *RPICamera) Initialize() error { return nil } -func (c *RPICamera) Close() { +func (c *camera) close() { c.pipeConf.write([]byte{'e'}) <-c.waitDone c.pipeConf.close() @@ -202,11 +196,11 @@ func (c *RPICamera) Close() { <-c.readerDone } -func (c *RPICamera) ReloadParams(params Params) { +func (c *camera) reloadParams(params params) { c.pipeConf.write(append([]byte{'c'}, params.serialize()...)) } -func (c *RPICamera) readReady() error { +func (c *camera) readReady() error { buf, err := c.pipeVideo.read() if err != nil { return err @@ -224,7 +218,7 @@ func (c *RPICamera) readReady() error { } } -func (c *RPICamera) readData() error { +func (c *camera) readData() error { for { buf, err := c.pipeVideo.read() if err != nil { diff --git a/internal/staticsources/rpicamera/camera_disabled.go b/internal/staticsources/rpicamera/camera_disabled.go new file mode 100644 index 00000000..60a726e2 --- /dev/null +++ b/internal/staticsources/rpicamera/camera_disabled.go @@ -0,0 +1,24 @@ +//go:build !linux || (!arm && !arm64) +// +build !linux !arm,!arm64 + +package rpicamera + +import ( + "fmt" + "time" +) + +type camera struct { + Params params + OnData func(time.Duration, [][]byte) +} + +func (c *camera) initialize() error { + return fmt.Errorf("server was compiled without support for the Raspberry Pi Camera") +} + +func (c *camera) close() { +} + +func (c *camera) reloadParams(_ params) { +} diff --git a/internal/staticsources/rpicamera/component.go b/internal/staticsources/rpicamera/component.go new file mode 100644 index 00000000..918e554e --- /dev/null +++ b/internal/staticsources/rpicamera/component.go @@ -0,0 +1,3 @@ +package rpicamera + +//go:generate go run ./mtxrpicamdownloader diff --git a/internal/staticsources/rpicamera/component_32.go b/internal/staticsources/rpicamera/component_32.go new file mode 100644 index 00000000..0cdeffd9 --- /dev/null +++ b/internal/staticsources/rpicamera/component_32.go @@ -0,0 +1,11 @@ +//go:build linux && arm +// +build linux,arm + +package rpicamera + +import ( + _ "embed" +) + +//go:embed mtxrpicam_32 +var component []byte diff --git a/internal/staticsources/rpicamera/component_64.go b/internal/staticsources/rpicamera/component_64.go new file mode 100644 index 00000000..d1c562a0 --- /dev/null +++ b/internal/staticsources/rpicamera/component_64.go @@ -0,0 +1,11 @@ +//go:build linux && arm64 +// +build linux,arm64 + +package rpicamera + +import ( + _ "embed" +) + +//go:embed mtxrpicam_64 +var component []byte diff --git a/internal/staticsources/rpicamera/mtxrpicamdownloader/VERSION b/internal/staticsources/rpicamera/mtxrpicamdownloader/VERSION new file mode 100644 index 00000000..0ec25f75 --- /dev/null +++ b/internal/staticsources/rpicamera/mtxrpicamdownloader/VERSION @@ -0,0 +1 @@ +v1.0.0 diff --git a/internal/staticsources/rpicamera/mtxrpicamdownloader/main.go b/internal/staticsources/rpicamera/mtxrpicamdownloader/main.go new file mode 100644 index 00000000..6f8ee9bc --- /dev/null +++ b/internal/staticsources/rpicamera/mtxrpicamdownloader/main.go @@ -0,0 +1,53 @@ +// Package main contains an utility to download hls.js +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "os" + "strings" +) + +func do() error { + buf, err := os.ReadFile("./mtxrpicamdownloader/VERSION") + if err != nil { + return err + } + version := strings.TrimSpace(string(buf)) + + log.Printf("downloading mediamtx-rpicamera version %s...", version) + + for _, f := range []string{"mtxrpicam_32", "mtxrpicam_64"} { + res, err := http.Get("https://github.com/bluenviron/mediamtx-rpicamera/releases/download/" + version + "/" + f) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("bad status code: %v", res.StatusCode) + } + + buf, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + if err = os.WriteFile(f, buf, 0o644); err != nil { + return err + } + } + + log.Println("ok") + return nil +} + +func main() { + err := do() + if err != nil { + log.Printf("ERR: %v", err) + os.Exit(1) + } +} diff --git a/internal/protocols/rpicamera/params.go b/internal/staticsources/rpicamera/params.go similarity index 53% rename from internal/protocols/rpicamera/params.go rename to internal/staticsources/rpicamera/params.go index 5262d36f..c847a388 100644 --- a/internal/protocols/rpicamera/params.go +++ b/internal/staticsources/rpicamera/params.go @@ -1,14 +1,6 @@ package rpicamera -import ( - "encoding/base64" - "reflect" - "strconv" - "strings" -) - -// Params is a set of camera parameters. -type Params struct { +type params struct { LogLevel string CameraID int Width int @@ -45,40 +37,3 @@ type Params struct { TextOverlayEnable bool TextOverlay string } - -func (p Params) serialize() []byte { //nolint:unused - rv := reflect.ValueOf(p) - rt := rv.Type() - nf := rv.NumField() - ret := make([]string, nf) - - for i := 0; i < nf; i++ { - entry := rt.Field(i).Name + ":" - f := rv.Field(i) - - switch f.Kind() { - case reflect.Int: - entry += strconv.FormatInt(f.Int(), 10) - - case reflect.Float64: - entry += strconv.FormatFloat(f.Float(), 'f', -1, 64) - - case reflect.String: - entry += base64.StdEncoding.EncodeToString([]byte(f.String())) - - case reflect.Bool: - if f.Bool() { - entry += "1" - } else { - entry += "0" - } - - default: - panic("unhandled type") - } - - ret[i] = entry - } - - return []byte(strings.Join(ret, " ")) -} diff --git a/internal/staticsources/rpicamera/params_serialize.go b/internal/staticsources/rpicamera/params_serialize.go new file mode 100644 index 00000000..362f6f8f --- /dev/null +++ b/internal/staticsources/rpicamera/params_serialize.go @@ -0,0 +1,48 @@ +//go:build (linux && arm) || (linux && arm64) +// +build linux,arm linux,arm64 + +package rpicamera + +import ( + "encoding/base64" + "reflect" + "strconv" + "strings" +) + +func (p params) serialize() []byte { + rv := reflect.ValueOf(p) + rt := rv.Type() + nf := rv.NumField() + ret := make([]string, nf) + + for i := 0; i < nf; i++ { + entry := rt.Field(i).Name + ":" + f := rv.Field(i) + + switch f.Kind() { + case reflect.Int: + entry += strconv.FormatInt(f.Int(), 10) + + case reflect.Float64: + entry += strconv.FormatFloat(f.Float(), 'f', -1, 64) + + case reflect.String: + entry += base64.StdEncoding.EncodeToString([]byte(f.String())) + + case reflect.Bool: + if f.Bool() { + entry += "1" + } else { + entry += "0" + } + + default: + panic("unhandled type") + } + + ret[i] = entry + } + + return []byte(strings.Join(ret, " ")) +} diff --git a/internal/protocols/rpicamera/pipe.go b/internal/staticsources/rpicamera/pipe.go similarity index 93% rename from internal/protocols/rpicamera/pipe.go rename to internal/staticsources/rpicamera/pipe.go index 4b037e8c..926ad77c 100644 --- a/internal/protocols/rpicamera/pipe.go +++ b/internal/staticsources/rpicamera/pipe.go @@ -1,5 +1,5 @@ -//go:build rpicamera -// +build rpicamera +//go:build (linux && arm) || (linux && arm64) +// +build linux,arm linux,arm64 package rpicamera diff --git a/internal/staticsources/rpicamera/source.go b/internal/staticsources/rpicamera/source.go index 924a9ab3..1a4d49c6 100644 --- a/internal/staticsources/rpicamera/source.go +++ b/internal/staticsources/rpicamera/source.go @@ -10,13 +10,12 @@ import ( "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/protocols/rpicamera" "github.com/bluenviron/mediamtx/internal/stream" "github.com/bluenviron/mediamtx/internal/unit" ) -func paramsFromConf(logLevel conf.LogLevel, cnf *conf.Path) rpicamera.Params { - return rpicamera.Params{ +func paramsFromConf(logLevel conf.LogLevel, cnf *conf.Path) params { + return params{ LogLevel: func() string { switch logLevel { case conf.LogLevel(logger.Debug): @@ -110,15 +109,15 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { }) } - cam := &rpicamera.RPICamera{ + cam := &camera{ Params: paramsFromConf(s.LogLevel, params.Conf), OnData: onData, } - err := cam.Initialize() + err := cam.initialize() if err != nil { return err } - defer cam.Close() + defer cam.close() defer func() { if stream != nil { @@ -129,7 +128,7 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { for { select { case cnf := <-params.ReloadConf: - cam.ReloadParams(paramsFromConf(s.LogLevel, cnf)) + cam.reloadParams(paramsFromConf(s.LogLevel, cnf)) case <-params.Context.Done(): return nil diff --git a/scripts/binaries.mk b/scripts/binaries.mk index b49d755c..aaed26c0 100644 --- a/scripts/binaries.mk +++ b/scripts/binaries.mk @@ -1,20 +1,6 @@ BINARY_NAME = mediamtx 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 libfreetype-dev xxd wget -WORKDIR /s/internal/protocols/rpicamera/exe -COPY internal/protocols/rpicamera/exe . -RUN 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 libfreetype-dev xxd wget -WORKDIR /s/internal/protocols/rpicamera/exe -COPY internal/protocols/rpicamera/exe . -RUN make -j$$(nproc) - FROM $(BASE_IMAGE) AS build-base RUN apk add --no-cache zip make git tar WORKDIR /s @@ -29,38 +15,39 @@ RUN cp mediamtx.yml LICENSE tmp/ RUN go generate ./... FROM build-base AS build-windows-amd64 -RUN GOOS=windows GOARCH=amd64 go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME).exe +ENV GOOS=windows GOARCH=amd64 +RUN go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME).exe RUN cd tmp && zip -q ../binaries/$(BINARY_NAME)_$${VERSION}_windows_amd64.zip $(BINARY_NAME).exe mediamtx.yml LICENSE FROM build-base AS build-linux-amd64 -RUN GOOS=linux GOARCH=amd64 go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) +ENV GOOS=linux GOARCH=amd64 +RUN go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) RUN tar -C tmp -czf binaries/$(BINARY_NAME)_$${VERSION}_linux_amd64.tar.gz --owner=0 --group=0 $(BINARY_NAME) mediamtx.yml LICENSE FROM build-base AS build-darwin-amd64 -RUN GOOS=darwin GOARCH=amd64 go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) +ENV GOOS=darwin GOARCH=amd64 +RUN go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) RUN tar -C tmp -czf binaries/$(BINARY_NAME)_$${VERSION}_darwin_amd64.tar.gz --owner=0 --group=0 $(BINARY_NAME) mediamtx.yml LICENSE FROM build-base AS build-darwin-arm64 -RUN GOOS=darwin GOARCH=arm64 go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) +ENV GOOS=darwin GOARCH=arm64 +RUN go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) RUN tar -C tmp -czf binaries/$(BINARY_NAME)_$${VERSION}_darwin_arm64.tar.gz --owner=0 --group=0 $(BINARY_NAME) mediamtx.yml LICENSE FROM build-base AS build-linux-armv6 -COPY --from=rpicamera32 /s/internal/protocols/rpicamera/exe/exe internal/protocols/rpicamera/exe/ -RUN GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) -tags rpicamera +ENV GOOS=linux GOARCH=arm GOARM=6 +RUN go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) RUN tar -C tmp -czf binaries/$(BINARY_NAME)_$${VERSION}_linux_armv6.tar.gz --owner=0 --group=0 $(BINARY_NAME) mediamtx.yml LICENSE -RUN rm internal/protocols/rpicamera/exe/exe FROM build-base AS build-linux-armv7 -COPY --from=rpicamera32 /s/internal/protocols/rpicamera/exe/exe internal/protocols/rpicamera/exe/ -RUN GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) -tags rpicamera +ENV GOOS=linux GOARCH=arm GOARM=7 +RUN go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) RUN tar -C tmp -czf binaries/$(BINARY_NAME)_$${VERSION}_linux_armv7.tar.gz --owner=0 --group=0 $(BINARY_NAME) mediamtx.yml LICENSE -RUN rm internal/protocols/rpicamera/exe/exe FROM build-base AS build-linux-arm64 -COPY --from=rpicamera64 /s/internal/protocols/rpicamera/exe/exe internal/protocols/rpicamera/exe/ -RUN GOOS=linux GOARCH=arm64 go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) -tags rpicamera +ENV GOOS=linux GOARCH=arm64 +RUN go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) RUN tar -C tmp -czf binaries/$(BINARY_NAME)_$${VERSION}_linux_arm64v8.tar.gz --owner=0 --group=0 $(BINARY_NAME) mediamtx.yml LICENSE -RUN rm internal/protocols/rpicamera/exe/exe FROM $(BASE_IMAGE) COPY --from=build-windows-amd64 /s/binaries /s/binaries