Compare commits

...

32 Commits
v5.17 ... v5.24

Author SHA1 Message Date
Maxim Devaev
bfa1516491 Bump version: 5.23 → 5.24 2022-10-19 08:13:54 +03:00
tomaszduda23
36c9ff22b3 set HDMI EDID before starting ustreamer (#179)
* build docker image

* Push each image to an user github registry.
It can be used during development for testing.

* set HDMI EDID before starting ustreamer

* Update README.md
2022-10-18 20:11:29 +03:00
Maxim Devaev
17f54a7977 Merge pull request #178 from tomaszduda23/github_registry
GitHub registry
2022-10-18 12:08:58 +03:00
Maxim Devaev
574986d0fd added --instance-id to the manpage 2022-10-16 02:54:28 +03:00
Maxim Devaev
d5ce2e835f report version 2022-10-13 02:10:16 +03:00
Maxim Devaev
6201554ba1 instance id 2022-10-12 20:00:37 +03:00
Maxim Devaev
75dee4e91d Merge pull request #177 from tomaszduda23/master
build docker image
2022-10-09 21:09:32 +03:00
Tomasz Duda
e1b4e0db66 Push each image to an user github registry.
It can be used during development for testing.
2022-10-09 15:17:35 +02:00
Tomasz Duda
0d37b09bb4 build docker image 2022-10-09 13:28:46 +02:00
Maxim Devaev
18767b68ff Merge pull request #176 from tomaszduda23/master
check compilation
2022-10-03 16:47:05 +03:00
Tomasz Duda
7975615c6c check compilation 2022-10-03 02:31:31 +02:00
Maxim Devaev
90f09b197e Bump version: 5.22 → 5.23 2022-09-25 17:17:28 +03:00
Maxim Devaev
687e97d523 Issue #169: Reverted MIN_QP to 16 2022-09-25 17:16:31 +03:00
Maxim Devaev
c1675001fa enabled paypal 2022-09-23 19:04:48 +03:00
Maxim Devaev
69cc45a2a0 Bump version: 5.21 → 5.22 2022-09-09 16:25:02 +03:00
Maxim Devaev
ece96b5834 changed arch mirror 2022-09-09 16:22:54 +03:00
Maxim Devaev
383ed7530b Issue #169: changed min QP to avoid stream corruption 2022-09-09 15:49:00 +03:00
Maxim Devaev
7f620c758f new style typing 2022-09-04 17:12:56 +03:00
Maxim Devaev
fb50eea526 Bump version: 5.20 → 5.21 2022-08-27 07:27:40 +03:00
Maxim Devaev
63f757a9da readme update 2022-08-27 07:25:44 +03:00
Maxim Devaev
4ec02d46b9 Bump version: 5.19 → 5.20 2022-08-17 17:59:16 +03:00
Maxim Devaev
527afb66df fixed arch deps 2022-08-17 17:55:59 +03:00
Maxim Devaev
5502758a7e Bump version: 5.18 → 5.19 2022-07-30 13:10:16 +03:00
Maxim Devaev
faa1776407 simplified us_errno_to_string() 2022-07-30 13:05:00 +03:00
Maxim Devaev
3d7fb8c8dd fixed #162: optional sigabbrev_np() 2022-07-30 02:59:21 +03:00
Maxim Devaev
b5f814d71e Bump version: 5.17 → 5.18 2022-07-29 15:16:49 +03:00
Maxim Devaev
6eafd4156a us_signum_to_string() 2022-07-29 14:34:41 +03:00
Maxim Devaev
df1e4eaa06 refactoring 2022-07-29 13:44:48 +03:00
Maxim Devaev
d2bef81b03 refactoring 2022-07-29 13:05:47 +03:00
Maxim Devaev
7b3dffd072 refactoring 2022-07-29 12:41:47 +03:00
Maxim Devaev
1a6e9998fb missing destroy 2022-07-29 12:21:13 +03:00
Maxim Devaev
9ab9561803 global variables prefix 2022-07-21 16:29:20 +03:00
32 changed files with 347 additions and 145 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 5.17
current_version = 5.24
parse = (?P<major>\d+)\.(?P<minor>\d+)
serialize =
{major}.{minor}

View File

@@ -7,3 +7,4 @@
!python/**
!janus/**
!man/**
!pkg/docker/entry.sh

2
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,4 @@
# These are supported funding model platforms
patreon: pikvm
#custom: https://www.paypal.me/mdevaev
custom: https://paypal.me/pikvm

View File

@@ -0,0 +1,81 @@
name: Build Alpine
on:
push:
branches: [master, build_*]
tags: [v*]
pull_request:
branches: [master]
jobs:
buildx:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Login to DockerHub Container Registry
if: startsWith(github.ref, 'refs/tags/v')
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ secrets.DOCKERHUB_USERNAME }}/ustreamer,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
ghcr.io/${{ github.repository }},enable=${{ github.event_name != 'pull_request' }}
tags: |
type=ref,event=tag
type=sha,format=long
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
driver-opts: image=moby/buildkit:master
-
name: Build and export to Docker
uses: docker/build-push-action@v3
with:
context: .
load: true
tags: ustreamer
file: pkg/docker/Dockerfile.alpine
-
name: Test
run: |
echo version: $(docker run --rm -e NO_EDID=1 -t ustreamer --version)
echo -e "features:\n$(docker run --rm -e NO_EDID=1 -t ustreamer --features)"
-
name: Build multi arch
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/arm64,linux/amd64,linux/arm/v7
file: pkg/docker/Dockerfile.alpine
-
name: Push
if: steps.meta.outputs.tags != ''
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/arm64,linux/amd64,linux/arm/v7
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
file: pkg/docker/Dockerfile.alpine

View File

@@ -5,7 +5,7 @@
[[Русская версия]](README.ru.md)
µStreamer is a lightweight and very quick server to stream [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) video from any V4L2 device to the net. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc.
µStreamer is a part of the [Pi-KVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
µStreamer is a part of the [PiKVM](https://github.com/pikvm/pikvm) project designed to stream [VGA](https://www.amazon.com/dp/B0126O0RDC) and [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) screencast hardware data with the highest resolution and FPS possible.
µStreamer is very similar to [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) with ```input_uvc.so``` and ```output_http.so``` plugins, however, there are some major differences. The key ones are:
@@ -81,6 +81,33 @@ $ ./ustreamer \
You can always view the full list of options with ```ustreamer --help```.
# Docker (Raspberry Pi 4 HDMI)
## Preparations
Add following lines to /boot/firmware/usercfg.txt:
```
gpu_mem=128
dtoverlay=tc358743
```
Check size of CMA:
```bash
$ dmesg | grep cma-reserved
[ 0.000000] Memory: 7700524K/8244224K available (11772K kernel code, 1278K rwdata, 4320K rodata, 4096K init, 1077K bss, 281556K reserved, 262144K cma-reserved)
```
If it is smaller than 128M add following to /boot/firmware/cmdline.txt:
```
cma=128M
```
Save changes and reboot.
## Launch
Start container:
```bash
$ docker run --device /dev/video0:/dev/video0 -p 8080:8080 pikvm/ustreamer:latest
```
Then access the web interface at port 8080 (e.g. http://raspberrypi.local:8080).
## EDID
Container will set HDMI EDID before starging ustreamer. Use `-e NO_EDID=1` to not set EDID. Use `-e EDID_HEX=xx` to specify custom EDID data.
-----
# Raspberry Pi Camera Example
Example usage for the Raspberry Pi v1 camera:

View File

@@ -5,7 +5,7 @@
[[English version]](README.md)
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPEG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [PiKVM](https://github.com/pikvm/pikvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:

View File

@@ -32,7 +32,7 @@
#define US_JLOG_ERROR(x_prefix, x_msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
#define US_JLOG_PERROR(x_prefix, x_msg, ...) { \
char m_perror_buf[1024] = {0}; \
char *m_perror_ptr = us_errno_to_string(errno, m_perror_buf, 1023); \
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_ptr); \
char *const m_perror_str = us_errno_to_string(errno); \
JANUS_LOG(LOG_ERR, "[%s/%-9s] " x_msg ": %s\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__, m_perror_str); \
free(m_perror_str); \
}

View File

@@ -250,7 +250,7 @@ static int _plugin_init(janus_callbacks *gw, const char *config_dir_path) {
// sysctl -w net.core.rmem_max=1000000
// sysctl -w net.core.wmem_max=1000000
US_JLOG_INFO("main", "Initializing plugin ...");
US_JLOG_INFO("main", "Initializing PiKVM uStreamer plugin %s ...", US_VERSION);
if (gw == NULL || config_dir_path == NULL || ((_g_config = us_config_init(config_dir_path)) == NULL)) {
return -1;
}

View File

@@ -40,6 +40,9 @@ us_queue_s *us_queue_init(unsigned capacity) {
}
void us_queue_destroy(us_queue_s *queue) {
US_COND_DESTROY(queue->empty_cond);
US_COND_DESTROY(queue->full_cond);
US_MUTEX_DESTROY(queue->mutex);
free(queue->items);
free(queue);
}
@@ -73,7 +76,7 @@ int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
++queue->in;
queue->in %= queue->capacity;
US_MUTEX_UNLOCK(queue->mutex);
assert(!pthread_cond_broadcast(&queue->empty_cond));
US_COND_BROADCAST(queue->empty_cond);
return 0;
}
@@ -85,7 +88,7 @@ int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
++queue->out;
queue->out %= queue->capacity;
US_MUTEX_UNLOCK(queue->mutex);
assert(!pthread_cond_broadcast(&queue->full_cond));
US_COND_BROADCAST(queue->full_cond);
return 0;
}

View File

@@ -3,7 +3,7 @@ FROM archlinux/archlinux:base-devel
RUN mkdir -p /etc/pacman.d/hooks \
&& ln -s /dev/null /etc/pacman.d/hooks/30-systemd-tmpfiles.hook
RUN echo "Server = http://mirror.yandex.ru/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist
RUN echo 'Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch' > /etc/pacman.d/mirrorlist
RUN pacman -Syu --noconfirm \
&& pacman -S --needed --noconfirm \

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 5.17" "January 2021"
.TH USTREAMER-DUMP 1 "version 5.24" "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 5.17" "November 2020"
.TH USTREAMER 1 "version 5.24" "November 2020"
.SH NAME
ustreamer \- stream MJPEG video from any V4L2 device to the network
@@ -10,7 +10,7 @@ ustreamer \- stream MJPEG video from any V4L2 device to the network
.RI [OPTIONS]
.SH DESCRIPTION
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPEG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the Pi-KVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPEG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the PiKVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
.SH USAGE
Without arguments, \fBustreamer\fR will try to open \fB/dev/video0\fR with 640x480 resolution and start streaming on \fBhttp://127\.0\.0\.1:8080\fR\. You can override this behavior using parameters \fB\-\-device\fR, \fB\-\-host\fR and \fB\-\-port\fR\. For example, to stream to the world, run: \fBustreamer --device=/dev/video1 --host=0.0.0.0 --port=80\fR
@@ -203,6 +203,9 @@ Default: disabled.
.BR \-\-allow\-origin\ \fIstr
Set Access\-Control\-Allow\-Origin header. Default: disabled.
.TP
.BR \-\-instance\-id\ \fIstr
A short string identifier to be displayed in the /state handle. It must satisfy regexp ^[a-zA-Z0-9\./+_-]*$. Default: an empty string.
.TP
.BR \-\-server\-timeout\ \fIsec
Timeout for client connections. Default: 10.

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=5.17
pkgver=5.24
pkgrel=1
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
url="https://github.com/pikvm/ustreamer"
@@ -22,8 +22,8 @@ if [ -e /usr/bin/python3 ]; then
makedepends+=(python-setuptools)
fi
if [ -e /usr/include/janus/plugins/plugin.h ];then
depends+=(janus-gateway-pikvm alsa-lib opus)
makedepends+=(janus-gateway-pikvm alsa-lib opus)
depends+=(janus-gateway alsa-lib opus)
makedepends+=(janus-gateway alsa-lib opus)
_options="$_options WITH_JANUS=1"
fi

View File

@@ -0,0 +1,34 @@
FROM alpine:3.16 as build
RUN apk add --no-cache \
alpine-sdk \
linux-headers \
libjpeg-turbo-dev \
libevent-dev \
libbsd-dev \
libgpiod-dev
WORKDIR /build/ustreamer/
COPY . .
RUN make -j5 WITH_GPIO=1
FROM alpine:3.16 as run
RUN apk add --no-cache \
libevent \
libjpeg-turbo \
libevent \
libgpiod \
libbsd \
v4l-utils
WORKDIR /ustreamer
COPY --from=build /build/ustreamer/src/ustreamer.bin ustreamer
RUN wget https://raw.githubusercontent.com/pikvm/kvmd/master/configs/kvmd/tc358743-edid.hex -O /edid.hex
COPY pkg/docker/entry.sh /
EXPOSE 8080
ENTRYPOINT ["/entry.sh"]
CMD ["--dv-timings", "--format", "UYVY"]
# vim: syntax=dockerfile

14
pkg/docker/entry.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/sh
set -e
[ -z "$NO_EDID" ] && {
[ -n "$EDID_HEX" ] && echo "$EDID_HEX" > /edid.hex
while true; do
v4l2-ctl --device=/dev/video0 --set-edid=file=/edid.hex --fix-edid-checksums --info-edid && break
echo 'Failed to set EDID. Reetrying...'
sleep 1
done
}
./ustreamer --host=0.0.0.0 $@

View File

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

View File

@@ -1,14 +1,12 @@
import os
from typing import List
from setuptools import Extension
from setuptools import setup
# =====
def _find_sources(suffix: str) -> List[str]:
sources: List[str] = []
def _find_sources(suffix: str) -> list[str]:
sources: list[str] = []
for (root_path, _, names) in os.walk("src"):
for name in names:
if name.endswith(suffix):
@@ -19,7 +17,7 @@ def _find_sources(suffix: str) -> List[str]:
if __name__ == "__main__":
setup(
name="ustreamer",
version="5.17",
version="5.24",
description="uStreamer tools",
author="Maxim Devaev",
author_email="mdevaev@gmail.com",

View File

@@ -151,12 +151,12 @@ int main(int argc, char *argv[]) {
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_log_level, US_LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_log_level, US_LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(us_log_level, US_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_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_g_log_level, US_LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(us_g_log_level, US_LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(us_g_log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(us_g_log_colored, false);
case _O_HELP: _help(stdout); return 0;
case _O_VERSION: puts(US_VERSION); return 0;
@@ -195,12 +195,9 @@ int main(int argc, char *argv[]) {
static void _signal_handler(int signum) {
switch (signum) {
case SIGTERM: US_LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
case SIGINT: US_LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
case SIGPIPE: US_LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
default: US_LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
}
char *const name = us_signum_to_string(signum);
US_LOG_INFO_NOLOCK("===== Stopping by %s =====", name);
free(name);
_g_stop = true;
}
@@ -330,7 +327,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", us_log_level);
SAY(" Default: %d.\n", us_g_log_level);
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");

View File

@@ -23,7 +23,7 @@
#pragma once
#define US_VERSION_MAJOR 5
#define US_VERSION_MINOR 17
#define US_VERSION_MINOR 24
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)

View File

@@ -23,8 +23,8 @@
#include "logging.h"
enum us_log_level_t us_log_level;
enum us_log_level_t us_g_log_level;
bool us_log_colored;
bool us_g_log_colored;
pthread_mutex_t us_log_mutex;
pthread_mutex_t us_g_log_mutex;

View File

@@ -45,23 +45,23 @@ enum us_log_level_t {
};
extern enum us_log_level_t us_log_level;
extern enum us_log_level_t us_g_log_level;
extern bool us_log_colored;
extern bool us_g_log_colored;
extern pthread_mutex_t us_log_mutex;
extern pthread_mutex_t us_g_log_mutex;
#define US_LOGGING_INIT { \
us_log_level = US_LOG_LEVEL_INFO; \
us_log_colored = isatty(2); \
US_MUTEX_INIT(us_log_mutex); \
us_g_log_level = US_LOG_LEVEL_INFO; \
us_g_log_colored = isatty(2); \
US_MUTEX_INIT(us_g_log_mutex); \
}
#define US_LOGGING_DESTROY US_MUTEX_DESTROY(us_log_mutex)
#define US_LOGGING_DESTROY US_MUTEX_DESTROY(us_g_log_mutex)
#define US_LOGGING_LOCK US_MUTEX_LOCK(us_log_mutex)
#define US_LOGGING_UNLOCK US_MUTEX_UNLOCK(us_log_mutex)
#define US_LOGGING_LOCK US_MUTEX_LOCK(us_g_log_mutex)
#define US_LOGGING_UNLOCK US_MUTEX_UNLOCK(us_g_log_mutex)
#define US_COLOR_GRAY "\x1b[30;1m"
@@ -84,7 +84,7 @@ extern pthread_mutex_t us_log_mutex;
}
#define US_SEP_DEBUG(x_ch) { \
if (us_log_level >= US_LOG_LEVEL_DEBUG) { \
if (us_g_log_level >= US_LOG_LEVEL_DEBUG) { \
US_SEP_INFO(x_ch); \
} \
}
@@ -93,7 +93,7 @@ extern pthread_mutex_t us_log_mutex;
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
us_thread_get_name(m_tname_buf); \
if (us_log_colored) { \
if (us_g_log_colored) { \
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
" [%.03Lf %9s]" " -- " US_COLOR_RESET x_msg_color x_msg US_COLOR_RESET, \
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
@@ -116,9 +116,9 @@ extern pthread_mutex_t us_log_mutex;
}
#define US_LOG_PERROR(x_msg, ...) { \
char m_perror_buf[1024] = {0}; \
char *m_perror_ptr = us_errno_to_string(errno, m_perror_buf, 1024); \
US_LOG_ERROR(x_msg ": %s", ##__VA_ARGS__, m_perror_ptr); \
char *const m_perror_str = us_errno_to_string(errno); \
US_LOG_ERROR(x_msg ": %s", ##__VA_ARGS__, m_perror_str); \
free(m_perror_str); \
}
#define US_LOG_INFO(x_msg, ...) { \
@@ -130,33 +130,33 @@ extern pthread_mutex_t us_log_mutex;
}
#define US_LOG_PERF(x_msg, ...) { \
if (us_log_level >= US_LOG_LEVEL_PERF) { \
if (us_g_log_level >= US_LOG_LEVEL_PERF) { \
US_LOG_PRINTF(US_COLOR_CYAN, "PERF ", US_COLOR_CYAN, x_msg, ##__VA_ARGS__); \
} \
}
#define US_LOG_PERF_FPS(x_msg, ...) { \
if (us_log_level >= US_LOG_LEVEL_PERF) { \
if (us_g_log_level >= US_LOG_LEVEL_PERF) { \
US_LOG_PRINTF(US_COLOR_YELLOW, "PERF ", US_COLOR_YELLOW, x_msg, ##__VA_ARGS__); \
} \
}
#define US_LOG_VERBOSE(x_msg, ...) { \
if (us_log_level >= US_LOG_LEVEL_VERBOSE) { \
if (us_g_log_level >= US_LOG_LEVEL_VERBOSE) { \
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg, ##__VA_ARGS__); \
} \
}
#define US_LOG_VERBOSE_PERROR(x_msg, ...) { \
if (us_log_level >= US_LOG_LEVEL_VERBOSE) { \
char m_perror_buf[1024] = {0}; \
char *m_perror_ptr = us_errno_to_string(errno, m_perror_buf, 1023); \
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg ": %s", ##__VA_ARGS__, m_perror_ptr); \
if (us_g_log_level >= US_LOG_LEVEL_VERBOSE) { \
char *m_perror_str = us_errno_to_string(errno); \
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg ": %s", ##__VA_ARGS__, m_perror_str); \
free(m_perror_str); \
} \
}
#define US_LOG_DEBUG(x_msg, ...) { \
if (us_log_level >= US_LOG_LEVEL_DEBUG) { \
if (us_g_log_level >= US_LOG_LEVEL_DEBUG) { \
US_LOG_PRINTF(US_COLOR_GRAY, "DEBUG", US_COLOR_GRAY, x_msg, ##__VA_ARGS__); \
} \
}

View File

@@ -67,7 +67,8 @@
#define US_COND_INIT(x_cond) assert(!pthread_cond_init(&(x_cond), NULL))
#define US_COND_DESTROY(x_cond) assert(!pthread_cond_destroy(&(x_cond)))
#define US_COND_SIGNAL(x_cond) assert(!pthread_cond_signal(&(x_cond)))
#define US_COND_WAIT_TRUE(x_var, x_cond, x_mutex) { while(!(x_var)) assert(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
#define US_COND_BROADCAST(x_cond) assert(!pthread_cond_broadcast(&(x_cond)))
#define US_COND_WAIT_FOR(x_var, x_cond, x_mutex) { while(!(x_var)) assert(!pthread_cond_wait(&(x_cond), &(x_mutex))); }
#ifdef WITH_PTHREAD_NP

View File

@@ -38,6 +38,12 @@
#include <sys/types.h>
#include <sys/file.h>
#if defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
# define HAS_SIGABBREV_NP
#else
# include <signal.h>
#endif
#ifdef NDEBUG
# error WTF dude? Asserts are good things!
@@ -181,15 +187,34 @@ INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
return retval;
}
INLINE char *us_errno_to_string(int error, char *buf, size_t size) {
assert(buf != NULL);
assert(size > 0);
INLINE char *us_errno_to_string(int error) {
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
const char *str = "!!! newlocale() error !!!";
strncpy(buf, (locale ? strerror_l(error, locale) : str), size - 1);
buf[size - 1] = '\0';
char *buf;
if (locale) {
buf = us_strdup(strerror_l(error, locale));
freelocale(locale);
} else {
buf = us_strdup("!!! newlocale() error !!!");
}
return buf;
}
INLINE char *us_signum_to_string(int signum) {
# ifdef HAS_SIGABBREV_NP
const char *const name = sigabbrev_np(signum);
# else
const char *const name = (
signum == SIGTERM ? "TERM" :
signum == SIGINT ? "INT" :
signum == SIGPIPE ? "PIPE" :
NULL
);
# endif
char *buf;
if (name != NULL) {
US_ASPRINTF(buf, "SIG%s", name);
} else {
US_ASPRINTF(buf, "SIG[%d]", signum);
}
return buf;
}

View File

@@ -23,7 +23,7 @@
#include "gpio.h"
us_gpio_s us_gpio = {
us_gpio_s us_g_gpio = {
.path = "/dev/gpiochip0",
.consumer_prefix = "ustreamer",
@@ -51,42 +51,42 @@ static void _gpio_output_destroy(us_gpio_output_s *output);
void us_gpio_init(void) {
assert(us_gpio.chip == NULL);
assert(us_g_gpio.chip == NULL);
if (
us_gpio.prog_running.pin >= 0
|| us_gpio.stream_online.pin >= 0
|| us_gpio.has_http_clients.pin >= 0
us_g_gpio.prog_running.pin >= 0
|| us_g_gpio.stream_online.pin >= 0
|| us_g_gpio.has_http_clients.pin >= 0
) {
US_MUTEX_INIT(us_gpio.mutex);
US_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);
US_MUTEX_INIT(us_g_gpio.mutex);
US_LOG_INFO("GPIO: Using chip device: %s", us_g_gpio.path);
if ((us_g_gpio.chip = gpiod_chip_open(us_g_gpio.path)) != NULL) {
_gpio_output_init(&us_g_gpio.prog_running);
_gpio_output_init(&us_g_gpio.stream_online);
_gpio_output_init(&us_g_gpio.has_http_clients);
} else {
US_LOG_PERROR("GPIO: Can't initialize chip device %s", us_gpio.path);
US_LOG_PERROR("GPIO: Can't initialize chip device %s", us_g_gpio.path);
}
}
}
void us_gpio_destroy(void) {
_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 != NULL) {
gpiod_chip_close(us_gpio.chip);
us_gpio.chip = NULL;
US_MUTEX_DESTROY(us_gpio.mutex);
_gpio_output_destroy(&us_g_gpio.prog_running);
_gpio_output_destroy(&us_g_gpio.stream_online);
_gpio_output_destroy(&us_g_gpio.has_http_clients);
if (us_g_gpio.chip != NULL) {
gpiod_chip_close(us_g_gpio.chip);
us_g_gpio.chip = NULL;
US_MUTEX_DESTROY(us_g_gpio.mutex);
}
}
int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
int retval = 0;
assert(us_gpio.chip != NULL);
assert(us_g_gpio.chip != NULL);
assert(output->line != NULL);
assert(output->state != state); // Must be checked in macro for the performance
US_MUTEX_LOCK(us_gpio.mutex);
US_MUTEX_LOCK(us_g_gpio.mutex);
if (gpiod_line_set_value(output->line, (int)state) < 0) { \
US_LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
@@ -94,18 +94,18 @@ int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
retval = -1;
}
US_MUTEX_UNLOCK(us_gpio.mutex);
US_MUTEX_UNLOCK(us_g_gpio.mutex);
return retval;
}
static void _gpio_output_init(us_gpio_output_s *output) {
assert(us_gpio.chip != NULL);
assert(us_g_gpio.chip != NULL);
assert(output->line == NULL);
US_ASPRINTF(output->consumer, "%s::%s", us_gpio.consumer_prefix, output->role);
US_ASPRINTF(output->consumer, "%s::%s", us_g_gpio.consumer_prefix, output->role);
if (output->pin >= 0) {
if ((output->line = gpiod_chip_get_line(us_gpio.chip, output->pin)) != NULL) {
if ((output->line = gpiod_chip_get_line(us_g_gpio.chip, output->pin)) != NULL) {
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) {
US_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 {
} us_gpio_s;
extern us_gpio_s us_gpio;
extern us_gpio_s us_g_gpio;
void us_gpio_init(void);
@@ -73,15 +73,15 @@ int us_gpio_inner_set(us_gpio_output_s *output, bool state);
}
INLINE void us_gpio_set_prog_running(bool state) {
SET_STATE(us_gpio.prog_running, state);
SET_STATE(us_g_gpio.prog_running, state);
}
INLINE void us_gpio_set_stream_online(bool state) {
SET_STATE(us_gpio.stream_online, state);
SET_STATE(us_g_gpio.stream_online, state);
}
INLINE void us_gpio_set_has_http_clients(bool state) {
SET_STATE(us_gpio.has_http_clients, state);
SET_STATE(us_g_gpio.has_http_clients, state);
}
#undef SET_STATE

View File

@@ -28,11 +28,11 @@ char *us_bufferevent_format_reason(short what) {
US_CALLOC(reason, 2048);
// evutil_socket_error_to_string() is not thread-safe
char perror_buf[1024] = {0};
const char *perror_ptr = us_errno_to_string(EVUTIL_SOCKET_ERROR(), perror_buf, 1024);
char *const perror_str = us_errno_to_string(EVUTIL_SOCKET_ERROR());
bool first = true;
strcat(reason, perror_ptr);
strcat(reason, perror_str);
free(perror_str);
strcat(reason, " (");
# define FILL_REASON(x_bev, x_name) { \

View File

@@ -75,6 +75,7 @@ us_server_s *us_server_init(us_stream_s *stream) {
server->passwd = "";
server->static_path = "";
server->allow_origin = "";
server->instance_id = "";
server->timeout = 10;
server->run = run;
@@ -400,7 +401,9 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
_A_EVBUFFER_ADD_PRINTF(buf,
"{\"ok\": true, \"result\": {"
" \"instance_id\": \"%s\","
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},",
server->instance_id,
us_encoder_type_to_string(enc_type),
enc_quality
);

View File

@@ -149,6 +149,7 @@ typedef struct us_server_sx {
char *passwd;
char *static_path;
char *allow_origin;
char *instance_id;
unsigned drop_same_frames;
unsigned fake_width;

View File

@@ -40,12 +40,9 @@
#endif
typedef struct {
us_stream_s *stream;
us_server_s *server;
} _main_context_s;
static us_stream_s *_g_stream = NULL;
static us_server_s *_g_server = NULL;
static _main_context_s *_ctx;
static void _block_thread_signals(void) {
sigset_t mask;
@@ -58,25 +55,23 @@ static void _block_thread_signals(void) {
static void *_stream_loop_thread(UNUSED void *arg) {
US_THREAD_RENAME("stream");
_block_thread_signals();
us_stream_loop(_ctx->stream);
us_stream_loop(_g_stream);
return NULL;
}
static void *_server_loop_thread(UNUSED void *arg) {
US_THREAD_RENAME("http");
_block_thread_signals();
us_server_loop(_ctx->server);
us_server_loop(_g_server);
return NULL;
}
static void _signal_handler(int signum) {
switch (signum) {
case SIGTERM: US_LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
case SIGINT: US_LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
default: US_LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
}
us_stream_loop_break(_ctx->stream);
us_server_loop_break(_ctx->server);
char *const name = us_signum_to_string(signum);
US_LOG_INFO_NOLOCK("===== Stopping by %s =====", name);
free(name);
us_stream_loop_break(_g_stream);
us_server_loop_break(_g_server);
}
static void _install_signal_handlers(void) {
@@ -107,22 +102,17 @@ int main(int argc, char *argv[]) {
us_options_s *options = us_options_init(argc, argv);
us_device_s *dev = us_device_init();
us_encoder_s *enc = us_encoder_init();
us_stream_s *stream = us_stream_init(dev, enc);
us_server_s *server = us_server_init(stream);
_g_stream = us_stream_init(dev, enc);
_g_server = us_server_init(_g_stream);
if ((exit_code = options_parse(options, dev, enc, stream, server)) == 0) {
if ((exit_code = options_parse(options, dev, enc, _g_stream, _g_server)) == 0) {
# ifdef WITH_GPIO
us_gpio_init();
# endif
_install_signal_handlers();
_main_context_s ctx;
ctx.stream = stream;
ctx.server = server;
_ctx = &ctx;
if ((exit_code = us_server_listen(server)) == 0) {
if ((exit_code = us_server_listen(_g_server)) == 0) {
# ifdef WITH_GPIO
us_gpio_set_prog_running(true);
# endif
@@ -141,8 +131,8 @@ int main(int argc, char *argv[]) {
# endif
}
us_server_destroy(server);
us_stream_destroy(stream);
us_server_destroy(_g_server);
us_stream_destroy(_g_stream);
us_encoder_destroy(enc);
us_device_destroy(dev);
us_options_destroy(options);

View File

@@ -82,6 +82,7 @@ enum _US_OPT_VALUES {
_O_PASSWD,
_O_STATIC,
_O_ALLOW_ORIGIN,
_O_INSTANCE_ID,
_O_TCP_NODELAY,
_O_SERVER_TIMEOUT,
@@ -177,6 +178,7 @@ static const struct option _LONG_OPTS[] = {
{"static", required_argument, NULL, _O_STATIC},
{"drop-same-frames", required_argument, NULL, _O_DROP_SAME_FRAMES},
{"allow-origin", required_argument, NULL, _O_ALLOW_ORIGIN},
{"instance-id", required_argument, NULL, _O_INSTANCE_ID},
{"fake-resolution", required_argument, NULL, _O_FAKE_RESOLUTION},
{"tcp-nodelay", no_argument, NULL, _O_TCP_NODELAY},
{"server-timeout", required_argument, NULL, _O_SERVER_TIMEOUT},
@@ -228,6 +230,7 @@ static const struct option _LONG_OPTS[] = {
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited);
static int _check_instance_id(const char *str);
static void _features(void);
static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
@@ -419,6 +422,13 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, US_VIDEO_MAX_FPS, 0);
case _O_FAKE_RESOLUTION: OPT_RESOLUTION("--fake-resolution", server->fake_width, server->fake_height, false);
case _O_ALLOW_ORIGIN: OPT_SET(server->allow_origin, optarg);
case _O_INSTANCE_ID:
if (_check_instance_id(optarg) != 0) {
printf("Invalid instance ID, it should be like: ^[a-zA-Z0-9\\./+_-]*$\n");
return -1;
}
server->instance_id = optarg;
break;
case _O_TCP_NODELAY: OPT_SET(server->tcp_nodelay, true);
case _O_SERVER_TIMEOUT: OPT_NUMBER("--server-timeout", server->timeout, 1, 60, 0);
@@ -437,11 +447,11 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
# undef ADD_SINK
# ifdef WITH_GPIO
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);
case _O_GPIO_DEVICE: OPT_SET(us_g_gpio.path, optarg);
case _O_GPIO_CONSUMER_PREFIX: OPT_SET(us_g_gpio.consumer_prefix, optarg);
case _O_GPIO_PROG_RUNNING: OPT_NUMBER("--gpio-prog-running", us_g_gpio.prog_running.pin, 0, 256, 0);
case _O_GPIO_STREAM_ONLINE: OPT_NUMBER("--gpio-stream-online", us_g_gpio.stream_online.pin, 0, 256, 0);
case _O_GPIO_HAS_HTTP_CLIENTS: OPT_NUMBER("--gpio-has-http-clients", us_g_gpio.has_http_clients.pin, 0, 256, 0);
# endif
# ifdef HAS_PDEATHSIG
@@ -457,12 +467,12 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
# endif
case _O_NOTIFY_PARENT: OPT_SET(server->notify_parent, true);
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_log_level, US_LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_log_level, US_LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(us_log_level, US_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_LOG_LEVEL: OPT_NUMBER("--log-level", us_g_log_level, US_LOG_LEVEL_INFO, US_LOG_LEVEL_DEBUG, 0);
case _O_PERF: OPT_SET(us_g_log_level, US_LOG_LEVEL_PERF);
case _O_VERBOSE: OPT_SET(us_g_log_level, US_LOG_LEVEL_VERBOSE);
case _O_DEBUG: OPT_SET(us_g_log_level, US_LOG_LEVEL_DEBUG);
case _O_FORCE_LOG_COLORS: OPT_SET(us_g_log_colored, true);
case _O_NO_LOG_COLORS: OPT_SET(us_g_log_colored, false);
case _O_HELP: _help(stdout, dev, enc, stream, server); return 1;
case _O_VERSION: puts(US_VERSION); return 1;
@@ -473,6 +483,8 @@ int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us
}
}
US_LOG_INFO("Starting PiKVM uStreamer %s ...", US_VERSION);
options->blank = us_blank_frame_init(blank_path);
stream->blank = options->blank;
@@ -530,6 +542,18 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
return 0;
}
static int _check_instance_id(const char *str) {
for (const char *ptr = str; *ptr; ++ptr) {
if (!(isascii(*ptr) && (
isalpha(*ptr) || isdigit(*ptr)
|| *ptr == '.' || *ptr == '/' || *ptr == '+' || *ptr == '_' || *ptr == '-'
))) {
return -1;
}
}
return 0;
}
static void _features(void) {
# ifdef WITH_GPIO
puts("+ WITH_GPIO");
@@ -657,6 +681,8 @@ static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *st
SAY(" --tcp-nodelay ────────────── Set TCP_NODELAY flag to the client /stream socket. Only for TCP socket.");
SAY(" Default: disabled.\n");
SAY(" --allow-origin <str> ─────── Set Access-Control-Allow-Origin header. Default: disabled.\n");
SAY(" --instance-id <str> ──────── A short string identifier to be displayed in the /state handle.");
SAY(" It must satisfy regexp ^[a-zA-Z0-9\\./+_-]*$. Default: an empty string.\n");
SAY(" --server-timeout <sec> ───── Timeout for client connections. Default: %u.\n", server->timeout);
# define ADD_SINK(x_name, x_opt) \
SAY(x_name " sink options:"); \
@@ -676,8 +702,8 @@ static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *st
# ifdef WITH_GPIO
SAY("GPIO options:");
SAY("═════════════");
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-device </dev/path> ───── Path to GPIO character device. Default: %s.\n", us_g_gpio.path);
SAY(" --gpio-consumer-prefix <str> ── Consumer prefix for GPIO outputs. Default: %s.\n", us_g_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");
@@ -702,7 +728,7 @@ static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *st
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
SAY(" Enabling debugging messages can slow down the program.");
SAY(" Available levels: 0 (info), 1 (performance), 2 (verbose), 3 (debug).");
SAY(" Default: %d.\n", us_log_level);
SAY(" Default: %d.\n", us_g_log_level);
SAY(" --perf ───────────── Enable performance messages (same as --log-level=1). Default: disabled.\n");
SAY(" --verbose ────────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n");
SAY(" --debug ──────────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n");

View File

@@ -104,7 +104,7 @@ us_worker_s *us_workers_pool_wait(us_workers_pool_s *pool) {
us_worker_s *ready_wr = NULL;
US_MUTEX_LOCK(pool->free_workers_mutex);
US_COND_WAIT_TRUE(pool->free_workers, pool->free_workers_cond, pool->free_workers_mutex);
US_COND_WAIT_FOR(pool->free_workers, pool->free_workers_cond, pool->free_workers_mutex);
US_MUTEX_UNLOCK(pool->free_workers_mutex);
if (pool->oldest_wr && !atomic_load(&pool->oldest_wr->has_job)) {
@@ -185,7 +185,7 @@ static void *_worker_thread(void *v_worker) {
US_LOG_DEBUG("Worker %s waiting for a new job ...", wr->name);
US_MUTEX_LOCK(wr->has_job_mutex);
US_COND_WAIT_TRUE(atomic_load(&wr->has_job), wr->has_job_cond, wr->has_job_mutex);
US_COND_WAIT_FOR(atomic_load(&wr->has_job), wr->has_job_cond, wr->has_job_mutex);
US_MUTEX_UNLOCK(wr->has_job_mutex);
if (!atomic_load(&wr->pool->stop)) {

View File

@@ -26,13 +26,11 @@ import os
import io
import struct
from typing import Tuple
import common
# =====
def _get_jpeg_size(data: bytes) -> Tuple[int, int]:
def _get_jpeg_size(data: bytes) -> tuple[int, int]:
# https://sheep.horse/2013/9/finding_the_dimensions_of_a_jpeg_file_in_python.html
stream = io.BytesIO(data)