Compare commits

...

28 Commits
v3.22 ... v3.25

Author SHA1 Message Date
Devaev Maxim
b329dfed1f Bump version: 3.24 → 3.25 2021-04-07 00:46:39 +03:00
Devaev Maxim
62dc2e5ad5 Fixed #105: using $(MAKE) 2021-04-06 23:43:32 +03:00
Devaev Maxim
7ea81fa627 fix 2021-04-03 06:42:33 +03:00
Devaev Maxim
8ddf77a3d3 Bump version: 3.23 → 3.24 2021-04-03 02:12:22 +03:00
Devaev Maxim
fde89465ac global variables prefix 2021-04-02 13:31:34 +03:00
Devaev Maxim
aa1d78a3cd fix 2021-04-02 12:39:09 +03:00
Devaev Maxim
6dfe077775 refactoring 2021-04-02 12:38:38 +03:00
Devaev Maxim
dd6dc866a6 Fixed #103: symlinks without .bin suffix 2021-04-02 09:01:29 +03:00
Devaev Maxim
3bf68884f5 improved linting 2021-04-02 02:18:27 +03:00
Devaev Maxim
e1b2eceea5 split makefiles 2021-04-02 01:00:22 +03:00
Devaev Maxim
7ce11fecb6 RN 2021-04-01 13:14:58 +03:00
Devaev Maxim
cbc6145977 using strerror_l() 2021-04-01 10:21:12 +03:00
Devaev Maxim
24f7fb797b refactoring 2021-04-01 09:32:29 +03:00
Devaev Maxim
71f1d397bf moved errno_to_string() to the tools 2021-04-01 08:49:18 +03:00
Devaev Maxim
00e83c155e removed managed flag 2021-03-30 10:16:55 +03:00
Devaev Maxim
1f186a0afe refactoring 2021-03-30 10:11:14 +03:00
Devaev Maxim
3c7075d0d2 removed frame name 2021-03-30 10:02:03 +03:00
Devaev Maxim
c1d7bd1595 link fix 2021-03-30 03:21:04 +03:00
Devaev Maxim
04d1d3d5a6 some common macroses 2021-03-30 02:18:40 +03:00
Devaev Maxim
01bc0529e7 refactoring 2021-03-28 22:48:17 +03:00
Devaev Maxim
181231f3ff common list operations 2021-03-28 21:51:34 +03:00
Devaev Maxim
58569f0315 separate major and minor numbers 2021-03-28 08:04:12 +03:00
Maxim Devaev
5903fcf718 Update README.ru.md 2021-03-27 22:45:24 +03:00
Maxim Devaev
1a820b23a5 Merge pull request #101 from Lennie/patch-1
Update README.md
2021-03-27 22:44:51 +03:00
Lennie
67b767b152 Update README.md
On my system I needed to use -d my guess is it was just a typo from you ?
2021-03-27 20:34:33 +01:00
Devaev Maxim
74bf710bb6 Bump version: 3.22 → 3.23 2021-03-26 22:03:40 +03:00
Devaev Maxim
1d3b428a75 using asprintf 2021-03-25 14:23:43 +03:00
Devaev Maxim
749bc5caf7 const 2021-03-25 13:35:56 +03:00
42 changed files with 526 additions and 503 deletions

View File

@@ -1,14 +1,22 @@
[bumpversion]
commit = True
tag = True
current_version = 3.22
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
current_version = 3.25
parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize =
{major}.{minor}
[bumpversion:file:src/libs/config.h]
search = VERSION "{current_version}"
replace = VERSION "{new_version}"
parse = (?P<major>\d+)
serialize = {major}
search = VERSION_MAJOR {current_version}
replace = VERSION_MAJOR {new_version}
[bumpversion:file:./src/libs/config.h]
parse = <major>\d+\.(?P<minor>\d+)
serialize = {minor}
search = VERSION_MINOR {current_version}
replace = VERSION_MINOR {new_version}
[bumpversion:file:python/setup.py]
search = version="{current_version}"

9
.gitignore vendored
View File

@@ -5,11 +5,12 @@
/pkg/arch/v*.tar.gz
/pkg/arch/ustreamer-*.pkg.tar.xz
/pkg/arch/ustreamer-*.pkg.tar.zst
/build/
/src/build/
/src/*.bin
/python/build/
/config.mk
/vgcore.*
/ustreamer
/ustreamer-dump
/*.so
/config.mk
/vgcore.*
/*.sock
/*.so

168
Makefile
View File

@@ -1,7 +1,5 @@
-include config.mk
USTR ?= ustreamer
DUMP ?= ustreamer-dump
DESTDIR ?=
PREFIX ?= /usr/local
MANPREFIX ?= $(PREFIX)/share/man
@@ -14,147 +12,71 @@ LDFLAGS ?=
RPI_VC_HEADERS ?= /opt/vc/include
RPI_VC_LIBS ?= /opt/vc/lib
BUILD ?= build
export
LINTERS_IMAGE ?= $(USTR)-linters
_LINTERS_IMAGE ?= ustreamer-linters
# =====
_CFLAGS = -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
_LDFLAGS = $(LDFLAGS)
_COMMON_LIBS = -lm -ljpeg -pthread -lrt
_USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads
_USTR_SRCS = $(shell ls \
src/libs/*.c \
src/ustreamer/*.c \
src/ustreamer/http/*.c \
src/ustreamer/data/*.c \
src/ustreamer/encoders/cpu/*.c \
src/ustreamer/encoders/hw/*.c \
)
_DUMP_LIBS = $(_COMMON_LIBS)
_DUMP_SRCS = $(shell ls \
src/libs/*.c \
src/dump/*.c \
)
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifneq ($(call optbool,$(WITH_OMX)),)
_USTR_LIBS += -lbcm_host -lvcos -lvcsm -lopenmaxil -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lmmal_components -L$(RPI_VC_LIBS)
override _CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
_USTR_SRCS += $(shell ls \
src/ustreamer/encoders/omx/*.c \
src/ustreamer/h264/*.c \
)
endif
ifneq ($(call optbool,$(WITH_GPIO)),)
_USTR_LIBS += -lgpiod
override _CFLAGS += -DWITH_GPIO
_USTR_SRCS += $(shell ls src/ustreamer/gpio/*.c)
endif
WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override _CFLAGS += -DWITH_PTHREAD_NP
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
endif
# =====
all: $(USTR) $(DUMP) python
install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(MANPREFIX)/man1
install -m755 $(USTR) $(DESTDIR)$(PREFIX)/bin/$(USTR)
install -m755 $(DUMP) $(DESTDIR)$(PREFIX)/bin/$(DUMP)
install -m644 man/$(USTR).1 $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
install -m644 man/$(DUMP).1 $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$(USTR).1
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$(DUMP).1
all:
+ $(MAKE) apps
ifneq ($(call optbool,$(WITH_PYTHON)),)
cd python && $(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
+ $(MAKE) python
endif
install-strip: install
strip $(DESTDIR)$(PREFIX)/bin/$(USTR)
strip $(DESTDIR)$(PREFIX)/bin/$(DUMP)
regen:
tools/make-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK
tools/make-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
$(USTR): $(_USTR_SRCS:%.c=$(BUILD)/%.o)
# $(info ========================================)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
# $(info :: CC = $(CC))
# $(info :: LIBS = $(_USTR_LIBS))
# $(info :: CFLAGS = $(_CFLAGS))
# $(info :: LDFLAGS = $(_LDFLAGS))
$(DUMP): $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
# $(info ========================================)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
# $(info :: CC = $(CC))
# $(info :: LIBS = $(_DUMP_LIBS))
# $(info :: CFLAGS = $(_CFLAGS))
# $(info :: LDFLAGS = $(_LDFLAGS))
$(BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(_CFLAGS)
apps:
$(MAKE) -C src
@ ln -sf src/ustreamer.bin ustreamer
@ ln -sf src/ustreamer-dump.bin ustreamer-dump
python:
ifneq ($(call optbool,$(WITH_PYTHON)),)
$(info == PY_BUILD ustreamer-*.so)
@ cd python && $(PY) setup.py build
$(MAKE) -C python
@ ln -sf python/build/lib.*/*.so .
else
@ true
install: all
$(MAKE) -C src install
ifneq ($(call optbool,$(WITH_PYTHON)),)
$(MAKE) -C python install
endif
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
for man in $(shell ls man); do \
install -m644 man/$$man $(DESTDIR)$(MANPREFIX)/man1/$$man; \
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$$man; \
done
install-strip: install
$(MAKE) -C src install-strip
regen:
tools/$(MAKE)-jpeg-h.py src/ustreamer/data/blank.jpeg src/ustreamer/data/blank_jpeg.c BLANK
tools/$(MAKE)-html-h.py src/ustreamer/data/index.html src/ustreamer/data/index_html.c INDEX
release:
make clean
make tox
make push
make bump V=$(V)
make push
make clean
$(MAKE) clean
$(MAKE) tox
$(MAKE) push
$(MAKE) bump V=$(V)
$(MAKE) push
$(MAKE) clean
tox: linters
time docker run --rm \
--volume `pwd`:/src:ro \
--volume `pwd`/linters:/src/linters:rw \
-t $(LINTERS_IMAGE) bash -c " \
-t $(_LINTERS_IMAGE) bash -c " \
cd /src \
&& tox -q -c linters/tox.ini $(if $(E),-e $(E),-p auto) \
"
@@ -164,7 +86,7 @@ linters:
docker build \
$(if $(call optbool,$(NC)),--no-cache,) \
--rm \
--tag $(LINTERS_IMAGE) \
--tag $(_LINTERS_IMAGE) \
-f linters/Dockerfile linters
@@ -180,14 +102,14 @@ push:
clean-all: linters clean
- docker run --rm \
--volume `pwd`:/src \
-it $(LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
-it $(_LINTERS_IMAGE) bash -c "cd src && rm -rf linters/{.tox,.mypy_cache}"
clean:
rm -rf pkg/arch/pkg pkg/arch/src pkg/arch/v*.tar.gz pkg/arch/ustreamer-*.pkg.tar.{xz,zst}
rm -rf $(USTR) $(DUMP) $(BUILD) python/build vgcore.* *.sock *.so
rm -f ustreamer ustreamer-dump *.so
$(MAKE) -C src clean
$(MAKE) -C python clean
.PHONY: python linters
_OBJS = $(_USTR_SRCS:%.c=$(BUILD)/%.o) $(_DUMP_SRCS:%.c=$(BUILD)/%.o)
-include $(_OBJS:%.o=%.d)

View File

@@ -99,8 +99,8 @@ v4l2 utilities provide the tools to manage USB webcam setting and information. S
* List of available video devices: `v4l2-ctl --list-devices`.
* List available control settings: `v4l2-ctl -d /dev/video0 --list-ctrls`.
* List available video formats: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
* Read the current setting: `v4l2-ctl d /dev/video0 --get-ctrl=exposure_auto`.
* Change the setting value: `v4l2-ctl d /dev/video0 --set-ctrl=exposure_auto=1`.
* Read the current setting: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
* Change the setting value: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
[Here](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/) you can find more examples. Documentation is available in [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).

View File

@@ -99,8 +99,8 @@ V4L2 предоставляет ряд официальных утилит дл
* Вывести список видеоустройств: `v4l2-ctl --list-devices`.
* Вывести список доступных контролов устройства: `v4l2-ctl -d /dev/video0 --list-ctrls`.
* Вывести список доступных форматов видео: `v4l2-ctl -d /dev/video0 --list-formats-ext`.
* Показать текущее значение контрола: `v4l2-ctl d /dev/video0 --get-ctrl=exposure_auto`.
* Изменить значение контрола: `v4l2-ctl d /dev/video0 --set-ctrl=exposure_auto=1`.
* Показать текущее значение контрола: `v4l2-ctl -d /dev/video0 --get-ctrl=exposure_auto`.
* Изменить значение контрола: `v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1`.
Больше примеров вы можете найти [здесь](https://www.kurokesu.com/main/2016/01/16/manual-usb-camera-settings-in-linux/), а документацию в [`man v4l2-ctl`](https://www.mankier.com/1/v4l2-ctl).

3
linters/cppcheck.h Normal file
View File

@@ -0,0 +1,3 @@
#define CHAR_BIT 8
#define WITH_OMX
#define WITH_GPIO

View File

@@ -1,12 +1,12 @@
[tox]
envlist = cppcheck-src, cppcheck-python, flake8, pylint, mypy, vulture, htmlhint
envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
skipsdist = true
[testenv]
basepython = python3.9
changedir = /src
[testenv:cppcheck-src]
[testenv:cppcheck]
whitelist_externals = cppcheck
commands = cppcheck \
--force \
@@ -18,25 +18,8 @@ commands = cppcheck \
--suppress=variableScope \
--inline-suppr \
--library=python \
-DCHAR_BIT=8 \
-DWITH_OMX \
-DWITH_GPIO \
src python
[testenv:cppcheck-python]
whitelist_externals = cppcheck
commands = cppcheck \
--force \
--std=c11 \
--error-exitcode=1 \
--quiet \
--enable=warning,unusedFunction,portability,performance,style \
--suppress=assignmentInAssert \
--suppress=variableScope \
--inline-suppr \
--library=python \
-DCHAR_BIT=8 \
python
--include=linters/cppcheck.h \
src python/ustreamer.c janus/rtp.h janus/rtp.c janus/plugin.c
[testenv:flake8]
whitelist_externals = bash

View File

@@ -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 3.22" "January 2021"
.TH USTREAMER-DUMP 1 "version 3.25" "January 2021"
.SH NAME
ustreamer-dump \- Dump uStreamer's memory sink to file

View 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 3.22" "November 2020"
.TH USTREAMER 1 "version 3.25" "November 2020"
.SH NAME
ustreamer \- stream MJPG video from any V4L2 device to the network

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=3.22
pkgver=3.25
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ustreamer
PKG_VERSION:=3.22
PKG_VERSION:=3.25
PKG_RELEASE:=1
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>

20
python/Makefile Normal file
View File

@@ -0,0 +1,20 @@
-include ../config.mk
DESTDIR ?=
PREFIX ?= /usr/local
PY ?= python3
# =====
all:
$(info == PY_BUILD ustreamer-*.so)
@ $(PY) setup.py build
install:
$(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
clean:
rm -rf build

1
python/frame.c Symbolic link
View File

@@ -0,0 +1 @@
../src/libs/frame.c

1
python/frame.h Symbolic link
View File

@@ -0,0 +1 @@
../src/libs/frame.h

1
python/memsinksh.h Symbolic link
View File

@@ -0,0 +1 @@
../src/libs/memsinksh.h

View File

@@ -1,3 +1,5 @@
import os
from distutils.core import Extension
from distutils.core import setup
@@ -6,7 +8,7 @@ from distutils.core import setup
if __name__ == "__main__":
setup(
name="ustreamer",
version="3.22",
version="3.25",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",
@@ -16,11 +18,8 @@ if __name__ == "__main__":
"ustreamer",
libraries=["rt", "m", "pthread"],
undef_macros=["NDEBUG"],
sources=["ustreamer.c"],
depends=[
"../src/libs/tools.h",
"../src/libs/memsinksh.h",
],
sources=[name for name in os.listdir(".") if name.endswith(".c")],
depends=[name for name in os.listdir(".") if name.endswith(".h")],
),
],
)

1
python/tools.h Symbolic link
View File

@@ -0,0 +1 @@
../src/libs/tools.h

View File

@@ -13,31 +13,11 @@
#include <Python.h>
#include "../src/libs/tools.h" // Just a header without C-sources
#include "../src/libs/memsinksh.h" // No sources again
#include "tools.h"
#include "frame.h"
#include "memsinksh.h"
typedef struct {
uint64_t id;
long double ts;
uint8_t *data;
size_t used;
size_t allocated;
unsigned width;
unsigned height;
unsigned format;
unsigned stride;
bool online;
bool key;
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
} tmp_frame_s;
typedef struct {
PyObject_HEAD
@@ -49,29 +29,28 @@ typedef struct {
int fd;
memsink_shared_s *mem;
tmp_frame_s *tmp_frame;
uint64_t frame_id;
long double frame_ts;
frame_s *frame;
} MemsinkObject;
#define MEM(_next) self->mem->_next
#define TMP(_next) self->tmp_frame->_next
#define MEM(_next) self->mem->_next
#define FRAME(_next) self->frame->_next
static void MemsinkObject_destroy_internals(MemsinkObject *self) {
if (self->mem != NULL) {
munmap(self->mem, sizeof(memsink_shared_s));
memsink_shared_unmap(self->mem);
self->mem = NULL;
}
if (self->fd > 0) {
close(self->fd);
self->fd = -1;
}
if (self->tmp_frame) {
if (TMP(data)) {
free(TMP(data));
}
free(self->tmp_frame);
self->tmp_frame = NULL;
if (self->frame) {
frame_destroy(self->frame);
self->frame = NULL;
}
}
@@ -99,29 +78,15 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
# undef SET_DOUBLE
A_CALLOC(self->tmp_frame, 1);
TMP(allocated) = 512 * 1024;
A_REALLOC(TMP(data), TMP(allocated));
self->frame = frame_init();
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
PyErr_SetFromErrno(PyExc_OSError);
goto error;
}
if ((self->mem = mmap(
NULL,
sizeof(memsink_shared_s),
PROT_READ | PROT_WRITE,
MAP_SHARED,
self->fd,
0
)) == MAP_FAILED) {
if ((self->mem = memsink_shared_map(self->fd)) == NULL) {
PyErr_SetFromErrno(PyExc_OSError);
self->mem = NULL;
goto error;
}
if (self->mem == NULL) {
PyErr_SetString(PyExc_RuntimeError, "Memory mapping is NULL"); \
goto error;
}
@@ -177,24 +142,16 @@ static int wait_frame(MemsinkObject *self) {
RETURN_OS_ERROR;
} else if (retval == 0) {
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && TMP(id) != MEM(id)) {
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && MEM(id) != self->frame_id) {
if (self->drop_same_frames > 0) {
# define CMP(_field) (TMP(_field) == MEM(_field))
if (
CMP(used)
&& CMP(width)
&& CMP(height)
&& CMP(format)
&& CMP(stride)
&& CMP(online)
&& CMP(key)
&& (TMP(ts) + self->drop_same_frames > now)
&& !memcmp(TMP(data), MEM(data), MEM(used))
FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
&& (self->frame_ts + self->drop_same_frames > now)
&& !memcmp(FRAME(data), MEM(data), MEM(used))
) {
TMP(id) = MEM(id);
self->frame_id = MEM(id);
goto drop;
}
# undef CMP
}
Py_BLOCK_THREADS
@@ -236,30 +193,11 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
default: return NULL;
}
# define COPY(_field) TMP(_field) = MEM(_field)
COPY(width);
COPY(height);
COPY(format);
COPY(stride);
COPY(online);
COPY(key);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
COPY(used);
# undef COPY
if (TMP(allocated) < MEM(used)) {
size_t size = MEM(used) + (512 * 1024);
A_REALLOC(TMP(data), size);
TMP(allocated) = size;
}
memcpy(TMP(data), MEM(data), MEM(used));
TMP(used) = MEM(used);
TMP(id) = MEM(id);
TMP(ts) = get_now_monotonic();
MEM(last_client_ts) = TMP(ts);
frame_set_data(self->frame, MEM(data), MEM(used));
FRAME_COPY_META(self->mem, self->frame);
self->frame_id = MEM(id);
self->frame_ts = get_now_monotonic();
MEM(last_client_ts) = self->frame_ts;
if (flock(self->fd, LOCK_UN) < 0) {
return PyErr_SetFromErrno(PyExc_OSError);
@@ -281,7 +219,7 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
} \
Py_DECREF(_tmp); \
}
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(TMP(_key)))
# define SET_NUMBER(_key, _from, _to) SET_VALUE(#_key, Py##_to##_From##_from(FRAME(_key)))
SET_NUMBER(width, Long, Long);
SET_NUMBER(height, Long, Long);
@@ -292,7 +230,7 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
SET_NUMBER(grab_ts, Double, Float);
SET_NUMBER(encode_begin_ts, Double, Float);
SET_NUMBER(encode_end_ts, Double, Float);
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)TMP(data), TMP(used)));
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)FRAME(data), FRAME(used)));
# undef SET_NUMBER
# undef SET_VALUE
@@ -376,5 +314,5 @@ PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
return module;
}
#undef TMP
#undef FRAME
#undef MEM

113
src/Makefile Normal file
View File

@@ -0,0 +1,113 @@
DESTDIR ?=
PREFIX ?= /usr/local
CC ?= gcc
CFLAGS ?= -O3
LDFLAGS ?=
RPI_VC_HEADERS ?= /opt/vc/include
RPI_VC_LIBS ?= /opt/vc/lib
# =====
_USTR = ustreamer.bin
_DUMP = ustreamer-dump.bin
_CFLAGS = -MD -c -std=c11 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
_LDFLAGS = $(LDFLAGS)
_COMMON_LIBS = -lm -ljpeg -pthread -lrt
_USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads
_USTR_SRCS = $(shell ls \
libs/*.c \
ustreamer/*.c \
ustreamer/http/*.c \
ustreamer/data/*.c \
ustreamer/encoders/cpu/*.c \
ustreamer/encoders/hw/*.c \
)
_DUMP_LIBS = $(_COMMON_LIBS)
_DUMP_SRCS = $(shell ls \
libs/*.c \
dump/*.c \
)
_BUILD = build
define optbool
$(filter $(shell echo $(1) | tr A-Z a-z), yes on 1)
endef
ifneq ($(call optbool,$(WITH_OMX)),)
_USTR_LIBS += -lbcm_host -lvcos -lvcsm -lopenmaxil -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lmmal_components -L$(RPI_VC_LIBS)
override _CFLAGS += -DWITH_OMX -DOMX_SKIP64BIT -I$(RPI_VC_HEADERS)
_USTR_SRCS += $(shell ls \
ustreamer/encoders/omx/*.c \
ustreamer/h264/*.c \
)
endif
ifneq ($(call optbool,$(WITH_GPIO)),)
_USTR_LIBS += -lgpiod
override _CFLAGS += -DWITH_GPIO
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
endif
WITH_PTHREAD_NP ?= 1
ifneq ($(call optbool,$(WITH_PTHREAD_NP)),)
override _CFLAGS += -DWITH_PTHREAD_NP
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
endif
# =====
all: $(_USTR) $(_DUMP)
install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin
install -m755 $(_USTR) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
install -m755 $(_DUMP) $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
install-strip: install
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_USTR))
strip $(DESTDIR)$(PREFIX)/bin/$(subst .bin,,$(_DUMP))
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
$(info == LD $@)
@ $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
$(_BUILD)/%.o: %.c
$(info -- CC $<)
@ mkdir -p $(dir $@) || true
@ $(CC) $< -o $@ $(_CFLAGS)
clean:
rm -rf $(_USTR) $(_DUMP) $(_BUILD)
_OBJS = $(_USTR_SRCS:%.c=$(_BUILD)/%.o) $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
-include $(_OBJS:%.o=%.d)

View File

@@ -128,12 +128,12 @@ int main(int argc, char *argv[]) {
case _O_OUTPUT: OPT_SET(output_path, optarg);
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(log_level, LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(log_colored, false);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
case _O_HELP: _help(stdout); return 0;
case _O_VERSION: puts(VERSION); return 0;
@@ -202,7 +202,7 @@ static void _install_signal_handlers(void) {
}
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx) {
frame_s *frame = frame_init("input");
frame_s *frame = frame_init();
memsink_s *sink = NULL;
if ((sink = memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
@@ -287,7 +287,7 @@ static void _help(FILE *fp) {
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
SAY(" Enabling debugging messages can slow down the program.");
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
SAY(" Default: %d.\n", log_level);
SAY(" Default: %d.\n", us_log_level);
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");

View File

@@ -22,6 +22,11 @@
#pragma once
#ifndef VERSION
# define VERSION "3.22"
#endif
#define VERSION_MAJOR 3
#define VERSION_MINOR 25
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
#define VERSION MAKE_VERSION1(VERSION_MAJOR, VERSION_MINOR)
#define VERSION_U ((unsigned)(VERSION_MAJOR * 1000 + VERSION_MINOR))

View File

@@ -23,17 +23,14 @@
#include "frame.h"
frame_s *frame_init(const char *name) {
frame_s *frame_init(void) {
frame_s *frame;
A_CALLOC(frame, 1);
frame->name = name;
frame->managed = true;
frame_realloc_data(frame, 512 * 1024);
return frame;
}
void frame_destroy(frame_s *frame) {
assert(frame->managed);
if (frame->data) {
free(frame->data);
}
@@ -41,68 +38,36 @@ void frame_destroy(frame_s *frame) {
}
void frame_realloc_data(frame_s *frame, size_t size) {
assert(frame->managed);
if (frame->allocated < size) {
LOG_DEBUG("Increasing frame buffer '%s': %zu -> %zu (+%zu)",
frame->name, frame->allocated, size, size - frame->allocated);
A_REALLOC(frame->data, size);
frame->allocated = size;
}
}
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) {
assert(frame->managed);
frame_realloc_data(frame, size);
memcpy(frame->data, data, size);
frame->used = size;
}
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size) {
assert(frame->managed);
size_t new_used = frame->used + size;
frame_realloc_data(frame, new_used);
memcpy(frame->data + frame->used, data, size);
frame->used = new_used;
}
#define COPY(_field) dest->_field = src->_field
void frame_copy(const frame_s *src, frame_s *dest) {
assert(dest->managed);
frame_set_data(dest, src->data, src->used);
COPY(used);
frame_copy_meta(src, dest);
FRAME_COPY_META(src, dest);
}
void frame_copy_meta(const frame_s *src, frame_s *dest) {
// Don't copy the name
COPY(width);
COPY(height);
COPY(format);
COPY(stride);
COPY(online);
COPY(key);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
}
#undef COPY
bool frame_compare(const frame_s *a, const frame_s *b) {
# define CMP(_field) (a->_field == b->_field)
return (
a->allocated && b->allocated
&& CMP(used)
&& CMP(width)
&& CMP(height)
&& CMP(format)
&& CMP(stride)
&& CMP(online)
&& CMP(key)
&& FRAME_COMPARE_META_USED_NOTS(a, b)
&& !memcmp(a->data, b->data, b->used)
);
# undef CMP
}
unsigned frame_get_padding(const frame_s *frame) {

View File

@@ -32,12 +32,9 @@
#include <linux/videodev2.h>
#include "tools.h"
#include "logging.h"
typedef struct {
const char *name;
uint8_t *data;
size_t used;
size_t allocated;
@@ -56,12 +53,37 @@ typedef struct {
long double grab_ts;
long double encode_begin_ts;
long double encode_end_ts;
bool managed;
} frame_s;
frame_s *frame_init(const char *name);
#define FRAME_COPY_META(_src, _dest) { \
_dest->width = _src->width; \
_dest->height = _src->height; \
_dest->format = _src->format; \
_dest->stride = _src->stride; \
_dest->online = _src->online; \
_dest->key = _src->key; \
_dest->grab_ts = _src->grab_ts; \
_dest->encode_begin_ts = _src->encode_begin_ts; \
_dest->encode_end_ts = _src->encode_end_ts; \
}
inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
FRAME_COPY_META(src, dest);
}
#define FRAME_COMPARE_META_USED_NOTS(_a, _b) ( \
_a->used == _b->used \
&& _a->width == _b->width \
&& _a->height == _b->height \
&& _a->format == _b->format \
&& _a->stride == _b->stride \
&& _a->online == _b->online \
&& _a->key == _b->key \
)
frame_s *frame_init(void);
void frame_destroy(frame_s *frame);
void frame_realloc_data(frame_s *frame, size_t size);
@@ -69,7 +91,6 @@ void frame_set_data(frame_s *frame, const uint8_t *data, size_t size);
void frame_append_data(frame_s *frame, const uint8_t *data, size_t size);
void frame_copy(const frame_s *src, frame_s *dest);
void frame_copy_meta(const frame_s *src, frame_s *dest);
bool frame_compare(const frame_s *a, const frame_s *b);
unsigned frame_get_padding(const frame_s *frame);

71
src/libs/list.h Normal file
View File

@@ -0,0 +1,71 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
# #
# Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
*****************************************************************************/
#pragma once
#include <assert.h>
#define LIST_STRUCT(...) \
__VA_ARGS__ *prev; \
__VA_ARGS__ *next;
#define LIST_ITERATE(_first, _item, ...) { \
for (__typeof__(_first) _item = _first; _item;) { \
__typeof__(_first) _next = _item->next; \
__VA_ARGS__ \
_item = _next; \
} \
}
#define LIST_APPEND(_first, _item) { \
if (_first == NULL) { \
_first = _item; \
} else { \
__typeof__(_first) _last = _first; \
for (; _last->next; _last = _last->next); \
_item->prev = _last; \
_last->next = _item; \
} \
}
#define LIST_APPEND_C(_first, _item, _count) { \
LIST_APPEND(_first, _item); \
++(_count); \
}
#define LIST_REMOVE(_first, _item) { \
if (_item->prev == NULL) { \
_first = _item->next; \
} else { \
_item->prev->next = _item->next; \
} \
if (_item->next != NULL) { \
_item->next->prev = _item->prev; \
} \
}
#define LIST_REMOVE_C(_first, _item, _count) { \
LIST_REMOVE(_first, _item); \
assert((_count) >= 1); \
--(_count); \
}

View File

@@ -23,8 +23,8 @@
#include "logging.h"
enum log_level_t log_level;
enum log_level_t us_log_level;
bool log_colored;
bool us_log_colored;
pthread_mutex_t log_mutex;
pthread_mutex_t us_log_mutex;

View File

@@ -45,23 +45,23 @@ enum log_level_t {
};
extern enum log_level_t log_level;
extern enum log_level_t us_log_level;
extern bool log_colored;
extern bool us_log_colored;
extern pthread_mutex_t log_mutex;
extern pthread_mutex_t us_log_mutex;
#define LOGGING_INIT { \
log_level = LOG_LEVEL_INFO; \
log_colored = isatty(2); \
A_MUTEX_INIT(&log_mutex); \
us_log_level = LOG_LEVEL_INFO; \
us_log_colored = isatty(2); \
A_MUTEX_INIT(&us_log_mutex); \
}
#define LOGGING_DESTROY A_MUTEX_DESTROY(&log_mutex)
#define LOGGING_DESTROY A_MUTEX_DESTROY(&us_log_mutex)
#define LOGGING_LOCK A_MUTEX_LOCK(&log_mutex)
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&log_mutex)
#define LOGGING_LOCK A_MUTEX_LOCK(&us_log_mutex)
#define LOGGING_UNLOCK A_MUTEX_UNLOCK(&us_log_mutex)
#define COLOR_GRAY "\x1b[30;1m"
@@ -84,7 +84,7 @@ extern pthread_mutex_t log_mutex;
}
#define SEP_DEBUG(_ch) { \
if (log_level >= LOG_LEVEL_DEBUG) { \
if (us_log_level >= LOG_LEVEL_DEBUG) { \
SEP_INFO(_ch); \
} \
}
@@ -93,7 +93,7 @@ extern pthread_mutex_t log_mutex;
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
char _tname_buf[MAX_THREAD_NAME] = {0}; \
thread_get_name(_tname_buf); \
if (log_colored) { \
if (us_log_colored) { \
fprintf(stderr, COLOR_GRAY "-- " _label_color _label COLOR_GRAY \
" [%.03Lf %9s]" " -- " COLOR_RESET _msg_color _msg COLOR_RESET, \
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
@@ -130,25 +130,25 @@ extern pthread_mutex_t log_mutex;
}
#define LOG_PERF(_msg, ...) { \
if (log_level >= LOG_LEVEL_PERF) { \
if (us_log_level >= LOG_LEVEL_PERF) { \
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \
} \
}
#define LOG_PERF_FPS(_msg, ...) { \
if (log_level >= LOG_LEVEL_PERF) { \
if (us_log_level >= LOG_LEVEL_PERF) { \
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \
} \
}
#define LOG_VERBOSE(_msg, ...) { \
if (log_level >= LOG_LEVEL_VERBOSE) { \
if (us_log_level >= LOG_LEVEL_VERBOSE) { \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \
} \
}
#define LOG_VERBOSE_PERROR(_msg, ...) { \
if (log_level >= LOG_LEVEL_VERBOSE) { \
if (us_log_level >= LOG_LEVEL_VERBOSE) { \
char _perror_buf[1024] = {0}; \
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg ": %s", ##__VA_ARGS__, _perror_ptr); \
@@ -156,17 +156,7 @@ extern pthread_mutex_t log_mutex;
}
#define LOG_DEBUG(_msg, ...) { \
if (log_level >= LOG_LEVEL_DEBUG) { \
if (us_log_level >= LOG_LEVEL_DEBUG) { \
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \
} \
}
INLINE char *errno_to_string(int error, char *buf, size_t size) {
# if defined(__GLIBC__) && defined(_GNU_SOURCE)
return strerror_r(error, buf, size);
# else
strerror_r(error, buf, size);
return buf;
# endif
}

View File

@@ -54,14 +54,7 @@ memsink_s *memsink_init(
goto error;
}
if ((sink->mem = mmap(
NULL,
sizeof(memsink_shared_s),
PROT_READ | PROT_WRITE,
MAP_SHARED,
sink->fd,
0
)) == MAP_FAILED) {
if ((sink->mem = memsink_shared_map(sink->fd)) == NULL) {
LOG_PERROR("%s-sink: Can't mmap shared memory", name);
goto error;
}
@@ -75,7 +68,7 @@ memsink_s *memsink_init(
void memsink_destroy(memsink_s *sink) {
if (sink->mem != MAP_FAILED) {
if (munmap(sink->mem, sizeof(memsink_shared_s)) < 0) {
if (memsink_shared_unmap(sink->mem) < 0) {
LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
}
}
@@ -93,7 +86,7 @@ void memsink_destroy(memsink_s *sink) {
}
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
// Возвращает true, если если клиенты ИЛИ изменились метаданные
// Возвращает true, если есть клиенты ИЛИ изменились метаданные
assert(sink->server);
@@ -108,9 +101,7 @@ bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
sink->has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
# define NEQ(_field) (sink->mem->_field != frame->_field)
bool retval = (sink->has_clients || NEQ(width) || NEQ(height) || NEQ(format) || NEQ(stride) || NEQ(online) || NEQ(key));
# undef NEQ
bool retval = (sink->has_clients || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
@@ -133,24 +124,16 @@ int memsink_server_put(memsink_s *sink, const frame_s *frame) {
if (flock_timedwait_monotonic(sink->fd, 1) == 0) {
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
# define COPY(_field) sink->mem->_field = frame->_field
sink->last_id = get_now_id();
sink->mem->id = sink->last_id;
COPY(used);
COPY(width);
COPY(height);
COPY(format);
COPY(stride);
COPY(online);
COPY(key);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
sink->has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
memcpy(sink->mem->data, frame->data, frame->used);
sink->mem->used = frame->used;
FRAME_COPY_META(frame, sink->mem);
sink->mem->magic = MEMSINK_MAGIC;
sink->mem->version = MEMSINK_VERSION;
# undef COPY
sink->has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
if (flock(sink->fd, LOCK_UN) < 0) {
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
@@ -189,19 +172,9 @@ int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress u
goto done;
}
if (sink->mem->id != sink->last_id) { // When updated
# define COPY(_field) frame->_field = sink->mem->_field
sink->last_id = sink->mem->id;
COPY(width);
COPY(height);
COPY(format);
COPY(stride);
COPY(online);
COPY(key);
COPY(grab_ts);
COPY(encode_begin_ts);
COPY(encode_end_ts);
frame_set_data(frame, sink->mem->data, sink->mem->used);
# undef COPY
FRAME_COPY_META(sink->mem, frame);
retval = 0;
}
sink->mem->last_client_ts = get_now_monotonic();

View File

@@ -26,6 +26,7 @@
#include <stdbool.h>
#include <sys/types.h>
#include <sys/mman.h>
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
@@ -59,3 +60,25 @@ typedef struct {
uint8_t data[MEMSINK_MAX_DATA];
} memsink_shared_s;
INLINE memsink_shared_s *memsink_shared_map(int fd) {
memsink_shared_s *mem = mmap(
NULL,
sizeof(memsink_shared_s),
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0
);
if (mem == MAP_FAILED) {
return NULL;
}
assert(mem != NULL);
return mem;
}
INLINE int memsink_shared_unmap(memsink_shared_s *mem) {
assert(mem != NULL);
return munmap(mem, sizeof(memsink_shared_s));
}

View File

@@ -26,7 +26,9 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <errno.h>
#include <math.h>
#include <time.h>
@@ -35,17 +37,21 @@
#include <sys/file.h>
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
#define RN "\r\n"
#define INLINE inline __attribute__((always_inline))
#define UNUSED __attribute__((unused))
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
INLINE char *bool_to_string(bool flag) {
#define A_ASPRINTF(_dest, _fmt, ...) assert(asprintf(&(_dest), _fmt, ##__VA_ARGS__) >= 0)
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
INLINE const char *bool_to_string(bool flag) {
return (flag ? "true" : "false");
}
@@ -141,3 +147,16 @@ INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
}
return retval;
}
INLINE char *errno_to_string(int error, char *buf, size_t size) {
assert(buf);
assert(size > 0);
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
char *str = "!!! newlocale() error !!!";
strncpy(buf, (locale ? strerror_l(error, locale) : str), size - 1);
buf[size - 1] = '\0';
if (locale) {
freelocale(locale);
}
return buf;
}

View File

@@ -87,6 +87,6 @@ static void _jpeg_error_handler(j_common_ptr jpeg) {
char msg[JMSG_LENGTH_MAX];
(*jpeg_error->mgr.format_message)(jpeg, msg);
LOG_ERROR("Can't decompress %s JPEG: %s", jpeg_error->frame->name, msg);
LOG_ERROR("Can't decompress JPEG: %s", msg);
longjmp(jpeg_error->jmp, -1);
}

View File

@@ -44,7 +44,7 @@ frame_s *blank_frame_init(const char *path) {
}
static frame_s *_init_internal(void) {
frame_s *blank = frame_init("blank_internal");
frame_s *blank = frame_init();
frame_set_data(blank, BLANK_JPEG_DATA, BLANK_JPEG_DATA_SIZE);
blank->width = BLANK_JPEG_WIDTH;
blank->height = BLANK_JPEG_HEIGHT;
@@ -55,7 +55,7 @@ static frame_s *_init_internal(void) {
static frame_s *_init_external(const char *path) {
FILE *fp = NULL;
frame_s *blank = frame_init("blank_external");
frame_s *blank = frame_init();
blank->format = V4L2_PIX_FMT_JPEG;
if ((fp = fopen(path, "rb")) == NULL) {
@@ -83,7 +83,7 @@ static frame_s *_init_external(const char *path) {
}
# undef CHUNK_SIZE
frame_s *decoded = frame_init("blank_external_decoded");
frame_s *decoded = frame_init();
if (unjpeg(blank, decoded, false) < 0) {
frame_destroy(decoded);
goto error;

View File

@@ -36,7 +36,7 @@ static const struct {
};
static void *_worker_job_init(worker_s *wr, void *v_enc);
static void *_worker_job_init(void *v_enc);
static void _worker_job_destroy(void *v_job);
static bool _worker_run_job(worker_s *wr);
@@ -212,23 +212,17 @@ void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *
A_MUTEX_UNLOCK(&ER(mutex));
}
static void *_worker_job_init(worker_s *wr, void *v_enc) {
static void *_worker_job_init(void *v_enc) {
encoder_job_s *job;
A_CALLOC(job, 1);
job->enc = (encoder_s *)v_enc;
const size_t dest_role_len = strlen(wr->name) + 16;
A_CALLOC(job->dest_role, dest_role_len);
snprintf(job->dest_role, dest_role_len, "%s_dest", wr->name);
job->dest = frame_init(job->dest_role);
job->dest = frame_init();
return (void *)job;
}
static void _worker_job_destroy(void *v_job) {
encoder_job_s *job = (encoder_job_s *)v_job;
frame_destroy(job->dest);
free(job->dest_role);
free(job);
}

View File

@@ -23,7 +23,7 @@
#include "gpio.h"
gpio_s gpio = {
gpio_s us_gpio = {
.path = "/dev/gpiochip0",
.consumer_prefix = "ustreamer",
@@ -51,42 +51,42 @@ static void _gpio_output_destroy(gpio_output_s *output);
void gpio_init(void) {
assert(gpio.chip == NULL);
assert(us_gpio.chip == NULL);
if (
gpio.prog_running.pin >= 0
|| gpio.stream_online.pin >= 0
|| gpio.has_http_clients.pin >= 0
us_gpio.prog_running.pin >= 0
|| us_gpio.stream_online.pin >= 0
|| us_gpio.has_http_clients.pin >= 0
) {
A_MUTEX_INIT(&gpio.mutex);
LOG_INFO("GPIO: Using chip device: %s", gpio.path);
if ((gpio.chip = gpiod_chip_open(gpio.path)) != NULL) {
_gpio_output_init(&gpio.prog_running);
_gpio_output_init(&gpio.stream_online);
_gpio_output_init(&gpio.has_http_clients);
A_MUTEX_INIT(&us_gpio.mutex);
LOG_INFO("GPIO: Using chip device: %s", us_gpio.path);
if ((us_gpio.chip = gpiod_chip_open(us_gpio.path)) != NULL) {
_gpio_output_init(&us_gpio.prog_running);
_gpio_output_init(&us_gpio.stream_online);
_gpio_output_init(&us_gpio.has_http_clients);
} else {
LOG_PERROR("GPIO: Can't initialize chip device %s", gpio.path);
LOG_PERROR("GPIO: Can't initialize chip device %s", us_gpio.path);
}
}
}
void gpio_destroy(void) {
_gpio_output_destroy(&gpio.prog_running);
_gpio_output_destroy(&gpio.stream_online);
_gpio_output_destroy(&gpio.has_http_clients);
if (gpio.chip) {
gpiod_chip_close(gpio.chip);
gpio.chip = NULL;
A_MUTEX_DESTROY(&gpio.mutex);
_gpio_output_destroy(&us_gpio.prog_running);
_gpio_output_destroy(&us_gpio.stream_online);
_gpio_output_destroy(&us_gpio.has_http_clients);
if (us_gpio.chip) {
gpiod_chip_close(us_gpio.chip);
us_gpio.chip = NULL;
A_MUTEX_DESTROY(&us_gpio.mutex);
}
}
int gpio_inner_set(gpio_output_s *output, bool state) {
int retval = 0;
assert(gpio.chip);
assert(us_gpio.chip);
assert(output->line);
assert(output->state != state); // Must be checked in macro for the performance
A_MUTEX_LOCK(&gpio.mutex);
A_MUTEX_LOCK(&us_gpio.mutex);
if (gpiod_line_set_value(output->line, (int)state) < 0) { \
LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
@@ -94,19 +94,18 @@ int gpio_inner_set(gpio_output_s *output, bool state) {
retval = -1;
}
A_MUTEX_UNLOCK(&gpio.mutex);
A_MUTEX_UNLOCK(&us_gpio.mutex);
return retval;
}
static void _gpio_output_init(gpio_output_s *output) {
assert(gpio.chip);
assert(us_gpio.chip);
assert(output->line == NULL);
A_CALLOC(output->consumer, strlen(gpio.consumer_prefix) + strlen(output->role) + 16);
sprintf(output->consumer, "%s::%s", gpio.consumer_prefix, output->role);
A_ASPRINTF(output->consumer, "%s::%s", us_gpio.consumer_prefix, output->role);
if (output->pin >= 0) {
if ((output->line = gpiod_chip_get_line(gpio.chip, output->pin)) != NULL) {
if ((output->line = gpiod_chip_get_line(us_gpio.chip, output->pin)) != NULL) {
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
_gpio_output_destroy(output);

View File

@@ -56,7 +56,7 @@ typedef struct {
} gpio_s;
extern gpio_s gpio;
extern gpio_s us_gpio;
void gpio_init(void);
@@ -73,15 +73,15 @@ int gpio_inner_set(gpio_output_s *output, bool state);
}
INLINE void gpio_set_prog_running(bool state) {
SET_STATE(gpio.prog_running, state);
SET_STATE(us_gpio.prog_running, state);
}
INLINE void gpio_set_stream_online(bool state) {
SET_STATE(gpio.stream_online, state);
SET_STATE(us_gpio.stream_online, state);
}
INLINE void gpio_set_has_http_clients(bool state) {
SET_STATE(gpio.has_http_clients, state);
SET_STATE(us_gpio.has_http_clients, state);
}
#undef SET_STATE

View File

@@ -27,8 +27,8 @@ h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop)
h264_stream_s *h264;
A_CALLOC(h264, 1);
h264->sink = sink;
h264->tmp_src = frame_init("h264_tmp_src");
h264->dest = frame_init("h264_dest");
h264->tmp_src = frame_init();
h264->dest = frame_init();
atomic_init(&h264->online, false);
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100

View File

@@ -51,7 +51,7 @@ static char *_http_get_client_hostport(struct evhttp_request *request);
server_s *server_init(stream_s *stream) {
exposed_s *exposed;
A_CALLOC(exposed, 1);
exposed->frame = frame_init("http_exposed");
exposed->frame = frame_init();
server_runtime_s *run;
A_CALLOC(run, 1);
@@ -93,13 +93,11 @@ void server_destroy(server_s *server) {
libevent_global_shutdown();
# endif
for (stream_client_s *client = RUN(stream_clients); client != NULL;) {
stream_client_s *next = client->next;
LIST_ITERATE(RUN(stream_clients), client, {
free(client->key);
free(client->hostport);
free(client);
client = next;
}
});
if (RUN(auth_token)) {
free(RUN(auth_token));
@@ -145,16 +143,14 @@ int server_listen(server_s *server) {
evhttp_set_timeout(RUN(http), server->timeout);
if (server->user[0] != '\0') {
char *raw_token;
char *encoded_token = NULL;
A_CALLOC(raw_token, strlen(server->user) + strlen(server->passwd) + 16);
sprintf(raw_token, "%s:%s", server->user, server->passwd);
char *raw_token;
A_ASPRINTF(raw_token, "%s:%s", server->user, server->passwd);
base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
free(raw_token);
A_CALLOC(RUN(auth_token), strlen(encoded_token) + 16);
sprintf(RUN(auth_token), "Basic %s", encoded_token);
A_ASPRINTF(RUN(auth_token), "Basic %s", encoded_token);
free(encoded_token);
LOG_INFO("Using HTTP basic auth");
@@ -369,7 +365,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
RUN(stream_clients_count)
));
for (stream_client_s * client = RUN(stream_clients); client != NULL; client = client->next) {
LIST_ITERATE(RUN(stream_clients), client, {
assert(evbuffer_add_printf(buf,
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
" \"dual_final_frames\": %s, \"zero_data\": %s}%s",
@@ -381,7 +377,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
bool_to_string(client->zero_data),
(client->next ? ", " : "")
));
}
});
assert(evbuffer_add_printf(buf, "}}}}"));
@@ -479,16 +475,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->hostport = _http_get_client_hostport(request);
client->id = get_now_id();
if (RUN(stream_clients) == NULL) {
RUN(stream_clients) = client;
} else {
stream_client_s *last = RUN(stream_clients);
for (; last->next != NULL; last = last->next);
client->prev = last;
last->next = client;
}
RUN(stream_clients_count) += 1;
LIST_APPEND_C(RUN(stream_clients), client, RUN(stream_clients_count));
if (RUN(stream_clients_count) == 1) {
atomic_store(&VID(has_clients), true);
@@ -522,7 +509,6 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
# define BOUNDARY "boundarydonotcross"
# define RN "\r\n"
stream_client_s *client = (stream_client_s *)v_client;
server_s *server = client->server;
@@ -647,7 +633,6 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
bufferevent_enable(buf_event, EV_READ);
# undef ADD_ADVANCE_HEADERS
# undef RN
# undef BOUNDARY
}
@@ -655,10 +640,7 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
stream_client_s *client = (stream_client_s *)v_client;
server_s *server = client->server;
char *reason = bufferevent_my_format_reason(what);
assert(RUN(stream_clients_count) > 0);
RUN(stream_clients_count) -= 1;
LIST_REMOVE_C(RUN(stream_clients), client, RUN(stream_clients_count));
if (RUN(stream_clients_count) == 0) {
atomic_store(&VID(has_clients), false);
@@ -667,34 +649,26 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
# endif
}
char *reason = bufferevent_my_format_reason(what);
LOG_INFO("HTTP: Disconnected client: %s, id=%" PRIx64 ", %s; clients now: %u",
client->hostport, client->id, reason, RUN(stream_clients_count));
free(reason);
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
if (conn) {
evhttp_connection_free(conn);
}
if (client->prev == NULL) {
RUN(stream_clients) = client->next;
} else {
client->prev->next = client->next;
}
if (client->next != NULL) {
client->next->prev = client->prev;
}
free(client->key);
free(client->hostport);
free(client);
free(reason);
}
static void _http_queue_send_stream(server_s *server, bool stream_updated, bool frame_updated) {
bool has_clients = false;
bool queued = false;
for (stream_client_s *client = RUN(stream_clients); client != NULL; client = client->next) {
LIST_ITERATE(RUN(stream_clients), client, {
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
if (conn) {
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
@@ -725,7 +699,7 @@ static void _http_queue_send_stream(server_s *server, bool stream_updated, bool
has_clients = true;
}
}
});
if (queued) {
static unsigned queued_fps_accum = 0;
@@ -857,10 +831,7 @@ static char *_http_get_client_hostport(struct evhttp_request *request) {
}
char *hostport;
size_t hostport_len = strlen(addr) + 64;
A_CALLOC(hostport, hostport_len);
snprintf(hostport, hostport_len, "[%s]:%u", addr, port);
A_ASPRINTF(hostport, "[%s]:%u", addr, port);
free(addr);
return hostport;
}

View File

@@ -58,6 +58,7 @@
#include "../../libs/process.h"
#include "../../libs/frame.h"
#include "../../libs/base64.h"
#include "../../libs/list.h"
#include "../data/index_html.h"
#include "../encoder.h"
#include "../stream.h"
@@ -91,8 +92,7 @@ typedef struct stream_client_sx {
unsigned fps_accum;
long long fps_accum_second;
struct stream_client_sx *prev;
struct stream_client_sx *next;
LIST_STRUCT(struct stream_client_sx);
} stream_client_s;
typedef struct {

View File

@@ -32,7 +32,7 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
goto error;
}
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16);
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
sprintf(path, "%s/%s", root_path, simplified_path);
struct stat st;

View File

@@ -440,11 +440,11 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
# undef ADD_SINK
# ifdef WITH_GPIO
case _O_GPIO_DEVICE: OPT_SET(gpio.path, optarg);
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(gpio.consumer_prefix, optarg);
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", gpio.prog_running.pin, 0, 256, 0);
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", gpio.stream_online.pin, 0, 256, 0);
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", gpio.has_http_clients.pin, 0, 256, 0);
case _O_GPIO_DEVICE: OPT_SET(us_gpio.path, optarg);
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(us_gpio.consumer_prefix, optarg);
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", us_gpio.prog_running.pin, 0, 256, 0);
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", us_gpio.stream_online.pin, 0, 256, 0);
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", us_gpio.has_http_clients.pin, 0, 256, 0);
# endif
# ifdef HAS_PDEATHSIG
@@ -459,12 +459,12 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
# endif
case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(log_level, LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(log_level, LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(log_level, LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(log_colored, false);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_log_level, LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(us_log_level, LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(us_log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(us_log_colored, false);
case _O_HELP: _help(stdout, dev, enc, stream, server); return 1;
case _O_VERSION: puts(VERSION); return 1;
@@ -679,8 +679,8 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
# ifdef WITH_GPIO
SAY("GPIO options:");
SAY("═════════════");
SAY(" --gpio-device </dev/path> ───── Path to GPIO character device. Default: %s.\n", gpio.path);
SAY(" --gpio-consumer-prefix <str> ── Consumer prefix for GPIO outputs. Default: %s.\n", gpio.consumer_prefix);
SAY(" --gpio-device </dev/path> ───── Path to GPIO character device. Default: %s.\n", us_gpio.path);
SAY(" --gpio-consumer-prefix <str> ── Consumer prefix for GPIO outputs. Default: %s.\n", us_gpio.consumer_prefix);
SAY(" --gpio-prog-running <pin> ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n");
SAY(" --gpio-stream-online <pin> ──── Set 1 while streaming. Default: disabled.\n");
SAY(" --gpio-has-http-clients <pin> ─ Set 1 while stream has at least one client. Default: disabled.\n");
@@ -703,7 +703,7 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
SAY(" Enabling debugging messages can slow down the program.");
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
SAY(" Default: %d.\n", log_level);
SAY(" Default: %d.\n", us_log_level);
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");

View File

@@ -52,7 +52,7 @@ stream_s *stream_init(device_s *dev, encoder_s *enc) {
video_s *video;
A_CALLOC(video, 1);
video->frame = frame_init("stream_video");
video->frame = frame_init();
atomic_init(&video->updated, false);
A_MUTEX_INIT(&video->mutex);
atomic_init(&video->has_clients, false);

View File

@@ -28,9 +28,9 @@ static void *_worker_thread(void *v_worker);
workers_pool_s *workers_pool_init(
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
void *(*job_init)(worker_s *wr, void *arg), void *job_init_arg,
void (*job_destroy)(void *),
bool (*run_job)(worker_s *)) {
workers_pool_job_init_f job_init, void *job_init_arg,
workers_pool_job_destroy_f job_destroy,
workers_pool_run_job_f run_job) {
LOG_INFO("Creating pool %s with %u workers ...", name, n_workers);
@@ -49,21 +49,18 @@ workers_pool_s *workers_pool_init(
A_MUTEX_INIT(&pool->free_workers_mutex);
A_COND_INIT(&pool->free_workers_cond);
const size_t wr_name_len = strlen(wr_prefix) + 64;
for (unsigned number = 0; number < pool->n_workers; ++number) {
# define WR(_next) pool->workers[number]._next
WR(number) = number;
A_CALLOC(WR(name), wr_name_len);
snprintf(WR(name), wr_name_len, "%s-%u", wr_prefix, number);
A_ASPRINTF(WR(name), "%s-%u", wr_prefix, number);
A_MUTEX_INIT(&WR(has_job_mutex));
atomic_init(&WR(has_job), false);
A_COND_INIT(&WR(has_job_cond));
WR(pool) = pool;
WR(job) = job_init(&pool->workers[number], job_init_arg);
WR(job) = job_init(job_init_arg);
A_THREAD_CREATE(&WR(tid), _worker_thread, (void *)&(pool->workers[number]));
pool->free_workers += 1;

View File

@@ -55,12 +55,16 @@ typedef struct worker_sx {
struct workers_pool_sx *pool;
} worker_s;
typedef void *(*workers_pool_job_init_f)(void *arg);
typedef void (*workers_pool_job_destroy_f)(void *job);
typedef bool (*workers_pool_run_job_f)(worker_s *wr);
typedef struct workers_pool_sx {
const char *name;
long double desired_interval;
bool (*run_job)(worker_s *wr);
void (*job_destroy)(void *job);
workers_pool_job_destroy_f job_destroy;
workers_pool_run_job_f run_job;
unsigned n_workers;
worker_s *workers;
@@ -79,9 +83,9 @@ typedef struct workers_pool_sx {
workers_pool_s *workers_pool_init(
const char *name, const char *wr_prefix, unsigned n_workers, long double desired_interval,
void *(*job_init)(worker_s *wr, void *arg), void *job_init_arg,
void (*job_destroy)(void *job),
bool (*run_job)(worker_s *));
workers_pool_job_init_f job_init, void *job_init_arg,
workers_pool_job_destroy_f job_destroy,
workers_pool_run_job_f run_job);
void workers_pool_destroy(workers_pool_s *pool);