mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d64077c2d5 | ||
|
|
83f12baa61 | ||
|
|
b6fac2608d | ||
|
|
e6ebc12505 | ||
|
|
8c92ab6f47 | ||
|
|
7dc492d875 | ||
|
|
d43014346d | ||
|
|
bcd447963c | ||
|
|
eec6cfd0d4 | ||
|
|
f177300e69 | ||
|
|
7015a26a63 | ||
|
|
290282b6b6 | ||
|
|
a339ff5d06 | ||
|
|
8d4e9a6ca0 | ||
|
|
f0f5fcd67f | ||
|
|
02fabc7b45 | ||
|
|
bdf55396e5 | ||
|
|
976f466038 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 6.6
|
||||
current_version = 6.9
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 6.6" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 6.9" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 6.6" "November 2020"
|
||||
.TH USTREAMER 1 "version 6.9" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=6.6
|
||||
pkgver=6.9
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
@@ -19,7 +19,7 @@ _options="WITH_GPIO=1 WITH_SYSTEMD=1"
|
||||
if [ -e /usr/bin/python3 ]; then
|
||||
_options="$_options WITH_PYTHON=1"
|
||||
depends+=(python)
|
||||
makedepends+=(python-setuptools python-pip python-build)
|
||||
makedepends+=(python-setuptools python-pip python-build python-wheel)
|
||||
fi
|
||||
if [ -e /usr/include/janus/plugins/plugin.h ];then
|
||||
depends+=(janus-gateway alsa-lib opus)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=6.6
|
||||
PKG_VERSION:=6.9
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="6.6",
|
||||
version="6.9",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
35
src/Makefile
35
src/Makefile
@@ -12,11 +12,11 @@ _DUMP = ustreamer-dump.bin
|
||||
_V4P = ustreamer-v4p.bin
|
||||
|
||||
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
||||
_LDFLAGS = $(LDFLAGS)
|
||||
|
||||
_COMMON_LIBS = -lm -ljpeg -pthread -lrt -latomic
|
||||
_USTR_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic -levent -levent_pthreads
|
||||
_DUMP_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic
|
||||
_V4P_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic
|
||||
|
||||
_USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads
|
||||
_USTR_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
ustreamer/*.c \
|
||||
@@ -27,15 +27,14 @@ _USTR_SRCS = $(shell ls \
|
||||
ustreamer/*.c \
|
||||
)
|
||||
|
||||
_DUMP_LIBS = $(_COMMON_LIBS)
|
||||
_DUMP_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
dump/*.c \
|
||||
)
|
||||
|
||||
_V4P_LIBS = $(_COMMON_LIBS)
|
||||
_V4P_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
libs/drm/*.c \
|
||||
v4p/*.c \
|
||||
)
|
||||
|
||||
@@ -52,16 +51,16 @@ endef
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||
_USTR_LIBS += -lgpiod
|
||||
override _CFLAGS += -DWITH_GPIO $(shell pkg-config --atleast-version=2 libgpiod 2> /dev/null && echo -DHAVE_GPIOD2)
|
||||
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
|
||||
override _USTR_LDFLAGS += -lgpiod
|
||||
override _USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_SYSTEMD)),)
|
||||
_USTR_LIBS += -lsystemd
|
||||
override _CFLAGS += -DWITH_SYSTEMD
|
||||
_USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
|
||||
override _USTR_LDFLAGS += -lsystemd
|
||||
override _USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
|
||||
endif
|
||||
|
||||
|
||||
@@ -73,10 +72,10 @@ endif
|
||||
|
||||
WITH_SETPROCTITLE ?= 1
|
||||
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
|
||||
ifeq ($(shell uname -s | tr A-Z a-z),linux)
|
||||
_USTR_LIBS += -lbsd
|
||||
endif
|
||||
override _CFLAGS += -DWITH_SETPROCTITLE
|
||||
ifeq ($(shell uname -s | tr A-Z a-z),linux)
|
||||
override _USTR_LDFLAGS += -lbsd
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
@@ -84,8 +83,10 @@ 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)
|
||||
override _CFLAGS += -DWITH_V4P $(shell pkg-config --cflags libdrm)
|
||||
override _V4P_LDFLAGS += $(shell pkg-config --libs libdrm)
|
||||
override _USTR_SRCS += $(shell ls libs/drm/*.c)
|
||||
override _USTR_LDFLAGS += $(shell pkg-config --libs libdrm)
|
||||
endif
|
||||
|
||||
|
||||
@@ -108,17 +109,17 @@ install-strip: install
|
||||
|
||||
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_USTR_LDFLAGS)
|
||||
|
||||
|
||||
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_DUMP_LDFLAGS)
|
||||
|
||||
|
||||
$(_V4P): $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_V4P_LDFLAGS) $(_V4P_LIBS)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_V4P_LDFLAGS)
|
||||
|
||||
|
||||
$(_BUILD)/%.o: %.c
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,7 @@ typedef struct {
|
||||
int dma_fd;
|
||||
bool grabbed;
|
||||
atomic_int refs;
|
||||
} us_hw_buffer_s;
|
||||
} us_capture_hwbuf_s;
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
@@ -62,13 +62,13 @@ typedef struct {
|
||||
uint jpeg_quality;
|
||||
uz raw_size;
|
||||
uint n_bufs;
|
||||
us_hw_buffer_s *hw_bufs;
|
||||
us_capture_hwbuf_s *bufs;
|
||||
bool dma;
|
||||
enum v4l2_buf_type capture_type;
|
||||
bool capture_mplane;
|
||||
bool streamon;
|
||||
int open_error_reported;
|
||||
} us_device_runtime_s;
|
||||
} us_capture_runtime_s;
|
||||
|
||||
typedef enum {
|
||||
CTL_MODE_NONE = 0,
|
||||
@@ -104,6 +104,8 @@ typedef struct {
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
|
||||
bool format_swap_rgb;
|
||||
uint jpeg_quality;
|
||||
v4l2_std_id standard;
|
||||
enum v4l2_memory io_method;
|
||||
@@ -116,22 +118,22 @@ typedef struct {
|
||||
bool persistent;
|
||||
uint timeout;
|
||||
us_controls_s ctl;
|
||||
us_device_runtime_s *run;
|
||||
} us_device_s;
|
||||
us_capture_runtime_s *run;
|
||||
} us_capture_s;
|
||||
|
||||
|
||||
us_device_s *us_device_init(void);
|
||||
void us_device_destroy(us_device_s *dev);
|
||||
us_capture_s *us_capture_init(void);
|
||||
void us_capture_destroy(us_capture_s *cap);
|
||||
|
||||
int us_device_parse_format(const char *str);
|
||||
int us_device_parse_standard(const char *str);
|
||||
int us_device_parse_io_method(const char *str);
|
||||
int us_capture_parse_format(const char *str);
|
||||
int us_capture_parse_standard(const char *str);
|
||||
int us_capture_parse_io_method(const char *str);
|
||||
|
||||
int us_device_open(us_device_s *dev);
|
||||
void us_device_close(us_device_s *dev);
|
||||
int us_capture_open(us_capture_s *cap);
|
||||
void us_capture_close(us_capture_s *cap);
|
||||
|
||||
int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw);
|
||||
int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw);
|
||||
int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw);
|
||||
int us_capture_hwbuf_release(us_capture_s *cap, us_capture_hwbuf_s *hw);
|
||||
|
||||
void us_device_buffer_incref(us_hw_buffer_s *hw);
|
||||
void us_device_buffer_decref(us_hw_buffer_s *hw);
|
||||
void us_capture_hwbuf_incref(us_capture_hwbuf_s *hw);
|
||||
void us_capture_hwbuf_decref(us_capture_hwbuf_s *hw);
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
#define US_VERSION_MAJOR 6
|
||||
#define US_VERSION_MINOR 6
|
||||
#define US_VERSION_MINOR 9
|
||||
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
|
||||
@@ -37,17 +37,18 @@
|
||||
#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 "../libs/frametext.h"
|
||||
#include "../types.h"
|
||||
#include "../tools.h"
|
||||
#include "../logging.h"
|
||||
#include "../frame.h"
|
||||
#include "../frametext.h"
|
||||
#include "../capture.h"
|
||||
|
||||
|
||||
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_buf);
|
||||
static int _drm_check_status(us_drm_s *drm);
|
||||
static void _drm_ensure_dpms_power(us_drm_s *drm, bool on);
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev);
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_capture_s *cap);
|
||||
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
|
||||
|
||||
static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz);
|
||||
@@ -57,11 +58,11 @@ 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__)
|
||||
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
|
||||
|
||||
|
||||
us_drm_s *us_drm_init(void) {
|
||||
@@ -78,7 +79,7 @@ us_drm_s *us_drm_init(void) {
|
||||
US_CALLOC(drm, 1);
|
||||
// drm->path = "/dev/dri/card0";
|
||||
drm->path = "/dev/dri/by-path/platform-gpu-card";
|
||||
drm->port = "HDMI-A-1";
|
||||
drm->port = "HDMI-A-2"; // OUT2 on PiKVM V4 Plus
|
||||
drm->timeout = 5;
|
||||
drm->run = run;
|
||||
return drm;
|
||||
@@ -90,7 +91,7 @@ void us_drm_destroy(us_drm_s *drm) {
|
||||
US_DELETE(drm, free); // cppcheck-suppress uselessAssignmentPtrArg
|
||||
}
|
||||
|
||||
int us_drm_open(us_drm_s *drm, const us_device_s *dev) {
|
||||
int us_drm_open(us_drm_s *drm, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd < 0);
|
||||
@@ -101,33 +102,33 @@ int us_drm_open(us_drm_s *drm, const us_device_s *dev) {
|
||||
default: goto error;
|
||||
}
|
||||
|
||||
_D_LOG_INFO("Configuring DRM device for %s ...", (dev == NULL ? "STUB" : "DMA"));
|
||||
_LOG_INFO("Configuring DRM device for %s ...", (cap == NULL ? "STUB" : "DMA"));
|
||||
|
||||
if ((run->fd = open(drm->path, O_RDWR | O_CLOEXEC | O_NONBLOCK)) < 0) {
|
||||
_D_LOG_PERROR("Can't open DRM device");
|
||||
_LOG_PERROR("Can't open DRM device");
|
||||
goto error;
|
||||
}
|
||||
_D_LOG_DEBUG("DRM device fd=%d opened", run->fd);
|
||||
_LOG_DEBUG("DRM device fd=%d opened", run->fd);
|
||||
|
||||
int stub = 0; // Open the real device with DMA
|
||||
if (dev == NULL) {
|
||||
if (cap == NULL) {
|
||||
stub = US_DRM_STUB_USER;
|
||||
} else if (dev->run->format != V4L2_PIX_FMT_RGB24) {
|
||||
} else if (cap->run->format != V4L2_PIX_FMT_RGB24 && cap->run->format != V4L2_PIX_FMT_BGR24) {
|
||||
stub = US_DRM_STUB_BAD_FORMAT;
|
||||
char fourcc_str[8];
|
||||
us_fourcc_to_string(dev->run->format, fourcc_str, 8);
|
||||
_D_LOG_ERROR("Input format %s is not supported, forcing to STUB ...", fourcc_str);
|
||||
us_fourcc_to_string(cap->run->format, fourcc_str, 8);
|
||||
_LOG_ERROR("Input format %s is not supported, forcing to STUB ...", fourcc_str);
|
||||
}
|
||||
|
||||
# define CHECK_CAP(x_cap) { \
|
||||
_D_LOG_DEBUG("Checking %s ...", #x_cap); \
|
||||
_LOG_DEBUG("Checking %s ...", #x_cap); \
|
||||
u64 m_check; \
|
||||
if (drmGetCap(run->fd, x_cap, &m_check) < 0) { \
|
||||
_D_LOG_PERROR("Can't check " #x_cap); \
|
||||
_LOG_PERROR("Can't check " #x_cap); \
|
||||
goto error; \
|
||||
} \
|
||||
if (!m_check) { \
|
||||
_D_LOG_ERROR(#x_cap " is not supported"); \
|
||||
_LOG_ERROR(#x_cap " is not supported"); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
@@ -137,9 +138,9 @@ int us_drm_open(us_drm_s *drm, const us_device_s *dev) {
|
||||
}
|
||||
# undef CHECK_CAP
|
||||
|
||||
const uint width = (stub > 0 ? 0 : dev->run->width);
|
||||
const uint height = (stub > 0 ? 0 : dev->run->height);
|
||||
const uint hz = (stub > 0 ? 0 : dev->run->hz);
|
||||
const uint width = (stub > 0 ? 0 : cap->run->width);
|
||||
const uint height = (stub > 0 ? 0 : cap->run->height);
|
||||
const uint hz = (stub > 0 ? 0 : cap->run->hz);
|
||||
switch (_drm_find_sink(drm, width, height, hz)) {
|
||||
case 0: break;
|
||||
case -2: goto unplugged;
|
||||
@@ -148,24 +149,24 @@ int us_drm_open(us_drm_s *drm, const us_device_s *dev) {
|
||||
if ((stub == 0) && (width != run->mode.hdisplay || height < run->mode.vdisplay)) {
|
||||
// We'll try to show something instead of nothing if height != vdisplay
|
||||
stub = US_DRM_STUB_BAD_RESOLUTION;
|
||||
_D_LOG_ERROR("There is no appropriate modes for the capture, forcing to STUB ...");
|
||||
_LOG_ERROR("There is no appropriate modes for the capture, forcing to STUB ...");
|
||||
}
|
||||
|
||||
if (_drm_init_buffers(drm, (stub > 0 ? NULL : dev)) < 0) {
|
||||
if (_drm_init_buffers(drm, (stub > 0 ? NULL : cap)) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
|
||||
_D_LOG_DEBUG("Setting up CRTC ...");
|
||||
_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");
|
||||
_LOG_PERROR("Can't set CRTC");
|
||||
goto error;
|
||||
}
|
||||
|
||||
run->opened_for_stub = (stub > 0);
|
||||
run->exposing_dma_fd = -1;
|
||||
run->unplugged_reported = false;
|
||||
_D_LOG_INFO("Opened for %s ...", (run->opened_for_stub ? "STUB" : "DMA"));
|
||||
_LOG_INFO("Opened for %s ...", (run->opened_for_stub ? "STUB" : "DMA"));
|
||||
return stub;
|
||||
|
||||
error:
|
||||
@@ -174,7 +175,7 @@ error:
|
||||
|
||||
unplugged:
|
||||
if (!run->unplugged_reported) {
|
||||
_D_LOG_ERROR("Display is not plugged");
|
||||
_LOG_ERROR("Display is not plugged");
|
||||
run->unplugged_reported = true;
|
||||
}
|
||||
us_drm_close(drm);
|
||||
@@ -193,33 +194,33 @@ void us_drm_close(us_drm_s *drm) {
|
||||
}
|
||||
|
||||
if (run->saved_crtc != NULL) {
|
||||
_D_LOG_DEBUG("Restoring CRTC ...");
|
||||
_LOG_DEBUG("Restoring CRTC ...");
|
||||
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 && errno != ENOENT) {
|
||||
_D_LOG_PERROR("Can't restore CRTC");
|
||||
_LOG_PERROR("Can't restore CRTC");
|
||||
}
|
||||
drmModeFreeCrtc(run->saved_crtc);
|
||||
run->saved_crtc = NULL;
|
||||
}
|
||||
|
||||
if (run->bufs != NULL) {
|
||||
_D_LOG_DEBUG("Releasing buffers ...");
|
||||
_LOG_DEBUG("Releasing buffers ...");
|
||||
for (uint n_buf = 0; n_buf < run->n_bufs; ++n_buf) {
|
||||
us_drm_buffer_s *const buf = &run->bufs[n_buf];
|
||||
if (buf->fb_added && drmModeRmFB(run->fd, buf->id) < 0) {
|
||||
_D_LOG_PERROR("Can't remove buffer=%u", n_buf);
|
||||
_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);
|
||||
_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
|
||||
}
|
||||
}
|
||||
if (buf->data != NULL && munmap(buf->data, buf->allocated)) {
|
||||
_D_LOG_PERROR("Can't unmap buffer=%u", n_buf);
|
||||
_LOG_PERROR("Can't unmap buffer=%u", n_buf);
|
||||
}
|
||||
}
|
||||
US_DELETE(run->bufs, free);
|
||||
@@ -236,7 +237,7 @@ void us_drm_close(us_drm_s *drm) {
|
||||
run->stub_n_buf = 0;
|
||||
|
||||
if (say) {
|
||||
_D_LOG_INFO("Closed");
|
||||
_LOG_INFO("Closed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,13 +276,13 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(run->fd, &fds);
|
||||
|
||||
_D_LOG_DEBUG("Calling select() for VSync ...");
|
||||
_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);
|
||||
_LOG_PERROR("Can't select(%d) device for VSync", run->fd);
|
||||
return -1;
|
||||
} else if (result == 0) {
|
||||
_D_LOG_ERROR("Device timeout while waiting VSync");
|
||||
_LOG_ERROR("Device timeout while waiting VSync");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -289,9 +290,9 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
.version = DRM_EVENT_CONTEXT_VERSION,
|
||||
.page_flip_handler = _drm_vsync_callback,
|
||||
};
|
||||
_D_LOG_DEBUG("Handling DRM event (maybe VSync) ...");
|
||||
_LOG_DEBUG("Handling DRM event (maybe VSync) ...");
|
||||
if (drmHandleEvent(run->fd, &ctx) < 0) {
|
||||
_D_LOG_PERROR("Can't handle DRM event");
|
||||
_LOG_PERROR("Can't handle DRM event");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -305,10 +306,10 @@ static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void
|
||||
us_drm_buffer_s *const buf = v_buf;
|
||||
*buf->ctx.has_vsync = true;
|
||||
*buf->ctx.exposing_dma_fd = -1;
|
||||
_D_LOG_DEBUG("Got VSync signal");
|
||||
_LOG_DEBUG("Got VSync signal");
|
||||
}
|
||||
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev) {
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
@@ -324,24 +325,19 @@ int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev
|
||||
# define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay)
|
||||
switch (stub) {
|
||||
case US_DRM_STUB_BAD_RESOLUTION: {
|
||||
assert(dev != NULL);
|
||||
assert(cap != NULL);
|
||||
char msg[1024];
|
||||
US_SNPRINTF(msg, 1023,
|
||||
"=== PiKVM ==="
|
||||
"\n \n< UNSUPPORTED RESOLUTION >"
|
||||
"\n \n< %ux%up%.02f >"
|
||||
"\n \nby this display",
|
||||
dev->run->width, dev->run->height, dev->run->hz);
|
||||
cap->run->width, cap->run->height, cap->run->hz);
|
||||
DRAW_MSG(msg);
|
||||
break;
|
||||
};
|
||||
case US_DRM_STUB_BAD_FORMAT:
|
||||
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 -");
|
||||
DRAW_MSG("=== PiKVM ===\n \n< UNSUPPORTED CAPTURE FORMAT >");
|
||||
break;
|
||||
case US_DRM_STUB_NO_SIGNAL:
|
||||
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >");
|
||||
@@ -359,24 +355,24 @@ int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev
|
||||
|
||||
run->has_vsync = false;
|
||||
|
||||
_D_LOG_DEBUG("Copying STUB frame ...")
|
||||
_LOG_DEBUG("Copying STUB frame ...")
|
||||
memcpy(buf->data, run->ft->frame->data, US_MIN(run->ft->frame->used, buf->allocated));
|
||||
|
||||
_D_LOG_DEBUG("Exposing STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
_LOG_DEBUG("Exposing STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
const int retval = drmModePageFlip(
|
||||
run->fd, run->crtc_id, buf->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
|
||||
buf);
|
||||
if (retval < 0) {
|
||||
_D_LOG_PERROR("Can't expose STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
_LOG_PERROR("Can't expose STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
}
|
||||
_D_LOG_DEBUG("Exposed STUB framebuffer n_buf=%u", run->stub_n_buf);
|
||||
_LOG_DEBUG("Exposed STUB framebuffer n_buf=%u", run->stub_n_buf);
|
||||
|
||||
run->stub_n_buf = (run->stub_n_buf + 1) % run->n_bufs;
|
||||
return retval;
|
||||
}
|
||||
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw) {
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_capture_hwbuf_s *hw) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
us_drm_buffer_s *const buf = &run->bufs[hw->buf.index];
|
||||
|
||||
@@ -392,15 +388,15 @@ int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw) {
|
||||
|
||||
run->has_vsync = false;
|
||||
|
||||
_D_LOG_DEBUG("Exposing DMA framebuffer n_buf=%u ...", hw->buf.index);
|
||||
_LOG_DEBUG("Exposing DMA framebuffer n_buf=%u ...", hw->buf.index);
|
||||
const int retval = drmModePageFlip(
|
||||
run->fd, run->crtc_id, buf->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
|
||||
buf);
|
||||
if (retval < 0) {
|
||||
_D_LOG_PERROR("Can't expose DMA framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
_LOG_PERROR("Can't expose DMA framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
}
|
||||
_D_LOG_DEBUG("Exposed DMA framebuffer n_buf=%u", run->stub_n_buf);
|
||||
_LOG_DEBUG("Exposed DMA framebuffer n_buf=%u", run->stub_n_buf);
|
||||
run->exposing_dma_fd = hw->dma_fd;
|
||||
return retval;
|
||||
}
|
||||
@@ -409,35 +405,35 @@ static int _drm_check_status(us_drm_s *drm) {
|
||||
us_drm_runtime_s *run = drm->run;
|
||||
|
||||
if (run->status_fd < 0) {
|
||||
_D_LOG_DEBUG("Trying to find status file ...");
|
||||
_LOG_DEBUG("Trying to find status file ...");
|
||||
struct stat st;
|
||||
if (stat(drm->path, &st) < 0) {
|
||||
_D_LOG_PERROR("Can't stat() DRM device");
|
||||
_LOG_PERROR("Can't stat() DRM device");
|
||||
goto error;
|
||||
}
|
||||
const uint mi = minor(st.st_rdev);
|
||||
_D_LOG_DEBUG("DRM device minor(st_rdev)=%u", mi);
|
||||
_LOG_DEBUG("DRM device minor(st_rdev)=%u", mi);
|
||||
|
||||
char path[128];
|
||||
US_SNPRINTF(path, 127, "/sys/class/drm/card%u-%s/status", mi, drm->port);
|
||||
_D_LOG_DEBUG("Opening status file %s ...", path);
|
||||
_LOG_DEBUG("Opening status file %s ...", path);
|
||||
if ((run->status_fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) {
|
||||
_D_LOG_PERROR("Can't open status file: %s", path);
|
||||
_LOG_PERROR("Can't open status file: %s", path);
|
||||
goto error;
|
||||
}
|
||||
_D_LOG_DEBUG("Status file fd=%d opened", run->status_fd);
|
||||
_LOG_DEBUG("Status file fd=%d opened", run->status_fd);
|
||||
}
|
||||
|
||||
char status_ch;
|
||||
if (read(run->status_fd, &status_ch, 1) != 1) {
|
||||
_D_LOG_PERROR("Can't read status file");
|
||||
_LOG_PERROR("Can't read status file");
|
||||
goto error;
|
||||
}
|
||||
if (lseek(run->status_fd, 0, SEEK_SET) != 0) {
|
||||
_D_LOG_PERROR("Can't rewind status file");
|
||||
_LOG_PERROR("Can't rewind status file");
|
||||
goto error;
|
||||
}
|
||||
_D_LOG_DEBUG("Current display status: %c", status_ch);
|
||||
_LOG_DEBUG("Current display status: %c", status_ch);
|
||||
return (status_ch == 'd' ? -2 : 0);
|
||||
|
||||
error:
|
||||
@@ -448,24 +444,26 @@ error:
|
||||
static void _drm_ensure_dpms_power(us_drm_s *drm, bool on) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
if (run->dpms_id > 0 && run->dpms_state != (int)on) {
|
||||
_D_LOG_INFO("Changing DPMS power mode: %d -> %u ...", run->dpms_state, on);
|
||||
_LOG_INFO("Changing DPMS power mode: %d -> %u ...", run->dpms_state, on);
|
||||
if (drmModeConnectorSetProperty(
|
||||
run->fd, run->conn_id, run->dpms_id,
|
||||
(on ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)
|
||||
) < 0) {
|
||||
_D_LOG_PERROR("Can't set DPMS power=%u (ignored)", on);
|
||||
_LOG_PERROR("Can't set DPMS power=%u (ignored)", on);
|
||||
}
|
||||
}
|
||||
run->dpms_state = (int)on;
|
||||
}
|
||||
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
const uint n_bufs = (dev == NULL ? 4 : dev->run->n_bufs);
|
||||
const char *name = (dev == NULL ? "STUB" : "DMA");
|
||||
const uint n_bufs = (cap == NULL ? 4 : cap->run->n_bufs);
|
||||
const char *name = (cap == NULL ? "STUB" : "DMA");
|
||||
|
||||
_D_LOG_DEBUG("Initializing %u %s buffers ...", n_bufs, name);
|
||||
_LOG_DEBUG("Initializing %u %s buffers ...", n_bufs, name);
|
||||
|
||||
uint format = DRM_FORMAT_RGB888;
|
||||
|
||||
US_CALLOC(run->bufs, n_bufs);
|
||||
for (run->n_bufs = 0; run->n_bufs < n_bufs; ++run->n_bufs) {
|
||||
@@ -479,14 +477,14 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
u32 strides[4] = {0};
|
||||
u32 offsets[4] = {0};
|
||||
|
||||
if (dev == NULL) {
|
||||
if (cap == NULL) {
|
||||
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 %s buffer=%u", name, n_buf);
|
||||
_LOG_PERROR("Can't create %s buffer=%u", name, n_buf);
|
||||
return -1;
|
||||
}
|
||||
buf->handle = create.handle;
|
||||
@@ -494,7 +492,7 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
|
||||
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);
|
||||
_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
|
||||
return -1;
|
||||
}
|
||||
if ((buf->data = mmap(
|
||||
@@ -502,7 +500,7 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
run->fd, map.offset
|
||||
)) == MAP_FAILED) {
|
||||
_D_LOG_PERROR("Can't map buffer=%u", n_buf);
|
||||
_LOG_PERROR("Can't map buffer=%u", n_buf);
|
||||
return -1;
|
||||
}
|
||||
memset(buf->data, 0, create.size);
|
||||
@@ -512,20 +510,25 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
strides[0] = create.pitch;
|
||||
|
||||
} else {
|
||||
if (drmPrimeFDToHandle(run->fd, dev->run->hw_bufs[n_buf].dma_fd, &buf->handle) < 0) {
|
||||
_D_LOG_PERROR("Can't import DMA buffer=%u from capture device", n_buf);
|
||||
if (drmPrimeFDToHandle(run->fd, cap->run->bufs[n_buf].dma_fd, &buf->handle) < 0) {
|
||||
_LOG_PERROR("Can't import DMA buffer=%u from capture device", n_buf);
|
||||
return -1;
|
||||
}
|
||||
handles[0] = buf->handle;
|
||||
strides[0] = dev->run->stride;
|
||||
strides[0] = cap->run->stride;
|
||||
|
||||
switch (cap->run->format) {
|
||||
case V4L2_PIX_FMT_RGB24: format = (DRM_FORMAT_BIG_ENDIAN ? DRM_FORMAT_BGR888 : DRM_FORMAT_RGB888); break;
|
||||
case V4L2_PIX_FMT_BGR24: format = (DRM_FORMAT_BIG_ENDIAN ? DRM_FORMAT_RGB888 : DRM_FORMAT_BGR888); break;
|
||||
}
|
||||
}
|
||||
|
||||
if (drmModeAddFB2(
|
||||
run->fd,
|
||||
run->mode.hdisplay, run->mode.vdisplay, DRM_FORMAT_RGB888,
|
||||
run->mode.hdisplay, run->mode.vdisplay, format,
|
||||
handles, strides, offsets, &buf->id, 0
|
||||
)) {
|
||||
_D_LOG_PERROR("Can't setup buffer=%u", n_buf);
|
||||
_LOG_PERROR("Can't setup buffer=%u", n_buf);
|
||||
return -1;
|
||||
}
|
||||
buf->fb_added = true;
|
||||
@@ -538,22 +541,22 @@ static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
|
||||
|
||||
run->crtc_id = 0;
|
||||
|
||||
_D_LOG_DEBUG("Trying to find the appropriate sink ...");
|
||||
_LOG_DEBUG("Trying to find the appropriate sink ...");
|
||||
|
||||
drmModeRes *res = drmModeGetResources(run->fd);
|
||||
if (res == NULL) {
|
||||
_D_LOG_PERROR("Can't get resources info");
|
||||
_LOG_PERROR("Can't get resources info");
|
||||
goto done;
|
||||
}
|
||||
if (res->count_connectors <= 0) {
|
||||
_D_LOG_ERROR("Can't find any connectors");
|
||||
_LOG_ERROR("Can't find any connectors");
|
||||
goto done;
|
||||
}
|
||||
|
||||
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);
|
||||
_LOG_PERROR("Can't get connector index=%d", ci);
|
||||
goto done;
|
||||
}
|
||||
|
||||
@@ -565,37 +568,37 @@ static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
|
||||
drmModeFreeConnector(conn);
|
||||
continue;
|
||||
}
|
||||
_D_LOG_INFO("Using connector %s: conn_type=%d, conn_type_id=%d",
|
||||
_LOG_INFO("Using connector %s: conn_type=%d, conn_type_id=%d",
|
||||
drm->port, conn->connector_type, conn->connector_type_id);
|
||||
|
||||
if (conn->connection != DRM_MODE_CONNECTED) {
|
||||
_D_LOG_ERROR("Connector for port %s has !DRM_MODE_CONNECTED", drm->port);
|
||||
_LOG_ERROR("Connector for port %s has !DRM_MODE_CONNECTED", drm->port);
|
||||
drmModeFreeConnector(conn);
|
||||
goto done;
|
||||
}
|
||||
|
||||
drmModeModeInfo *best;
|
||||
if ((best = _find_best_mode(conn, width, height, hz)) == NULL) {
|
||||
_D_LOG_ERROR("Can't find any appropriate display modes");
|
||||
_LOG_ERROR("Can't find any appropriate display modes");
|
||||
drmModeFreeConnector(conn);
|
||||
goto unplugged;
|
||||
}
|
||||
_D_LOG_INFO("Using best mode: %ux%up%.02f",
|
||||
_LOG_INFO("Using best mode: %ux%up%.02f",
|
||||
best->hdisplay, best->vdisplay, _get_refresh_rate(best));
|
||||
|
||||
if ((run->dpms_id = _find_dpms(run->fd, conn)) > 0) {
|
||||
_D_LOG_INFO("Using DPMS: id=%u", run->dpms_id);
|
||||
_LOG_INFO("Using DPMS: id=%u", run->dpms_id);
|
||||
} else {
|
||||
_D_LOG_INFO("Using DPMS: None");
|
||||
_LOG_INFO("Using DPMS: None");
|
||||
}
|
||||
|
||||
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");
|
||||
_LOG_ERROR("Can't find CRTC");
|
||||
drmModeFreeConnector(conn);
|
||||
goto done;
|
||||
}
|
||||
_D_LOG_INFO("Using CRTC: id=%u", run->crtc_id);
|
||||
_LOG_INFO("Using CRTC: id=%u", run->crtc_id);
|
||||
|
||||
run->conn_id = conn->connector_id;
|
||||
memcpy(&run->mode, best, sizeof(drmModeModeInfo));
|
||||
@@ -25,10 +25,10 @@
|
||||
|
||||
#include <xf86drmMode.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/frametext.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../types.h"
|
||||
#include "../frame.h"
|
||||
#include "../frametext.h"
|
||||
#include "../capture.h"
|
||||
|
||||
|
||||
typedef enum {
|
||||
@@ -83,10 +83,10 @@ typedef struct {
|
||||
us_drm_s *us_drm_init(void);
|
||||
void us_drm_destroy(us_drm_s *drm);
|
||||
|
||||
int us_drm_open(us_drm_s *drm, const us_device_s *dev);
|
||||
int us_drm_open(us_drm_s *drm, const us_capture_s *cap);
|
||||
void us_drm_close(us_drm_s *drm);
|
||||
|
||||
int us_drm_dpms_power_off(us_drm_s *drm);
|
||||
int us_drm_wait_for_vsync(us_drm_s *drm);
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev);
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw);
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap);
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_capture_hwbuf_s *hw);
|
||||
@@ -68,5 +68,5 @@ uz us_memsink_calculate_size(const char *obj) {
|
||||
}
|
||||
|
||||
u8 *us_memsink_get_data(us_memsink_shared_s *mem) {
|
||||
return (u8*)(mem + sizeof(us_memsink_shared_s));
|
||||
return (u8*)(mem) + sizeof(us_memsink_shared_s);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
|
||||
#define US_MEMSINK_VERSION ((u32)5)
|
||||
#define US_MEMSINK_VERSION ((u32)6)
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -91,13 +91,13 @@ const char *us_encoder_type_to_string(us_encoder_type_e type) {
|
||||
return _ENCODER_TYPES[0].name;
|
||||
}
|
||||
|
||||
void us_encoder_open(us_encoder_s *enc, us_device_s *dev) {
|
||||
void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
|
||||
assert(enc->run->pool == NULL);
|
||||
|
||||
# define DR(x_next) dev->run->x_next
|
||||
# define DR(x_next) cap->run->x_next
|
||||
|
||||
us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
|
||||
unsigned quality = dev->jpeg_quality;
|
||||
unsigned quality = cap->jpeg_quality;
|
||||
unsigned n_workers = US_MIN(enc->n_workers, DR(n_bufs));
|
||||
bool cpu_forced = false;
|
||||
|
||||
@@ -139,7 +139,7 @@ void us_encoder_open(us_encoder_s *enc, us_device_s *dev) {
|
||||
|
||||
use_cpu:
|
||||
type = US_ENCODER_TYPE_CPU;
|
||||
quality = dev->jpeg_quality;
|
||||
quality = cap->jpeg_quality;
|
||||
|
||||
ok:
|
||||
if (type == US_ENCODER_TYPE_NOOP) {
|
||||
@@ -159,8 +159,8 @@ void us_encoder_open(us_encoder_s *enc, us_device_s *dev) {
|
||||
US_MUTEX_UNLOCK(_ER(mutex));
|
||||
|
||||
const long double desired_interval = (
|
||||
dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)
|
||||
? (long double)1 / dev->desired_fps
|
||||
cap->desired_fps > 0 && (cap->desired_fps < cap->run->hw_fps || cap->run->hw_fps == 0)
|
||||
? (long double)1 / cap->desired_fps
|
||||
: 0
|
||||
);
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../libs/capture.h"
|
||||
|
||||
#include "workers.h"
|
||||
#include "m2m.h"
|
||||
@@ -75,9 +75,9 @@ typedef struct {
|
||||
} us_encoder_s;
|
||||
|
||||
typedef struct {
|
||||
us_encoder_s *enc;
|
||||
us_hw_buffer_s *hw;
|
||||
us_frame_s *dest;
|
||||
us_encoder_s *enc;
|
||||
us_capture_hwbuf_s *hw;
|
||||
us_frame_s *dest;
|
||||
} us_encoder_job_s;
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ void us_encoder_destroy(us_encoder_s *enc);
|
||||
int us_encoder_parse_type(const char *str);
|
||||
const char *us_encoder_type_to_string(us_encoder_type_e type);
|
||||
|
||||
void us_encoder_open(us_encoder_s *enc, us_device_s *dev);
|
||||
void us_encoder_open(us_encoder_s *enc, us_capture_s *cap);
|
||||
void us_encoder_close(us_encoder_s *enc);
|
||||
|
||||
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality);
|
||||
|
||||
@@ -102,11 +102,11 @@ static const char *_http_get_header(struct evhttp_request *request, const char *
|
||||
static char *_http_get_client_hostport(struct evhttp_request *request);
|
||||
|
||||
|
||||
#define _S_LOG_ERROR(x_msg, ...) US_LOG_ERROR("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _S_LOG_PERROR(x_msg, ...) US_LOG_PERROR("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _S_LOG_INFO(x_msg, ...) US_LOG_INFO("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _S_LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _S_LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("HTTP: " x_msg, ##__VA_ARGS__)
|
||||
|
||||
#define _A_EVBUFFER_NEW(x_buf) assert((x_buf = evbuffer_new()) != NULL)
|
||||
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size))
|
||||
@@ -188,7 +188,7 @@ int us_server_listen(us_server_s *server) {
|
||||
|
||||
{
|
||||
if (server->static_path[0] != '\0') {
|
||||
_S_LOG_INFO("Enabling the file server: %s", server->static_path);
|
||||
_LOG_INFO("Enabling the file server: %s", server->static_path);
|
||||
evhttp_set_gencb(run->http, _http_callback_static, (void*)server);
|
||||
} else {
|
||||
assert(!evhttp_set_cb(run->http, "/", _http_callback_root, (void*)server));
|
||||
@@ -205,8 +205,8 @@ int us_server_listen(us_server_s *server) {
|
||||
|
||||
{
|
||||
struct timeval interval = {0};
|
||||
if (stream->dev->desired_fps > 0) {
|
||||
interval.tv_usec = 1000000 / (stream->dev->desired_fps * 2);
|
||||
if (stream->cap->desired_fps > 0) {
|
||||
interval.tv_usec = 1000000 / (stream->cap->desired_fps * 2);
|
||||
} else {
|
||||
interval.tv_usec = 16000; // ~60fps
|
||||
}
|
||||
@@ -227,11 +227,11 @@ int us_server_listen(us_server_s *server) {
|
||||
US_ASPRINTF(run->auth_token, "Basic %s", encoded_token);
|
||||
free(encoded_token);
|
||||
|
||||
_S_LOG_INFO("Using HTTP basic auth");
|
||||
_LOG_INFO("Using HTTP basic auth");
|
||||
}
|
||||
|
||||
if (server->unix_path[0] != '\0') {
|
||||
_S_LOG_DEBUG("Binding server to UNIX socket '%s' ...", server->unix_path);
|
||||
_LOG_DEBUG("Binding server to UNIX socket '%s' ...", server->unix_path);
|
||||
if ((run->ext_fd = us_evhttp_bind_unix(
|
||||
run->http,
|
||||
server->unix_path,
|
||||
@@ -240,33 +240,33 @@ int us_server_listen(us_server_s *server) {
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
_S_LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
|
||||
_LOG_INFO("Listening HTTP on UNIX socket '%s'", server->unix_path);
|
||||
|
||||
# ifdef WITH_SYSTEMD
|
||||
} else if (server->systemd) {
|
||||
_S_LOG_DEBUG("Binding HTTP to systemd socket ...");
|
||||
_LOG_DEBUG("Binding HTTP to systemd socket ...");
|
||||
if ((run->ext_fd = us_evhttp_bind_systemd(run->http)) < 0) {
|
||||
return -1;
|
||||
}
|
||||
_S_LOG_INFO("Listening systemd socket ...");
|
||||
_LOG_INFO("Listening systemd socket ...");
|
||||
# endif
|
||||
|
||||
} else {
|
||||
_S_LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||
_LOG_DEBUG("Binding HTTP to [%s]:%u ...", server->host, server->port);
|
||||
if (evhttp_bind_socket(run->http, server->host, server->port) < 0) {
|
||||
_S_LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port)
|
||||
_LOG_PERROR("Can't bind HTTP on [%s]:%u", server->host, server->port)
|
||||
return -1;
|
||||
}
|
||||
_S_LOG_INFO("Listening HTTP on [%s]:%u", server->host, server->port);
|
||||
_LOG_INFO("Listening HTTP on [%s]:%u", server->host, server->port);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void us_server_loop(us_server_s *server) {
|
||||
_S_LOG_INFO("Starting eventloop ...");
|
||||
_LOG_INFO("Starting eventloop ...");
|
||||
event_base_dispatch(server->run->base);
|
||||
_S_LOG_INFO("Eventloop stopped");
|
||||
_LOG_INFO("Eventloop stopped");
|
||||
}
|
||||
|
||||
void us_server_loop_break(us_server_s *server) {
|
||||
@@ -407,18 +407,18 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
|
||||
}
|
||||
|
||||
if ((fd = open(static_path, O_RDONLY)) < 0) {
|
||||
_S_LOG_PERROR("Can't open found static file %s", static_path);
|
||||
_LOG_PERROR("Can't open found static file %s", static_path);
|
||||
goto not_found;
|
||||
}
|
||||
|
||||
{
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) < 0) {
|
||||
_S_LOG_PERROR("Can't stat() found static file %s", static_path);
|
||||
_LOG_PERROR("Can't stat() found static file %s", static_path);
|
||||
goto not_found;
|
||||
}
|
||||
if (st.st_size > 0 && evbuffer_add_file(buf, fd, 0, st.st_size) < 0) {
|
||||
_S_LOG_ERROR("Can't serve static file %s", static_path);
|
||||
_LOG_ERROR("Can't serve static file %s", static_path);
|
||||
goto not_found;
|
||||
}
|
||||
|
||||
@@ -512,7 +512,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
|
||||
(server->fake_width ? server->fake_width : width),
|
||||
(server->fake_height ? server->fake_height : height),
|
||||
us_bool_to_string(online),
|
||||
stream->dev->desired_fps,
|
||||
stream->cap->desired_fps,
|
||||
captured_fps,
|
||||
ex->queued_fps,
|
||||
run->stream_clients_count
|
||||
@@ -599,17 +599,17 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
# endif
|
||||
}
|
||||
|
||||
_S_LOG_INFO("NEW client (now=%u): %s, id=%" PRIx64,
|
||||
_LOG_INFO("NEW client (now=%u): %s, id=%" PRIx64,
|
||||
run->stream_clients_count, client->hostport, client->id);
|
||||
|
||||
struct bufferevent *const buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
if (server->tcp_nodelay && run->ext_fd >= 0) {
|
||||
_S_LOG_DEBUG("Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||
_LOG_DEBUG("Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||
const evutil_socket_t fd = bufferevent_getfd(buf_event);
|
||||
assert(fd >= 0);
|
||||
int on = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) != 0) {
|
||||
_S_LOG_PERROR("Can't set TCP_NODELAY to the client %s", client->hostport);
|
||||
_LOG_PERROR("Can't set TCP_NODELAY to the client %s", client->hostport);
|
||||
}
|
||||
}
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client);
|
||||
@@ -786,7 +786,7 @@ static void _http_callback_stream_error(struct bufferevent *buf_event, short wha
|
||||
}
|
||||
|
||||
char *const reason = us_bufferevent_format_reason(what);
|
||||
_S_LOG_INFO("DEL client (now=%u): %s, id=%" PRIx64 ", %s",
|
||||
_LOG_INFO("DEL client (now=%u): %s, id=%" PRIx64 ", %s",
|
||||
run->stream_clients_count, client->hostport, client->id, reason);
|
||||
free(reason);
|
||||
|
||||
@@ -942,7 +942,7 @@ static void _http_refresher(int fd, short what, void *v_server) {
|
||||
stream_updated = true;
|
||||
us_ring_consumer_release(ring, ri);
|
||||
} else if (ex->expose_end_ts + 1 < us_get_now_monotonic()) {
|
||||
_S_LOG_DEBUG("Repeating exposed ...");
|
||||
_LOG_DEBUG("Repeating exposed ...");
|
||||
ex->expose_begin_ts = us_get_now_monotonic();
|
||||
ex->expose_cmp_ts = ex->expose_begin_ts;
|
||||
ex->expose_end_ts = ex->expose_begin_ts;
|
||||
@@ -972,7 +972,7 @@ static void _http_refresher(int fd, short what, void *v_server) {
|
||||
static bool _expose_frame(us_server_s *server, const us_frame_s *frame) {
|
||||
us_server_exposed_s *const ex = server->run->exposed;
|
||||
|
||||
_S_LOG_DEBUG("Updating exposed frame (online=%d) ...", frame->online);
|
||||
_LOG_DEBUG("Updating exposed frame (online=%d) ...", frame->online);
|
||||
ex->expose_begin_ts = us_get_now_monotonic();
|
||||
|
||||
if (server->drop_same_frames && frame->online) {
|
||||
@@ -984,13 +984,13 @@ static bool _expose_frame(us_server_s *server, const us_frame_s *frame) {
|
||||
) {
|
||||
ex->expose_cmp_ts = us_get_now_monotonic();
|
||||
ex->expose_end_ts = ex->expose_cmp_ts;
|
||||
_S_LOG_VERBOSE("Dropped same frame number %u; cmp_time=%.06Lf",
|
||||
_LOG_VERBOSE("Dropped same frame number %u; cmp_time=%.06Lf",
|
||||
ex->dropped, (ex->expose_cmp_ts - ex->expose_begin_ts));
|
||||
ex->dropped += 1;
|
||||
return false; // Not updated
|
||||
} else {
|
||||
ex->expose_cmp_ts = us_get_now_monotonic();
|
||||
_S_LOG_VERBOSE("Passed same frame check (need_drop=%d, maybe_same=%d); cmp_time=%.06Lf",
|
||||
_LOG_VERBOSE("Passed same frame check (need_drop=%d, maybe_same=%d); cmp_time=%.06Lf",
|
||||
need_drop, maybe_same, (ex->expose_cmp_ts - ex->expose_begin_ts));
|
||||
}
|
||||
}
|
||||
@@ -1007,7 +1007,7 @@ static bool _expose_frame(us_server_s *server, const us_frame_s *frame) {
|
||||
ex->expose_cmp_ts = ex->expose_begin_ts;
|
||||
ex->expose_end_ts = us_get_now_monotonic();
|
||||
|
||||
_S_LOG_VERBOSE("Exposed frame: online=%d, exp_time=%.06Lf",
|
||||
_LOG_VERBOSE("Exposed frame: online=%d, exp_time=%.06Lf",
|
||||
ex->frame->online, (ex->expose_end_ts - ex->expose_begin_ts));
|
||||
return true; // Updated
|
||||
}
|
||||
|
||||
@@ -56,11 +56,11 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc);
|
||||
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key);
|
||||
|
||||
|
||||
#define _E_LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_INFO(x_msg, ...) US_LOG_INFO("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
|
||||
|
||||
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, uint bitrate, uint gop) {
|
||||
@@ -85,7 +85,7 @@ us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, u
|
||||
}
|
||||
|
||||
void us_m2m_encoder_destroy(us_m2m_encoder_s *enc) {
|
||||
_E_LOG_INFO("Destroying encoder ...");
|
||||
_LOG_INFO("Destroying encoder ...");
|
||||
_m2m_encoder_cleanup(enc);
|
||||
free(enc->path);
|
||||
free(enc->name);
|
||||
@@ -104,17 +104,17 @@ int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_fra
|
||||
|
||||
force_key = (enc->output_format == V4L2_PIX_FMT_H264 && (force_key || run->last_online != src->online));
|
||||
|
||||
_E_LOG_DEBUG("Compressing new frame; force_key=%d ...", force_key);
|
||||
_LOG_DEBUG("Compressing new frame; force_key=%d ...", force_key);
|
||||
|
||||
if (_m2m_encoder_compress_raw(enc, src, dest, force_key) < 0) {
|
||||
_m2m_encoder_cleanup(enc);
|
||||
_E_LOG_ERROR("Encoder destroyed due an error (compress)");
|
||||
_LOG_ERROR("Encoder destroyed due an error (compress)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
us_frame_encoding_end(dest);
|
||||
|
||||
_E_LOG_VERBOSE("Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d",
|
||||
_LOG_VERBOSE("Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d",
|
||||
dest->used, dest->encode_end_ts - dest->encode_begin_ts, force_key);
|
||||
|
||||
run->last_online = src->online;
|
||||
@@ -151,7 +151,7 @@ static us_m2m_encoder_s *_m2m_encoder_init(
|
||||
|
||||
#define _E_XIOCTL(x_request, x_value, x_msg, ...) { \
|
||||
if (us_xioctl(run->fd, x_request, x_value) < 0) { \
|
||||
_E_LOG_PERROR(x_msg, ##__VA_ARGS__); \
|
||||
_LOG_PERROR(x_msg, ##__VA_ARGS__); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
@@ -170,9 +170,9 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
return; // Configured already
|
||||
}
|
||||
|
||||
_E_LOG_INFO("Configuring encoder: DMA=%d ...", dma);
|
||||
_LOG_INFO("Configuring encoder: DMA=%d ...", dma);
|
||||
|
||||
_E_LOG_DEBUG("Encoder changes: width=%u->%u, height=%u->%u, input_format=%u->%u, stride=%u->%u, dma=%u->%u",
|
||||
_LOG_DEBUG("Encoder changes: width=%u->%u, height=%u->%u, input_format=%u->%u, stride=%u->%u, dma=%u->%u",
|
||||
run->p_width, frame->width,
|
||||
run->p_height, frame->height,
|
||||
run->p_input_format, frame->format,
|
||||
@@ -187,18 +187,18 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
run->p_stride = frame->stride;
|
||||
run->p_dma = dma;
|
||||
|
||||
_E_LOG_DEBUG("Opening encoder device ...");
|
||||
_LOG_DEBUG("Opening encoder device ...");
|
||||
if ((run->fd = open(enc->path, O_RDWR)) < 0) {
|
||||
_E_LOG_PERROR("Can't open encoder device");
|
||||
_LOG_PERROR("Can't open encoder device");
|
||||
goto error;
|
||||
}
|
||||
_E_LOG_DEBUG("Encoder device fd=%d opened", run->fd);
|
||||
_LOG_DEBUG("Encoder device fd=%d opened", run->fd);
|
||||
|
||||
# define SET_OPTION(x_cid, x_value) { \
|
||||
struct v4l2_control m_ctl = {0}; \
|
||||
m_ctl.id = x_cid; \
|
||||
m_ctl.value = x_value; \
|
||||
_E_LOG_DEBUG("Configuring option " #x_cid " ..."); \
|
||||
_LOG_DEBUG("Configuring option " #x_cid " ..."); \
|
||||
_E_XIOCTL(VIDIOC_S_CTRL, &m_ctl, "Can't set option " #x_cid); \
|
||||
}
|
||||
if (enc->output_format == V4L2_PIX_FMT_H264) {
|
||||
@@ -227,10 +227,10 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
fmt.fmt.pix_mp.height = run->p_height;
|
||||
fmt.fmt.pix_mp.pixelformat = run->p_input_format;
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // libcamera currently has no means to request the right colour space
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // FIXME: Wrong colors
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = run->p_stride;
|
||||
_E_LOG_DEBUG("Configuring INPUT format ...");
|
||||
_LOG_DEBUG("Configuring INPUT format ...");
|
||||
_E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set INPUT format");
|
||||
}
|
||||
|
||||
@@ -249,13 +249,13 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
// https://github.com/raspberrypi/linux/pull/5232
|
||||
fmt.fmt.pix_mp.plane_fmt[0].sizeimage = (1024 + 512) << 10; // 1.5Mb
|
||||
}
|
||||
_E_LOG_DEBUG("Configuring OUTPUT format ...");
|
||||
_LOG_DEBUG("Configuring OUTPUT format ...");
|
||||
_E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
|
||||
if (fmt.fmt.pix_mp.pixelformat != enc->output_format) {
|
||||
char fourcc_str[8];
|
||||
_E_LOG_ERROR("The OUTPUT format can't be configured as %s",
|
||||
_LOG_ERROR("The OUTPUT format can't be configured as %s",
|
||||
us_fourcc_to_string(enc->output_format, fourcc_str, 8));
|
||||
_E_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
|
||||
_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@@ -277,7 +277,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
setfps.parm.output.timeperframe.numerator = 1;
|
||||
setfps.parm.output.timeperframe.denominator = run->fps_limit;
|
||||
_E_LOG_DEBUG("Configuring INPUT FPS ...");
|
||||
_LOG_DEBUG("Configuring INPUT FPS ...");
|
||||
_E_XIOCTL(VIDIOC_S_PARM, &setfps, "Can't set INPUT FPS");
|
||||
}
|
||||
|
||||
@@ -296,21 +296,21 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
|
||||
|
||||
{
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
_E_LOG_DEBUG("Starting INPUT ...");
|
||||
_LOG_DEBUG("Starting INPUT ...");
|
||||
_E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start INPUT");
|
||||
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
_E_LOG_DEBUG("Starting OUTPUT ...");
|
||||
_LOG_DEBUG("Starting OUTPUT ...");
|
||||
_E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start OUTPUT");
|
||||
}
|
||||
|
||||
run->ready = true;
|
||||
_E_LOG_INFO("Encoder is ready");
|
||||
_LOG_INFO("Encoder is ready");
|
||||
return;
|
||||
|
||||
error:
|
||||
_m2m_encoder_cleanup(enc);
|
||||
_E_LOG_ERROR("Encoder destroyed due an error (prepare)");
|
||||
_LOG_ERROR("Encoder destroyed due an error (prepare)");
|
||||
}
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
@@ -319,20 +319,20 @@ static int _m2m_encoder_init_buffers(
|
||||
|
||||
us_m2m_encoder_runtime_s *const run = enc->run;
|
||||
|
||||
_E_LOG_DEBUG("Initializing %s buffers ...", name);
|
||||
_LOG_DEBUG("Initializing %s buffers ...", name);
|
||||
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = type;
|
||||
req.memory = (dma ? V4L2_MEMORY_DMABUF : V4L2_MEMORY_MMAP);
|
||||
|
||||
_E_LOG_DEBUG("Requesting %u %s buffers ...", req.count, name);
|
||||
_LOG_DEBUG("Requesting %u %s buffers ...", req.count, name);
|
||||
_E_XIOCTL(VIDIOC_REQBUFS, &req, "Can't request %s buffers", name);
|
||||
if (req.count < 1) {
|
||||
_E_LOG_ERROR("Insufficient %s buffer memory: %u", name, req.count);
|
||||
_LOG_ERROR("Insufficient %s buffer memory: %u", name, req.count);
|
||||
goto error;
|
||||
}
|
||||
_E_LOG_DEBUG("Got %u %s buffers", req.count, name);
|
||||
_LOG_DEBUG("Got %u %s buffers", req.count, name);
|
||||
|
||||
if (dma) {
|
||||
*n_bufs_ptr = req.count;
|
||||
@@ -349,25 +349,25 @@ static int _m2m_encoder_init_buffers(
|
||||
buf.length = 1;
|
||||
buf.m.planes = &plane;
|
||||
|
||||
_E_LOG_DEBUG("Querying %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_LOG_DEBUG("Querying %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_E_XIOCTL(VIDIOC_QUERYBUF, &buf, "Can't query %s buffer=%u", name, *n_bufs_ptr);
|
||||
|
||||
_E_LOG_DEBUG("Mapping %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_LOG_DEBUG("Mapping %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
if (((*bufs_ptr)[*n_bufs_ptr].data = mmap(
|
||||
NULL, plane.length,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
run->fd, plane.m.mem_offset
|
||||
)) == MAP_FAILED) {
|
||||
_E_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
|
||||
_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
|
||||
goto error;
|
||||
}
|
||||
assert((*bufs_ptr)[*n_bufs_ptr].data != NULL);
|
||||
(*bufs_ptr)[*n_bufs_ptr].allocated = plane.length;
|
||||
|
||||
_E_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &buf, "Can't queue %s buffer=%u", name, *n_bufs_ptr);
|
||||
}
|
||||
_E_LOG_DEBUG("All %s buffers are ready", name);
|
||||
_LOG_DEBUG("All %s buffers are ready", name);
|
||||
return 0;
|
||||
|
||||
error: // Mostly for _E_XIOCTL
|
||||
@@ -383,9 +383,9 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
|
||||
say = true;
|
||||
# define STOP_STREAM(x_name, x_type) { \
|
||||
enum v4l2_buf_type m_type_var = x_type; \
|
||||
_E_LOG_DEBUG("Stopping %s ...", x_name); \
|
||||
_LOG_DEBUG("Stopping %s ...", x_name); \
|
||||
if (us_xioctl(run->fd, VIDIOC_STREAMOFF, &m_type_var) < 0) { \
|
||||
_E_LOG_PERROR("Can't stop %s", x_name); \
|
||||
_LOG_PERROR("Can't stop %s", x_name); \
|
||||
} \
|
||||
}
|
||||
STOP_STREAM("OUTPUT", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
|
||||
@@ -400,7 +400,7 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
|
||||
us_m2m_buffer_s *m_buf = &run->x_target##_bufs[m_index]; \
|
||||
if (m_buf->allocated > 0 && m_buf->data != NULL) { \
|
||||
if (munmap(m_buf->data, m_buf->allocated) < 0) { \
|
||||
_E_LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_index); \
|
||||
_LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_index); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
@@ -415,7 +415,7 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
|
||||
if (run->fd >= 0) {
|
||||
say = true;
|
||||
if (close(run->fd) < 0) {
|
||||
_E_LOG_PERROR("Can't close encoder device");
|
||||
_LOG_PERROR("Can't close encoder device");
|
||||
}
|
||||
run->fd = -1;
|
||||
}
|
||||
@@ -424,7 +424,7 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
|
||||
run->ready = false;
|
||||
|
||||
if (say) {
|
||||
_E_LOG_INFO("Encoder closed");
|
||||
_LOG_INFO("Encoder closed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
struct v4l2_control ctl = {0};
|
||||
ctl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME;
|
||||
ctl.value = 1;
|
||||
_E_LOG_DEBUG("Forcing keyframe ...")
|
||||
_LOG_DEBUG("Forcing keyframe ...")
|
||||
_E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
|
||||
}
|
||||
|
||||
@@ -452,17 +452,17 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
input_buf.memory = V4L2_MEMORY_DMABUF;
|
||||
input_buf.field = V4L2_FIELD_NONE;
|
||||
input_plane.m.fd = src->dma_fd;
|
||||
_E_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index);
|
||||
_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index);
|
||||
} else {
|
||||
input_buf.memory = V4L2_MEMORY_MMAP;
|
||||
_E_LOG_DEBUG("Grabbing INPUT buffer ...");
|
||||
_LOG_DEBUG("Grabbing INPUT buffer ...");
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't grab INPUT buffer");
|
||||
if (input_buf.index >= run->n_input_bufs) {
|
||||
_E_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
|
||||
_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
|
||||
input_buf.index, run->n_input_bufs);
|
||||
goto error;
|
||||
}
|
||||
_E_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index);
|
||||
_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index);
|
||||
}
|
||||
|
||||
const u64 now_ts = us_get_now_monotonic_u64();
|
||||
@@ -481,7 +481,7 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
|
||||
const char *input_name = (run->p_dma ? "INPUT-DMA" : "INPUT");
|
||||
|
||||
_E_LOG_DEBUG("Sending%s %s buffer ...", (!run->p_dma ? " (releasing)" : ""), input_name);
|
||||
_LOG_DEBUG("Sending%s %s buffer ...", (!run->p_dma ? " (releasing)" : ""), input_name);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &input_buf, "Can't send %s buffer", input_name);
|
||||
|
||||
// Для не-DMA отправка буфера по факту являтся освобождением этого буфера
|
||||
@@ -493,20 +493,20 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
|
||||
while (true) {
|
||||
if (us_get_now_monotonic() > deadline_ts) {
|
||||
_E_LOG_ERROR("Waiting for the encoder is too long");
|
||||
_LOG_ERROR("Waiting for the encoder is too long");
|
||||
goto error;
|
||||
}
|
||||
|
||||
struct pollfd enc_poll = {run->fd, POLLIN, 0};
|
||||
_E_LOG_DEBUG("Polling encoder ...");
|
||||
_LOG_DEBUG("Polling encoder ...");
|
||||
if (poll(&enc_poll, 1, 1000) < 0 && errno != EINTR) {
|
||||
_E_LOG_PERROR("Can't poll encoder");
|
||||
_LOG_PERROR("Can't poll encoder");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (enc_poll.revents & POLLIN) {
|
||||
if (!input_released) {
|
||||
_E_LOG_DEBUG("Releasing %s buffer=%u ...", input_name, input_buf.index);
|
||||
_LOG_DEBUG("Releasing %s buffer=%u ...", input_name, input_buf.index);
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't release %s buffer=%u",
|
||||
input_name, input_buf.index);
|
||||
input_released = true;
|
||||
@@ -518,7 +518,7 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
output_buf.memory = V4L2_MEMORY_MMAP;
|
||||
output_buf.length = 1;
|
||||
output_buf.m.planes = &output_plane;
|
||||
_E_LOG_DEBUG("Fetching OUTPUT buffer ...");
|
||||
_LOG_DEBUG("Fetching OUTPUT buffer ...");
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &output_buf, "Can't fetch OUTPUT buffer");
|
||||
|
||||
bool done = false;
|
||||
@@ -526,7 +526,7 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
// Енкодер первый раз может выдать буфер с мусором и нулевым таймстампом,
|
||||
// так что нужно убедиться, что мы читаем выходной буфер, соответствующий
|
||||
// входному (с тем же таймстампом).
|
||||
_E_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
|
||||
_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
|
||||
} else {
|
||||
us_frame_set_data(dest, run->output_bufs[output_buf.index].data, output_plane.bytesused);
|
||||
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
|
||||
@@ -534,7 +534,7 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
|
||||
done = true;
|
||||
}
|
||||
|
||||
_E_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index);
|
||||
_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &output_buf, "Can't release OUTPUT buffer=%u", output_buf.index);
|
||||
|
||||
if (done) {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../libs/capture.h"
|
||||
#include "../libs/signal.h"
|
||||
|
||||
#include "options.h"
|
||||
@@ -84,12 +84,12 @@ int main(int argc, char *argv[]) {
|
||||
US_THREAD_RENAME("main");
|
||||
|
||||
us_options_s *options = us_options_init(argc, argv);
|
||||
us_device_s *dev = us_device_init();
|
||||
us_capture_s *cap = us_capture_init();
|
||||
us_encoder_s *enc = us_encoder_init();
|
||||
_g_stream = us_stream_init(dev, enc);
|
||||
_g_stream = us_stream_init(cap, enc);
|
||||
_g_server = us_server_init(_g_stream);
|
||||
|
||||
if ((exit_code = options_parse(options, dev, enc, _g_stream, _g_server)) == 0) {
|
||||
if ((exit_code = options_parse(options, cap, enc, _g_stream, _g_server)) == 0) {
|
||||
# ifdef WITH_GPIO
|
||||
us_gpio_init();
|
||||
# endif
|
||||
@@ -118,7 +118,7 @@ int main(int argc, char *argv[]) {
|
||||
us_server_destroy(_g_server);
|
||||
us_stream_destroy(_g_stream);
|
||||
us_encoder_destroy(enc);
|
||||
us_device_destroy(dev);
|
||||
us_capture_destroy(cap);
|
||||
us_options_destroy(options);
|
||||
|
||||
if (exit_code == 0) {
|
||||
|
||||
@@ -61,6 +61,7 @@ enum _US_OPT_VALUES {
|
||||
|
||||
_O_DEVICE_TIMEOUT = 10000,
|
||||
_O_DEVICE_ERROR_DELAY,
|
||||
_O_FORMAT_SWAP_RGB,
|
||||
_O_M2M_DEVICE,
|
||||
|
||||
_O_IMAGE_DEFAULT,
|
||||
@@ -100,6 +101,10 @@ enum _US_OPT_VALUES {
|
||||
_O_H264_M2M_DEVICE,
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_V4P
|
||||
_O_V4P,
|
||||
# endif
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
_O_GPIO_DEVICE,
|
||||
_O_GPIO_CONSUMER_PREFIX,
|
||||
@@ -132,6 +137,7 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"input", required_argument, NULL, _O_INPUT},
|
||||
{"resolution", required_argument, NULL, _O_RESOLUTION},
|
||||
{"format", required_argument, NULL, _O_FORMAT},
|
||||
{"format-swap-rgb", required_argument, NULL, _O_FORMAT_SWAP_RGB},
|
||||
{"tv-standard", required_argument, NULL, _O_TV_STANDARD},
|
||||
{"io-method", required_argument, NULL, _O_IO_METHOD},
|
||||
{"desired-fps", required_argument, NULL, _O_DESIRED_FPS},
|
||||
@@ -204,6 +210,10 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"sink-client-ttl", required_argument, NULL, _O_JPEG_SINK_CLIENT_TTL},
|
||||
{"sink-timeout", required_argument, NULL, _O_JPEG_SINK_TIMEOUT},
|
||||
|
||||
# ifdef WITH_V4P
|
||||
{"v4p", no_argument, NULL, _O_V4P},
|
||||
# endif
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
{"gpio-device", required_argument, NULL, _O_GPIO_DEVICE},
|
||||
{"gpio-consumer-prefix", required_argument, NULL, _O_GPIO_CONSUMER_PREFIX},
|
||||
@@ -240,7 +250,7 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
|
||||
static int _check_instance_id(const char *str);
|
||||
|
||||
static void _features(void);
|
||||
static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server);
|
||||
static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server);
|
||||
|
||||
|
||||
us_options_s *us_options_init(unsigned argc, char *argv[]) {
|
||||
@@ -270,7 +280,7 @@ void us_options_destroy(us_options_s *options) {
|
||||
}
|
||||
|
||||
|
||||
int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) {
|
||||
int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) {
|
||||
# define OPT_SET(x_dest, x_value) { \
|
||||
x_dest = x_value; \
|
||||
break; \
|
||||
@@ -314,15 +324,15 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
|
||||
}
|
||||
|
||||
# define OPT_CTL_DEFAULT_NOBREAK(x_dest) { \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_DEFAULT; \
|
||||
cap->ctl.x_dest.mode = CTL_MODE_DEFAULT; \
|
||||
}
|
||||
|
||||
# define OPT_CTL_MANUAL(x_dest) { \
|
||||
if (!strcasecmp(optarg, "default")) { \
|
||||
OPT_CTL_DEFAULT_NOBREAK(x_dest); \
|
||||
} else { \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, dev->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
cap->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, cap->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
@@ -331,10 +341,10 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
|
||||
if (!strcasecmp(optarg, "default")) { \
|
||||
OPT_CTL_DEFAULT_NOBREAK(x_dest); \
|
||||
} else if (!strcasecmp(optarg, "auto")) { \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_AUTO; \
|
||||
cap->ctl.x_dest.mode = CTL_MODE_AUTO; \
|
||||
} else { \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, dev->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
cap->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, cap->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
@@ -359,28 +369,29 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
|
||||
|
||||
for (int ch; (ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
case _O_DEVICE: OPT_SET(dev->path, optarg);
|
||||
case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0);
|
||||
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
|
||||
case _O_DEVICE: OPT_SET(cap->path, optarg);
|
||||
case _O_INPUT: OPT_NUMBER("--input", cap->input, 0, 128, 0);
|
||||
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", cap->width, cap->height, true);
|
||||
# pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
# pragma GCC diagnostic push
|
||||
case _O_FORMAT: OPT_PARSE_ENUM("pixel format", dev->format, us_device_parse_format, US_FORMATS_STR);
|
||||
case _O_FORMAT: OPT_PARSE_ENUM("pixel format", cap->format, us_capture_parse_format, US_FORMATS_STR);
|
||||
# pragma GCC diagnostic pop
|
||||
case _O_TV_STANDARD: OPT_PARSE_ENUM("TV standard", dev->standard, us_device_parse_standard, US_STANDARDS_STR);
|
||||
case _O_IO_METHOD: OPT_PARSE_ENUM("IO method", dev->io_method, us_device_parse_io_method, US_IO_METHODS_STR);
|
||||
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, US_VIDEO_MAX_FPS, 0);
|
||||
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0);
|
||||
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
|
||||
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
|
||||
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_bufs, 1, 32, 0);
|
||||
case _O_FORMAT_SWAP_RGB: OPT_SET(cap->format_swap_rgb, true);
|
||||
case _O_TV_STANDARD: OPT_PARSE_ENUM("TV standard", cap->standard, us_capture_parse_standard, US_STANDARDS_STR);
|
||||
case _O_IO_METHOD: OPT_PARSE_ENUM("IO method", cap->io_method, us_capture_parse_io_method, US_IO_METHODS_STR);
|
||||
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", cap->desired_fps, 0, US_VIDEO_MAX_FPS, 0);
|
||||
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", cap->min_frame_size, 1, 8192, 0);
|
||||
case _O_PERSISTENT: OPT_SET(cap->persistent, true);
|
||||
case _O_DV_TIMINGS: OPT_SET(cap->dv_timings, true);
|
||||
case _O_BUFFERS: OPT_NUMBER("--buffers", cap->n_bufs, 1, 32, 0);
|
||||
case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0);
|
||||
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
|
||||
case _O_QUALITY: OPT_NUMBER("--quality", cap->jpeg_quality, 1, 100, 0);
|
||||
case _O_ENCODER: OPT_PARSE_ENUM("encoder type", enc->type, us_encoder_parse_type, ENCODER_TYPES_STR);
|
||||
case _O_GLITCHED_RESOLUTIONS: break; // Deprecated
|
||||
case _O_BLANK: break; // Deprecated
|
||||
case _O_LAST_AS_BLANK: break; // Deprecated
|
||||
case _O_SLOWDOWN: OPT_SET(stream->slowdown, true);
|
||||
case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", dev->timeout, 1, 60, 0);
|
||||
case _O_DEVICE_TIMEOUT: OPT_NUMBER("--device-timeout", cap->timeout, 1, 60, 0);
|
||||
case _O_DEVICE_ERROR_DELAY: OPT_NUMBER("--device-error-delay", stream->error_delay, 1, 60, 0);
|
||||
case _O_M2M_DEVICE: OPT_SET(enc->m2m_path, optarg);
|
||||
|
||||
@@ -451,6 +462,10 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
|
||||
|
||||
# ifdef WITH_V4P
|
||||
case _O_V4P: OPT_SET(stream->v4p, true);
|
||||
# endif
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
case _O_GPIO_DEVICE: OPT_SET(us_g_gpio.path, optarg);
|
||||
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(us_g_gpio.consumer_prefix, optarg);
|
||||
@@ -479,7 +494,7 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
|
||||
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, dev, enc, stream, server); return 1;
|
||||
case _O_HELP: _help(stdout, cap, enc, stream, server); return 1;
|
||||
case _O_VERSION: puts(US_VERSION); return 1;
|
||||
case _O_FEATURES: _features(); return 1;
|
||||
|
||||
@@ -588,7 +603,7 @@ static void _features(void) {
|
||||
# endif
|
||||
}
|
||||
|
||||
static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server) {
|
||||
static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server) {
|
||||
# define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
|
||||
SAY("═══════════════════════════════════════════════════");
|
||||
@@ -596,11 +611,13 @@ static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, con
|
||||
SAY("Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Capturing options:");
|
||||
SAY("══════════════════");
|
||||
SAY(" -d|--device </dev/path> ───────────── Path to V4L2 device. Default: %s.\n", dev->path);
|
||||
SAY(" -i|--input <N> ────────────────────── Input channel. Default: %u.\n", dev->input);
|
||||
SAY(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n", dev->width, dev->height);
|
||||
SAY(" -d|--device </dev/path> ───────────── Path to V4L2 device. Default: %s.\n", cap->path);
|
||||
SAY(" -i|--input <N> ────────────────────── Input channel. Default: %u.\n", cap->input);
|
||||
SAY(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n", cap->width, cap->height);
|
||||
SAY(" -m|--format <fmt> ─────────────────── Image format.");
|
||||
SAY(" Available: %s; default: YUYV.\n", US_FORMATS_STR);
|
||||
SAY(" --format-swap-rgb ──────────────── Enable R-G-B order swapping: RGB to BGR and vice versa.");
|
||||
SAY(" Default: disabled.\n");
|
||||
SAY(" -a|--tv-standard <std> ────────────── Force TV standard.");
|
||||
SAY(" Available: %s; default: disabled.\n", US_STANDARDS_STR);
|
||||
SAY(" -I|--io-method <method> ───────────── Set V4L2 IO method (see kernel documentation).");
|
||||
@@ -608,16 +625,16 @@ static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, con
|
||||
SAY(" Available: %s; default: MMAP.\n", US_IO_METHODS_STR);
|
||||
SAY(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n");
|
||||
SAY(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device");
|
||||
SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", dev->min_frame_size);
|
||||
SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", cap->min_frame_size);
|
||||
SAY(" -n|--persistent ───────────────────── Don't re-initialize device on timeout. Default: disabled.\n");
|
||||
SAY(" -t|--dv-timings ───────────────────── Enable DV-timings querying and events processing");
|
||||
SAY(" to automatic resolution change. Default: disabled.\n");
|
||||
SAY(" -b|--buffers <N> ──────────────────── The number of buffers to receive data from the device.");
|
||||
SAY(" Each buffer may processed using an independent thread.");
|
||||
SAY(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n", dev->n_bufs);
|
||||
SAY(" Default: %u (the number of CPU cores (but not more than 4) + 1).\n", cap->n_bufs);
|
||||
SAY(" -w|--workers <N> ──────────────────── The number of worker threads but not more than buffers.");
|
||||
SAY(" Default: %u (the number of CPU cores (but not more than 4)).\n", enc->n_workers);
|
||||
SAY(" -q|--quality <N> ──────────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.", dev->jpeg_quality);
|
||||
SAY(" -q|--quality <N> ──────────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.", cap->jpeg_quality);
|
||||
SAY(" Note: If HW encoding is used (JPEG source format selected),");
|
||||
SAY(" this parameter attempts to configure the camera");
|
||||
SAY(" or capture device hardware's internal encoder.");
|
||||
@@ -635,7 +652,7 @@ static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, con
|
||||
SAY(" -K|--last-as-blank <sec> ──────────── It doesn't do anything. Still here for compatibility.\n");
|
||||
SAY(" -l|--slowdown ─────────────────────── Slowdown capturing to 1 FPS or less when no stream or sink clients");
|
||||
SAY(" are connected. Useful to reduce CPU consumption. Default: disabled.\n");
|
||||
SAY(" --device-timeout <sec> ────────────── Timeout for device querying. Default: %u.\n", dev->timeout);
|
||||
SAY(" --device-timeout <sec> ────────────── Timeout for device querying. Default: %u.\n", cap->timeout);
|
||||
SAY(" --device-error-delay <sec> ────────── Delay before trying to connect to the device again");
|
||||
SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay);
|
||||
SAY(" --m2m-device </dev/path> ──────────── Path to V4L2 M2M encoder device. Default: auto select.\n");
|
||||
@@ -698,6 +715,12 @@ static void _help(FILE *fp, const us_device_s *dev, const us_encoder_s *enc, con
|
||||
SAY(" --h264-bitrate <kbps> ───────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
|
||||
SAY(" --h264-gop <N> ──────────────── Interval between keyframes. Default: %u.\n", stream->h264_gop);
|
||||
SAY(" --h264-m2m-device </dev/path> ─ Path to V4L2 M2M encoder device. Default: auto select.\n");
|
||||
# ifdef WITH_V4P
|
||||
SAY("Passthrough options for PiKVM V4:");
|
||||
SAY("═════════════════════════════════");
|
||||
SAY(" --v4p ─ Enable HDMI passthrough to OUT2 on the device: https://docs.pikvm.org/pass");
|
||||
SAY(" Default: disabled.\n");
|
||||
# endif
|
||||
# ifdef WITH_GPIO
|
||||
SAY("GPIO options:");
|
||||
SAY("═════════════");
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/options.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../libs/capture.h"
|
||||
|
||||
#include "encoder.h"
|
||||
#include "stream.h"
|
||||
@@ -62,4 +62,4 @@ typedef struct {
|
||||
us_options_s *us_options_init(unsigned argc, char *argv[]);
|
||||
void us_options_destroy(us_options_s *options);
|
||||
|
||||
int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
|
||||
int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
|
||||
|
||||
@@ -38,7 +38,10 @@
|
||||
#include "../libs/ring.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../libs/capture.h"
|
||||
#ifdef WITH_V4P
|
||||
# include "../libs/drm/drm.h"
|
||||
#endif
|
||||
|
||||
#include "blank.h"
|
||||
#include "encoder.h"
|
||||
@@ -51,7 +54,7 @@
|
||||
|
||||
typedef struct {
|
||||
pthread_t tid;
|
||||
us_device_s *dev;
|
||||
us_capture_s *cap;
|
||||
us_queue_s *queue;
|
||||
pthread_mutex_t *mutex;
|
||||
atomic_bool *stop;
|
||||
@@ -71,18 +74,24 @@ static void *_releaser_thread(void *v_ctx);
|
||||
static void *_jpeg_thread(void *v_ctx);
|
||||
static void *_h264_thread(void *v_ctx);
|
||||
static void *_raw_thread(void *v_ctx);
|
||||
#ifdef WITH_V4P
|
||||
static void *_drm_thread(void *v_ctx);
|
||||
#endif
|
||||
|
||||
static us_hw_buffer_s *_get_latest_hw(us_queue_s *queue);
|
||||
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *queue);
|
||||
|
||||
static bool _stream_has_jpeg_clients_cached(us_stream_s *stream);
|
||||
static bool _stream_has_any_clients_cached(us_stream_s *stream);
|
||||
static int _stream_init_loop(us_stream_s *stream);
|
||||
#ifdef WITH_V4P
|
||||
static void _stream_drm_ensure_no_signal(us_stream_s *stream);
|
||||
#endif
|
||||
static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame);
|
||||
static void _stream_expose_raw(us_stream_s *stream, const us_frame_s *frame);
|
||||
static void _stream_check_suicide(us_stream_s *stream);
|
||||
|
||||
|
||||
us_stream_s *us_stream_init(us_device_s *dev, us_encoder_s *enc) {
|
||||
us_stream_s *us_stream_init(us_capture_s *cap, us_encoder_s *enc) {
|
||||
us_stream_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
US_RING_INIT_WITH_ITEMS(run->http_jpeg_ring, 4, us_frame_init);
|
||||
@@ -95,15 +104,15 @@ us_stream_s *us_stream_init(us_device_s *dev, us_encoder_s *enc) {
|
||||
|
||||
us_stream_s *stream;
|
||||
US_CALLOC(stream, 1);
|
||||
stream->dev = dev;
|
||||
stream->cap = cap;
|
||||
stream->enc = enc;
|
||||
stream->error_delay = 1;
|
||||
stream->h264_bitrate = 5000; // Kbps
|
||||
stream->h264_gop = 30;
|
||||
stream->run = run;
|
||||
|
||||
us_blank_draw(run->blank, "< NO SIGNAL >", dev->width, dev->height);
|
||||
_stream_set_capture_state(stream, dev->width, dev->height, false, 0);
|
||||
us_blank_draw(run->blank, "< NO SIGNAL >", cap->width, cap->height);
|
||||
_stream_set_capture_state(stream, cap->width, cap->height, false, 0);
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -116,10 +125,10 @@ void us_stream_destroy(us_stream_s *stream) {
|
||||
|
||||
void us_stream_loop(us_stream_s *stream) {
|
||||
us_stream_runtime_s *const run = stream->run;
|
||||
us_device_s *const dev = stream->dev;
|
||||
us_capture_s *const cap = stream->cap;
|
||||
|
||||
US_LOG_INFO("Using V4L2 device: %s", dev->path);
|
||||
US_LOG_INFO("Using desired FPS: %u", dev->desired_fps);
|
||||
US_LOG_INFO("Using V4L2 device: %s", cap->path);
|
||||
US_LOG_INFO("Using desired FPS: %u", cap->desired_fps);
|
||||
|
||||
atomic_store(&run->http_last_request_ts, us_get_now_monotonic());
|
||||
|
||||
@@ -127,18 +136,26 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
run->h264 = us_h264_stream_init(stream->h264_sink, stream->h264_m2m_path, stream->h264_bitrate, stream->h264_gop);
|
||||
}
|
||||
|
||||
# ifdef WITH_V4P
|
||||
if (stream->v4p) {
|
||||
run->drm = us_drm_init();
|
||||
run->drm_opened = -1;
|
||||
US_LOG_INFO("Using passthrough: %s[%s]", run->drm->path, run->drm->port);
|
||||
}
|
||||
# endif
|
||||
|
||||
while (!_stream_init_loop(stream)) {
|
||||
atomic_bool threads_stop;
|
||||
atomic_init(&threads_stop, false);
|
||||
|
||||
pthread_mutex_t release_mutex;
|
||||
US_MUTEX_INIT(release_mutex);
|
||||
const uint n_releasers = dev->run->n_bufs;
|
||||
const uint n_releasers = cap->run->n_bufs;
|
||||
_releaser_context_s *releasers;
|
||||
US_CALLOC(releasers, n_releasers);
|
||||
for (uint index = 0; index < n_releasers; ++index) {
|
||||
_releaser_context_s *ctx = &releasers[index];
|
||||
ctx->dev = dev;
|
||||
ctx->cap = cap;
|
||||
ctx->queue = us_queue_init(1);
|
||||
ctx->mutex = &release_mutex;
|
||||
ctx->stop = &threads_stop;
|
||||
@@ -146,7 +163,7 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
}
|
||||
|
||||
_worker_context_s jpeg_ctx = {
|
||||
.queue = us_queue_init(dev->run->n_bufs),
|
||||
.queue = us_queue_init(cap->run->n_bufs),
|
||||
.stream = stream,
|
||||
.stop = &threads_stop,
|
||||
};
|
||||
@@ -154,7 +171,7 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
|
||||
_worker_context_s h264_ctx;
|
||||
if (run->h264 != NULL) {
|
||||
h264_ctx.queue = us_queue_init(dev->run->n_bufs);
|
||||
h264_ctx.queue = us_queue_init(cap->run->n_bufs);
|
||||
h264_ctx.stream = stream;
|
||||
h264_ctx.stop = &threads_stop;
|
||||
US_THREAD_CREATE(h264_ctx.tid, _h264_thread, &h264_ctx);
|
||||
@@ -168,6 +185,16 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
US_THREAD_CREATE(raw_ctx.tid, _raw_thread, &raw_ctx);
|
||||
}
|
||||
|
||||
# ifdef WITH_V4P
|
||||
_worker_context_s drm_ctx;
|
||||
if (stream->v4p) {
|
||||
drm_ctx.queue = us_queue_init(cap->run->n_bufs);
|
||||
drm_ctx.stream = stream;
|
||||
drm_ctx.stop = &threads_stop;
|
||||
US_THREAD_CREATE(drm_ctx.tid, _drm_thread, &drm_ctx); // cppcheck-suppress assertWithSideEffect
|
||||
}
|
||||
# endif
|
||||
|
||||
uint captured_fps_accum = 0;
|
||||
sll captured_fps_ts = 0;
|
||||
uint captured_fps = 0;
|
||||
@@ -176,8 +203,8 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
|
||||
uint slowdown_count = 0;
|
||||
while (!atomic_load(&run->stop) && !atomic_load(&threads_stop)) {
|
||||
us_hw_buffer_s *hw;
|
||||
switch (us_device_grab_buffer(dev, &hw)) {
|
||||
us_capture_hwbuf_s *hw;
|
||||
switch (us_capture_hwbuf_grab(cap, &hw)) {
|
||||
case -2: continue; // Broken frame
|
||||
case -1: goto close; // Error
|
||||
default: break; // Grabbed on >= 0
|
||||
@@ -192,21 +219,27 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
}
|
||||
captured_fps_accum += 1;
|
||||
|
||||
_stream_set_capture_state(stream, dev->run->width, dev->run->height, true, captured_fps);
|
||||
_stream_set_capture_state(stream, cap->run->width, cap->run->height, true, captured_fps);
|
||||
# ifdef WITH_GPIO
|
||||
us_gpio_set_stream_online(true);
|
||||
# endif
|
||||
|
||||
us_device_buffer_incref(hw); // JPEG
|
||||
us_capture_hwbuf_incref(hw); // JPEG
|
||||
us_queue_put(jpeg_ctx.queue, hw, 0);
|
||||
if (run->h264 != NULL) {
|
||||
us_device_buffer_incref(hw); // H264
|
||||
us_capture_hwbuf_incref(hw); // H264
|
||||
us_queue_put(h264_ctx.queue, hw, 0);
|
||||
}
|
||||
if (stream->raw_sink != NULL) {
|
||||
us_device_buffer_incref(hw); // RAW
|
||||
us_capture_hwbuf_incref(hw); // RAW
|
||||
us_queue_put(raw_ctx.queue, hw, 0);
|
||||
}
|
||||
# ifdef WITH_V4P
|
||||
if (stream->v4p) {
|
||||
us_capture_hwbuf_incref(hw); // DRM
|
||||
us_queue_put(drm_ctx.queue, hw, 0);
|
||||
}
|
||||
# endif
|
||||
us_queue_put(releasers[hw->buf.index].queue, hw, 0); // Plan to release
|
||||
|
||||
// Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков
|
||||
@@ -223,6 +256,13 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
close:
|
||||
atomic_store(&threads_stop, true);
|
||||
|
||||
# ifdef WITH_V4P
|
||||
if (stream->v4p) {
|
||||
US_THREAD_JOIN(drm_ctx.tid);
|
||||
us_queue_destroy(drm_ctx.queue);
|
||||
}
|
||||
# endif
|
||||
|
||||
if (stream->raw_sink != NULL) {
|
||||
US_THREAD_JOIN(raw_ctx.tid);
|
||||
us_queue_destroy(raw_ctx.queue);
|
||||
@@ -246,13 +286,16 @@ void us_stream_loop(us_stream_s *stream) {
|
||||
atomic_store(&threads_stop, false);
|
||||
|
||||
us_encoder_close(stream->enc);
|
||||
us_device_close(dev);
|
||||
us_capture_close(cap);
|
||||
|
||||
if (!atomic_load(&run->stop)) {
|
||||
US_SEP_INFO('=');
|
||||
}
|
||||
}
|
||||
|
||||
# ifdef WITH_V4P
|
||||
US_DELETE(run->drm, us_drm_destroy);
|
||||
# endif
|
||||
US_DELETE(run->h264, us_h264_stream_destroy);
|
||||
}
|
||||
|
||||
@@ -283,7 +326,7 @@ static void *_releaser_thread(void *v_ctx) {
|
||||
_releaser_context_s *ctx = v_ctx;
|
||||
|
||||
while (!atomic_load(ctx->stop)) {
|
||||
us_hw_buffer_s *hw;
|
||||
us_capture_hwbuf_s *hw;
|
||||
if (us_queue_get(ctx->queue, (void**)&hw, 0.1) < 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -296,7 +339,7 @@ static void *_releaser_thread(void *v_ctx) {
|
||||
}
|
||||
|
||||
US_MUTEX_LOCK(*ctx->mutex);
|
||||
const int released = us_device_release_buffer(ctx->dev, hw);
|
||||
const int released = us_capture_hwbuf_release(ctx->cap, hw);
|
||||
US_MUTEX_UNLOCK(*ctx->mutex);
|
||||
if (released < 0) {
|
||||
goto done;
|
||||
@@ -321,7 +364,7 @@ static void *_jpeg_thread(void *v_ctx) {
|
||||
us_encoder_job_s *const ready_job = ready_wr->job;
|
||||
|
||||
if (ready_job->hw != NULL) {
|
||||
us_device_buffer_decref(ready_job->hw);
|
||||
us_capture_hwbuf_decref(ready_job->hw);
|
||||
ready_job->hw = NULL;
|
||||
if (ready_wr->job_failed) {
|
||||
// pass
|
||||
@@ -337,7 +380,7 @@ static void *_jpeg_thread(void *v_ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
us_hw_buffer_s *hw = _get_latest_hw(ctx->queue);
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue);
|
||||
if (hw == NULL) {
|
||||
continue;
|
||||
}
|
||||
@@ -345,7 +388,7 @@ static void *_jpeg_thread(void *v_ctx) {
|
||||
const bool update_required = (stream->jpeg_sink != NULL && us_memsink_server_check(stream->jpeg_sink, NULL));
|
||||
if (!update_required && !_stream_has_jpeg_clients_cached(stream)) {
|
||||
US_LOG_VERBOSE("JPEG: Passed encoding because nobody is watching");
|
||||
us_device_buffer_decref(hw);
|
||||
us_capture_hwbuf_decref(hw);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -354,7 +397,7 @@ static void *_jpeg_thread(void *v_ctx) {
|
||||
fluency_passed += 1;
|
||||
US_LOG_VERBOSE("JPEG: Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf",
|
||||
fluency_passed, now_ts, grab_after_ts);
|
||||
us_device_buffer_decref(hw);
|
||||
us_capture_hwbuf_decref(hw);
|
||||
continue;
|
||||
}
|
||||
fluency_passed = 0;
|
||||
@@ -379,19 +422,19 @@ static void *_h264_thread(void *v_ctx) {
|
||||
ldf last_encode_ts = us_get_now_monotonic();
|
||||
|
||||
while (!atomic_load(ctx->stop)) {
|
||||
us_hw_buffer_s *hw = _get_latest_hw(ctx->queue);
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue);
|
||||
if (hw == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!us_memsink_server_check(h264->sink, NULL)) {
|
||||
us_device_buffer_decref(hw);
|
||||
us_capture_hwbuf_decref(hw);
|
||||
US_LOG_VERBOSE("H264: Passed encoding because nobody is watching");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hw->raw.grab_ts < grab_after_ts) {
|
||||
us_device_buffer_decref(hw);
|
||||
us_capture_hwbuf_decref(hw);
|
||||
US_LOG_VERBOSE("H264: Passed encoding for FPS limit: %u", h264->enc->run->fps_limit);
|
||||
continue;
|
||||
}
|
||||
@@ -409,7 +452,7 @@ static void *_h264_thread(void *v_ctx) {
|
||||
const ldf frame_interval = (ldf)1 / h264->enc->run->fps_limit;
|
||||
grab_after_ts = hw->raw.grab_ts + frame_interval - 0.01;
|
||||
|
||||
us_device_buffer_decref(hw);
|
||||
us_capture_hwbuf_decref(hw);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -419,30 +462,89 @@ static void *_raw_thread(void *v_ctx) {
|
||||
_worker_context_s *ctx = v_ctx;
|
||||
|
||||
while (!atomic_load(ctx->stop)) {
|
||||
us_hw_buffer_s *hw = _get_latest_hw(ctx->queue);
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue);
|
||||
if (hw == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!us_memsink_server_check(ctx->stream->raw_sink, NULL)) {
|
||||
us_device_buffer_decref(hw);
|
||||
us_capture_hwbuf_decref(hw);
|
||||
US_LOG_VERBOSE("RAW: Passed publishing because nobody is watching");
|
||||
continue;
|
||||
}
|
||||
|
||||
us_memsink_server_put(ctx->stream->raw_sink, &hw->raw, false);
|
||||
us_device_buffer_decref(hw);
|
||||
us_capture_hwbuf_decref(hw);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static us_hw_buffer_s *_get_latest_hw(us_queue_s *queue) {
|
||||
us_hw_buffer_s *hw;
|
||||
#ifdef WITH_V4P
|
||||
static void *_drm_thread(void *v_ctx) {
|
||||
US_THREAD_SETTLE("str_drm");
|
||||
_worker_context_s *ctx = v_ctx;
|
||||
us_stream_runtime_s *run = ctx->stream->run;
|
||||
|
||||
// Close previously opened DRM for a stub
|
||||
us_drm_close(run->drm);
|
||||
run->drm_opened = -1;
|
||||
|
||||
us_capture_hwbuf_s *prev_hw = NULL;
|
||||
while (!atomic_load(ctx->stop)) {
|
||||
# define CHECK(x_arg) if ((x_arg) < 0) { goto close; }
|
||||
# define SLOWDOWN { \
|
||||
ldf m_next_ts = us_get_now_monotonic() + 1; \
|
||||
while (!atomic_load(ctx->stop) && us_get_now_monotonic() < m_next_ts) { \
|
||||
us_capture_hwbuf_s *m_pass_hw = _get_latest_hw(ctx->queue); \
|
||||
if (m_pass_hw != NULL) { \
|
||||
us_capture_hwbuf_decref(m_pass_hw); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
CHECK(run->drm_opened = us_drm_open(run->drm, ctx->stream->cap));
|
||||
|
||||
while (!atomic_load(ctx->stop)) {
|
||||
CHECK(us_drm_wait_for_vsync(run->drm));
|
||||
US_DELETE(prev_hw, us_capture_hwbuf_decref);
|
||||
|
||||
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue);
|
||||
if (hw == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (run->drm_opened == 0) {
|
||||
CHECK(us_drm_expose_dma(run->drm, hw));
|
||||
prev_hw = hw;
|
||||
continue;
|
||||
}
|
||||
|
||||
CHECK(us_drm_expose_stub(run->drm, run->drm_opened, ctx->stream->cap));
|
||||
us_capture_hwbuf_decref(hw);
|
||||
|
||||
SLOWDOWN;
|
||||
}
|
||||
|
||||
close:
|
||||
us_drm_close(run->drm);
|
||||
run->drm_opened = -1;
|
||||
US_DELETE(prev_hw, us_capture_hwbuf_decref);
|
||||
SLOWDOWN;
|
||||
|
||||
# undef SLOWDOWN
|
||||
# undef CHECK
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *queue) {
|
||||
us_capture_hwbuf_s *hw;
|
||||
if (us_queue_get(queue, (void**)&hw, 0.1) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
while (!us_queue_is_empty(queue)) { // Берем только самый свежий кадр
|
||||
us_device_buffer_decref(hw);
|
||||
us_capture_hwbuf_decref(hw);
|
||||
assert(!us_queue_get(queue, (void**)&hw, 0));
|
||||
}
|
||||
return hw;
|
||||
@@ -460,6 +562,9 @@ static bool _stream_has_jpeg_clients_cached(us_stream_s *stream) {
|
||||
static bool _stream_has_any_clients_cached(us_stream_s *stream) {
|
||||
const us_stream_runtime_s *const run = stream->run;
|
||||
return (
|
||||
# ifdef WITH_V4P
|
||||
stream->v4p ||
|
||||
# endif
|
||||
_stream_has_jpeg_clients_cached(stream)
|
||||
|| (run->h264 != NULL && atomic_load(&run->h264->sink->has_clients))
|
||||
|| (stream->raw_sink != NULL && atomic_load(&stream->raw_sink->has_clients))
|
||||
@@ -489,24 +594,30 @@ static int _stream_init_loop(us_stream_s *stream) {
|
||||
|
||||
_stream_check_suicide(stream);
|
||||
|
||||
stream->dev->dma_export = (
|
||||
stream->cap->dma_export = (
|
||||
stream->enc->type == US_ENCODER_TYPE_M2M_VIDEO
|
||||
|| stream->enc->type == US_ENCODER_TYPE_M2M_IMAGE
|
||||
|| run->h264 != NULL
|
||||
);
|
||||
switch (us_device_open(stream->dev)) {
|
||||
switch (us_capture_open(stream->cap)) {
|
||||
case -2:
|
||||
if (!waiting_reported) {
|
||||
waiting_reported = true;
|
||||
US_LOG_INFO("Waiting for the capture device ...");
|
||||
}
|
||||
# ifdef WITH_V4P
|
||||
_stream_drm_ensure_no_signal(stream);
|
||||
# endif
|
||||
goto offline_and_retry;
|
||||
case -1:
|
||||
waiting_reported = false;
|
||||
# ifdef WITH_V4P
|
||||
_stream_drm_ensure_no_signal(stream);
|
||||
# endif
|
||||
goto offline_and_retry;
|
||||
default: break;
|
||||
}
|
||||
us_encoder_open(stream->enc, stream->dev);
|
||||
us_encoder_open(stream->enc, stream->cap);
|
||||
return 0;
|
||||
|
||||
offline_and_retry:
|
||||
@@ -516,11 +627,11 @@ static int _stream_init_loop(us_stream_s *stream) {
|
||||
}
|
||||
if (count % 10 == 0) {
|
||||
// Каждую секунду повторяем blank
|
||||
uint width = stream->dev->run->width;
|
||||
uint height = stream->dev->run->height;
|
||||
uint width = stream->cap->run->width;
|
||||
uint height = stream->cap->run->height;
|
||||
if (width == 0 || height == 0) {
|
||||
width = stream->dev->width;
|
||||
height = stream->dev->height;
|
||||
width = stream->cap->width;
|
||||
height = stream->cap->height;
|
||||
}
|
||||
us_blank_draw(run->blank, "< NO SIGNAL >", width, height);
|
||||
|
||||
@@ -538,6 +649,42 @@ static int _stream_init_loop(us_stream_s *stream) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef WITH_V4P
|
||||
static void _stream_drm_ensure_no_signal(us_stream_s *stream) {
|
||||
us_stream_runtime_s *const run = stream->run;
|
||||
|
||||
if (!stream->v4p) {
|
||||
return;
|
||||
}
|
||||
|
||||
# define CHECK(x_arg) if ((x_arg) < 0) { goto close; }
|
||||
if (run->drm_opened <= 0) {
|
||||
us_drm_close(run->drm);
|
||||
run->drm_blank_at_ts = 0;
|
||||
CHECK(run->drm_opened = us_drm_open(run->drm, NULL));
|
||||
}
|
||||
|
||||
ldf now_ts = us_get_now_monotonic();
|
||||
if (run->drm_blank_at_ts == 0) {
|
||||
run->drm_blank_at_ts = now_ts + 5;
|
||||
}
|
||||
|
||||
if (now_ts <= run->drm_blank_at_ts) {
|
||||
CHECK(us_drm_wait_for_vsync(run->drm));
|
||||
CHECK(us_drm_expose_stub(run->drm, US_DRM_STUB_NO_SIGNAL, NULL));
|
||||
} else {
|
||||
// US_ONCE({ US_LOG_INFO("DRM: Turning off the display by timeout ..."); });
|
||||
CHECK(us_drm_dpms_power_off(run->drm));
|
||||
}
|
||||
return;
|
||||
# undef CHECK
|
||||
|
||||
close:
|
||||
us_drm_close(run->drm);
|
||||
run->drm_opened = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame) {
|
||||
us_stream_runtime_s *const run = stream->run;
|
||||
int ri;
|
||||
|
||||
@@ -30,7 +30,10 @@
|
||||
#include "../libs/queue.h"
|
||||
#include "../libs/ring.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../libs/capture.h"
|
||||
#ifdef WITH_V4P
|
||||
# include "../libs/drm/drm.h"
|
||||
#endif
|
||||
|
||||
#include "blank.h"
|
||||
#include "encoder.h"
|
||||
@@ -38,7 +41,13 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
us_h264_stream_s *h264;
|
||||
us_h264_stream_s *h264;
|
||||
|
||||
# ifdef WITH_V4P
|
||||
us_drm_s *drm;
|
||||
int drm_opened;
|
||||
ldf drm_blank_at_ts;
|
||||
# endif
|
||||
|
||||
us_ring_s *http_jpeg_ring;
|
||||
atomic_bool http_has_clients;
|
||||
@@ -52,7 +61,7 @@ typedef struct {
|
||||
} us_stream_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
us_device_s *dev;
|
||||
us_capture_s *cap;
|
||||
us_encoder_s *enc;
|
||||
|
||||
int last_as_blank;
|
||||
@@ -68,11 +77,15 @@ typedef struct {
|
||||
uint h264_gop;
|
||||
char *h264_m2m_path;
|
||||
|
||||
# ifdef WITH_V4P
|
||||
bool v4p;
|
||||
# endif
|
||||
|
||||
us_stream_runtime_s *run;
|
||||
} us_stream_s;
|
||||
|
||||
|
||||
us_stream_s *us_stream_init(us_device_s *dev, us_encoder_s *enc);
|
||||
us_stream_s *us_stream_init(us_capture_s *cap, us_encoder_s *enc);
|
||||
void us_stream_destroy(us_stream_s *stream);
|
||||
|
||||
void us_stream_loop(us_stream_s *stream);
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../libs/capture.h"
|
||||
#include "../libs/signal.h"
|
||||
#include "../libs/options.h"
|
||||
|
||||
#include "drm.h"
|
||||
#include "../libs/drm/drm.h"
|
||||
|
||||
|
||||
enum _OPT_VALUES {
|
||||
@@ -160,16 +160,16 @@ static void _signal_handler(int signum) {
|
||||
|
||||
static void _main_loop(void) {
|
||||
us_drm_s *drm = us_drm_init();
|
||||
drm->port = "HDMI-A-2";
|
||||
|
||||
us_device_s *dev = us_device_init();
|
||||
dev->path = "/dev/kvmd-video";
|
||||
dev->n_bufs = 6;
|
||||
dev->format = V4L2_PIX_FMT_RGB24;
|
||||
dev->dv_timings = true;
|
||||
dev->persistent = true;
|
||||
dev->dma_export = true;
|
||||
dev->dma_required = true;
|
||||
us_capture_s *cap = us_capture_init();
|
||||
cap->path = "/dev/kvmd-video";
|
||||
cap->n_bufs = 6;
|
||||
cap->format = V4L2_PIX_FMT_RGB24;
|
||||
cap->format_swap_rgb = true;
|
||||
cap->dv_timings = true;
|
||||
cap->persistent = true;
|
||||
cap->dma_export = true;
|
||||
cap->dma_required = true;
|
||||
|
||||
int once = 0;
|
||||
ldf blank_at_ts = 0;
|
||||
@@ -181,18 +181,17 @@ static void _main_loop(void) {
|
||||
blank_at_ts = 0;
|
||||
CHECK(drm_opened = us_drm_open(drm, NULL));
|
||||
}
|
||||
assert(drm_opened > 0);
|
||||
|
||||
if (atomic_load(&_g_ustreamer_online)) {
|
||||
blank_at_ts = 0;
|
||||
US_ONCE({ US_LOG_INFO("DRM: Online stream is active, stopping capture ..."); });
|
||||
US_ONCE({ US_LOG_INFO("DRM: Online stream is active, pausing the service ..."); });
|
||||
CHECK(us_drm_wait_for_vsync(drm));
|
||||
CHECK(us_drm_expose_stub(drm, US_DRM_STUB_BUSY, NULL));
|
||||
_slowdown();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (us_device_open(dev) < 0) {
|
||||
if (us_capture_open(cap) < 0) {
|
||||
ldf now_ts = us_get_now_monotonic();
|
||||
if (blank_at_ts == 0) {
|
||||
blank_at_ts = now_ts + 5;
|
||||
@@ -211,9 +210,9 @@ static void _main_loop(void) {
|
||||
once = 0;
|
||||
blank_at_ts = 0;
|
||||
us_drm_close(drm);
|
||||
CHECK(drm_opened = us_drm_open(drm, dev));
|
||||
CHECK(drm_opened = us_drm_open(drm, cap));
|
||||
|
||||
us_hw_buffer_s *prev_hw = NULL;
|
||||
us_capture_hwbuf_s *prev_hw = NULL;
|
||||
while (!atomic_load(&_g_stop)) {
|
||||
if (atomic_load(&_g_ustreamer_online)) {
|
||||
goto close;
|
||||
@@ -222,12 +221,12 @@ static void _main_loop(void) {
|
||||
CHECK(us_drm_wait_for_vsync(drm));
|
||||
|
||||
if (prev_hw != NULL) {
|
||||
CHECK(us_device_release_buffer(dev, prev_hw));
|
||||
CHECK(us_capture_hwbuf_release(cap, prev_hw));
|
||||
prev_hw = NULL;
|
||||
}
|
||||
|
||||
us_hw_buffer_s *hw;
|
||||
switch (us_device_grab_buffer(dev, &hw)) {
|
||||
us_capture_hwbuf_s *hw;
|
||||
switch (us_capture_hwbuf_grab(cap, &hw)) {
|
||||
case -2: continue; // Broken frame
|
||||
case -1: goto close; // Any error
|
||||
default: break; // Grabbed on >= 0
|
||||
@@ -236,28 +235,26 @@ static void _main_loop(void) {
|
||||
if (drm_opened == 0) {
|
||||
CHECK(us_drm_expose_dma(drm, hw));
|
||||
prev_hw = hw;
|
||||
} else {
|
||||
CHECK(us_drm_expose_stub(drm, drm_opened, dev));
|
||||
CHECK(us_device_release_buffer(dev, hw));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (drm_opened > 0) {
|
||||
_slowdown();
|
||||
}
|
||||
CHECK(us_drm_expose_stub(drm, drm_opened, cap));
|
||||
CHECK(us_capture_hwbuf_release(cap, hw));
|
||||
_slowdown();
|
||||
}
|
||||
|
||||
close:
|
||||
us_drm_close(drm);
|
||||
drm_opened = -1;
|
||||
|
||||
us_device_close(dev);
|
||||
us_capture_close(cap);
|
||||
|
||||
_slowdown();
|
||||
|
||||
# undef CHECK
|
||||
}
|
||||
|
||||
us_device_destroy(dev);
|
||||
us_capture_destroy(cap);
|
||||
us_drm_destroy(drm);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user