mirror of
https://github.com/pikvm/ustreamer.git
synced 2025-12-23 18:50:00 +00:00
v4p
This commit is contained in:
parent
5026108079
commit
3715c89ec8
5
Makefile
5
Makefile
@ -36,8 +36,7 @@ endif
|
|||||||
|
|
||||||
apps:
|
apps:
|
||||||
$(MAKE) -C src
|
$(MAKE) -C src
|
||||||
$(ECHO) ln -sf src/ustreamer.bin ustreamer
|
$(ECHO) ln -sf src/*.bin .
|
||||||
$(ECHO) ln -sf src/ustreamer-dump.bin ustreamer-dump
|
|
||||||
|
|
||||||
|
|
||||||
python:
|
python:
|
||||||
@ -119,7 +118,7 @@ clean-all: linters clean
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
|
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
|
||||||
rm -f ustreamer ustreamer-dump *.so
|
rm -f *.bin *.so
|
||||||
$(MAKE) -C src clean
|
$(MAKE) -C src clean
|
||||||
$(MAKE) -C python clean
|
$(MAKE) -C python clean
|
||||||
$(MAKE) -C janus clean
|
$(MAKE) -C janus clean
|
||||||
|
|||||||
35
src/Makefile
35
src/Makefile
@ -9,6 +9,7 @@ LDFLAGS ?=
|
|||||||
# =====
|
# =====
|
||||||
_USTR = ustreamer.bin
|
_USTR = ustreamer.bin
|
||||||
_DUMP = ustreamer-dump.bin
|
_DUMP = ustreamer-dump.bin
|
||||||
|
_V4P = ustreamer-v4p.bin
|
||||||
|
|
||||||
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
||||||
_LDFLAGS = $(LDFLAGS)
|
_LDFLAGS = $(LDFLAGS)
|
||||||
@ -32,9 +33,19 @@ _DUMP_SRCS = $(shell ls \
|
|||||||
dump/*.c \
|
dump/*.c \
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_V4P_LIBS = $(_COMMON_LIBS)
|
||||||
|
_V4P_SRCS = $(shell ls \
|
||||||
|
libs/*.c \
|
||||||
|
v4p/*.c \
|
||||||
|
)
|
||||||
|
|
||||||
_BUILD = build
|
_BUILD = build
|
||||||
|
|
||||||
|
|
||||||
|
_TARGETS = $(_USTR) $(_DUMP)
|
||||||
|
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
|
|
||||||
|
|
||||||
define optbool
|
define optbool
|
||||||
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
|
||||||
endef
|
endef
|
||||||
@ -69,19 +80,25 @@ override _CFLAGS += -DWITH_SETPROCTITLE
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
WITH_V4P ?= 0
|
||||||
|
ifneq ($(call optbool,$(WITH_V4P)),)
|
||||||
|
override _TARGETS += $(_V4P)
|
||||||
|
override _OBJS += $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
|
override _CFLAGS += $(shell pkg-config --cflags libdrm)
|
||||||
|
_V4P_LDFLAGS = $(shell pkg-config --libs libdrm)
|
||||||
|
endif
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
all: $(_USTR) $(_DUMP)
|
all: $(_TARGETS)
|
||||||
|
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||||
install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
for i in $(_TARGETS); do install -m 755 $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$$i); done
|
||||||
install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
|
||||||
|
|
||||||
|
|
||||||
install-strip: install
|
install-strip: install
|
||||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
|
for i in $(_TARGETS); do strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$$i); done
|
||||||
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
|
|
||||||
|
|
||||||
|
|
||||||
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
|
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
@ -94,6 +111,11 @@ $(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
|||||||
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
||||||
|
|
||||||
|
|
||||||
|
$(_V4P): $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
|
||||||
|
$(info == LD $@)
|
||||||
|
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_V4P_LDFLAGS) $(_V4P_LIBS)
|
||||||
|
|
||||||
|
|
||||||
$(_BUILD)/%.o: %.c
|
$(_BUILD)/%.o: %.c
|
||||||
$(info -- CC $<)
|
$(info -- CC $<)
|
||||||
$(ECHO) mkdir -p $(dir $@) || true
|
$(ECHO) mkdir -p $(dir $@) || true
|
||||||
@ -101,8 +123,7 @@ $(_BUILD)/%.o: %.c
|
|||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(_USTR) $(_DUMP) $(_BUILD)
|
rm -rf $(_USTR) $(_DUMP) $(_V4P) $(_BUILD)
|
||||||
|
|
||||||
|
|
||||||
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
|
||||||
-include $(_OBJS:%.o=%.d)
|
-include $(_OBJS:%.o=%.d)
|
||||||
|
|||||||
40
src/libs/types.h
Normal file
40
src/libs/types.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef ssize_t sz;
|
||||||
|
typedef int8_t s8;
|
||||||
|
typedef int16_t s16;
|
||||||
|
typedef int32_t s32;
|
||||||
|
typedef int64_t s64;
|
||||||
|
|
||||||
|
typedef unsigned uint;
|
||||||
|
typedef size_t uz;
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint16_t u16;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
613
src/v4p/drm.c
Normal file
613
src/v4p/drm.c
Normal file
@ -0,0 +1,613 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "drm.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
#include <xf86drm.h>
|
||||||
|
#include <xf86drmMode.h>
|
||||||
|
#include <drm_fourcc.h>
|
||||||
|
#include <libdrm/drm.h>
|
||||||
|
|
||||||
|
#include "../libs/types.h"
|
||||||
|
#include "../libs/tools.h"
|
||||||
|
#include "../libs/logging.h"
|
||||||
|
#include "../libs/frame.h"
|
||||||
|
|
||||||
|
#include "ftext.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_run);
|
||||||
|
static int _drm_expose_raw(us_drm_s *drm, const us_frame_s *frame);
|
||||||
|
static void _drm_cleanup(us_drm_s *drm);
|
||||||
|
static void _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz);
|
||||||
|
static int _drm_init_video(us_drm_s *drm);
|
||||||
|
static int _drm_init_buffers(us_drm_s *drm);
|
||||||
|
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
|
||||||
|
|
||||||
|
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs);
|
||||||
|
static const char *_connector_type_to_string(u32 type);
|
||||||
|
static float _get_refresh_rate(const drmModeModeInfo *mode);
|
||||||
|
|
||||||
|
|
||||||
|
#define _D_LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||||
|
#define _D_LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||||
|
#define _D_LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
|
||||||
|
#define _D_LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
|
||||||
|
#define _D_LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
|
||||||
|
us_drm_s *us_drm_init(const char *path, const char *port) {
|
||||||
|
us_drm_runtime_s *run;
|
||||||
|
US_CALLOC(run, 1);
|
||||||
|
run->fd = -1;
|
||||||
|
run->conn_type = -1;
|
||||||
|
run->conn_type_id = -1;
|
||||||
|
run->ft = us_ftext_init();
|
||||||
|
run->state = US_DRM_STATE_CLOSED;
|
||||||
|
|
||||||
|
us_drm_s *drm;
|
||||||
|
US_CALLOC(drm, 1);
|
||||||
|
drm->path = us_strdup(path);
|
||||||
|
drm->port = us_strdup(port);
|
||||||
|
drm->n_bufs = 4;
|
||||||
|
drm->timeout = 5;
|
||||||
|
drm->run = run;
|
||||||
|
return drm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void us_drm_destroy(us_drm_s *drm) {
|
||||||
|
_drm_cleanup(drm);
|
||||||
|
us_ftext_destroy(drm->run->ft);
|
||||||
|
US_DELETE(drm->run, free);
|
||||||
|
US_DELETE(drm->port, free);
|
||||||
|
US_DELETE(drm->path, free);
|
||||||
|
US_DELETE(drm, free); // cppcheck-suppress uselessAssignmentPtrArg
|
||||||
|
}
|
||||||
|
|
||||||
|
int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||||
|
us_drm_runtime_s *const run = drm->run;
|
||||||
|
|
||||||
|
_drm_ensure(drm, NULL, 0);
|
||||||
|
if (run->state != US_DRM_STATE_OK) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (run->has_vsync) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timeval timeout = {.tv_sec = drm->timeout};
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(run->fd, &fds);
|
||||||
|
|
||||||
|
_D_LOG_DEBUG("Calling select() for VSync ...");
|
||||||
|
const int result = select(run->fd + 1, &fds, NULL, NULL, &timeout);
|
||||||
|
if (result < 0) {
|
||||||
|
_D_LOG_PERROR("Can't select(%d) device for VSync", run->fd);
|
||||||
|
goto error;
|
||||||
|
} else if (result == 0) {
|
||||||
|
_D_LOG_ERROR("Device timeout while waiting VSync");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
drmEventContext ctx = {
|
||||||
|
.version = DRM_EVENT_CONTEXT_VERSION,
|
||||||
|
.page_flip_handler = _drm_vsync_callback,
|
||||||
|
};
|
||||||
|
_D_LOG_DEBUG("Handling DRM event (maybe VSync) ...");
|
||||||
|
if (drmHandleEvent(run->fd, &ctx) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't handle DRM event");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
_drm_cleanup(drm);
|
||||||
|
_D_LOG_ERROR("Device destroyed due an error (vsync)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int us_drm_expose(us_drm_s *drm, us_drm_expose_e ex, const us_frame_s *frame, float hz) {
|
||||||
|
us_drm_runtime_s *const run = drm->run;
|
||||||
|
|
||||||
|
_drm_ensure(drm, frame, hz);
|
||||||
|
if (run->state != US_DRM_STATE_OK) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const drmModeModeInfo *const mode = &run->mode;
|
||||||
|
bool msg_drawn = false;
|
||||||
|
|
||||||
|
# define DRAW_MSG(x_msg) { \
|
||||||
|
us_ftext_draw(run->ft, (x_msg), mode->hdisplay, mode->vdisplay); \
|
||||||
|
frame = run->ft->frame; \
|
||||||
|
msg_drawn = true; \
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame == NULL) {
|
||||||
|
switch (ex) {
|
||||||
|
case US_DRM_EXPOSE_NO_SIGNAL:
|
||||||
|
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >");
|
||||||
|
break;
|
||||||
|
case US_DRM_EXPOSE_BUSY:
|
||||||
|
DRAW_MSG("=== PiKVM ===\n \n< ONLINE IS ACTIVE >");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DRAW_MSG("=== PiKVM ===\n \n< ??? >");
|
||||||
|
}
|
||||||
|
} else if (mode->hdisplay != frame->width/* || mode->vdisplay != frame->height*/) {
|
||||||
|
// XXX: At least we'll try to show something instead of nothing ^^^
|
||||||
|
char msg[1024];
|
||||||
|
US_SNPRINTF(msg, 1023,
|
||||||
|
"=== PiKVM ==="
|
||||||
|
"\n \n< UNSUPPORTED RESOLUTION >"
|
||||||
|
"\n \n< %ux%up%.02f >"
|
||||||
|
"\n \nby this display",
|
||||||
|
frame->width, frame->height, hz);
|
||||||
|
DRAW_MSG(msg);
|
||||||
|
} else if (frame->format != V4L2_PIX_FMT_RGB24) {
|
||||||
|
DRAW_MSG(
|
||||||
|
"=== PiKVM ==="
|
||||||
|
"\n \n< UNSUPPORTED CAPTURE FORMAT >"
|
||||||
|
"\n \nIt shouldn't happen ever."
|
||||||
|
"\n \nPlease check the logs and report a bug:"
|
||||||
|
"\n \n- https://github.com/pikvm/pikvm -");
|
||||||
|
}
|
||||||
|
|
||||||
|
# undef DRAW_MSG
|
||||||
|
|
||||||
|
if (_drm_expose_raw(drm, frame) < 0) {
|
||||||
|
_drm_cleanup(drm);
|
||||||
|
_D_LOG_ERROR("Device destroyed due an error (expose)");
|
||||||
|
}
|
||||||
|
return (msg_drawn ? -1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_run) {
|
||||||
|
(void)fd;
|
||||||
|
(void)n_frame;
|
||||||
|
(void)sec;
|
||||||
|
(void)usec;
|
||||||
|
us_drm_runtime_s *const run = v_run;
|
||||||
|
run->has_vsync = true;
|
||||||
|
_D_LOG_DEBUG("Got VSync signal");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _drm_expose_raw(us_drm_s *drm, const us_frame_s *frame) {
|
||||||
|
us_drm_runtime_s *const run = drm->run;
|
||||||
|
us_drm_buffer_s *const buf = &run->bufs[run->next_n_buf];
|
||||||
|
|
||||||
|
_D_LOG_DEBUG("Exposing%s framebuffer n_buf=%u, vsync=%d ...",
|
||||||
|
(frame == NULL ? " EMPTY" : ""), run->next_n_buf, run->has_vsync);
|
||||||
|
|
||||||
|
run->has_vsync = false;
|
||||||
|
|
||||||
|
if (frame == NULL) {
|
||||||
|
memset(buf->data, 0, buf->allocated);
|
||||||
|
} else {
|
||||||
|
memcpy(buf->data, frame->data, us_min_u(frame->used, buf->allocated));
|
||||||
|
}
|
||||||
|
|
||||||
|
const int retval = drmModePageFlip(
|
||||||
|
run->fd, run->crtc_id, buf->id,
|
||||||
|
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
|
||||||
|
run);
|
||||||
|
if (retval == 0) {
|
||||||
|
run->next_n_buf = (run->next_n_buf + 1) % run->n_bufs;
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _drm_cleanup(us_drm_s *drm) {
|
||||||
|
us_drm_runtime_s *const run = drm->run;
|
||||||
|
|
||||||
|
_D_LOG_DEBUG("Cleaning up ...");
|
||||||
|
|
||||||
|
if (run->saved_crtc != NULL) {
|
||||||
|
if (drmModeSetCrtc(run->fd,
|
||||||
|
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
|
||||||
|
run->saved_crtc->x, run->saved_crtc->y,
|
||||||
|
&run->conn_id, 1, &run->saved_crtc->mode
|
||||||
|
) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't restore CRTC");
|
||||||
|
}
|
||||||
|
drmModeFreeCrtc(run->saved_crtc);
|
||||||
|
run->saved_crtc = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (run->bufs != NULL) {
|
||||||
|
for (uint n_buf = 0; n_buf < run->n_bufs; ++n_buf) {
|
||||||
|
us_drm_buffer_s *const buf = &run->bufs[n_buf];
|
||||||
|
if (buf->data != NULL && munmap(buf->data, buf->allocated)) {
|
||||||
|
_D_LOG_PERROR("Can't unmap buffer=%u", n_buf);
|
||||||
|
}
|
||||||
|
if (buf->fb_added && drmModeRmFB(run->fd, buf->id) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't remove buffer=%u", n_buf);
|
||||||
|
}
|
||||||
|
if (buf->dumb_created) {
|
||||||
|
struct drm_mode_destroy_dumb destroy = {.handle = buf->handle};
|
||||||
|
if (drmIoctl(run->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
US_DELETE(run->bufs, free);
|
||||||
|
run->n_bufs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (run->fd >= 0) {
|
||||||
|
if (close(run->fd) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't close device");
|
||||||
|
}
|
||||||
|
run->fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
run->crtc_id = 0;
|
||||||
|
run->next_n_buf = 0;
|
||||||
|
run->has_vsync = false;
|
||||||
|
|
||||||
|
if (run->state == US_DRM_STATE_OK) {
|
||||||
|
_D_LOG_INFO("Stopped");
|
||||||
|
run->state = US_DRM_STATE_CLOSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _drm_ensure(us_drm_s *drm, const us_frame_s *frame, float hz) {
|
||||||
|
us_drm_runtime_s *const run = drm->run;
|
||||||
|
|
||||||
|
if (frame == NULL) {
|
||||||
|
if (run->state == US_DRM_STATE_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
run->p_width == frame->width
|
||||||
|
&& run->p_height == frame->height
|
||||||
|
&& run->p_hz == hz
|
||||||
|
&& run->state <= US_DRM_STATE_RETRY
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_drm_cleanup(drm);
|
||||||
|
|
||||||
|
if (run->state <= US_DRM_STATE_RETRY) {
|
||||||
|
_D_LOG_INFO("Configuring DRM device ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame == NULL) {
|
||||||
|
run->p_width = 0; // Find the native resolution
|
||||||
|
run->p_height = 0;
|
||||||
|
} else {
|
||||||
|
run->p_width = frame->width;
|
||||||
|
run->p_height = frame->height;
|
||||||
|
}
|
||||||
|
run->p_hz = hz;
|
||||||
|
|
||||||
|
if ((run->fd = open(drm->path, O_RDWR|O_CLOEXEC|O_NONBLOCK)) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't open DRM device");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
# define CHECK_CAP(x_cap) { \
|
||||||
|
u64 m_check; \
|
||||||
|
if (drmGetCap(run->fd, x_cap, &m_check) < 0) { \
|
||||||
|
_D_LOG_PERROR("Can't check " #x_cap); \
|
||||||
|
goto error; \
|
||||||
|
} \
|
||||||
|
if (!m_check) { \
|
||||||
|
_D_LOG_ERROR(#x_cap " is not supported"); \
|
||||||
|
goto error; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
CHECK_CAP(DRM_CAP_DUMB_BUFFER);
|
||||||
|
// CHECK_CAP(DRM_CAP_PRIME);
|
||||||
|
# undef CHECK_CAP
|
||||||
|
|
||||||
|
if (_drm_find_sink(drm, run->p_width, run->p_height, run->p_hz) < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (run->crtc_id == 0) {
|
||||||
|
if (run->state != US_DRM_STATE_NO_DISPLAY) {
|
||||||
|
_D_LOG_INFO("Using %s mode: Display unplugged", drm->port);
|
||||||
|
run->state = US_DRM_STATE_NO_DISPLAY;
|
||||||
|
}
|
||||||
|
goto error;
|
||||||
|
} else {
|
||||||
|
const float mode_hz = _get_refresh_rate(&run->mode);
|
||||||
|
if (frame == NULL) {
|
||||||
|
run->p_width = run->mode.hdisplay;
|
||||||
|
run->p_height = run->mode.vdisplay;
|
||||||
|
run->p_hz = mode_hz;
|
||||||
|
}
|
||||||
|
_D_LOG_INFO("Using %s mode: %ux%up%.02f",
|
||||||
|
drm->port, run->mode.hdisplay, run->mode.vdisplay, mode_hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_drm_init_buffers(drm) < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_drm_init_video(drm) < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
_D_LOG_INFO("Showing ...");
|
||||||
|
run->state = US_DRM_STATE_OK;
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (run->state == US_DRM_STATE_CLOSED) {
|
||||||
|
_D_LOG_ERROR("Device destroyed due an error (prepare)");
|
||||||
|
}
|
||||||
|
_drm_cleanup(drm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _drm_init_video(us_drm_s *drm) {
|
||||||
|
us_drm_runtime_s *const run = drm->run;
|
||||||
|
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
|
||||||
|
_D_LOG_DEBUG("Setting up CRTC ...");
|
||||||
|
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't set CRTC");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (_drm_expose_raw(drm, NULL) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't flip the first page");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _drm_init_buffers(us_drm_s *drm) {
|
||||||
|
us_drm_runtime_s *const run = drm->run;
|
||||||
|
|
||||||
|
_D_LOG_DEBUG("Initializing %u buffers ...", drm->n_bufs);
|
||||||
|
|
||||||
|
US_CALLOC(run->bufs, drm->n_bufs);
|
||||||
|
for (run->n_bufs = 0; run->n_bufs < drm->n_bufs; ++run->n_bufs) {
|
||||||
|
const uint n_buf = run->n_bufs;
|
||||||
|
us_drm_buffer_s *const buf = &run->bufs[n_buf];
|
||||||
|
|
||||||
|
struct drm_mode_create_dumb create = {
|
||||||
|
.width = run->mode.hdisplay,
|
||||||
|
.height = run->mode.vdisplay,
|
||||||
|
.bpp = 24,
|
||||||
|
};
|
||||||
|
if (drmIoctl(run->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't create dumb buffer=%u", n_buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buf->handle = create.handle;
|
||||||
|
buf->dumb_created = true;
|
||||||
|
|
||||||
|
u32 handles[4] = {create.handle};
|
||||||
|
u32 strides[4] = {create.pitch};
|
||||||
|
u32 offsets[4] = {0};
|
||||||
|
if (drmModeAddFB2(
|
||||||
|
run->fd,
|
||||||
|
run->mode.hdisplay, run->mode.vdisplay, DRM_FORMAT_RGB888,
|
||||||
|
handles, strides, offsets, &buf->id, 0
|
||||||
|
)) {
|
||||||
|
_D_LOG_PERROR("Can't setup buffer=%u", n_buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buf->fb_added = true;
|
||||||
|
|
||||||
|
struct drm_mode_map_dumb map = {.handle = create.handle};
|
||||||
|
if (drmIoctl(run->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) {
|
||||||
|
_D_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buf->data = mmap(
|
||||||
|
NULL, create.size,
|
||||||
|
PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||||
|
run->fd, map.offset
|
||||||
|
)) == MAP_FAILED) {
|
||||||
|
_D_LOG_PERROR("Can't map buffer=%u", n_buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buf->allocated = create.size;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
|
||||||
|
us_drm_runtime_s *const run = drm->run;
|
||||||
|
|
||||||
|
int retval = -1;
|
||||||
|
|
||||||
|
run->crtc_id = 0;
|
||||||
|
|
||||||
|
_D_LOG_DEBUG("Trying to find the appropriate sink ...");
|
||||||
|
|
||||||
|
drmModeRes *res = drmModeGetResources(run->fd);
|
||||||
|
if (res == NULL) {
|
||||||
|
_D_LOG_PERROR("Can't get resources info");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
_D_LOG_DEBUG("Found %u connectors", res->count_connectors);
|
||||||
|
|
||||||
|
for (int ci = 0; ci < res->count_connectors; ++ci) {
|
||||||
|
drmModeConnector *conn = drmModeGetConnector(run->fd, res->connectors[ci]);
|
||||||
|
if (conn == NULL) {
|
||||||
|
_D_LOG_PERROR("Can't get connector index=%d", ci);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (run->conn_type < 0) {
|
||||||
|
char port[32];
|
||||||
|
US_SNPRINTF(port, 31, "%s-%u",
|
||||||
|
_connector_type_to_string(conn->connector_type),
|
||||||
|
conn->connector_type_id);
|
||||||
|
if (!strcmp(port, drm->port)) {
|
||||||
|
run->conn_type = conn->connector_type;
|
||||||
|
run->conn_type_id = conn->connector_type_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(int)conn->connector_type != run->conn_type
|
||||||
|
|| (int)conn->connector_type_id != run->conn_type_id
|
||||||
|
) {
|
||||||
|
drmModeFreeConnector(conn);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_D_LOG_DEBUG("Found requested connector for port %s: conn_type=%d, conn_type_id=%d",
|
||||||
|
drm->port, run->conn_type, run->conn_type_id);
|
||||||
|
|
||||||
|
if (conn->connection != DRM_MODE_CONNECTED) {
|
||||||
|
_D_LOG_DEBUG("Display is not connected");
|
||||||
|
drmModeFreeConnector(conn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
drmModeModeInfo *best = NULL;// (conn->count_modes > 0 ? &conn->modes[0] : NULL);
|
||||||
|
drmModeModeInfo *closest = NULL;
|
||||||
|
drmModeModeInfo *pref = NULL;
|
||||||
|
for (int mi = 0; mi < conn->count_modes; ++mi) {
|
||||||
|
drmModeModeInfo *const mode = &conn->modes[mi];
|
||||||
|
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
|
||||||
|
continue; // Paranoia for size and discard interlaced
|
||||||
|
}
|
||||||
|
const float mode_hz = _get_refresh_rate(mode);
|
||||||
|
if (mode->hdisplay == width && mode->vdisplay == height) {
|
||||||
|
best = mode; // Any mode with exact resolution
|
||||||
|
if (hz > 0 && mode_hz == hz) {
|
||||||
|
break; // Exact mode with same freq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mode->hdisplay == width && mode->vdisplay < height) {
|
||||||
|
if (closest == NULL || _get_refresh_rate(closest) != hz) {
|
||||||
|
closest = mode; // Something like 1920x1080p60 for 1920x1200p60 source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pref == NULL && (mode->type & DRM_MODE_TYPE_PREFERRED)) {
|
||||||
|
pref = mode; // Preferred mode if nothing is found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best == NULL) {
|
||||||
|
best = closest;
|
||||||
|
}
|
||||||
|
if (best == NULL) {
|
||||||
|
best = pref;
|
||||||
|
}
|
||||||
|
if (best == NULL) {
|
||||||
|
best = (conn->count_modes > 0 ? &conn->modes[0] : NULL);
|
||||||
|
}
|
||||||
|
if (best == NULL) {
|
||||||
|
_D_LOG_ERROR("Can't find any resolutions");
|
||||||
|
drmModeFreeConnector(conn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
assert(best->hdisplay > 0);
|
||||||
|
assert(best->vdisplay > 0);
|
||||||
|
|
||||||
|
u32 taken_crtcs = 0; // Unused here
|
||||||
|
if ((run->crtc_id = _find_crtc(run->fd, res, conn, &taken_crtcs)) == 0) {
|
||||||
|
_D_LOG_ERROR("Can't find CRTC");
|
||||||
|
drmModeFreeConnector(conn);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
memcpy(&run->mode, best, sizeof(drmModeModeInfo));
|
||||||
|
run->conn_id = conn->connector_id;
|
||||||
|
|
||||||
|
drmModeFreeConnector(conn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
retval = 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
drmModeFreeResources(res);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 _find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, u32 *taken_crtcs) {
|
||||||
|
for (int ei = 0; ei < conn->count_encoders; ++ei) {
|
||||||
|
drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[ei]);
|
||||||
|
if (enc == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int ci = 0; ci < res->count_crtcs; ++ci) {
|
||||||
|
u32 bit = (1 << ci);
|
||||||
|
if (!(enc->possible_crtcs & bit)) {
|
||||||
|
continue; // Not compatible
|
||||||
|
}
|
||||||
|
if (*taken_crtcs & bit) {
|
||||||
|
continue; // Already taken
|
||||||
|
}
|
||||||
|
drmModeFreeEncoder(enc);
|
||||||
|
*taken_crtcs |= bit;
|
||||||
|
return res->crtcs[ci];
|
||||||
|
}
|
||||||
|
drmModeFreeEncoder(enc);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *_connector_type_to_string(u32 type) {
|
||||||
|
switch (type) {
|
||||||
|
# define CASE_NAME(x_suffix, x_name) \
|
||||||
|
case DRM_MODE_CONNECTOR_##x_suffix: return x_name;
|
||||||
|
CASE_NAME(VGA, "VGA");
|
||||||
|
CASE_NAME(DVII, "DVI-I");
|
||||||
|
CASE_NAME(DVID, "DVI-D");
|
||||||
|
CASE_NAME(DVIA, "DVI-A");
|
||||||
|
CASE_NAME(Composite, "Composite");
|
||||||
|
CASE_NAME(SVIDEO, "SVIDEO");
|
||||||
|
CASE_NAME(LVDS, "LVDS");
|
||||||
|
CASE_NAME(Component, "Component");
|
||||||
|
CASE_NAME(9PinDIN, "DIN");
|
||||||
|
CASE_NAME(DisplayPort, "DP");
|
||||||
|
CASE_NAME(HDMIA, "HDMI-A");
|
||||||
|
CASE_NAME(HDMIB, "HDMI-B");
|
||||||
|
CASE_NAME(TV, "TV");
|
||||||
|
CASE_NAME(eDP, "eDP");
|
||||||
|
CASE_NAME(VIRTUAL, "Virtual");
|
||||||
|
CASE_NAME(DSI, "DSI");
|
||||||
|
case DRM_MODE_CONNECTOR_Unknown: break;
|
||||||
|
# undef CASE_NAME
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
static float _get_refresh_rate(const drmModeModeInfo *mode) {
|
||||||
|
int mhz = (mode->clock * 1000000LL / mode->htotal + mode->vtotal / 2) / mode->vtotal;
|
||||||
|
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
|
||||||
|
mhz *= 2;
|
||||||
|
}
|
||||||
|
if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
|
||||||
|
mhz /= 2;
|
||||||
|
}
|
||||||
|
if (mode->vscan > 1) {
|
||||||
|
mhz /= mode->vscan;
|
||||||
|
}
|
||||||
|
return (float)mhz / 1000;
|
||||||
|
}
|
||||||
92
src/v4p/drm.h
Normal file
92
src/v4p/drm.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include <xf86drmMode.h>
|
||||||
|
|
||||||
|
#include "../libs/types.h"
|
||||||
|
#include "../libs/frame.h"
|
||||||
|
|
||||||
|
#include "ftext.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
US_DRM_EXPOSE_FRAME = 0,
|
||||||
|
US_DRM_EXPOSE_NO_SIGNAL,
|
||||||
|
US_DRM_EXPOSE_BUSY,
|
||||||
|
} us_drm_expose_e;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
US_DRM_STATE_OK = 0,
|
||||||
|
US_DRM_STATE_CLOSED,
|
||||||
|
US_DRM_STATE_RETRY, // Not used directly
|
||||||
|
US_DRM_STATE_NO_DISPLAY,
|
||||||
|
} us_drm_state_e;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 id;
|
||||||
|
u32 handle;
|
||||||
|
u8 *data;
|
||||||
|
uz allocated;
|
||||||
|
bool dumb_created;
|
||||||
|
bool fb_added;
|
||||||
|
} us_drm_buffer_s;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int fd;
|
||||||
|
int conn_type;
|
||||||
|
int conn_type_id;
|
||||||
|
u32 crtc_id;
|
||||||
|
drmModeModeInfo mode;
|
||||||
|
u32 conn_id;
|
||||||
|
us_drm_buffer_s *bufs;
|
||||||
|
unsigned n_bufs;
|
||||||
|
drmModeCrtc *saved_crtc;
|
||||||
|
unsigned next_n_buf;
|
||||||
|
bool has_vsync;
|
||||||
|
|
||||||
|
us_ftext_s *ft;
|
||||||
|
|
||||||
|
unsigned p_width;
|
||||||
|
unsigned p_height;
|
||||||
|
float p_hz;
|
||||||
|
|
||||||
|
us_drm_state_e state;
|
||||||
|
} us_drm_runtime_s;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *path;
|
||||||
|
char *port;
|
||||||
|
unsigned n_bufs;
|
||||||
|
unsigned timeout;
|
||||||
|
|
||||||
|
us_drm_runtime_s *run;
|
||||||
|
} us_drm_s;
|
||||||
|
|
||||||
|
|
||||||
|
us_drm_s *us_drm_init(const char *path, const char *port);
|
||||||
|
void us_drm_destroy(us_drm_s *drm);
|
||||||
|
|
||||||
|
int us_drm_wait_for_vsync(us_drm_s *drm);
|
||||||
|
int us_drm_expose(us_drm_s *drm, us_drm_expose_e ex, const us_frame_s *frame, float hz);
|
||||||
156
src/v4p/ftext.c
Normal file
156
src/v4p/ftext.c
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "ftext.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <linux/videodev2.h>
|
||||||
|
|
||||||
|
#include "../libs/tools.h"
|
||||||
|
#include "../libs/frame.h"
|
||||||
|
|
||||||
|
#include "ftext_font.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void _ftext_draw_line(
|
||||||
|
us_ftext_s *ft, const char *line,
|
||||||
|
uint scale_x, uint scale_y,
|
||||||
|
uint start_x, uint start_y);
|
||||||
|
|
||||||
|
|
||||||
|
us_ftext_s *us_ftext_init(void) {
|
||||||
|
us_ftext_s *ft;
|
||||||
|
US_CALLOC(ft, 1);
|
||||||
|
ft->frame = us_frame_init();
|
||||||
|
return ft;
|
||||||
|
}
|
||||||
|
|
||||||
|
void us_ftext_destroy(us_ftext_s *ft) {
|
||||||
|
us_frame_destroy(ft->frame);
|
||||||
|
US_DELETE(ft->text, free);
|
||||||
|
free(ft);
|
||||||
|
}
|
||||||
|
|
||||||
|
void us_ftext_draw(us_ftext_s *ft, const char *text, uint width, uint height) {
|
||||||
|
us_frame_s *const frame = ft->frame;
|
||||||
|
|
||||||
|
if (
|
||||||
|
frame->width == width && frame->height == height
|
||||||
|
&& ft->text != NULL && !strcmp(ft->text, text)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
US_DELETE(ft->text, free);
|
||||||
|
ft->text = us_strdup(text);
|
||||||
|
strcpy(ft->text, text);
|
||||||
|
frame->width = width;
|
||||||
|
frame->height = height;
|
||||||
|
frame->format = V4L2_PIX_FMT_RGB24;
|
||||||
|
frame->stride = width * 3;
|
||||||
|
frame->used = width * height * 3;
|
||||||
|
us_frame_realloc_data(frame, frame->used);
|
||||||
|
memset(frame->data, 0, frame->used);
|
||||||
|
|
||||||
|
if (frame->width == 0 || frame->height == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *str = us_strdup(text);
|
||||||
|
char *line;
|
||||||
|
char *rest;
|
||||||
|
|
||||||
|
uint block_width = 0;
|
||||||
|
uint block_height = 0;
|
||||||
|
while ((line = strtok_r((block_height == 0 ? str : NULL), "\n", &rest)) != NULL) {
|
||||||
|
block_width = us_max_u(strlen(line) * 8, block_width);
|
||||||
|
block_height += 8;
|
||||||
|
}
|
||||||
|
if (block_width == 0 || block_height == 0) {
|
||||||
|
goto empty;
|
||||||
|
}
|
||||||
|
uint scale_x = frame->width / block_width / 2;
|
||||||
|
uint scale_y = frame->height / block_height / 3;
|
||||||
|
if (scale_x < scale_y / 1.5) {
|
||||||
|
scale_y = scale_x * 1.5;
|
||||||
|
} else if (scale_y < scale_x * 1.5) {
|
||||||
|
scale_x = scale_y / 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(str, text);
|
||||||
|
|
||||||
|
const uint start_y = (frame->height >= (block_height * scale_y)
|
||||||
|
? ((frame->height - (block_height * scale_y)) / 2)
|
||||||
|
: 0);
|
||||||
|
uint n_line = 0;
|
||||||
|
while ((line = strtok_r((n_line == 0 ? str : NULL), "\n", &rest)) != NULL) {
|
||||||
|
const uint line_width = strlen(line) * 8 * scale_x;
|
||||||
|
const uint start_x = (frame->width >= line_width
|
||||||
|
? ((frame->width - line_width) / 2)
|
||||||
|
: 0);
|
||||||
|
_ftext_draw_line(ft, line, scale_x, scale_y, start_x, start_y + n_line * 8 * scale_y);
|
||||||
|
++n_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
empty:
|
||||||
|
free(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _ftext_draw_line(
|
||||||
|
us_ftext_s *ft, const char *line,
|
||||||
|
uint scale_x, uint scale_y,
|
||||||
|
uint start_x, uint start_y) {
|
||||||
|
|
||||||
|
us_frame_s *const frame = ft->frame;
|
||||||
|
|
||||||
|
const size_t len = strlen(line);
|
||||||
|
|
||||||
|
for (uint ch_y = 0; ch_y < 8 * scale_y; ++ch_y) {
|
||||||
|
const uint canvas_y = start_y + ch_y;
|
||||||
|
for (uint ch_x = 0; ch_x < 8 * len * scale_x; ++ch_x) {
|
||||||
|
if ((start_x + ch_x) >= frame->width) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const uint canvas_x = (start_x + ch_x) * 3;
|
||||||
|
const uint offset = canvas_y * frame->stride + canvas_x;
|
||||||
|
if (offset >= frame->used) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u8 ch = us_min_u(line[ch_x / 8 / scale_x], sizeof(US_FTEXT_FONT) / 8 - 1);
|
||||||
|
const uint ch_byte = (ch_y / scale_y) % 8;
|
||||||
|
const uint ch_bit = (ch_x / scale_x) % 8;
|
||||||
|
const bool pix_on = !!(US_FTEXT_FONT[ch][ch_byte] & (1 << ch_bit));
|
||||||
|
|
||||||
|
u8 *const b = &frame->data[offset]; // XXX: Big endian for Raspberry
|
||||||
|
u8 *const g = b + 1;
|
||||||
|
u8 *const r = g + 1;
|
||||||
|
|
||||||
|
*r = pix_on * 0x51;
|
||||||
|
*g = pix_on * 0x65;
|
||||||
|
*b = pix_on * 0x65;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/v4p/ftext.h
Normal file
39
src/v4p/ftext.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
#include "../libs/types.h"
|
||||||
|
#include "../libs/frame.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *text;
|
||||||
|
us_frame_s *frame;
|
||||||
|
} us_ftext_s;
|
||||||
|
|
||||||
|
|
||||||
|
us_ftext_s *us_ftext_init(void);
|
||||||
|
void us_ftext_destroy(us_ftext_s *ft);
|
||||||
|
|
||||||
|
void us_ftext_draw(us_ftext_s *ft, const char *text, uint width, uint height);
|
||||||
156
src/v4p/ftext_font.c
Normal file
156
src/v4p/ftext_font.c
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "ftext_font.h"
|
||||||
|
|
||||||
|
|
||||||
|
const u8 US_FTEXT_FONT[128][8] = {
|
||||||
|
// https://github.com/dhepper/font8x8/blob/master/font8x8_basic.h
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space)
|
||||||
|
{0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!)
|
||||||
|
{0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (")
|
||||||
|
{0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#)
|
||||||
|
{0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($)
|
||||||
|
{0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%)
|
||||||
|
{0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&)
|
||||||
|
{0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (')
|
||||||
|
{0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (()
|
||||||
|
{0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ())
|
||||||
|
{0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*)
|
||||||
|
{0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,)
|
||||||
|
{0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.)
|
||||||
|
{0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/)
|
||||||
|
{0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0)
|
||||||
|
{0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1)
|
||||||
|
{0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2)
|
||||||
|
{0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3)
|
||||||
|
{0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4)
|
||||||
|
{0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5)
|
||||||
|
{0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6)
|
||||||
|
{0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7)
|
||||||
|
{0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8)
|
||||||
|
{0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9)
|
||||||
|
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:)
|
||||||
|
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;)
|
||||||
|
{0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<)
|
||||||
|
{0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=)
|
||||||
|
{0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>)
|
||||||
|
{0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?)
|
||||||
|
{0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@)
|
||||||
|
{0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A)
|
||||||
|
{0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B)
|
||||||
|
{0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C)
|
||||||
|
{0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D)
|
||||||
|
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E)
|
||||||
|
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F)
|
||||||
|
{0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G)
|
||||||
|
{0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H)
|
||||||
|
{0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I)
|
||||||
|
{0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J)
|
||||||
|
{0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K)
|
||||||
|
{0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L)
|
||||||
|
{0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M)
|
||||||
|
{0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N)
|
||||||
|
{0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O)
|
||||||
|
{0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P)
|
||||||
|
{0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q)
|
||||||
|
{0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R)
|
||||||
|
{0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S)
|
||||||
|
{0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T)
|
||||||
|
{0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U)
|
||||||
|
{0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V)
|
||||||
|
{0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W)
|
||||||
|
{0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X)
|
||||||
|
{0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y)
|
||||||
|
{0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z)
|
||||||
|
{0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([)
|
||||||
|
{0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\)
|
||||||
|
{0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (])
|
||||||
|
{0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_)
|
||||||
|
{0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`)
|
||||||
|
{0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a)
|
||||||
|
{0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b)
|
||||||
|
{0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c)
|
||||||
|
{0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d)
|
||||||
|
{0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e)
|
||||||
|
{0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f)
|
||||||
|
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g)
|
||||||
|
{0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h)
|
||||||
|
{0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i)
|
||||||
|
{0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j)
|
||||||
|
{0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k)
|
||||||
|
{0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l)
|
||||||
|
{0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m)
|
||||||
|
{0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n)
|
||||||
|
{0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o)
|
||||||
|
{0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p)
|
||||||
|
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q)
|
||||||
|
{0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r)
|
||||||
|
{0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s)
|
||||||
|
{0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t)
|
||||||
|
{0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u)
|
||||||
|
{0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v)
|
||||||
|
{0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w)
|
||||||
|
{0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x)
|
||||||
|
{0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y)
|
||||||
|
{0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z)
|
||||||
|
{0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({)
|
||||||
|
{0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|)
|
||||||
|
{0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (})
|
||||||
|
{0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~)
|
||||||
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007F
|
||||||
|
};
|
||||||
28
src/v4p/ftext_font.h
Normal file
28
src/v4p/ftext_font.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../libs/types.h"
|
||||||
|
|
||||||
|
|
||||||
|
extern const u8 US_FTEXT_FONT[128][8];
|
||||||
333
src/v4p/main.c
Normal file
333
src/v4p/main.c
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
# #
|
||||||
|
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||||
|
# #
|
||||||
|
# This program is free software: you can redistribute it and/or modify #
|
||||||
|
# it under the terms of the GNU General Public License as published by #
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or #
|
||||||
|
# (at your option) any later version. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, #
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||||
|
# GNU General Public License for more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License #
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||||
|
# #
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../libs/types.h"
|
||||||
|
#include "../libs/const.h"
|
||||||
|
#include "../libs/tools.h"
|
||||||
|
#include "../libs/logging.h"
|
||||||
|
#include "../libs/device.h"
|
||||||
|
#include "../libs/options.h"
|
||||||
|
|
||||||
|
#include "drm.h"
|
||||||
|
|
||||||
|
|
||||||
|
enum _OPT_VALUES {
|
||||||
|
_O_UNIX_FOLLOW = 'f',
|
||||||
|
|
||||||
|
_O_HELP = 'h',
|
||||||
|
_O_VERSION = 'v',
|
||||||
|
|
||||||
|
_O_LOG_LEVEL = 10000,
|
||||||
|
_O_PERF,
|
||||||
|
_O_VERBOSE,
|
||||||
|
_O_DEBUG,
|
||||||
|
_O_FORCE_LOG_COLORS,
|
||||||
|
_O_NO_LOG_COLORS,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct option _LONG_OPTS[] = {
|
||||||
|
{"unix-follow", required_argument, NULL, _O_UNIX_FOLLOW},
|
||||||
|
|
||||||
|
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
|
||||||
|
{"perf", no_argument, NULL, _O_PERF},
|
||||||
|
{"verbose", no_argument, NULL, _O_VERBOSE},
|
||||||
|
{"debug", no_argument, NULL, _O_DEBUG},
|
||||||
|
{"force-log-colors", no_argument, NULL, _O_FORCE_LOG_COLORS},
|
||||||
|
{"no-log-colors", no_argument, NULL, _O_NO_LOG_COLORS},
|
||||||
|
|
||||||
|
{"help", no_argument, NULL, _O_HELP},
|
||||||
|
{"version", no_argument, NULL, _O_VERSION},
|
||||||
|
|
||||||
|
{NULL, 0, NULL, 0},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
volatile atomic_bool _g_stop = false;
|
||||||
|
atomic_bool _g_ustreamer_online = false;
|
||||||
|
|
||||||
|
|
||||||
|
static void _signal_handler(int signum);
|
||||||
|
static void _install_signal_handlers(void);
|
||||||
|
|
||||||
|
static void _main_loop();
|
||||||
|
static void *_follower_thread(void *v_unix_follow);
|
||||||
|
static void _slowdown(void);
|
||||||
|
|
||||||
|
static void _help(FILE *fp);
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
US_LOGGING_INIT;
|
||||||
|
US_THREAD_RENAME("main");
|
||||||
|
|
||||||
|
char *unix_follow = NULL;
|
||||||
|
|
||||||
|
# define OPT_SET(_dest, _value) { \
|
||||||
|
_dest = _value; \
|
||||||
|
break; \
|
||||||
|
}
|
||||||
|
|
||||||
|
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
|
||||||
|
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
|
||||||
|
if (errno || *_end || _tmp < _min || _tmp > _max) { \
|
||||||
|
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
|
||||||
|
return 1; \
|
||||||
|
} \
|
||||||
|
_dest = _tmp; \
|
||||||
|
break; \
|
||||||
|
}
|
||||||
|
|
||||||
|
char short_opts[128];
|
||||||
|
us_build_short_options(_LONG_OPTS, short_opts, 128);
|
||||||
|
|
||||||
|
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||||
|
switch (ch) {
|
||||||
|
case _O_UNIX_FOLLOW: OPT_SET(unix_follow, optarg);
|
||||||
|
|
||||||
|
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
|
||||||
|
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
|
||||||
|
case _O_VERBOSE: OPT_SET(us_g_log_level, US_LOG_LEVEL_VERBOSE);
|
||||||
|
case _O_DEBUG: OPT_SET(us_g_log_level, US_LOG_LEVEL_DEBUG);
|
||||||
|
case _O_FORCE_LOG_COLORS: OPT_SET(us_g_log_colored, true);
|
||||||
|
case _O_NO_LOG_COLORS: OPT_SET(us_g_log_colored, false);
|
||||||
|
|
||||||
|
case _O_HELP: _help(stdout); return 0;
|
||||||
|
case _O_VERSION: puts(US_VERSION); return 0;
|
||||||
|
|
||||||
|
case 0: break;
|
||||||
|
default: return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# undef OPT_NUMBER
|
||||||
|
# undef OPT_SET
|
||||||
|
|
||||||
|
_install_signal_handlers();
|
||||||
|
|
||||||
|
pthread_t follower_tid;
|
||||||
|
if (unix_follow != NULL) {
|
||||||
|
US_THREAD_CREATE(follower_tid, _follower_thread, unix_follow);
|
||||||
|
}
|
||||||
|
_main_loop(unix_follow);
|
||||||
|
if (unix_follow != NULL) {
|
||||||
|
US_THREAD_JOIN(follower_tid);
|
||||||
|
}
|
||||||
|
|
||||||
|
US_LOGGING_DESTROY;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _signal_handler(int signum) {
|
||||||
|
char *const name = us_signum_to_string(signum);
|
||||||
|
US_LOG_INFO_NOLOCK("===== Stopping by %s =====", name);
|
||||||
|
free(name);
|
||||||
|
atomic_store(&_g_stop, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _install_signal_handlers(void) {
|
||||||
|
struct sigaction sig_act = {0};
|
||||||
|
|
||||||
|
assert(!sigemptyset(&sig_act.sa_mask));
|
||||||
|
sig_act.sa_handler = _signal_handler;
|
||||||
|
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
|
||||||
|
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||||
|
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
|
||||||
|
|
||||||
|
US_LOG_DEBUG("Installing SIGINT handler ...");
|
||||||
|
assert(!sigaction(SIGINT, &sig_act, NULL));
|
||||||
|
|
||||||
|
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||||
|
assert(!sigaction(SIGTERM, &sig_act, NULL));
|
||||||
|
|
||||||
|
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||||
|
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _main_loop(void) {
|
||||||
|
us_drm_s *drm = us_drm_init("/dev/dri/card0", "HDMI-A-2");
|
||||||
|
drm->n_bufs = 4;
|
||||||
|
|
||||||
|
us_device_s *dev = us_device_init();
|
||||||
|
dev->path = "/dev/kvmd-video";
|
||||||
|
dev->n_bufs = drm->n_bufs;
|
||||||
|
dev->format = V4L2_PIX_FMT_RGB24;
|
||||||
|
dev->dv_timings = true;
|
||||||
|
dev->persistent = true;
|
||||||
|
|
||||||
|
while (!atomic_load(&_g_stop)) {
|
||||||
|
if (atomic_load(&_g_ustreamer_online)) {
|
||||||
|
if (us_drm_wait_for_vsync(drm) == 0) {
|
||||||
|
us_drm_expose(drm, US_DRM_EXPOSE_BUSY, NULL, 0);
|
||||||
|
}
|
||||||
|
if (dev->run->capturing) {
|
||||||
|
goto close;
|
||||||
|
} else {
|
||||||
|
_slowdown();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (us_device_open(dev) < 0) {
|
||||||
|
goto close;
|
||||||
|
}
|
||||||
|
if (us_device_switch_capturing(dev, true) < 0) {
|
||||||
|
goto close;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!atomic_load(&_g_stop)) {
|
||||||
|
if (atomic_load(&_g_ustreamer_online)) {
|
||||||
|
if (us_drm_wait_for_vsync(drm) == 0) {
|
||||||
|
us_drm_expose(drm, US_DRM_EXPOSE_BUSY, NULL, 0);
|
||||||
|
}
|
||||||
|
goto close;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (us_drm_wait_for_vsync(drm) < 0) {
|
||||||
|
_slowdown();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_read;
|
||||||
|
bool has_write;
|
||||||
|
bool has_error;
|
||||||
|
const int selected = us_device_select(dev, &has_read, &has_write, &has_error);
|
||||||
|
|
||||||
|
if (selected < 0) {
|
||||||
|
if (errno != EINTR) {
|
||||||
|
US_LOG_PERROR("Mainloop select() error");
|
||||||
|
goto close;
|
||||||
|
}
|
||||||
|
} else if (selected == 0) { // Persistent timeout
|
||||||
|
if (us_drm_expose(drm, US_DRM_EXPOSE_NO_SIGNAL, NULL, 0) < 0) {
|
||||||
|
_slowdown();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (has_read) {
|
||||||
|
US_LOG_DEBUG("Frame is ready");
|
||||||
|
us_hw_buffer_s *hw;
|
||||||
|
const int buf_index = us_device_grab_buffer(dev, &hw);
|
||||||
|
if (buf_index >= 0) {
|
||||||
|
if (us_drm_expose(drm, US_DRM_EXPOSE_FRAME, &hw->raw, dev->run->hz) < 0) {
|
||||||
|
_slowdown();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (us_device_release_buffer(dev, hw) < 0) {
|
||||||
|
goto close;
|
||||||
|
}
|
||||||
|
} else if (buf_index == -2) {
|
||||||
|
goto close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_error) {
|
||||||
|
US_LOG_INFO("Got V4L2 event");
|
||||||
|
if (us_device_consume_event(dev) < 0) {
|
||||||
|
goto close;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close:
|
||||||
|
us_device_switch_capturing(dev, false);
|
||||||
|
us_device_close(dev);
|
||||||
|
_slowdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
us_device_destroy(dev);
|
||||||
|
us_drm_destroy(drm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *_follower_thread(void *v_unix_follow) {
|
||||||
|
const char *path = v_unix_follow;
|
||||||
|
assert(path != NULL);
|
||||||
|
|
||||||
|
US_THREAD_RENAME("follower");
|
||||||
|
|
||||||
|
while (!atomic_load(&_g_stop)) {
|
||||||
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
assert(fd >= 0);
|
||||||
|
|
||||||
|
struct sockaddr_un addr = {0};
|
||||||
|
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
|
||||||
|
const bool online = !connect(fd, (struct sockaddr *)&addr, sizeof(addr));
|
||||||
|
atomic_store(&_g_ustreamer_online, online);
|
||||||
|
US_CLOSE_FD(fd, close); // cppcheck-suppress unreadVariable
|
||||||
|
|
||||||
|
usleep(200 * 1000);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _slowdown(void) {
|
||||||
|
if (!atomic_load(&_g_stop)) {
|
||||||
|
usleep(500 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _help(FILE *fp) {
|
||||||
|
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
|
||||||
|
SAY("\nuStreamer-V4P - Video passthrough for PiKVM V4 Plus");
|
||||||
|
SAY("═════════════════════════════════════════════════════");
|
||||||
|
SAY("Version: %s; license: GPLv3", US_VERSION);
|
||||||
|
SAY("Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||||
|
SAY("Example:");
|
||||||
|
SAY("════════");
|
||||||
|
SAY(" ustreamer-v4p\n");
|
||||||
|
SAY("Passthrough options:");
|
||||||
|
SAY("════════════════════");
|
||||||
|
SAY(" -f|--unix-follow <path> ──────── Pause the process if the specified socked exists.\n");
|
||||||
|
SAY("Logging options:");
|
||||||
|
SAY("════════════════");
|
||||||
|
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||||
|
SAY(" Enabling debugging messages can slow down the program.");
|
||||||
|
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
|
||||||
|
SAY(" Default: %d.\n", us_g_log_level);
|
||||||
|
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
|
||||||
|
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
|
||||||
|
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");
|
||||||
|
SAY(" --force-log-colors ─ Force color logging. Default: colored if stderr is a TTY.\n");
|
||||||
|
SAY(" --no-log-colors ──── Disable color logging. Default: ditto.\n");
|
||||||
|
SAY("Help options:");
|
||||||
|
SAY("═════════════");
|
||||||
|
SAY(" -h|--help ─────── Print this text and exit.\n");
|
||||||
|
SAY(" -v|--version ──── Print version and exit.\n");
|
||||||
|
# undef SAY
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user