mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 16:26:30 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfa1516491 | ||
|
|
36c9ff22b3 | ||
|
|
17f54a7977 | ||
|
|
574986d0fd | ||
|
|
d5ce2e835f | ||
|
|
6201554ba1 | ||
|
|
75dee4e91d | ||
|
|
e1b4e0db66 | ||
|
|
0d37b09bb4 | ||
|
|
18767b68ff | ||
|
|
7975615c6c | ||
|
|
90f09b197e | ||
|
|
687e97d523 | ||
|
|
c1675001fa | ||
|
|
69cc45a2a0 | ||
|
|
ece96b5834 | ||
|
|
383ed7530b | ||
|
|
7f620c758f | ||
|
|
fb50eea526 | ||
|
|
63f757a9da | ||
|
|
4ec02d46b9 | ||
|
|
527afb66df | ||
|
|
5502758a7e | ||
|
|
faa1776407 | ||
|
|
3d7fb8c8dd | ||
|
|
b5f814d71e | ||
|
|
6eafd4156a | ||
|
|
df1e4eaa06 | ||
|
|
d2bef81b03 | ||
|
|
7b3dffd072 | ||
|
|
1a6e9998fb | ||
|
|
9ab9561803 | ||
|
|
11f0b80228 | ||
|
|
85e2dbd69e | ||
|
|
b693c24411 | ||
|
|
54af47fc43 | ||
|
|
1c1e3b0875 | ||
|
|
2c9334d53f | ||
|
|
5c747a5b5d | ||
|
|
cbee3adb2e | ||
|
|
e3293d6887 | ||
|
|
9d1a42631e | ||
|
|
13f522e81d | ||
|
|
28f13f7514 | ||
|
|
2f86f818cc | ||
|
|
1ffcd83993 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 5.14
|
||||
current_version = 5.24
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
@@ -7,3 +7,4 @@
|
||||
!python/**
|
||||
!janus/**
|
||||
!man/**
|
||||
!pkg/docker/entry.sh
|
||||
|
||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,4 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: pikvm
|
||||
#custom: https://www.paypal.me/mdevaev
|
||||
custom: https://paypal.me/pikvm
|
||||
|
||||
81
.github/workflows/docker-alpine-image.yaml
vendored
Normal file
81
.github/workflows/docker-alpine-image.yaml
vendored
Normal 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
|
||||
29
README.md
29
README.md
@@ -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:
|
||||
|
||||
@@ -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```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
|
||||
|
||||
|
||||
@@ -23,29 +23,29 @@
|
||||
#include "audio.h"
|
||||
|
||||
|
||||
#define JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
|
||||
#define JLOG_PERROR_RES(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
|
||||
#define JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))
|
||||
#define _JLOG_PERROR_ALSA(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, snd_strerror(_err))
|
||||
#define _JLOG_PERROR_RES(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, speex_resampler_strerror(_err))
|
||||
#define _JLOG_PERROR_OPUS(_err, _prefix, _msg, ...) US_JLOG_ERROR(_prefix, _msg ": %s", ##__VA_ARGS__, opus_strerror(_err))
|
||||
|
||||
// A number of frames per 1 channel:
|
||||
// - https://github.com/xiph/opus/blob/7b05f44/src/opus_demo.c#L368
|
||||
#define HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
|
||||
#define HZ_TO_BUF16(_hz) (HZ_TO_FRAMES(_hz) * 2) // One stereo frame = (16bit L) + (16bit R)
|
||||
#define HZ_TO_BUF8(_hz) (HZ_TO_BUF16(_hz) * sizeof(int16_t))
|
||||
#define _HZ_TO_FRAMES(_hz) (6 * (_hz) / 50) // 120ms
|
||||
#define _HZ_TO_BUF16(_hz) (_HZ_TO_FRAMES(_hz) * 2) // One stereo frame = (16bit L) + (16bit R)
|
||||
#define _HZ_TO_BUF8(_hz) (_HZ_TO_BUF16(_hz) * sizeof(int16_t))
|
||||
|
||||
#define MIN_PCM_HZ 8000
|
||||
#define MAX_PCM_HZ 192000
|
||||
#define MAX_BUF16 HZ_TO_BUF16(MAX_PCM_HZ)
|
||||
#define MAX_BUF8 HZ_TO_BUF8(MAX_PCM_HZ)
|
||||
#define ENCODER_INPUT_HZ 48000
|
||||
#define _MIN_PCM_HZ 8000
|
||||
#define _MAX_PCM_HZ 192000
|
||||
#define _MAX_BUF16 _HZ_TO_BUF16(_MAX_PCM_HZ)
|
||||
#define _MAX_BUF8 _HZ_TO_BUF8(_MAX_PCM_HZ)
|
||||
#define _ENCODER_INPUT_HZ 48000
|
||||
|
||||
|
||||
typedef struct {
|
||||
int16_t data[MAX_BUF16];
|
||||
int16_t data[_MAX_BUF16];
|
||||
} _pcm_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[MAX_BUF8]; // Worst case
|
||||
uint8_t data[_MAX_BUF8]; // Worst case
|
||||
size_t used;
|
||||
uint64_t pts;
|
||||
} _enc_buffer_s;
|
||||
@@ -55,12 +55,12 @@ static void *_pcm_thread(void *v_audio);
|
||||
static void *_encoder_thread(void *v_audio);
|
||||
|
||||
|
||||
audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
audio_s *audio;
|
||||
A_CALLOC(audio, 1);
|
||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
|
||||
us_audio_s *audio;
|
||||
US_CALLOC(audio, 1);
|
||||
audio->pcm_hz = pcm_hz;
|
||||
audio->pcm_queue = queue_init(8);
|
||||
audio->enc_queue = queue_init(8);
|
||||
audio->pcm_queue = us_queue_init(8);
|
||||
audio->enc_queue = us_queue_init(8);
|
||||
atomic_init(&audio->stop, false);
|
||||
|
||||
int err;
|
||||
@@ -68,14 +68,14 @@ audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
{
|
||||
if ((err = snd_pcm_open(&audio->pcm, name, SND_PCM_STREAM_CAPTURE, 0)) < 0) {
|
||||
audio->pcm = NULL;
|
||||
JLOG_PERROR_ALSA(err, "audio", "Can't open PCM capture");
|
||||
_JLOG_PERROR_ALSA(err, "audio", "Can't open PCM capture");
|
||||
goto error;
|
||||
}
|
||||
assert(!snd_pcm_hw_params_malloc(&audio->pcm_params));
|
||||
|
||||
# define SET_PARAM(_msg, _func, ...) { \
|
||||
if ((err = _func(audio->pcm, audio->pcm_params, ##__VA_ARGS__)) < 0) { \
|
||||
JLOG_PERROR_ALSA(err, "audio", _msg); \
|
||||
_JLOG_PERROR_ALSA(err, "audio", _msg); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
@@ -85,30 +85,30 @@ audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
SET_PARAM("Can't set PCM channels numbre", snd_pcm_hw_params_set_channels, 2);
|
||||
SET_PARAM("Can't set PCM sampling format", snd_pcm_hw_params_set_format, SND_PCM_FORMAT_S16_LE);
|
||||
SET_PARAM("Can't set PCM sampling rate", snd_pcm_hw_params_set_rate_near, &audio->pcm_hz, 0);
|
||||
if (audio->pcm_hz < MIN_PCM_HZ || audio->pcm_hz > MAX_PCM_HZ) {
|
||||
JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
|
||||
audio->pcm_hz, MIN_PCM_HZ, MAX_PCM_HZ);
|
||||
if (audio->pcm_hz < _MIN_PCM_HZ || audio->pcm_hz > _MAX_PCM_HZ) {
|
||||
US_JLOG_ERROR("audio", "Unsupported PCM freq: %u; should be: %u <= F <= %u",
|
||||
audio->pcm_hz, _MIN_PCM_HZ, _MAX_PCM_HZ);
|
||||
goto error;
|
||||
}
|
||||
audio->pcm_frames = HZ_TO_FRAMES(audio->pcm_hz);
|
||||
audio->pcm_size = HZ_TO_BUF8(audio->pcm_hz);
|
||||
audio->pcm_frames = _HZ_TO_FRAMES(audio->pcm_hz);
|
||||
audio->pcm_size = _HZ_TO_BUF8(audio->pcm_hz);
|
||||
SET_PARAM("Can't apply PCM params", snd_pcm_hw_params);
|
||||
|
||||
# undef SET_PARAM
|
||||
}
|
||||
|
||||
if (audio->pcm_hz != ENCODER_INPUT_HZ) {
|
||||
audio->res = speex_resampler_init(2, audio->pcm_hz, ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
|
||||
if (audio->pcm_hz != _ENCODER_INPUT_HZ) {
|
||||
audio->res = speex_resampler_init(2, audio->pcm_hz, _ENCODER_INPUT_HZ, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
|
||||
if (err < 0) {
|
||||
audio->res = NULL;
|
||||
JLOG_PERROR_RES(err, "audio", "Can't create resampler");
|
||||
_JLOG_PERROR_RES(err, "audio", "Can't create resampler");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY
|
||||
audio->enc = opus_encoder_create(ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err);
|
||||
audio->enc = opus_encoder_create(_ENCODER_INPUT_HZ, 2, OPUS_APPLICATION_AUDIO, &err);
|
||||
assert(err == 0);
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_BITRATE(48000)));
|
||||
assert(!opus_encoder_ctl(audio->enc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)));
|
||||
@@ -116,50 +116,42 @@ audio_s *audio_init(const char *name, unsigned pcm_hz) {
|
||||
// OPUS_SET_INBAND_FEC(1), OPUS_SET_PACKET_LOSS_PERC(10): see rtpa.c
|
||||
}
|
||||
|
||||
JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz);
|
||||
US_JLOG_INFO("audio", "Pipeline configured on %uHz; capturing ...", audio->pcm_hz);
|
||||
audio->tids_created = true;
|
||||
A_THREAD_CREATE(&audio->enc_tid, _encoder_thread, audio);
|
||||
A_THREAD_CREATE(&audio->pcm_tid, _pcm_thread, audio);
|
||||
US_THREAD_CREATE(audio->enc_tid, _encoder_thread, audio);
|
||||
US_THREAD_CREATE(audio->pcm_tid, _pcm_thread, audio);
|
||||
|
||||
return audio;
|
||||
|
||||
error:
|
||||
audio_destroy(audio);
|
||||
us_audio_destroy(audio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void audio_destroy(audio_s *audio) {
|
||||
void us_audio_destroy(us_audio_s *audio) {
|
||||
if (audio->tids_created) {
|
||||
atomic_store(&audio->stop, true);
|
||||
A_THREAD_JOIN(audio->pcm_tid);
|
||||
A_THREAD_JOIN(audio->enc_tid);
|
||||
US_THREAD_JOIN(audio->pcm_tid);
|
||||
US_THREAD_JOIN(audio->enc_tid);
|
||||
}
|
||||
if (audio->enc) {
|
||||
opus_encoder_destroy(audio->enc);
|
||||
}
|
||||
if (audio->res) {
|
||||
speex_resampler_destroy(audio->res);
|
||||
}
|
||||
if (audio->pcm) {
|
||||
snd_pcm_close(audio->pcm);
|
||||
}
|
||||
if (audio->pcm_params) {
|
||||
snd_pcm_hw_params_free(audio->pcm_params);
|
||||
}
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(audio->enc_queue, free);
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(audio->pcm_queue, free);
|
||||
US_DELETE(audio->enc, opus_encoder_destroy);
|
||||
US_DELETE(audio->res, speex_resampler_destroy);
|
||||
US_DELETE(audio->pcm, snd_pcm_close);
|
||||
US_DELETE(audio->pcm_params, snd_pcm_hw_params_free);
|
||||
US_QUEUE_DELETE_WITH_ITEMS(audio->enc_queue, free);
|
||||
US_QUEUE_DELETE_WITH_ITEMS(audio->pcm_queue, free);
|
||||
if (audio->tids_created) {
|
||||
JLOG_INFO("audio", "Pipeline closed");
|
||||
US_JLOG_INFO("audio", "Pipeline closed");
|
||||
}
|
||||
free(audio);
|
||||
}
|
||||
|
||||
int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
|
||||
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
|
||||
if (atomic_load(&audio->stop)) {
|
||||
return -1;
|
||||
}
|
||||
_enc_buffer_s *buf;
|
||||
if (!queue_get(audio->enc_queue, (void **)&buf, 0.1)) {
|
||||
if (!us_queue_get(audio->enc_queue, (void **)&buf, 0.1)) {
|
||||
if (*size < buf->used) {
|
||||
free(buf);
|
||||
return -3;
|
||||
@@ -174,28 +166,28 @@ int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts
|
||||
}
|
||||
|
||||
static void *_pcm_thread(void *v_audio) {
|
||||
A_THREAD_RENAME("us_a_pcm");
|
||||
US_THREAD_RENAME("us_a_pcm");
|
||||
|
||||
audio_s *audio = (audio_s *)v_audio;
|
||||
uint8_t in[MAX_BUF8];
|
||||
us_audio_s *const audio = (us_audio_s *)v_audio;
|
||||
uint8_t in[_MAX_BUF8];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
|
||||
const int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
|
||||
if (frames < 0) {
|
||||
JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames");
|
||||
_JLOG_PERROR_ALSA(frames, "audio", "Fatal: Can't capture PCM frames");
|
||||
break;
|
||||
} else if (frames < (int)audio->pcm_frames) {
|
||||
JLOG_ERROR("audio", "Fatal: Too few PCM frames captured");
|
||||
US_JLOG_ERROR("audio", "Fatal: Too few PCM frames captured");
|
||||
break;
|
||||
}
|
||||
|
||||
if (queue_get_free(audio->pcm_queue)) {
|
||||
if (us_queue_get_free(audio->pcm_queue)) {
|
||||
_pcm_buffer_s *out;
|
||||
A_CALLOC(out, 1);
|
||||
US_CALLOC(out, 1);
|
||||
memcpy(out->data, in, audio->pcm_size);
|
||||
assert(!queue_put(audio->pcm_queue, out, 0));
|
||||
assert(!us_queue_put(audio->pcm_queue, out, 0));
|
||||
} else {
|
||||
JLOG_ERROR("audio", "PCM queue is full");
|
||||
US_JLOG_ERROR("audio", "PCM queue is full");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,42 +196,42 @@ static void *_pcm_thread(void *v_audio) {
|
||||
}
|
||||
|
||||
static void *_encoder_thread(void *v_audio) {
|
||||
A_THREAD_RENAME("us_a_enc");
|
||||
US_THREAD_RENAME("us_a_enc");
|
||||
|
||||
audio_s *audio = (audio_s *)v_audio;
|
||||
int16_t in_res[MAX_BUF16];
|
||||
us_audio_s *const audio = (us_audio_s *)v_audio;
|
||||
int16_t in_res[_MAX_BUF16];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
_pcm_buffer_s *in;
|
||||
if (!queue_get(audio->pcm_queue, (void **)&in, 0.1)) {
|
||||
if (!us_queue_get(audio->pcm_queue, (void **)&in, 0.1)) {
|
||||
int16_t *in_ptr;
|
||||
if (audio->res) {
|
||||
assert(audio->pcm_hz != ENCODER_INPUT_HZ);
|
||||
if (audio->res != NULL) {
|
||||
assert(audio->pcm_hz != _ENCODER_INPUT_HZ);
|
||||
uint32_t in_count = audio->pcm_frames;
|
||||
uint32_t out_count = HZ_TO_FRAMES(ENCODER_INPUT_HZ);
|
||||
uint32_t out_count = _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
|
||||
speex_resampler_process_interleaved_int(audio->res, in->data, &in_count, in_res, &out_count);
|
||||
in_ptr = in_res;
|
||||
} else {
|
||||
assert(audio->pcm_hz == ENCODER_INPUT_HZ);
|
||||
assert(audio->pcm_hz == _ENCODER_INPUT_HZ);
|
||||
in_ptr = in->data;
|
||||
}
|
||||
|
||||
_enc_buffer_s *out;
|
||||
A_CALLOC(out, 1);
|
||||
int size = opus_encode(audio->enc, in_ptr, HZ_TO_FRAMES(ENCODER_INPUT_HZ), out->data, ARRAY_LEN(out->data));
|
||||
US_CALLOC(out, 1);
|
||||
const int size = opus_encode(audio->enc, in_ptr, _HZ_TO_FRAMES(_ENCODER_INPUT_HZ), out->data, US_ARRAY_LEN(out->data));
|
||||
free(in);
|
||||
if (size < 0) {
|
||||
JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
|
||||
_JLOG_PERROR_OPUS(size, "audio", "Fatal: Can't encode PCM frame to OPUS");
|
||||
free(out);
|
||||
break;
|
||||
}
|
||||
out->used = size;
|
||||
out->pts = audio->pts;
|
||||
// https://datatracker.ietf.org/doc/html/rfc7587#section-4.2
|
||||
audio->pts += HZ_TO_FRAMES(ENCODER_INPUT_HZ);
|
||||
audio->pts += _HZ_TO_FRAMES(_ENCODER_INPUT_HZ);
|
||||
|
||||
if (queue_put(audio->enc_queue, out, 0) != 0) {
|
||||
JLOG_ERROR("audio", "OPUS encoder queue is full");
|
||||
if (us_queue_put(audio->enc_queue, out, 0) != 0) {
|
||||
US_JLOG_ERROR("audio", "OPUS encoder queue is full");
|
||||
free(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <opus/opus.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/array.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
#include "logging.h"
|
||||
@@ -51,18 +52,18 @@ typedef struct {
|
||||
SpeexResamplerState *res;
|
||||
OpusEncoder *enc;
|
||||
|
||||
queue_s *pcm_queue;
|
||||
queue_s *enc_queue;
|
||||
us_queue_s *pcm_queue;
|
||||
us_queue_s *enc_queue;
|
||||
uint32_t pts;
|
||||
|
||||
pthread_t pcm_tid;
|
||||
pthread_t enc_tid;
|
||||
bool tids_created;
|
||||
atomic_bool stop;
|
||||
} audio_s;
|
||||
} us_audio_s;
|
||||
|
||||
|
||||
audio_s *audio_init(const char *name, unsigned pcm_hz);
|
||||
void audio_destroy(audio_s *audio);
|
||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
|
||||
void us_audio_destroy(us_audio_s *audio);
|
||||
|
||||
int audio_get_encoded(audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
|
||||
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
|
||||
|
||||
@@ -28,53 +28,53 @@ static void *_audio_thread(void *v_client);
|
||||
static void *_common_thread(void *v_client, bool video);
|
||||
|
||||
|
||||
client_s *client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio) {
|
||||
client_s *client;
|
||||
A_CALLOC(client, 1);
|
||||
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio) {
|
||||
us_janus_client_s *client;
|
||||
US_CALLOC(client, 1);
|
||||
client->gw = gw;
|
||||
client->session = session;
|
||||
atomic_init(&client->transmit, true);
|
||||
|
||||
atomic_init(&client->stop, false);
|
||||
|
||||
client->video_queue = queue_init(1024);
|
||||
A_THREAD_CREATE(&client->video_tid, _video_thread, client);
|
||||
client->video_queue = us_queue_init(1024);
|
||||
US_THREAD_CREATE(client->video_tid, _video_thread, client);
|
||||
|
||||
if (has_audio) {
|
||||
client->audio_queue = queue_init(64);
|
||||
A_THREAD_CREATE(&client->audio_tid, _audio_thread, client);
|
||||
client->audio_queue = us_queue_init(64);
|
||||
US_THREAD_CREATE(client->audio_tid, _audio_thread, client);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
void client_destroy(client_s *client) {
|
||||
void us_janus_client_destroy(us_janus_client_s *client) {
|
||||
atomic_store(&client->stop, true);
|
||||
queue_put(client->video_queue, NULL, 0);
|
||||
us_queue_put(client->video_queue, NULL, 0);
|
||||
if (client->audio_queue != NULL) {
|
||||
queue_put(client->audio_queue, NULL, 0);
|
||||
us_queue_put(client->audio_queue, NULL, 0);
|
||||
}
|
||||
|
||||
A_THREAD_JOIN(client->video_tid);
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(client->video_queue, rtp_destroy);
|
||||
US_THREAD_JOIN(client->video_tid);
|
||||
US_QUEUE_DELETE_WITH_ITEMS(client->video_queue, us_rtp_destroy);
|
||||
if (client->audio_queue != NULL) {
|
||||
A_THREAD_JOIN(client->audio_tid);
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(client->audio_queue, rtp_destroy);
|
||||
US_THREAD_JOIN(client->audio_tid);
|
||||
US_QUEUE_DELETE_WITH_ITEMS(client->audio_queue, us_rtp_destroy);
|
||||
}
|
||||
free(client);
|
||||
}
|
||||
|
||||
void client_send(client_s *client, const rtp_s *rtp) {
|
||||
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp) {
|
||||
if (
|
||||
!atomic_load(&client->transmit)
|
||||
|| (!rtp->video && client->audio_queue == NULL)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
rtp_s *new = rtp_dup(rtp);
|
||||
if (queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
|
||||
JLOG_ERROR("client", "Session %p %s queue is full",
|
||||
us_rtp_s *const new = us_rtp_dup(rtp);
|
||||
if (us_queue_put((new->video ? client->video_queue : client->audio_queue), new, 0) != 0) {
|
||||
US_JLOG_ERROR("client", "Session %p %s queue is full",
|
||||
client->session, (new->video ? "video" : "audio"));
|
||||
rtp_destroy(new);
|
||||
us_rtp_destroy(new);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,13 +87,13 @@ static void *_audio_thread(void *v_client) {
|
||||
}
|
||||
|
||||
static void *_common_thread(void *v_client, bool video) {
|
||||
client_s *client = (client_s *)v_client;
|
||||
queue_s *queue = (video ? client->video_queue : client->audio_queue);
|
||||
us_janus_client_s *const client = (us_janus_client_s *)v_client;
|
||||
us_queue_s *const queue = (video ? client->video_queue : client->audio_queue);
|
||||
assert(queue != NULL); // Audio may be NULL
|
||||
|
||||
while (!atomic_load(&client->stop)) {
|
||||
rtp_s *rtp;
|
||||
if (!queue_get(queue, (void **)&rtp, 0.1)) {
|
||||
us_rtp_s *rtp;
|
||||
if (!us_queue_get(queue, (void **)&rtp, 0.1)) {
|
||||
if (rtp == NULL) {
|
||||
break;
|
||||
}
|
||||
@@ -103,13 +103,16 @@ static void *_common_thread(void *v_client, bool video) {
|
||||
packet.buffer = (char *)rtp->datagram;
|
||||
packet.length = rtp->used;
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
if (video) {
|
||||
// FIXME: Это очень эффективный способ уменьшить задержку, но WebRTC стек в хроме и фоксе
|
||||
// слишком корявый, чтобы обработать это, из-за чего на кейфреймах начинаются заикания.
|
||||
// - https://github.com/Glimesh/janus-ftl-plugin/issues/101
|
||||
if (rtp->zero_playout_delay) {
|
||||
packet.extensions.min_delay = 0;
|
||||
packet.extensions.max_delay = 0;
|
||||
}
|
||||
client->gw->relay_rtp(client->session, &packet);
|
||||
}
|
||||
rtp_destroy(rtp);
|
||||
us_rtp_destroy(rtp);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
typedef struct client_sx {
|
||||
typedef struct us_janus_client_sx {
|
||||
janus_callbacks *gw;
|
||||
janus_plugin_session *session;
|
||||
atomic_bool transmit;
|
||||
@@ -48,14 +48,14 @@ typedef struct client_sx {
|
||||
pthread_t audio_tid;
|
||||
atomic_bool stop;
|
||||
|
||||
queue_s *video_queue;
|
||||
queue_s *audio_queue;
|
||||
us_queue_s *video_queue;
|
||||
us_queue_s *audio_queue;
|
||||
|
||||
LIST_STRUCT(struct client_sx);
|
||||
} client_s;
|
||||
US_LIST_STRUCT(struct us_janus_client_sx);
|
||||
} us_janus_client_s;
|
||||
|
||||
|
||||
client_s *client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio);
|
||||
void client_destroy(client_s *client);
|
||||
us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_session *session, bool has_audio);
|
||||
void us_janus_client_destroy(us_janus_client_s *client);
|
||||
|
||||
void client_send(client_s *client, const rtp_s *rtp);
|
||||
void us_janus_client_send(us_janus_client_s *client, const us_rtp_s *rtp);
|
||||
|
||||
@@ -23,56 +23,77 @@
|
||||
#include "config.h"
|
||||
|
||||
|
||||
static char *_get_value(janus_config *config, const char *section, const char *option);
|
||||
static char *_get_value(janus_config *jcfg, const char *section, const char *option);
|
||||
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def);
|
||||
|
||||
|
||||
int read_config(const char *config_dir_path, char **video_sink_name, char **audio_dev_name, char **tc358743_dev_path) {
|
||||
int retval = 0;
|
||||
us_config_s *us_config_init(const char *config_dir_path) {
|
||||
us_config_s *config;
|
||||
US_CALLOC(config, 1);
|
||||
|
||||
char *config_file_path;
|
||||
janus_config *config = NULL;
|
||||
janus_config *jcfg = NULL;
|
||||
|
||||
A_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, PLUGIN_PACKAGE);
|
||||
JLOG_INFO("config", "Reading config file '%s' ...", config_file_path);
|
||||
US_ASPRINTF(config_file_path, "%s/%s.jcfg", config_dir_path, US_PLUGIN_PACKAGE);
|
||||
US_JLOG_INFO("config", "Reading config file '%s' ...", config_file_path);
|
||||
|
||||
config = janus_config_parse(config_file_path);
|
||||
if (config == NULL) {
|
||||
JLOG_ERROR("config", "Can't read config");
|
||||
jcfg = janus_config_parse(config_file_path);
|
||||
if (jcfg == NULL) {
|
||||
US_JLOG_ERROR("config", "Can't read config");
|
||||
goto error;
|
||||
}
|
||||
janus_config_print(config);
|
||||
janus_config_print(jcfg);
|
||||
|
||||
if (
|
||||
(*video_sink_name = _get_value(config, "memsink", "object")) == NULL
|
||||
&& (*video_sink_name = _get_value(config, "video", "sink")) == NULL
|
||||
(config->video_sink_name = _get_value(jcfg, "memsink", "object")) == NULL
|
||||
&& (config->video_sink_name = _get_value(jcfg, "video", "sink")) == NULL
|
||||
) {
|
||||
JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
|
||||
US_JLOG_ERROR("config", "Missing config value: video.sink (ex. memsink.object)");
|
||||
goto error;
|
||||
}
|
||||
if ((*audio_dev_name = _get_value(config, "audio", "device")) != NULL) {
|
||||
JLOG_INFO("config", "Enabled the experimental AUDIO feature");
|
||||
if ((*tc358743_dev_path = _get_value(config, "audio", "tc358743")) == NULL) {
|
||||
JLOG_INFO("config", "Missing config value: audio.tc358743");
|
||||
if ((config->video_zero_playout_delay = _get_bool(jcfg, "video", "zero_playout_delay", false)) == true) {
|
||||
US_JLOG_INFO("config", "Enabled the experimental Playout-Delay=0 RTP extension for VIDEO");
|
||||
}
|
||||
if ((config->audio_dev_name = _get_value(jcfg, "audio", "device")) != NULL) {
|
||||
US_JLOG_INFO("config", "Enabled the experimental AUDIO feature");
|
||||
if ((config->tc358743_dev_path = _get_value(jcfg, "audio", "tc358743")) == NULL) {
|
||||
US_JLOG_INFO("config", "Missing config value: audio.tc358743");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
goto ok;
|
||||
error:
|
||||
retval = -1;
|
||||
us_config_destroy(config);
|
||||
config = NULL;
|
||||
ok:
|
||||
if (config) {
|
||||
janus_config_destroy(config);
|
||||
}
|
||||
US_DELETE(jcfg, janus_config_destroy);
|
||||
free(config_file_path);
|
||||
return retval;
|
||||
return config;
|
||||
}
|
||||
|
||||
static char *_get_value(janus_config *config, const char *section, const char *option) {
|
||||
janus_config_category *section_obj = janus_config_get_create(config, NULL, janus_config_type_category, section);
|
||||
janus_config_item *option_obj = janus_config_get(config, section_obj, janus_config_type_item, option);
|
||||
void us_config_destroy(us_config_s *config) {
|
||||
US_DELETE(config->video_sink_name, free);
|
||||
US_DELETE(config->audio_dev_name, free);
|
||||
US_DELETE(config->tc358743_dev_path, free);
|
||||
free(config);
|
||||
}
|
||||
|
||||
static char *_get_value(janus_config *jcfg, const char *section, const char *option) {
|
||||
janus_config_category *section_obj = janus_config_get_create(jcfg, NULL, janus_config_type_category, section);
|
||||
janus_config_item *option_obj = janus_config_get(jcfg, section_obj, janus_config_type_item, option);
|
||||
if (option_obj == NULL || option_obj->value == NULL || option_obj->value[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
return strdup(option_obj->value);
|
||||
return us_strdup(option_obj->value);
|
||||
}
|
||||
|
||||
static bool _get_bool(janus_config *jcfg, const char *section, const char *option, bool def) {
|
||||
char *const tmp = _get_value(jcfg, section, option);
|
||||
bool value = def;
|
||||
if (tmp != NULL) {
|
||||
value = (!strcasecmp(tmp, "1") || !strcasecmp(tmp, "true") || !strcasecmp(tmp, "yes"));
|
||||
free(tmp);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -34,4 +34,14 @@
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
int read_config(const char *config_dir_path, char **video_sink_name, char **audio_dev_name, char **tc358743_dev_path);
|
||||
typedef struct {
|
||||
char *video_sink_name;
|
||||
bool video_zero_playout_delay;
|
||||
|
||||
char *audio_dev_name;
|
||||
char *tc358743_dev_path;
|
||||
} us_config_s;
|
||||
|
||||
|
||||
us_config_s *us_config_init(const char *config_dir_path);
|
||||
void us_config_destroy(us_config_s *config);
|
||||
|
||||
@@ -22,5 +22,5 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define PLUGIN_NAME "ustreamer"
|
||||
#define PLUGIN_PACKAGE "janus.plugin.ustreamer"
|
||||
#define US_PLUGIN_NAME "ustreamer"
|
||||
#define US_PLUGIN_PACKAGE "janus.plugin.ustreamer"
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
#include "const.h"
|
||||
|
||||
|
||||
#define JLOG_INFO(_prefix, _msg, ...) JANUS_LOG(LOG_INFO, "== %s/%-9s -- " _msg "\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_WARN(_prefix, _msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " _msg "\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__)
|
||||
#define JLOG_ERROR(_prefix, _msg, ...) JANUS_LOG(LOG_ERR, "== %s/%-9s -- " _msg "\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__)
|
||||
#define US_JLOG_INFO(x_prefix, x_msg, ...) JANUS_LOG(LOG_INFO, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
|
||||
#define US_JLOG_WARN(x_prefix, x_msg, ...) JANUS_LOG(LOG_WARN, "== %s/%-9s -- " x_msg "\n", US_PLUGIN_NAME, x_prefix, ##__VA_ARGS__)
|
||||
#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 JLOG_PERROR(_prefix, _msg, ...) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1023); \
|
||||
JANUS_LOG(LOG_ERR, "[%s/%-9s] " _msg ": %s\n", PLUGIN_NAME, _prefix, ##__VA_ARGS__, _perror_ptr); \
|
||||
#define US_JLOG_PERROR(x_prefix, x_msg, ...) { \
|
||||
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); \
|
||||
}
|
||||
|
||||
@@ -23,21 +23,21 @@
|
||||
#include "memsinkfd.h"
|
||||
|
||||
|
||||
int memsink_fd_wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id) {
|
||||
long double deadline_ts = get_now_monotonic() + 1; // wait_timeout
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id) {
|
||||
const long double deadline_ts = us_get_now_monotonic() + 1; // wait_timeout
|
||||
long double now;
|
||||
do {
|
||||
int result = flock_timedwait_monotonic(fd, 1); // lock_timeout
|
||||
now = get_now_monotonic();
|
||||
const int result = us_flock_timedwait_monotonic(fd, 1); // lock_timeout
|
||||
now = us_get_now_monotonic();
|
||||
if (result < 0 && errno != EWOULDBLOCK) {
|
||||
JLOG_PERROR("video", "Can't lock memsink");
|
||||
US_JLOG_PERROR("video", "Can't lock memsink");
|
||||
return -1;
|
||||
} else if (result == 0) {
|
||||
if (mem->magic == MEMSINK_MAGIC && mem->version == MEMSINK_VERSION && mem->id != last_id) {
|
||||
if (mem->magic == US_MEMSINK_MAGIC && mem->version == US_MEMSINK_VERSION && mem->id != last_id) {
|
||||
return 0;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
JLOG_PERROR("video", "Can't unlock memsink");
|
||||
US_JLOG_PERROR("video", "Can't unlock memsink");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -46,24 +46,24 @@ int memsink_fd_wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
frame_s *memsink_fd_get_frame(int fd, memsink_shared_s *mem, uint64_t *frame_id) {
|
||||
frame_s *frame = frame_init();
|
||||
frame_set_data(frame, mem->data, mem->used);
|
||||
FRAME_COPY_META(mem, frame);
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id) {
|
||||
us_frame_s *frame = us_frame_init();
|
||||
us_frame_set_data(frame, mem->data, mem->used);
|
||||
US_FRAME_COPY_META(mem, frame);
|
||||
*frame_id = mem->id;
|
||||
mem->last_client_ts = get_now_monotonic();
|
||||
mem->last_client_ts = us_get_now_monotonic();
|
||||
|
||||
bool ok = true;
|
||||
if (frame->format != V4L2_PIX_FMT_H264) {
|
||||
JLOG_ERROR("video", "Got non-H264 frame from memsink");
|
||||
US_JLOG_ERROR("video", "Got non-H264 frame from memsink");
|
||||
ok = false;
|
||||
}
|
||||
if (flock(fd, LOCK_UN) < 0) {
|
||||
JLOG_PERROR("video", "Can't unlock memsink");
|
||||
US_JLOG_PERROR("video", "Can't unlock memsink");
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) {
|
||||
frame_destroy(frame);
|
||||
us_frame_destroy(frame);
|
||||
frame = NULL;
|
||||
}
|
||||
return frame;
|
||||
|
||||
@@ -34,5 +34,5 @@
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
int memsink_fd_wait_frame(int fd, memsink_shared_s* mem, uint64_t last_id);
|
||||
frame_s *memsink_fd_get_frame(int fd, memsink_shared_s *mem, uint64_t *frame_id);
|
||||
int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s* mem, uint64_t last_id);
|
||||
us_frame_s *us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, uint64_t *frame_id);
|
||||
|
||||
@@ -55,17 +55,14 @@
|
||||
#include "config.h"
|
||||
|
||||
|
||||
static char *_g_video_sink_name = NULL;
|
||||
static char *_g_audio_dev_name = NULL;
|
||||
static char *_g_tc358743_dev_path = NULL;
|
||||
static us_config_s *_g_config = NULL;
|
||||
const useconds_t _g_watchers_polling = 100000;
|
||||
|
||||
const useconds_t _g_watchers_polling = 100000;
|
||||
|
||||
static client_s *_g_clients = NULL;
|
||||
static janus_callbacks *_g_gw = NULL;
|
||||
static queue_s *_g_video_queue = NULL;
|
||||
static rtpv_s *_g_rtpv = NULL;
|
||||
static rtpa_s *_g_rtpa = NULL;
|
||||
static us_janus_client_s *_g_clients = NULL;
|
||||
static janus_callbacks *_g_gw = NULL;
|
||||
static us_queue_s *_g_video_queue = NULL;
|
||||
static us_rtpv_s *_g_rtpv = NULL;
|
||||
static us_rtpa_s *_g_rtpa = NULL;
|
||||
|
||||
static pthread_t _g_video_rtp_tid;
|
||||
static atomic_bool _g_video_rtp_tid_created = false;
|
||||
@@ -81,84 +78,84 @@ static atomic_bool _g_stop = false;
|
||||
static atomic_bool _g_has_watchers = false;
|
||||
|
||||
|
||||
#define LOCK_VIDEO A_MUTEX_LOCK(&_g_video_lock)
|
||||
#define UNLOCK_VIDEO A_MUTEX_UNLOCK(&_g_video_lock)
|
||||
#define _LOCK_VIDEO US_MUTEX_LOCK(_g_video_lock)
|
||||
#define _UNLOCK_VIDEO US_MUTEX_UNLOCK(_g_video_lock)
|
||||
|
||||
#define LOCK_AUDIO A_MUTEX_LOCK(&_g_audio_lock)
|
||||
#define UNLOCK_AUDIO A_MUTEX_UNLOCK(&_g_audio_lock)
|
||||
#define _LOCK_AUDIO US_MUTEX_LOCK(_g_audio_lock)
|
||||
#define _UNLOCK_AUDIO US_MUTEX_UNLOCK(_g_audio_lock)
|
||||
|
||||
#define LOCK_ALL { LOCK_VIDEO; LOCK_AUDIO; }
|
||||
#define UNLOCK_ALL { UNLOCK_AUDIO; UNLOCK_VIDEO; }
|
||||
#define _LOCK_ALL { _LOCK_VIDEO; _LOCK_AUDIO; }
|
||||
#define _UNLOCK_ALL { _UNLOCK_AUDIO; _UNLOCK_VIDEO; }
|
||||
|
||||
#define READY atomic_load(&_g_ready)
|
||||
#define STOP atomic_load(&_g_stop)
|
||||
#define HAS_WATCHERS atomic_load(&_g_has_watchers)
|
||||
#define _READY atomic_load(&_g_ready)
|
||||
#define _STOP atomic_load(&_g_stop)
|
||||
#define _HAS_WATCHERS atomic_load(&_g_has_watchers)
|
||||
|
||||
|
||||
janus_plugin *create(void);
|
||||
|
||||
|
||||
#define IF_NOT_REPORTED(...) { \
|
||||
unsigned _error_code = __LINE__; \
|
||||
#define _IF_NOT_REPORTED(...) { \
|
||||
const unsigned _error_code = __LINE__; \
|
||||
if (error_reported != _error_code) { __VA_ARGS__; error_reported = _error_code; } \
|
||||
}
|
||||
|
||||
static void *_video_rtp_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_video_rtp");
|
||||
US_THREAD_RENAME("us_video_rtp");
|
||||
atomic_store(&_g_video_rtp_tid_created, true);
|
||||
|
||||
while (!STOP) {
|
||||
frame_s *frame;
|
||||
if (queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
|
||||
LOCK_VIDEO;
|
||||
rtpv_wrap(_g_rtpv, frame);
|
||||
UNLOCK_VIDEO;
|
||||
frame_destroy(frame);
|
||||
while (!_STOP) {
|
||||
us_frame_s *frame;
|
||||
if (us_queue_get(_g_video_queue, (void **)&frame, 0.1) == 0) {
|
||||
_LOCK_VIDEO;
|
||||
us_rtpv_wrap(_g_rtpv, frame);
|
||||
_UNLOCK_VIDEO;
|
||||
us_frame_destroy(frame);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_video_sink_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_video_sink");
|
||||
US_THREAD_RENAME("us_video_sink");
|
||||
atomic_store(&_g_video_sink_tid_created, true);
|
||||
|
||||
uint64_t frame_id = 0;
|
||||
unsigned error_reported = 0;
|
||||
|
||||
while (!STOP) {
|
||||
if (!HAS_WATCHERS) {
|
||||
IF_NOT_REPORTED({ JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
||||
while (!_STOP) {
|
||||
if (!_HAS_WATCHERS) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_INFO("video", "No active watchers, memsink disconnected"); });
|
||||
usleep(_g_watchers_polling);
|
||||
continue;
|
||||
}
|
||||
|
||||
int fd = -1;
|
||||
memsink_shared_s *mem = NULL;
|
||||
us_memsink_shared_s *mem = NULL;
|
||||
|
||||
if ((fd = shm_open(_g_video_sink_name, O_RDWR, 0)) <= 0) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Can't open memsink"); });
|
||||
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't open memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
if ((mem = memsink_shared_map(fd)) == NULL) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Can't map memsink"); });
|
||||
if ((mem = us_memsink_shared_map(fd)) == NULL) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Can't map memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
error_reported = 0;
|
||||
|
||||
JLOG_INFO("video", "Memsink opened; reading frames ...");
|
||||
while (!STOP && HAS_WATCHERS) {
|
||||
int result = memsink_fd_wait_frame(fd, mem, frame_id);
|
||||
US_JLOG_INFO("video", "Memsink opened; reading frames ...");
|
||||
while (!_STOP && _HAS_WATCHERS) {
|
||||
const int result = us_memsink_fd_wait_frame(fd, mem, frame_id);
|
||||
if (result == 0) {
|
||||
frame_s *frame = memsink_fd_get_frame(fd, mem, &frame_id);
|
||||
us_frame_s *const frame = us_memsink_fd_get_frame(fd, mem, &frame_id);
|
||||
if (frame == NULL) {
|
||||
goto close_memsink;
|
||||
}
|
||||
if (queue_put(_g_video_queue, frame, 0) != 0) {
|
||||
IF_NOT_REPORTED({ JLOG_PERROR("video", "Video queue is full"); });
|
||||
frame_destroy(frame);
|
||||
if (us_queue_put(_g_video_queue, frame, 0) != 0) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_PERROR("video", "Video queue is full"); });
|
||||
us_frame_destroy(frame);
|
||||
}
|
||||
} else if (result == -1) {
|
||||
goto close_memsink;
|
||||
@@ -167,13 +164,11 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
||||
|
||||
close_memsink:
|
||||
if (mem != NULL) {
|
||||
JLOG_INFO("video", "Memsink closed");
|
||||
memsink_shared_unmap(mem);
|
||||
mem = NULL;
|
||||
US_JLOG_INFO("video", "Memsink closed");
|
||||
us_memsink_shared_unmap(mem);
|
||||
}
|
||||
if (fd > 0) {
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
@@ -181,72 +176,70 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
||||
}
|
||||
|
||||
static void *_audio_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("us_audio");
|
||||
US_THREAD_RENAME("us_audio");
|
||||
atomic_store(&_g_audio_tid_created, true);
|
||||
assert(_g_audio_dev_name);
|
||||
assert(_g_tc358743_dev_path);
|
||||
assert(_g_config->audio_dev_name != NULL);
|
||||
assert(_g_config->tc358743_dev_path != NULL);
|
||||
|
||||
unsigned error_reported = 0;
|
||||
|
||||
while (!STOP) {
|
||||
if (!HAS_WATCHERS) {
|
||||
while (!_STOP) {
|
||||
if (!_HAS_WATCHERS) {
|
||||
usleep(_g_watchers_polling);
|
||||
continue;
|
||||
}
|
||||
|
||||
tc358743_info_s info = {0};
|
||||
audio_s *audio = NULL;
|
||||
us_tc358743_info_s info = {0};
|
||||
us_audio_s *audio = NULL;
|
||||
|
||||
if (tc358743_read_info(_g_tc358743_dev_path, &info) < 0) {
|
||||
if (us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0) {
|
||||
goto close_audio;
|
||||
}
|
||||
if (!info.has_audio) {
|
||||
IF_NOT_REPORTED({ JLOG_INFO("audio", "No audio presented from the host"); });
|
||||
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "No audio presented from the host"); });
|
||||
goto close_audio;
|
||||
}
|
||||
IF_NOT_REPORTED({ JLOG_INFO("audio", "Detected host audio"); });
|
||||
if ((audio = audio_init(_g_audio_dev_name, info.audio_hz)) == NULL) {
|
||||
_IF_NOT_REPORTED({ US_JLOG_INFO("audio", "Detected host audio"); });
|
||||
if ((audio = us_audio_init(_g_config->audio_dev_name, info.audio_hz)) == NULL) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
error_reported = 0;
|
||||
|
||||
while (!STOP && HAS_WATCHERS) {
|
||||
while (!_STOP && _HAS_WATCHERS) {
|
||||
if (
|
||||
tc358743_read_info(_g_tc358743_dev_path, &info) < 0
|
||||
us_tc358743_read_info(_g_config->tc358743_dev_path, &info) < 0
|
||||
|| !info.has_audio
|
||||
|| audio->pcm_hz != info.audio_hz
|
||||
) {
|
||||
goto close_audio;
|
||||
}
|
||||
|
||||
size_t size = RTP_DATAGRAM_SIZE - RTP_HEADER_SIZE;
|
||||
size_t size = US_RTP_DATAGRAM_SIZE - US_RTP_HEADER_SIZE;
|
||||
uint8_t data[size];
|
||||
uint64_t pts;
|
||||
int result = audio_get_encoded(audio, data, &size, &pts);
|
||||
const int result = us_audio_get_encoded(audio, data, &size, &pts);
|
||||
if (result == 0) {
|
||||
LOCK_AUDIO;
|
||||
rtpa_wrap(_g_rtpa, data, size, pts);
|
||||
UNLOCK_AUDIO;
|
||||
_LOCK_AUDIO;
|
||||
us_rtpa_wrap(_g_rtpa, data, size, pts);
|
||||
_UNLOCK_AUDIO;
|
||||
} else if (result == -1) {
|
||||
goto close_audio;
|
||||
}
|
||||
}
|
||||
|
||||
close_audio:
|
||||
if (audio != NULL) {
|
||||
audio_destroy(audio);
|
||||
}
|
||||
US_DELETE(audio, us_audio_destroy);
|
||||
sleep(1); // error_delay
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#undef IF_NOT_REPORTED
|
||||
#undef _IF_NOT_REPORTED
|
||||
|
||||
static void _relay_rtp_clients(const rtp_s *rtp) {
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
client_send(client, rtp);
|
||||
static void _relay_rtp_clients(const us_rtp_s *rtp) {
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
us_janus_client_send(client, rtp);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -257,125 +250,117 @@ 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
|
||||
|
||||
JLOG_INFO("main", "Initializing plugin ...");
|
||||
if (gw == NULL || config_dir_path == NULL || read_config(config_dir_path,
|
||||
&_g_video_sink_name,
|
||||
&_g_audio_dev_name,
|
||||
&_g_tc358743_dev_path
|
||||
) < 0) {
|
||||
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;
|
||||
}
|
||||
_g_gw = gw;
|
||||
|
||||
_g_video_queue = queue_init(1024);
|
||||
_g_rtpv = rtpv_init(_relay_rtp_clients);
|
||||
if (_g_audio_dev_name) {
|
||||
_g_rtpa = rtpa_init(_relay_rtp_clients);
|
||||
A_THREAD_CREATE(&_g_audio_tid, _audio_thread, NULL);
|
||||
_g_video_queue = us_queue_init(1024);
|
||||
_g_rtpv = us_rtpv_init(_relay_rtp_clients, _g_config->video_zero_playout_delay);
|
||||
if (_g_config->audio_dev_name != NULL) {
|
||||
_g_rtpa = us_rtpa_init(_relay_rtp_clients);
|
||||
US_THREAD_CREATE(_g_audio_tid, _audio_thread, NULL);
|
||||
}
|
||||
A_THREAD_CREATE(&_g_video_rtp_tid, _video_rtp_thread, NULL);
|
||||
A_THREAD_CREATE(&_g_video_sink_tid, _video_sink_thread, NULL);
|
||||
US_THREAD_CREATE(_g_video_rtp_tid, _video_rtp_thread, NULL);
|
||||
US_THREAD_CREATE(_g_video_sink_tid, _video_sink_thread, NULL);
|
||||
|
||||
atomic_store(&_g_ready, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _plugin_destroy(void) {
|
||||
JLOG_INFO("main", "Destroying plugin ...");
|
||||
US_JLOG_INFO("main", "Destroying plugin ...");
|
||||
|
||||
atomic_store(&_g_stop, true);
|
||||
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { A_THREAD_JOIN(_tid); } }
|
||||
# define JOIN(_tid) { if (atomic_load(&_tid##_created)) { US_THREAD_JOIN(_tid); } }
|
||||
JOIN(_g_video_sink_tid);
|
||||
JOIN(_g_video_rtp_tid);
|
||||
JOIN(_g_audio_tid);
|
||||
# undef JOIN
|
||||
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
LIST_REMOVE(_g_clients, client);
|
||||
client_destroy(client);
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
US_LIST_REMOVE(_g_clients, client);
|
||||
us_janus_client_destroy(client);
|
||||
});
|
||||
|
||||
QUEUE_FREE_ITEMS_AND_DESTROY(_g_video_queue, frame_destroy);
|
||||
US_QUEUE_DELETE_WITH_ITEMS(_g_video_queue, us_frame_destroy);
|
||||
|
||||
# define DEL(_func, _var) { if (_var) { _func(_var); } }
|
||||
DEL(rtpa_destroy, _g_rtpa);
|
||||
DEL(rtpv_destroy, _g_rtpv);
|
||||
DEL(free, _g_tc358743_dev_path);
|
||||
DEL(free, _g_audio_dev_name);
|
||||
DEL(free, _g_video_sink_name);
|
||||
# undef DEL
|
||||
US_DELETE(_g_rtpa, us_rtpa_destroy);
|
||||
US_DELETE(_g_rtpv, us_rtpv_destroy);
|
||||
US_DELETE(_g_config, us_config_destroy);
|
||||
}
|
||||
|
||||
#define IF_DISABLED(...) { if (!READY || STOP) { __VA_ARGS__ } }
|
||||
#define _IF_DISABLED(...) { if (!_READY || _STOP) { __VA_ARGS__ } }
|
||||
|
||||
static void _plugin_create_session(janus_plugin_session *session, int *err) {
|
||||
IF_DISABLED({ *err = -1; return; });
|
||||
LOCK_ALL;
|
||||
JLOG_INFO("main", "Creating session %p ...", session);
|
||||
client_s *client = client_init(_g_gw, session, (_g_audio_dev_name != NULL));
|
||||
LIST_APPEND(_g_clients, client);
|
||||
_IF_DISABLED({ *err = -1; return; });
|
||||
_LOCK_ALL;
|
||||
US_JLOG_INFO("main", "Creating session %p ...", session);
|
||||
us_janus_client_s *const client = us_janus_client_init(_g_gw, session, (_g_config->audio_dev_name != NULL));
|
||||
US_LIST_APPEND(_g_clients, client);
|
||||
atomic_store(&_g_has_watchers, true);
|
||||
UNLOCK_ALL;
|
||||
_UNLOCK_ALL;
|
||||
}
|
||||
|
||||
static void _plugin_destroy_session(janus_plugin_session* session, int *err) {
|
||||
IF_DISABLED({ *err = -1; return; });
|
||||
LOCK_ALL;
|
||||
_IF_DISABLED({ *err = -1; return; });
|
||||
_LOCK_ALL;
|
||||
bool found = false;
|
||||
bool has_watchers = false;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
JLOG_INFO("main", "Removing session %p ...", session);
|
||||
LIST_REMOVE(_g_clients, client);
|
||||
client_destroy(client);
|
||||
US_JLOG_INFO("main", "Removing session %p ...", session);
|
||||
US_LIST_REMOVE(_g_clients, client);
|
||||
us_janus_client_destroy(client);
|
||||
found = true;
|
||||
} else {
|
||||
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
||||
}
|
||||
});
|
||||
if (!found) {
|
||||
JLOG_WARN("main", "No session %p", session);
|
||||
US_JLOG_WARN("main", "No session %p", session);
|
||||
*err = -2;
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
UNLOCK_ALL;
|
||||
_UNLOCK_ALL;
|
||||
}
|
||||
|
||||
static json_t *_plugin_query_session(janus_plugin_session *session) {
|
||||
IF_DISABLED({ return NULL; });
|
||||
_IF_DISABLED({ return NULL; });
|
||||
json_t *info = NULL;
|
||||
LOCK_ALL;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
_LOCK_ALL;
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
info = json_string("session_found");
|
||||
break;
|
||||
}
|
||||
});
|
||||
UNLOCK_ALL;
|
||||
_UNLOCK_ALL;
|
||||
return info;
|
||||
}
|
||||
|
||||
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
|
||||
IF_DISABLED({ return; });
|
||||
LOCK_ALL;
|
||||
_IF_DISABLED({ return; });
|
||||
_LOCK_ALL;
|
||||
bool found = false;
|
||||
bool has_watchers = false;
|
||||
LIST_ITERATE(_g_clients, client, {
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
atomic_store(&client->transmit, transmit);
|
||||
// JLOG_INFO("main", "%s session %p", msg, session);
|
||||
// US_JLOG_INFO("main", "%s session %p", msg, session);
|
||||
found = true;
|
||||
}
|
||||
has_watchers = (has_watchers || atomic_load(&client->transmit));
|
||||
});
|
||||
if (!found) {
|
||||
JLOG_WARN("main", "No session %p", session);
|
||||
US_JLOG_WARN("main", "No session %p", session);
|
||||
}
|
||||
atomic_store(&_g_has_watchers, has_watchers);
|
||||
UNLOCK_ALL;
|
||||
_UNLOCK_ALL;
|
||||
}
|
||||
|
||||
#undef IF_DISABLED
|
||||
#undef _IF_DISABLED
|
||||
|
||||
static void _plugin_setup_media(janus_plugin_session *session) { _set_transmit(session, "Unmuted", true); }
|
||||
static void _plugin_hangup_media(janus_plugin_session *session) { _set_transmit(session, "Muted", false); }
|
||||
@@ -386,8 +371,8 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
assert(transaction != NULL);
|
||||
|
||||
# define FREE_MSG_JSEP { \
|
||||
if (msg) json_decref(msg); \
|
||||
if (jsep) json_decref(jsep); \
|
||||
US_DELETE(msg, json_decref); \
|
||||
US_DELETE(jsep, json_decref); \
|
||||
}
|
||||
|
||||
if (session == NULL || msg == NULL) {
|
||||
@@ -396,37 +381,37 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
return janus_plugin_result_new(JANUS_PLUGIN_ERROR, (msg ? "No session" : "No message"), NULL);
|
||||
}
|
||||
|
||||
# define PUSH_ERROR(_error, _reason) { \
|
||||
/*JLOG_ERROR("main", "Message error in session %p: %s", session, _reason);*/ \
|
||||
json_t *_event = json_object(); \
|
||||
json_object_set_new(_event, "ustreamer", json_string("event")); \
|
||||
json_object_set_new(_event, "error_code", json_integer(_error)); \
|
||||
json_object_set_new(_event, "error", json_string(_reason)); \
|
||||
_g_gw->push_event(session, create(), transaction, _event, NULL); \
|
||||
json_decref(_event); \
|
||||
# define PUSH_ERROR(x_error, x_reason) { \
|
||||
/*US_JLOG_ERROR("main", "Message error in session %p: %s", session, x_reason);*/ \
|
||||
json_t *m_event = json_object(); \
|
||||
json_object_set_new(m_event, "ustreamer", json_string("event")); \
|
||||
json_object_set_new(m_event, "error_code", json_integer(x_error)); \
|
||||
json_object_set_new(m_event, "error", json_string(x_reason)); \
|
||||
_g_gw->push_event(session, create(), transaction, m_event, NULL); \
|
||||
json_decref(m_event); \
|
||||
}
|
||||
|
||||
json_t *request_obj = json_object_get(msg, "request");
|
||||
json_t *const request_obj = json_object_get(msg, "request");
|
||||
if (request_obj == NULL) {
|
||||
PUSH_ERROR(400, "Request missing");
|
||||
goto ok_wait;
|
||||
}
|
||||
|
||||
const char *request_str = json_string_value(request_obj);
|
||||
if (!request_str) {
|
||||
const char *const request_str = json_string_value(request_obj);
|
||||
if (request_str == NULL) {
|
||||
PUSH_ERROR(400, "Request not a string");
|
||||
goto ok_wait;
|
||||
}
|
||||
// JLOG_INFO("main", "Message: %s", request_str);
|
||||
// US_JLOG_INFO("main", "Message: %s", request_str);
|
||||
|
||||
# define PUSH_STATUS(_status, _jsep) { \
|
||||
json_t *_event = json_object(); \
|
||||
json_object_set_new(_event, "ustreamer", json_string("event")); \
|
||||
json_t *_result = json_object(); \
|
||||
json_object_set_new(_result, "status", json_string(_status)); \
|
||||
json_object_set_new(_event, "result", _result); \
|
||||
_g_gw->push_event(session, create(), transaction, _event, _jsep); \
|
||||
json_decref(_event); \
|
||||
# define PUSH_STATUS(x_status, x_jsep) { \
|
||||
json_t *const m_event = json_object(); \
|
||||
json_object_set_new(m_event, "ustreamer", json_string("event")); \
|
||||
json_t *const m_result = json_object(); \
|
||||
json_object_set_new(m_result, "status", json_string(x_status)); \
|
||||
json_object_set_new(m_event, "result", m_result); \
|
||||
_g_gw->push_event(session, create(), transaction, m_event, x_jsep); \
|
||||
json_decref(m_event); \
|
||||
}
|
||||
|
||||
if (!strcmp(request_str, "start")) {
|
||||
@@ -438,24 +423,24 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
} else if (!strcmp(request_str, "watch")) {
|
||||
char *sdp;
|
||||
{
|
||||
char *video_sdp = rtpv_make_sdp(_g_rtpv);
|
||||
char *const video_sdp = us_rtpv_make_sdp(_g_rtpv);
|
||||
if (video_sdp == NULL) {
|
||||
PUSH_ERROR(503, "Haven't received SPS/PPS from memsink yet");
|
||||
goto ok_wait;
|
||||
}
|
||||
char *audio_sdp = (_g_rtpa ? rtpa_make_sdp(_g_rtpa) : strdup(""));
|
||||
A_ASPRINTF(sdp,
|
||||
char *const audio_sdp = (_g_rtpa ? us_rtpa_make_sdp(_g_rtpa) : us_strdup(""));
|
||||
US_ASPRINTF(sdp,
|
||||
"v=0" RN
|
||||
"o=- %" PRIu64 " 1 IN IP4 0.0.0.0" RN
|
||||
"s=PiKVM uStreamer" RN
|
||||
"t=0 0" RN
|
||||
"%s%s",
|
||||
get_now_id() >> 1, audio_sdp, video_sdp
|
||||
us_get_now_id() >> 1, audio_sdp, video_sdp
|
||||
);
|
||||
free(audio_sdp);
|
||||
free(video_sdp);
|
||||
}
|
||||
json_t *offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
|
||||
json_t *const offer_jsep = json_pack("{ssss}", "type", "offer", "sdp", sdp);
|
||||
free(sdp);
|
||||
PUSH_STATUS("started", offer_jsep);
|
||||
json_decref(offer_jsep);
|
||||
@@ -477,12 +462,12 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
// ***** Plugin *****
|
||||
|
||||
static int _plugin_get_api_compatibility(void) { return JANUS_PLUGIN_API_VERSION; }
|
||||
static int _plugin_get_version(void) { return VERSION_U; }
|
||||
static const char *_plugin_get_version_string(void) { return VERSION; }
|
||||
static int _plugin_get_version(void) { return US_VERSION_U; }
|
||||
static const char *_plugin_get_version_string(void) { return US_VERSION; }
|
||||
static const char *_plugin_get_description(void) { return "PiKVM uStreamer Janus plugin for H.264 video"; }
|
||||
static const char *_plugin_get_name(void) { return PLUGIN_NAME; }
|
||||
static const char *_plugin_get_name(void) { return US_PLUGIN_NAME; }
|
||||
static const char *_plugin_get_author(void) { return "Maxim Devaev <mdevaev@gmail.com>"; }
|
||||
static const char *_plugin_get_package(void) { return PLUGIN_PACKAGE; }
|
||||
static const char *_plugin_get_package(void) { return US_PLUGIN_PACKAGE; }
|
||||
|
||||
static void _plugin_incoming_rtp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtp *packet) {
|
||||
// Just a stub to avoid logging spam about the plugin's purpose
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
#include "queue.h"
|
||||
|
||||
|
||||
queue_s *queue_init(unsigned capacity) {
|
||||
queue_s *queue;
|
||||
A_CALLOC(queue, 1);
|
||||
A_CALLOC(queue->items, capacity);
|
||||
us_queue_s *us_queue_init(unsigned capacity) {
|
||||
us_queue_s *queue;
|
||||
US_CALLOC(queue, 1);
|
||||
US_CALLOC(queue->items, capacity);
|
||||
queue->capacity = capacity;
|
||||
A_MUTEX_INIT(&queue->mutex);
|
||||
US_MUTEX_INIT(queue->mutex);
|
||||
|
||||
pthread_condattr_t attrs;
|
||||
assert(!pthread_condattr_init(&attrs));
|
||||
@@ -39,61 +39,64 @@ queue_s *queue_init(unsigned capacity) {
|
||||
return queue;
|
||||
}
|
||||
|
||||
void queue_destroy(queue_s *queue) {
|
||||
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);
|
||||
}
|
||||
|
||||
#define WAIT_OR_UNLOCK(_var, _cond) { \
|
||||
struct timespec _ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &_ts)); \
|
||||
ld_to_timespec(timespec_to_ld(&_ts) + timeout, &_ts); \
|
||||
while (_var) { \
|
||||
int err = pthread_cond_timedwait(_cond, &queue->mutex, &_ts); \
|
||||
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \
|
||||
struct timespec m_ts; \
|
||||
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
|
||||
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
|
||||
while (x_var) { \
|
||||
const int err = pthread_cond_timedwait(&(x_cond), &queue->mutex, &m_ts); \
|
||||
if (err == ETIMEDOUT) { \
|
||||
A_MUTEX_UNLOCK(&queue->mutex); \
|
||||
US_MUTEX_UNLOCK(queue->mutex); \
|
||||
return -1; \
|
||||
} \
|
||||
assert(!err); \
|
||||
} \
|
||||
}
|
||||
|
||||
int queue_put(queue_s *queue, void *item, long double timeout) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
if (timeout == 0) {
|
||||
if (queue->size == queue->capacity) {
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
WAIT_OR_UNLOCK(queue->size == queue->capacity, &queue->full_cond);
|
||||
_WAIT_OR_UNLOCK(queue->size == queue->capacity, queue->full_cond);
|
||||
}
|
||||
queue->items[queue->in] = item;
|
||||
++queue->size;
|
||||
++queue->in;
|
||||
queue->in %= queue->capacity;
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
assert(!pthread_cond_broadcast(&queue->empty_cond));
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
US_COND_BROADCAST(queue->empty_cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int queue_get(queue_s *queue, void **item, long double timeout) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
WAIT_OR_UNLOCK(queue->size == 0, &queue->empty_cond);
|
||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond);
|
||||
*item = queue->items[queue->out];
|
||||
--queue->size;
|
||||
++queue->out;
|
||||
queue->out %= queue->capacity;
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
assert(!pthread_cond_broadcast(&queue->full_cond));
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
US_COND_BROADCAST(queue->full_cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef WAIT_OR_UNLOCK
|
||||
#undef _WAIT_OR_UNLOCK
|
||||
|
||||
int queue_get_free(queue_s *queue) {
|
||||
A_MUTEX_LOCK(&queue->mutex);
|
||||
unsigned size = queue->size;
|
||||
A_MUTEX_UNLOCK(&queue->mutex);
|
||||
int us_queue_get_free(us_queue_s *queue) {
|
||||
US_MUTEX_LOCK(queue->mutex);
|
||||
const unsigned size = queue->size;
|
||||
US_MUTEX_UNLOCK(queue->mutex);
|
||||
return queue->capacity - size;
|
||||
}
|
||||
|
||||
@@ -44,24 +44,25 @@ typedef struct {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t full_cond;
|
||||
pthread_cond_t empty_cond;
|
||||
} queue_s;
|
||||
} us_queue_s;
|
||||
|
||||
|
||||
#define QUEUE_FREE_ITEMS_AND_DESTROY(_queue, _free_item) { \
|
||||
while (!queue_get_free(_queue)) { \
|
||||
void *_ptr; \
|
||||
assert(!queue_get(_queue, &_ptr, 0.1)); \
|
||||
if (_ptr != NULL) { \
|
||||
_free_item(_ptr); \
|
||||
#define US_QUEUE_DELETE_WITH_ITEMS(x_queue, x_free_item) { \
|
||||
if (x_queue) { \
|
||||
while (!us_queue_get_free(x_queue)) { \
|
||||
void *m_ptr; \
|
||||
if (!us_queue_get(x_queue, &m_ptr, 0)) { \
|
||||
US_DELETE(m_ptr, x_free_item); \
|
||||
} \
|
||||
} \
|
||||
us_queue_destroy(x_queue); \
|
||||
} \
|
||||
queue_destroy(_queue); \
|
||||
}
|
||||
|
||||
|
||||
queue_s *queue_init(unsigned capacity);
|
||||
void queue_destroy(queue_s *queue);
|
||||
us_queue_s *us_queue_init(unsigned capacity);
|
||||
void us_queue_destroy(us_queue_s *queue);
|
||||
|
||||
int queue_put(queue_s *queue, void *item, long double timeout);
|
||||
int queue_get(queue_s *queue, void **item, long double timeout);
|
||||
int queue_get_free(queue_s *queue);
|
||||
int us_queue_put(us_queue_s *queue, void *item, long double timeout);
|
||||
int us_queue_get(us_queue_s *queue, void **item, long double timeout);
|
||||
int us_queue_get_free(us_queue_s *queue);
|
||||
|
||||
@@ -26,27 +26,28 @@
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
rtp_s *rtp_init(unsigned payload, bool video) {
|
||||
rtp_s *rtp;
|
||||
A_CALLOC(rtp, 1);
|
||||
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay) {
|
||||
us_rtp_s *rtp;
|
||||
US_CALLOC(rtp, 1);
|
||||
rtp->payload = payload;
|
||||
rtp->video = video;
|
||||
rtp->ssrc = triple_u32(get_now_monotonic_u64());
|
||||
rtp->zero_playout_delay = zero_playout_delay; // See client.c
|
||||
rtp->ssrc = us_triple_u32(us_get_now_monotonic_u64());
|
||||
return rtp;
|
||||
}
|
||||
|
||||
rtp_s *rtp_dup(const rtp_s *rtp) {
|
||||
rtp_s *new;
|
||||
A_CALLOC(new, 1);
|
||||
memcpy(new, rtp, sizeof(rtp_s));
|
||||
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp) {
|
||||
us_rtp_s *new;
|
||||
US_CALLOC(new, 1);
|
||||
memcpy(new, rtp, sizeof(us_rtp_s));
|
||||
return new;
|
||||
}
|
||||
|
||||
void rtp_destroy(rtp_s *rtp) {
|
||||
void us_rtp_destroy(us_rtp_s *rtp) {
|
||||
free(rtp);
|
||||
}
|
||||
|
||||
void rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked) {
|
||||
uint32_t word0 = 0x80000000;
|
||||
if (marked) {
|
||||
word0 |= 1 << 23;
|
||||
|
||||
@@ -32,25 +32,26 @@
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/47635545/why-webrtc-chose-rtp-max-packet-size-to-1200-bytes
|
||||
#define RTP_DATAGRAM_SIZE 1200
|
||||
#define RTP_HEADER_SIZE 12
|
||||
#define US_RTP_DATAGRAM_SIZE 1200
|
||||
#define US_RTP_HEADER_SIZE 12
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned payload;
|
||||
bool video;
|
||||
bool zero_playout_delay;
|
||||
uint32_t ssrc;
|
||||
|
||||
uint16_t seq;
|
||||
uint8_t datagram[RTP_DATAGRAM_SIZE];
|
||||
uint8_t datagram[US_RTP_DATAGRAM_SIZE];
|
||||
size_t used;
|
||||
} rtp_s;
|
||||
} us_rtp_s;
|
||||
|
||||
typedef void (*rtp_callback_f)(const rtp_s *rtp);
|
||||
typedef void (*us_rtp_callback_f)(const us_rtp_s *rtp);
|
||||
|
||||
|
||||
rtp_s *rtp_init(unsigned payload, bool video);
|
||||
rtp_s *rtp_dup(const rtp_s *rtp);
|
||||
void rtp_destroy(rtp_s *rtp);
|
||||
us_rtp_s *us_rtp_init(unsigned payload, bool video, bool zero_playout_delay);
|
||||
us_rtp_s *us_rtp_dup(const us_rtp_s *rtp);
|
||||
void us_rtp_destroy(us_rtp_s *rtp);
|
||||
|
||||
void rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked);
|
||||
void us_rtp_write_header(us_rtp_s *rtp, uint32_t pts, bool marked);
|
||||
|
||||
@@ -23,23 +23,23 @@
|
||||
#include "rtpa.h"
|
||||
|
||||
|
||||
rtpa_s *rtpa_init(rtp_callback_f callback) {
|
||||
rtpa_s *rtpa;
|
||||
A_CALLOC(rtpa, 1);
|
||||
rtpa->rtp = rtp_init(111, false);
|
||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback) {
|
||||
us_rtpa_s *rtpa;
|
||||
US_CALLOC(rtpa, 1);
|
||||
rtpa->rtp = us_rtp_init(111, false, false);
|
||||
rtpa->callback = callback;
|
||||
return rtpa;
|
||||
}
|
||||
|
||||
void rtpa_destroy(rtpa_s *rtpa) {
|
||||
rtp_destroy(rtpa->rtp);
|
||||
void us_rtpa_destroy(us_rtpa_s *rtpa) {
|
||||
us_rtp_destroy(rtpa->rtp);
|
||||
free(rtpa);
|
||||
}
|
||||
|
||||
char *rtpa_make_sdp(rtpa_s *rtpa) {
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa) {
|
||||
# define PAYLOAD rtpa->rtp->payload
|
||||
char *sdp;
|
||||
A_ASPRINTF(sdp,
|
||||
US_ASPRINTF(sdp,
|
||||
"m=audio 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u OPUS/48000/2" RN
|
||||
@@ -56,11 +56,11 @@ char *rtpa_make_sdp(rtpa_s *rtpa) {
|
||||
return sdp;
|
||||
}
|
||||
|
||||
void rtpa_wrap(rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
|
||||
if (size + RTP_HEADER_SIZE <= RTP_DATAGRAM_SIZE) {
|
||||
rtp_write_header(rtpa->rtp, pts, false);
|
||||
memcpy(rtpa->rtp->datagram + RTP_HEADER_SIZE, data, size);
|
||||
rtpa->rtp->used = size + RTP_HEADER_SIZE;
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts) {
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
us_rtp_write_header(rtpa->rtp, pts, false);
|
||||
memcpy(rtpa->rtp->datagram + US_RTP_HEADER_SIZE, data, size);
|
||||
rtpa->rtp->used = size + US_RTP_HEADER_SIZE;
|
||||
rtpa->callback(rtpa->rtp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,13 +36,13 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
rtp_s *rtp;
|
||||
rtp_callback_f callback;
|
||||
} rtpa_s;
|
||||
us_rtp_s *rtp;
|
||||
us_rtp_callback_f callback;
|
||||
} us_rtpa_s;
|
||||
|
||||
|
||||
rtpa_s *rtpa_init(rtp_callback_f callback);
|
||||
void rtpa_destroy(rtpa_s *rtpa);
|
||||
us_rtpa_s *us_rtpa_init(us_rtp_callback_f callback);
|
||||
void us_rtpa_destroy(us_rtpa_s *rtpa);
|
||||
|
||||
char *rtpa_make_sdp(rtpa_s *rtpa);
|
||||
void rtpa_wrap(rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
|
||||
char *us_rtpa_make_sdp(us_rtpa_s *rtpa);
|
||||
void us_rtpa_wrap(us_rtpa_s *rtpa, const uint8_t *data, size_t size, uint32_t pts);
|
||||
|
||||
@@ -26,50 +26,50 @@
|
||||
#include "rtpv.h"
|
||||
|
||||
|
||||
void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked);
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||
|
||||
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback) {
|
||||
rtpv_s *rtpv;
|
||||
A_CALLOC(rtpv, 1);
|
||||
rtpv->rtp = rtp_init(96, true);
|
||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay) {
|
||||
us_rtpv_s *rtpv;
|
||||
US_CALLOC(rtpv, 1);
|
||||
rtpv->rtp = us_rtp_init(96, true, zero_playout_delay);
|
||||
rtpv->callback = callback;
|
||||
rtpv->sps = frame_init();
|
||||
rtpv->pps = frame_init();
|
||||
A_MUTEX_INIT(&rtpv->mutex);
|
||||
rtpv->sps = us_frame_init();
|
||||
rtpv->pps = us_frame_init();
|
||||
US_MUTEX_INIT(rtpv->mutex);
|
||||
return rtpv;
|
||||
}
|
||||
|
||||
void rtpv_destroy(rtpv_s *rtpv) {
|
||||
A_MUTEX_DESTROY(&rtpv->mutex);
|
||||
frame_destroy(rtpv->pps);
|
||||
frame_destroy(rtpv->sps);
|
||||
rtp_destroy(rtpv->rtp);
|
||||
void us_rtpv_destroy(us_rtpv_s *rtpv) {
|
||||
US_MUTEX_DESTROY(rtpv->mutex);
|
||||
us_frame_destroy(rtpv->pps);
|
||||
us_frame_destroy(rtpv->sps);
|
||||
us_rtp_destroy(rtpv->rtp);
|
||||
free(rtpv);
|
||||
}
|
||||
|
||||
char *rtpv_make_sdp(rtpv_s *rtpv) {
|
||||
A_MUTEX_LOCK(&rtpv->mutex);
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
US_MUTEX_LOCK(rtpv->mutex);
|
||||
|
||||
if (rtpv->sps->used == 0 || rtpv->pps->used == 0) {
|
||||
A_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
US_MUTEX_UNLOCK(rtpv->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *sps = NULL;
|
||||
char *pps = NULL;
|
||||
base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
|
||||
base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
|
||||
us_base64_encode(rtpv->sps->data, rtpv->sps->used, &sps, NULL);
|
||||
us_base64_encode(rtpv->pps->data, rtpv->pps->used, &pps, NULL);
|
||||
|
||||
A_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
US_MUTEX_UNLOCK(rtpv->mutex);
|
||||
|
||||
# define PAYLOAD rtpv->rtp->payload
|
||||
// https://tools.ietf.org/html/rfc6184
|
||||
// https://github.com/meetecho/janus-gateway/issues/2443
|
||||
char *sdp;
|
||||
A_ASPRINTF(sdp,
|
||||
US_ASPRINTF(sdp,
|
||||
"m=video 1 RTP/SAVPF %u" RN
|
||||
"c=IN IP4 0.0.0.0" RN
|
||||
"a=rtpmap:%u H264/90000" RN
|
||||
@@ -81,13 +81,14 @@ char *rtpv_make_sdp(rtpv_s *rtpv) {
|
||||
"a=rtcp-fb:%u nack pli" RN
|
||||
"a=rtcp-fb:%u goog-remb" RN
|
||||
"a=ssrc:%" PRIu32 " cname:ustreamer" RN
|
||||
"a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN
|
||||
"%s" // playout-delay
|
||||
"a=sendonly" RN,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
PAYLOAD, sps,
|
||||
PAYLOAD, pps,
|
||||
PAYLOAD, PAYLOAD, PAYLOAD,
|
||||
rtpv->rtp->ssrc
|
||||
rtpv->rtp->ssrc,
|
||||
(rtpv->rtp->zero_playout_delay ? "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" RN : "")
|
||||
);
|
||||
# undef PAYLOAD
|
||||
|
||||
@@ -96,19 +97,19 @@ char *rtpv_make_sdp(rtpv_s *rtpv) {
|
||||
return sdp;
|
||||
}
|
||||
|
||||
#define PRE 3 // Annex B prefix length
|
||||
#define _PRE 3 // Annex B prefix length
|
||||
|
||||
void rtpv_wrap(rtpv_s *rtpv, const frame_s *frame) {
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame) {
|
||||
// There is a complicated logic here but everything works as it should:
|
||||
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
||||
|
||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||
|
||||
const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
ssize_t last_offset = -PRE;
|
||||
const uint32_t pts = us_get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
ssize_t last_offset = -_PRE;
|
||||
|
||||
while (true) { // Find and iterate by nalus
|
||||
const size_t next_start = last_offset + PRE;
|
||||
const size_t next_start = last_offset + _PRE;
|
||||
ssize_t offset = _find_annexb(frame->data + next_start, frame->used - next_start);
|
||||
if (offset < 0) {
|
||||
break;
|
||||
@@ -116,8 +117,8 @@ void rtpv_wrap(rtpv_s *rtpv, const frame_s *frame) {
|
||||
offset += next_start;
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *data = frame->data + last_offset + PRE;
|
||||
size_t size = offset - last_offset - PRE;
|
||||
const uint8_t *const data = frame->data + last_offset + _PRE;
|
||||
size_t size = offset - last_offset - _PRE;
|
||||
if (data[size - 1] == 0) { // Check for extra 00
|
||||
--size;
|
||||
}
|
||||
@@ -128,53 +129,53 @@ void rtpv_wrap(rtpv_s *rtpv, const frame_s *frame) {
|
||||
}
|
||||
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *data = frame->data + last_offset + PRE;
|
||||
size_t size = frame->used - last_offset - PRE;
|
||||
const uint8_t *const data = frame->data + last_offset + _PRE;
|
||||
size_t size = frame->used - last_offset - _PRE;
|
||||
_rtpv_process_nalu(rtpv, data, size, pts, true);
|
||||
}
|
||||
}
|
||||
|
||||
void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
|
||||
void _rtpv_process_nalu(us_rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t pts, bool marked) {
|
||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
||||
const unsigned type = data[0] & 0x1F;
|
||||
|
||||
frame_s *ps = NULL;
|
||||
us_frame_s *ps = NULL;
|
||||
switch (type) {
|
||||
case 7: ps = rtpv->sps; break;
|
||||
case 8: ps = rtpv->pps; break;
|
||||
}
|
||||
if (ps) {
|
||||
A_MUTEX_LOCK(&rtpv->mutex);
|
||||
frame_set_data(ps, data, size);
|
||||
A_MUTEX_UNLOCK(&rtpv->mutex);
|
||||
if (ps != NULL) {
|
||||
US_MUTEX_LOCK(rtpv->mutex);
|
||||
us_frame_set_data(ps, data, size);
|
||||
US_MUTEX_UNLOCK(rtpv->mutex);
|
||||
}
|
||||
|
||||
# define DG rtpv->rtp->datagram
|
||||
|
||||
if (size + RTP_HEADER_SIZE <= RTP_DATAGRAM_SIZE) {
|
||||
rtp_write_header(rtpv->rtp, pts, marked);
|
||||
memcpy(DG + RTP_HEADER_SIZE, data, size);
|
||||
rtpv->rtp->used = size + RTP_HEADER_SIZE;
|
||||
if (size + US_RTP_HEADER_SIZE <= US_RTP_DATAGRAM_SIZE) {
|
||||
us_rtp_write_header(rtpv->rtp, pts, marked);
|
||||
memcpy(DG + US_RTP_HEADER_SIZE, data, size);
|
||||
rtpv->rtp->used = size + US_RTP_HEADER_SIZE;
|
||||
rtpv->callback(rtpv->rtp);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t fu_overhead = RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||
const size_t fu_overhead = US_RTP_HEADER_SIZE + 2; // FU-A overhead
|
||||
|
||||
const uint8_t *src = data + 1;
|
||||
ssize_t remaining = size - 1;
|
||||
|
||||
bool first = true;
|
||||
while (remaining > 0) {
|
||||
ssize_t frag_size = RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
ssize_t frag_size = US_RTP_DATAGRAM_SIZE - fu_overhead;
|
||||
const bool last = (remaining <= frag_size);
|
||||
if (last) {
|
||||
frag_size = remaining;
|
||||
}
|
||||
|
||||
rtp_write_header(rtpv->rtp, pts, (marked && last));
|
||||
us_rtp_write_header(rtpv->rtp, pts, (marked && last));
|
||||
|
||||
DG[RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||
DG[US_RTP_HEADER_SIZE] = 28 | (ref_idc << 5);
|
||||
|
||||
uint8_t fu = type;
|
||||
if (first) {
|
||||
@@ -183,7 +184,7 @@ void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t
|
||||
if (last) {
|
||||
fu |= 0x40;
|
||||
}
|
||||
DG[RTP_HEADER_SIZE + 1] = fu;
|
||||
DG[US_RTP_HEADER_SIZE + 1] = fu;
|
||||
|
||||
memcpy(DG + fu_overhead, src, frag_size);
|
||||
rtpv->rtp->used = fu_overhead + frag_size;
|
||||
@@ -199,8 +200,8 @@ void _rtpv_process_nalu(rtpv_s *rtpv, const uint8_t *data, size_t size, uint32_t
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size) {
|
||||
// Parses buffer for 00 00 01 start codes
|
||||
if (size >= PRE) {
|
||||
for (size_t index = 0; index <= size - PRE; ++index) {
|
||||
if (size >= _PRE) {
|
||||
for (size_t index = 0; index <= size - _PRE; ++index) {
|
||||
if (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 1) {
|
||||
return index;
|
||||
}
|
||||
@@ -209,4 +210,4 @@ static ssize_t _find_annexb(const uint8_t *data, size_t size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef PRE
|
||||
#undef _PRE
|
||||
|
||||
@@ -42,16 +42,16 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
rtp_s *rtp;
|
||||
rtp_callback_f callback;
|
||||
frame_s *sps; // Actually not a frame, just a bytes storage
|
||||
frame_s *pps;
|
||||
pthread_mutex_t mutex;
|
||||
} rtpv_s;
|
||||
us_rtp_s *rtp;
|
||||
us_rtp_callback_f callback;
|
||||
us_frame_s *sps; // Actually not a frame, just a bytes storage
|
||||
us_frame_s *pps;
|
||||
pthread_mutex_t mutex;
|
||||
} us_rtpv_s;
|
||||
|
||||
|
||||
rtpv_s *rtpv_init(rtp_callback_f callback);
|
||||
void rtpv_destroy(rtpv_s *rtpv);
|
||||
us_rtpv_s *us_rtpv_init(us_rtp_callback_f callback, bool zero_playout_delay);
|
||||
void us_rtpv_destroy(us_rtpv_s *rtpv);
|
||||
|
||||
char *rtpv_make_sdp(rtpv_s *rtpv);
|
||||
void rtpv_wrap(rtpv_s *rtpv, const frame_s *frame);
|
||||
char *us_rtpv_make_sdp(us_rtpv_s *rtpv);
|
||||
void us_rtpv_wrap(us_rtpv_s *rtpv, const us_frame_s *frame);
|
||||
|
||||
@@ -34,24 +34,24 @@
|
||||
#endif
|
||||
|
||||
|
||||
int tc358743_read_info(const char *path, tc358743_info_s *info) {
|
||||
MEMSET_ZERO(*info);
|
||||
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info) {
|
||||
US_MEMSET_ZERO(*info);
|
||||
|
||||
int fd = -1;
|
||||
if ((fd = open(path, O_RDWR)) < 0) {
|
||||
JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
|
||||
US_JLOG_PERROR("audio", "Can't open TC358743 V4L2 device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
# define READ_CID(_cid, _field) { \
|
||||
struct v4l2_control ctl = {0}; \
|
||||
ctl.id = _cid; \
|
||||
if (xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) { \
|
||||
JLOG_PERROR("audio", "Can't get value of " #_cid); \
|
||||
# define READ_CID(x_cid, x_field) { \
|
||||
struct v4l2_control m_ctl = {0}; \
|
||||
m_ctl.id = x_cid; \
|
||||
if (us_xioctl(fd, VIDIOC_G_CTRL, &m_ctl) < 0) { \
|
||||
US_JLOG_PERROR("audio", "Can't get value of " #x_cid); \
|
||||
close(fd); \
|
||||
return -1; \
|
||||
} \
|
||||
info->_field = ctl.value; \
|
||||
info->x_field = m_ctl.value; \
|
||||
}
|
||||
|
||||
READ_CID(TC358743_CID_AUDIO_PRESENT, has_audio);
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
typedef struct {
|
||||
bool has_audio;
|
||||
unsigned audio_hz;
|
||||
} tc358743_info_s;
|
||||
} us_tc358743_info_s;
|
||||
|
||||
|
||||
int tc358743_read_info(const char *path, tc358743_info_s *info);
|
||||
int us_tc358743_read_info(const char *path, us_tc358743_info_s *info);
|
||||
|
||||
1
janus/src/uslibs/array.h
Symbolic link
1
janus/src/uslibs/array.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/array.h
|
||||
@@ -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 \
|
||||
|
||||
@@ -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.14" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 5.24" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 5.14" "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.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=5.14
|
||||
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
|
||||
|
||||
|
||||
34
pkg/docker/Dockerfile.alpine
Normal file
34
pkg/docker/Dockerfile.alpine
Normal 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
14
pkg/docker/entry.sh
Executable 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 $@
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=5.14
|
||||
PKG_VERSION:=5.24
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -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.14",
|
||||
version="5.24",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
@@ -27,34 +27,34 @@ typedef struct {
|
||||
double drop_same_frames;
|
||||
|
||||
int fd;
|
||||
memsink_shared_s *mem;
|
||||
us_memsink_shared_s *mem;
|
||||
|
||||
uint64_t frame_id;
|
||||
long double frame_ts;
|
||||
frame_s *frame;
|
||||
} MemsinkObject;
|
||||
uint64_t frame_id;
|
||||
long double frame_ts;
|
||||
us_frame_s *frame;
|
||||
} _MemsinkObject;
|
||||
|
||||
|
||||
#define MEM(_next) self->mem->_next
|
||||
#define FRAME(_next) self->frame->_next
|
||||
#define _MEM(x_next) self->mem->x_next
|
||||
#define _FRAME(x_next) self->frame->x_next
|
||||
|
||||
|
||||
static void MemsinkObject_destroy_internals(MemsinkObject *self) {
|
||||
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
|
||||
if (self->mem != NULL) {
|
||||
memsink_shared_unmap(self->mem);
|
||||
us_memsink_shared_unmap(self->mem);
|
||||
self->mem = NULL;
|
||||
}
|
||||
if (self->fd > 0) {
|
||||
if (self->fd >= 0) {
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
}
|
||||
if (self->frame) {
|
||||
frame_destroy(self->frame);
|
||||
if (self->frame != NULL) {
|
||||
us_frame_destroy(self->frame);
|
||||
self->frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
self->lock_timeout = 1;
|
||||
self->wait_timeout = 1;
|
||||
|
||||
@@ -78,14 +78,14 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
|
||||
|
||||
# undef SET_DOUBLE
|
||||
|
||||
self->frame = frame_init();
|
||||
self->frame = us_frame_init();
|
||||
|
||||
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((self->mem = memsink_shared_map(self->fd)) == NULL) {
|
||||
if ((self->mem = us_memsink_shared_map(self->fd)) == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
@@ -93,37 +93,37 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
|
||||
return 0;
|
||||
|
||||
error:
|
||||
MemsinkObject_destroy_internals(self);
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_repr(MemsinkObject *self) {
|
||||
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
|
||||
char repr[1024];
|
||||
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
|
||||
return Py_BuildValue("s", repr);
|
||||
}
|
||||
|
||||
static void MemsinkObject_dealloc(MemsinkObject *self) {
|
||||
MemsinkObject_destroy_internals(self);
|
||||
static void _MemsinkObject_dealloc(_MemsinkObject *self) {
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
PyObject_Del(self);
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_close(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
MemsinkObject_destroy_internals(self);
|
||||
static PyObject *_MemsinkObject_close(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
_MemsinkObject_destroy_internals(self);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_enter(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
static PyObject *_MemsinkObject_enter(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
Py_INCREF(self);
|
||||
return (PyObject *)self;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_exit(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
return PyObject_CallMethod((PyObject *)self, "close", "");
|
||||
}
|
||||
|
||||
static int wait_frame(MemsinkObject *self) {
|
||||
long double deadline_ts = get_now_monotonic() + self->wait_timeout;
|
||||
static int _wait_frame(_MemsinkObject *self) {
|
||||
const long double deadline_ts = us_get_now_monotonic() + self->wait_timeout;
|
||||
|
||||
# define RETURN_OS_ERROR { \
|
||||
Py_BLOCK_THREADS \
|
||||
@@ -135,21 +135,21 @@ static int wait_frame(MemsinkObject *self) {
|
||||
do {
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
|
||||
int retval = flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
||||
now = get_now_monotonic();
|
||||
const int retval = us_flock_timedwait_monotonic(self->fd, self->lock_timeout);
|
||||
now = us_get_now_monotonic();
|
||||
|
||||
if (retval < 0 && errno != EWOULDBLOCK) {
|
||||
RETURN_OS_ERROR;
|
||||
|
||||
} else if (retval == 0) {
|
||||
if (MEM(magic) == MEMSINK_MAGIC && MEM(version) == MEMSINK_VERSION && MEM(id) != self->frame_id) {
|
||||
if (_MEM(magic) == US_MEMSINK_MAGIC && _MEM(version) == US_MEMSINK_VERSION && _MEM(id) != self->frame_id) {
|
||||
if (self->drop_same_frames > 0) {
|
||||
if (
|
||||
FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
||||
US_FRAME_COMPARE_META_USED_NOTS(self->mem, self->frame)
|
||||
&& (self->frame_ts + self->drop_same_frames > now)
|
||||
&& !memcmp(FRAME(data), MEM(data), MEM(used))
|
||||
&& !memcmp(_FRAME(data), _MEM(data), _MEM(used))
|
||||
) {
|
||||
self->frame_id = MEM(id);
|
||||
self->frame_id = _MEM(id);
|
||||
goto drop;
|
||||
}
|
||||
}
|
||||
@@ -181,23 +181,23 @@ static int wait_frame(MemsinkObject *self) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
if (self->mem == NULL || self->fd <= 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Closed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (wait_frame(self)) {
|
||||
switch (_wait_frame(self)) {
|
||||
case 0: break;
|
||||
case -2: Py_RETURN_NONE;
|
||||
default: return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
us_frame_set_data(self->frame, _MEM(data), _MEM(used));
|
||||
US_FRAME_COPY_META(self->mem, self->frame);
|
||||
self->frame_id = _MEM(id);
|
||||
self->frame_ts = us_get_now_monotonic();
|
||||
_MEM(last_client_ts) = self->frame_ts;
|
||||
|
||||
if (flock(self->fd, LOCK_UN) < 0) {
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
@@ -219,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(FRAME(_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);
|
||||
@@ -230,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 *)FRAME(data), FRAME(used)));
|
||||
SET_VALUE("data", PyBytes_FromStringAndSize((const char *)_FRAME(data), _FRAME(used)));
|
||||
|
||||
# undef SET_NUMBER
|
||||
# undef SET_VALUE
|
||||
@@ -238,12 +238,12 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
|
||||
return dict_frame;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
static PyObject *_MemsinkObject_is_opened(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
return PyBool_FromLong(self->mem != NULL && self->fd > 0);
|
||||
}
|
||||
|
||||
#define FIELD_GETTER(_field, _from, _to) \
|
||||
static PyObject *MemsinkObject_getter_##_field(MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
||||
static PyObject *_MemsinkObject_getter_##_field(_MemsinkObject *self, void *Py_UNUSED(closure)) { \
|
||||
return Py##_to##_From##_from(self->_field); \
|
||||
}
|
||||
|
||||
@@ -254,9 +254,9 @@ FIELD_GETTER(drop_same_frames, Double, Float)
|
||||
|
||||
#undef FIELD_GETTER
|
||||
|
||||
static PyMethodDef MemsinkObject_methods[] = {
|
||||
static PyMethodDef _MemsinkObject_methods[] = {
|
||||
# define ADD_METHOD(_name, _method, _flags) \
|
||||
{.ml_name = _name, .ml_meth = (PyCFunction)MemsinkObject_##_method, .ml_flags = (_flags)}
|
||||
{.ml_name = _name, .ml_meth = (PyCFunction)_MemsinkObject_##_method, .ml_flags = (_flags)}
|
||||
ADD_METHOD("close", close, METH_NOARGS),
|
||||
ADD_METHOD("__enter__", enter, METH_NOARGS),
|
||||
ADD_METHOD("__exit__", exit, METH_VARARGS),
|
||||
@@ -266,8 +266,8 @@ static PyMethodDef MemsinkObject_methods[] = {
|
||||
# undef ADD_METHOD
|
||||
};
|
||||
|
||||
static PyGetSetDef MemsinkObject_getsets[] = {
|
||||
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)MemsinkObject_getter_##_field}
|
||||
static PyGetSetDef _MemsinkObject_getsets[] = {
|
||||
# define ADD_GETTER(_field) {.name = #_field, .get = (getter)_MemsinkObject_getter_##_field}
|
||||
ADD_GETTER(obj),
|
||||
ADD_GETTER(lock_timeout),
|
||||
ADD_GETTER(wait_timeout),
|
||||
@@ -276,43 +276,40 @@ static PyGetSetDef MemsinkObject_getsets[] = {
|
||||
# undef ADD_GETTER
|
||||
};
|
||||
|
||||
static PyTypeObject MemsinkType = {
|
||||
static PyTypeObject _MemsinkType = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "ustreamer.Memsink",
|
||||
.tp_basicsize = sizeof(MemsinkObject),
|
||||
.tp_basicsize = sizeof(_MemsinkObject),
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_new = PyType_GenericNew,
|
||||
.tp_init = (initproc)MemsinkObject_init,
|
||||
.tp_dealloc = (destructor)MemsinkObject_dealloc,
|
||||
.tp_repr = (reprfunc)MemsinkObject_repr,
|
||||
.tp_methods = MemsinkObject_methods,
|
||||
.tp_getset = MemsinkObject_getsets,
|
||||
.tp_init = (initproc)_MemsinkObject_init,
|
||||
.tp_dealloc = (destructor)_MemsinkObject_dealloc,
|
||||
.tp_repr = (reprfunc)_MemsinkObject_repr,
|
||||
.tp_methods = _MemsinkObject_methods,
|
||||
.tp_getset = _MemsinkObject_getsets,
|
||||
};
|
||||
|
||||
static PyModuleDef ustreamer_Module = {
|
||||
static PyModuleDef _Module = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
.m_name = "ustreamer",
|
||||
.m_size = -1,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit_ustreamer(void) { // cppcheck-suppress unusedFunction
|
||||
PyObject *module = PyModule_Create(&ustreamer_Module);
|
||||
PyObject *module = PyModule_Create(&_Module);
|
||||
if (module == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyType_Ready(&MemsinkType) < 0) {
|
||||
if (PyType_Ready(&_MemsinkType) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_INCREF(&MemsinkType);
|
||||
Py_INCREF(&_MemsinkType);
|
||||
|
||||
if (PyModule_AddObject(module, "Memsink", (PyObject *)&MemsinkType) < 0) {
|
||||
if (PyModule_AddObject(module, "Memsink", (PyObject *)&_MemsinkType) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
#undef FRAME
|
||||
#undef MEM
|
||||
|
||||
@@ -23,17 +23,17 @@
|
||||
#include "file.h"
|
||||
|
||||
|
||||
output_file_s *output_file_init(const char *path, bool json) {
|
||||
output_file_s *output;
|
||||
A_CALLOC(output, 1);
|
||||
us_output_file_s *us_output_file_init(const char *path, bool json) {
|
||||
us_output_file_s *output;
|
||||
US_CALLOC(output, 1);
|
||||
|
||||
if (!strcmp(path, "-")) {
|
||||
LOG_INFO("Using output: <stdout>");
|
||||
US_LOG_INFO("Using output: <stdout>");
|
||||
output->fp = stdout;
|
||||
} else {
|
||||
LOG_INFO("Using output: %s", path);
|
||||
US_LOG_INFO("Using output: %s", path);
|
||||
if ((output->fp = fopen(path, "wb")) == NULL) {
|
||||
LOG_PERROR("Can't open output file");
|
||||
US_LOG_PERROR("Can't open output file");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@@ -42,14 +42,14 @@ output_file_s *output_file_init(const char *path, bool json) {
|
||||
return output;
|
||||
|
||||
error:
|
||||
output_file_destroy(output);
|
||||
us_output_file_destroy(output);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void output_file_write(void *v_output, const frame_s *frame) {
|
||||
output_file_s *output = (output_file_s *)v_output;
|
||||
void us_output_file_write(void *v_output, const us_frame_s *frame) {
|
||||
us_output_file_s *output = (us_output_file_s *)v_output;
|
||||
if (output->json) {
|
||||
base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
|
||||
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
|
||||
fprintf(output->fp,
|
||||
"{\"size\": %zu, \"width\": %u, \"height\": %u,"
|
||||
" \"format\": %u, \"stride\": %u, \"online\": %u,"
|
||||
@@ -65,14 +65,12 @@ void output_file_write(void *v_output, const frame_s *frame) {
|
||||
fflush(output->fp);
|
||||
}
|
||||
|
||||
void output_file_destroy(void *v_output) {
|
||||
output_file_s *output = (output_file_s *)v_output;
|
||||
if (output->base64_data) {
|
||||
free(output->base64_data);
|
||||
}
|
||||
void us_output_file_destroy(void *v_output) {
|
||||
us_output_file_s *output = (us_output_file_s *)v_output;
|
||||
US_DELETE(output->base64_data, free);
|
||||
if (output->fp && output->fp != stdout) {
|
||||
if (fclose(output->fp) < 0) {
|
||||
LOG_PERROR("Can't close output file");
|
||||
US_LOG_PERROR("Can't close output file");
|
||||
}
|
||||
}
|
||||
free(output);
|
||||
|
||||
@@ -41,9 +41,9 @@ typedef struct {
|
||||
FILE *fp;
|
||||
char *base64_data;
|
||||
size_t base64_allocated;
|
||||
} output_file_s;
|
||||
} us_output_file_s;
|
||||
|
||||
|
||||
output_file_s *output_file_init(const char *path, bool json);
|
||||
void output_file_write(void *v_output, const frame_s *frame);
|
||||
void output_file_destroy(void *v_output);
|
||||
us_output_file_s *us_output_file_init(const char *path, bool json);
|
||||
void us_output_file_write(void *v_output, const us_frame_s *frame);
|
||||
void us_output_file_destroy(void *v_output);
|
||||
|
||||
@@ -82,12 +82,12 @@ static const struct option _LONG_OPTS[] = {
|
||||
};
|
||||
|
||||
|
||||
volatile bool global_stop = false;
|
||||
volatile bool _g_stop = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
void *v_output;
|
||||
void (*write)(void *v_output, const frame_s *frame);
|
||||
void (*write)(void *v_output, const us_frame_s *frame);
|
||||
void (*destroy)(void *v_output);
|
||||
} _output_context_s;
|
||||
|
||||
@@ -104,8 +104,8 @@ static void _help(FILE *fp);
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
LOGGING_INIT;
|
||||
A_THREAD_RENAME("main");
|
||||
US_LOGGING_INIT;
|
||||
US_THREAD_RENAME("main");
|
||||
|
||||
char *sink_name = NULL;
|
||||
unsigned sink_timeout = 1;
|
||||
@@ -140,7 +140,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
char short_opts[128];
|
||||
build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
us_build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
|
||||
for (int ch; (ch = getopt_long(argc, argv, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
@@ -151,15 +151,15 @@ 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, 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_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(VERSION); return 0;
|
||||
case _O_VERSION: puts(US_VERSION); return 0;
|
||||
|
||||
case 0: break;
|
||||
default: return 1;
|
||||
@@ -178,15 +178,15 @@ int main(int argc, char *argv[]) {
|
||||
_output_context_s ctx = {0};
|
||||
|
||||
if (output_path && output_path[0] != '\0') {
|
||||
if ((ctx.v_output = (void *)output_file_init(output_path, output_json)) == NULL) {
|
||||
if ((ctx.v_output = (void *)us_output_file_init(output_path, output_json)) == NULL) {
|
||||
return 1;
|
||||
}
|
||||
ctx.write = output_file_write;
|
||||
ctx.destroy = output_file_destroy;
|
||||
ctx.write = us_output_file_write;
|
||||
ctx.destroy = us_output_file_destroy;
|
||||
}
|
||||
|
||||
_install_signal_handlers();
|
||||
int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx));
|
||||
const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx));
|
||||
if (ctx.v_output && ctx.destroy) {
|
||||
ctx.destroy(ctx.v_output);
|
||||
}
|
||||
@@ -195,13 +195,10 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
|
||||
static void _signal_handler(int signum) {
|
||||
switch (signum) {
|
||||
case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
|
||||
case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
|
||||
case SIGPIPE: LOG_INFO_NOLOCK("===== Stopping by SIGPIPE ====="); break;
|
||||
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
|
||||
}
|
||||
global_stop = true;
|
||||
char *const name = us_signum_to_string(signum);
|
||||
US_LOG_INFO_NOLOCK("===== Stopping by %s =====", name);
|
||||
free(name);
|
||||
_g_stop = true;
|
||||
}
|
||||
|
||||
static void _install_signal_handlers(void) {
|
||||
@@ -213,13 +210,13 @@ static void _install_signal_handlers(void) {
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGPIPE));
|
||||
|
||||
LOG_DEBUG("Installing SIGINT handler ...");
|
||||
US_LOG_DEBUG("Installing SIGINT handler ...");
|
||||
assert(!sigaction(SIGINT, &sig_act, NULL));
|
||||
|
||||
LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
|
||||
LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
}
|
||||
|
||||
@@ -232,12 +229,12 @@ static int _dump_sink(
|
||||
count = -1;
|
||||
}
|
||||
|
||||
useconds_t interval_us = interval * 1000000;
|
||||
const useconds_t interval_us = interval * 1000000;
|
||||
|
||||
frame_s *frame = frame_init();
|
||||
memsink_s *sink = NULL;
|
||||
us_frame_s *frame = us_frame_init();
|
||||
us_memsink_s *sink = NULL;
|
||||
|
||||
if ((sink = memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
|
||||
if ((sink = us_memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -247,32 +244,32 @@ static int _dump_sink(
|
||||
|
||||
long double last_ts = 0;
|
||||
|
||||
while (!global_stop) {
|
||||
int error = memsink_client_get(sink, frame);
|
||||
while (!_g_stop) {
|
||||
int error = us_memsink_client_get(sink, frame);
|
||||
if (error == 0) {
|
||||
const long double now = get_now_monotonic();
|
||||
const long long now_second = floor_ms(now);
|
||||
const long double now = us_get_now_monotonic();
|
||||
const long long now_second = us_floor_ms(now);
|
||||
|
||||
char fourcc_str[8];
|
||||
LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
|
||||
US_LOG_VERBOSE("Frame: size=%zu, res=%ux%u, fourcc=%s, stride=%u, online=%d, key=%d, latency=%.3Lf, diff=%.3Lf",
|
||||
frame->used, frame->width, frame->height,
|
||||
fourcc_to_string(frame->format, fourcc_str, 8),
|
||||
us_fourcc_to_string(frame->format, fourcc_str, 8),
|
||||
frame->stride, frame->online, frame->key,
|
||||
now - frame->grab_ts, (last_ts ? now - last_ts : 0));
|
||||
last_ts = now;
|
||||
|
||||
LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
|
||||
US_LOG_DEBUG(" grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
|
||||
frame->grab_ts, frame->encode_begin_ts, frame->encode_end_ts);
|
||||
|
||||
if (now_second != fps_second) {
|
||||
fps = fps_accum;
|
||||
fps_accum = 0;
|
||||
fps_second = now_second;
|
||||
LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
|
||||
US_LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
|
||||
}
|
||||
fps_accum += 1;
|
||||
|
||||
if (ctx->v_output) {
|
||||
if (ctx->v_output != NULL) {
|
||||
ctx->write(ctx->v_output, frame);
|
||||
}
|
||||
|
||||
@@ -300,12 +297,10 @@ static int _dump_sink(
|
||||
retval = -1;
|
||||
|
||||
ok:
|
||||
if (sink) {
|
||||
memsink_destroy(sink);
|
||||
}
|
||||
frame_destroy(frame);
|
||||
US_DELETE(sink, us_memsink_destroy);
|
||||
us_frame_destroy(frame);
|
||||
|
||||
LOG_INFO("Bye-bye");
|
||||
US_LOG_INFO("Bye-bye");
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -313,7 +308,7 @@ static void _help(FILE *fp) {
|
||||
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
|
||||
SAY("═════════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", VERSION);
|
||||
SAY("Version: %s; license: GPLv3", US_VERSION);
|
||||
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Example:");
|
||||
SAY("════════");
|
||||
@@ -332,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");
|
||||
|
||||
37
src/libs/array.h
Normal file
37
src/libs/array.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2022 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 US_ARRAY_LEN(x_array) (sizeof(x_array) / sizeof((x_array)[0]))
|
||||
|
||||
#define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \
|
||||
const int m_len = US_ARRAY_LEN(x_array); \
|
||||
assert(x_start <= m_len); \
|
||||
for (int m_index = x_start; m_index < m_len; ++m_index) { \
|
||||
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_index]; \
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
}
|
||||
@@ -37,11 +37,11 @@ static const char _ENCODING_TABLE[] = {
|
||||
static const unsigned _MOD_TABLE[] = {0, 2, 1};
|
||||
|
||||
|
||||
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
|
||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated) {
|
||||
const size_t encoded_size = 4 * ((size + 2) / 3) + 1; // +1 for '\0'
|
||||
|
||||
if (*encoded == NULL || (allocated && *allocated < encoded_size)) {
|
||||
A_REALLOC(*encoded, encoded_size);
|
||||
US_REALLOC(*encoded, encoded_size);
|
||||
if (allocated) {
|
||||
*allocated = encoded_size;
|
||||
}
|
||||
@@ -54,7 +54,7 @@ void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *all
|
||||
OCTET(octet_c);
|
||||
# undef OCTET
|
||||
|
||||
unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||
const unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||
|
||||
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
|
||||
ENCODE(3);
|
||||
|
||||
@@ -31,4 +31,4 @@
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
void base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
|
||||
void us_base64_encode(const uint8_t *data, size_t size, char **encoded, size_t *allocated);
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION_MAJOR 5
|
||||
#define VERSION_MINOR 14
|
||||
#define US_VERSION_MAJOR 5
|
||||
#define US_VERSION_MINOR 24
|
||||
|
||||
#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 US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
#define US_VERSION US_MAKE_VERSION1(US_VERSION_MAJOR, US_VERSION_MINOR)
|
||||
|
||||
#define VERSION_U ((unsigned)(VERSION_MAJOR * 1000 + VERSION_MINOR))
|
||||
#define US_VERSION_U ((unsigned)(US_VERSION_MAJOR * 1000 + US_VERSION_MINOR))
|
||||
|
||||
@@ -23,55 +23,53 @@
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
frame_s *frame_init(void) {
|
||||
frame_s *frame;
|
||||
A_CALLOC(frame, 1);
|
||||
frame_realloc_data(frame, 512 * 1024);
|
||||
us_frame_s *us_frame_init(void) {
|
||||
us_frame_s *frame;
|
||||
US_CALLOC(frame, 1);
|
||||
us_frame_realloc_data(frame, 512 * 1024);
|
||||
frame->dma_fd = -1;
|
||||
return frame;
|
||||
}
|
||||
|
||||
void frame_destroy(frame_s *frame) {
|
||||
if (frame->data) {
|
||||
free(frame->data);
|
||||
}
|
||||
void us_frame_destroy(us_frame_s *frame) {
|
||||
US_DELETE(frame->data, free);
|
||||
free(frame);
|
||||
}
|
||||
|
||||
void frame_realloc_data(frame_s *frame, size_t size) {
|
||||
void us_frame_realloc_data(us_frame_s *frame, size_t size) {
|
||||
if (frame->allocated < size) {
|
||||
A_REALLOC(frame->data, size);
|
||||
US_REALLOC(frame->data, size);
|
||||
frame->allocated = size;
|
||||
}
|
||||
}
|
||||
|
||||
void frame_set_data(frame_s *frame, const uint8_t *data, size_t size) {
|
||||
frame_realloc_data(frame, size);
|
||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
||||
us_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) {
|
||||
size_t new_used = frame->used + size;
|
||||
frame_realloc_data(frame, new_used);
|
||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size) {
|
||||
const size_t new_used = frame->used + size;
|
||||
us_frame_realloc_data(frame, new_used);
|
||||
memcpy(frame->data + frame->used, data, size);
|
||||
frame->used = new_used;
|
||||
}
|
||||
|
||||
void frame_copy(const frame_s *src, frame_s *dest) {
|
||||
frame_set_data(dest, src->data, src->used);
|
||||
FRAME_COPY_META(src, dest);
|
||||
void us_frame_copy(const us_frame_s *src, us_frame_s *dest) {
|
||||
us_frame_set_data(dest, src->data, src->used);
|
||||
US_FRAME_COPY_META(src, dest);
|
||||
}
|
||||
|
||||
bool frame_compare(const frame_s *a, const frame_s *b) {
|
||||
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b) {
|
||||
return (
|
||||
a->allocated && b->allocated
|
||||
&& FRAME_COMPARE_META_USED_NOTS(a, b)
|
||||
&& US_FRAME_COMPARE_META_USED_NOTS(a, b)
|
||||
&& !memcmp(a->data, b->data, b->used)
|
||||
);
|
||||
}
|
||||
|
||||
unsigned frame_get_padding(const frame_s *frame) {
|
||||
unsigned us_frame_get_padding(const us_frame_s *frame) {
|
||||
unsigned bytes_per_pixel = 0;
|
||||
switch (frame->format) {
|
||||
case V4L2_PIX_FMT_YUYV:
|
||||
@@ -89,7 +87,7 @@ unsigned frame_get_padding(const frame_s *frame) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *fourcc_to_string(unsigned format, char *buf, size_t size) {
|
||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size) {
|
||||
assert(size >= 8);
|
||||
buf[0] = format & 0x7F;
|
||||
buf[1] = (format >> 8) & 0x7F;
|
||||
|
||||
@@ -35,84 +35,84 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
int dma_fd;
|
||||
uint8_t *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
int dma_fd;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
// Stride is a bytesperline in V4L2
|
||||
// https://www.kernel.org/doc/html/v4.14/media/uapi/v4l/pixfmt-v4l2.html
|
||||
// https://medium.com/@oleg.shipitko/what-does-stride-mean-in-image-processing-bba158a72bcd
|
||||
|
||||
bool online;
|
||||
bool key;
|
||||
bool online;
|
||||
bool key;
|
||||
|
||||
long double grab_ts;
|
||||
long double encode_begin_ts;
|
||||
long double encode_end_ts;
|
||||
} frame_s;
|
||||
} us_frame_s;
|
||||
|
||||
|
||||
#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; \
|
||||
#define US_FRAME_COPY_META(x_src, x_dest) { \
|
||||
x_dest->width = x_src->width; \
|
||||
x_dest->height = x_src->height; \
|
||||
x_dest->format = x_src->format; \
|
||||
x_dest->stride = x_src->stride; \
|
||||
x_dest->online = x_src->online; \
|
||||
x_dest->key = x_src->key; \
|
||||
x_dest->grab_ts = x_src->grab_ts; \
|
||||
x_dest->encode_begin_ts = x_src->encode_begin_ts; \
|
||||
x_dest->encode_end_ts = x_src->encode_end_ts; \
|
||||
}
|
||||
|
||||
static inline void frame_copy_meta(const frame_s *src, frame_s *dest) {
|
||||
FRAME_COPY_META(src, dest);
|
||||
static inline void us_frame_copy_meta(const us_frame_s *src, us_frame_s *dest) {
|
||||
US_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 \
|
||||
#define US_FRAME_COMPARE_META_USED_NOTS(x_a, x_b) ( \
|
||||
x_a->used == x_b->used \
|
||||
&& x_a->width == x_b->width \
|
||||
&& x_a->height == x_b->height \
|
||||
&& x_a->format == x_b->format \
|
||||
&& x_a->stride == x_b->stride \
|
||||
&& x_a->online == x_b->online \
|
||||
&& x_a->key == x_b->key \
|
||||
)
|
||||
|
||||
|
||||
static inline void frame_encoding_begin(const frame_s *src, frame_s *dest, unsigned format) {
|
||||
static inline void us_frame_encoding_begin(const us_frame_s *src, us_frame_s *dest, unsigned format) {
|
||||
assert(src->used > 0);
|
||||
frame_copy_meta(src, dest);
|
||||
dest->encode_begin_ts = get_now_monotonic();
|
||||
us_frame_copy_meta(src, dest);
|
||||
dest->encode_begin_ts = us_get_now_monotonic();
|
||||
dest->format = format;
|
||||
dest->stride = 0;
|
||||
dest->used = 0;
|
||||
}
|
||||
|
||||
static inline void frame_encoding_end(frame_s *dest) {
|
||||
static inline void us_frame_encoding_end(us_frame_s *dest) {
|
||||
assert(dest->used > 0);
|
||||
dest->encode_end_ts = get_now_monotonic();
|
||||
dest->encode_end_ts = us_get_now_monotonic();
|
||||
}
|
||||
|
||||
|
||||
frame_s *frame_init(void);
|
||||
void frame_destroy(frame_s *frame);
|
||||
us_frame_s *us_frame_init(void);
|
||||
void us_frame_destroy(us_frame_s *frame);
|
||||
|
||||
void frame_realloc_data(frame_s *frame, size_t size);
|
||||
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 us_frame_realloc_data(us_frame_s *frame, size_t size);
|
||||
void us_frame_set_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
||||
void us_frame_append_data(us_frame_s *frame, const uint8_t *data, size_t size);
|
||||
|
||||
void frame_copy(const frame_s *src, frame_s *dest);
|
||||
bool frame_compare(const frame_s *a, const frame_s *b);
|
||||
void us_frame_copy(const us_frame_s *src, us_frame_s *dest);
|
||||
bool us_frame_compare(const us_frame_s *a, const us_frame_s *b);
|
||||
|
||||
unsigned frame_get_padding(const frame_s *frame);
|
||||
unsigned us_frame_get_padding(const us_frame_s *frame);
|
||||
|
||||
const char *fourcc_to_string(unsigned format, char *buf, size_t size);
|
||||
const char *us_fourcc_to_string(unsigned format, char *buf, size_t size);
|
||||
|
||||
static inline bool is_jpeg(unsigned format) {
|
||||
static inline bool us_is_jpeg(unsigned format) {
|
||||
return (format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
||||
}
|
||||
|
||||
@@ -25,47 +25,47 @@
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define LIST_STRUCT(...) \
|
||||
#define US_LIST_STRUCT(...) \
|
||||
__VA_ARGS__ *prev; \
|
||||
__VA_ARGS__ *next;
|
||||
|
||||
#define LIST_ITERATE(_first, _item, ...) { \
|
||||
for (__typeof__(_first) _item = _first; _item;) { \
|
||||
__typeof__(_first) _next = _item->next; \
|
||||
#define US_LIST_ITERATE(x_first, x_item, ...) { \
|
||||
for (__typeof__(x_first) x_item = x_first; x_item;) { \
|
||||
__typeof__(x_first) m_next = x_item->next; \
|
||||
__VA_ARGS__ \
|
||||
_item = _next; \
|
||||
x_item = m_next; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LIST_APPEND(_first, _item) { \
|
||||
if (_first == NULL) { \
|
||||
_first = _item; \
|
||||
#define US_LIST_APPEND(x_first, x_item) { \
|
||||
if (x_first == NULL) { \
|
||||
x_first = x_item; \
|
||||
} else { \
|
||||
__typeof__(_first) _last = _first; \
|
||||
for (; _last->next; _last = _last->next); \
|
||||
_item->prev = _last; \
|
||||
_last->next = _item; \
|
||||
__typeof__(x_first) m_last = x_first; \
|
||||
for (; m_last->next; m_last = m_last->next); \
|
||||
x_item->prev = m_last; \
|
||||
m_last->next = x_item; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LIST_APPEND_C(_first, _item, _count) { \
|
||||
LIST_APPEND(_first, _item); \
|
||||
++(_count); \
|
||||
#define US_LIST_APPEND_C(x_first, x_item, x_count) { \
|
||||
US_LIST_APPEND(x_first, x_item); \
|
||||
++(x_count); \
|
||||
}
|
||||
|
||||
#define LIST_REMOVE(_first, _item) { \
|
||||
if (_item->prev == NULL) { \
|
||||
_first = _item->next; \
|
||||
#define US_LIST_REMOVE(x_first, x_item) { \
|
||||
if (x_item->prev == NULL) { \
|
||||
x_first = x_item->next; \
|
||||
} else { \
|
||||
_item->prev->next = _item->next; \
|
||||
x_item->prev->next = x_item->next; \
|
||||
} \
|
||||
if (_item->next != NULL) { \
|
||||
_item->next->prev = _item->prev; \
|
||||
if (x_item->next != NULL) { \
|
||||
x_item->next->prev = x_item->prev; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LIST_REMOVE_C(_first, _item, _count) { \
|
||||
LIST_REMOVE(_first, _item); \
|
||||
assert((_count) >= 1); \
|
||||
--(_count); \
|
||||
#define US_LIST_REMOVE_C(x_first, x_item, x_count) { \
|
||||
US_LIST_REMOVE(x_first, x_item); \
|
||||
assert((x_count) >= 1); \
|
||||
--(x_count); \
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
enum 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;
|
||||
|
||||
@@ -37,126 +37,126 @@
|
||||
#include "threading.h"
|
||||
|
||||
|
||||
enum log_level_t {
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_PERF,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
LOG_LEVEL_DEBUG,
|
||||
enum us_log_level_t {
|
||||
US_LOG_LEVEL_INFO,
|
||||
US_LOG_LEVEL_PERF,
|
||||
US_LOG_LEVEL_VERBOSE,
|
||||
US_LOG_LEVEL_DEBUG,
|
||||
};
|
||||
|
||||
|
||||
extern enum 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 LOGGING_INIT { \
|
||||
us_log_level = LOG_LEVEL_INFO; \
|
||||
us_log_colored = isatty(2); \
|
||||
A_MUTEX_INIT(&us_log_mutex); \
|
||||
#define US_LOGGING_INIT { \
|
||||
us_g_log_level = US_LOG_LEVEL_INFO; \
|
||||
us_g_log_colored = isatty(2); \
|
||||
US_MUTEX_INIT(us_g_log_mutex); \
|
||||
}
|
||||
|
||||
#define LOGGING_DESTROY A_MUTEX_DESTROY(&us_log_mutex)
|
||||
#define US_LOGGING_DESTROY US_MUTEX_DESTROY(us_g_log_mutex)
|
||||
|
||||
#define LOGGING_LOCK A_MUTEX_LOCK(&us_log_mutex)
|
||||
#define LOGGING_UNLOCK A_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 COLOR_GRAY "\x1b[30;1m"
|
||||
#define COLOR_RED "\x1b[31;1m"
|
||||
#define COLOR_GREEN "\x1b[32;1m"
|
||||
#define COLOR_YELLOW "\x1b[33;1m"
|
||||
#define COLOR_BLUE "\x1b[34;1m"
|
||||
#define COLOR_CYAN "\x1b[36;1m"
|
||||
#define COLOR_RESET "\x1b[0m"
|
||||
#define US_COLOR_GRAY "\x1b[30;1m"
|
||||
#define US_COLOR_RED "\x1b[31;1m"
|
||||
#define US_COLOR_GREEN "\x1b[32;1m"
|
||||
#define US_COLOR_YELLOW "\x1b[33;1m"
|
||||
#define US_COLOR_BLUE "\x1b[34;1m"
|
||||
#define US_COLOR_CYAN "\x1b[36;1m"
|
||||
#define US_COLOR_RESET "\x1b[0m"
|
||||
|
||||
|
||||
#define SEP_INFO(_ch) { \
|
||||
LOGGING_LOCK; \
|
||||
for (int _i = 0; _i < 80; ++_i) { \
|
||||
fputc(_ch, stderr); \
|
||||
#define US_SEP_INFO(x_ch) { \
|
||||
US_LOGGING_LOCK; \
|
||||
for (int m_count = 0; m_count < 80; ++m_count) { \
|
||||
fputc((x_ch), stderr); \
|
||||
} \
|
||||
fputc('\n', stderr); \
|
||||
fflush(stderr); \
|
||||
LOGGING_UNLOCK; \
|
||||
US_LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define SEP_DEBUG(_ch) { \
|
||||
if (us_log_level >= LOG_LEVEL_DEBUG) { \
|
||||
SEP_INFO(_ch); \
|
||||
#define US_SEP_DEBUG(x_ch) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_DEBUG) { \
|
||||
US_SEP_INFO(x_ch); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
#define LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ...) { \
|
||||
char _tname_buf[MAX_THREAD_NAME] = {0}; \
|
||||
thread_get_name(_tname_buf); \
|
||||
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__); \
|
||||
#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_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__); \
|
||||
} else { \
|
||||
fprintf(stderr, "-- " _label " [%.03Lf %9s] -- " _msg, \
|
||||
get_now_monotonic(), _tname_buf, ##__VA_ARGS__); \
|
||||
fprintf(stderr, "-- " x_label " [%.03Lf %9s] -- " x_msg, \
|
||||
us_get_now_monotonic(), m_tname_buf, ##__VA_ARGS__); \
|
||||
} \
|
||||
fputc('\n', stderr); \
|
||||
fflush(stderr); \
|
||||
}
|
||||
|
||||
#define LOG_PRINTF(_label_color, _label, _msg_color, _msg, ...) { \
|
||||
LOGGING_LOCK; \
|
||||
LOG_PRINTF_NOLOCK(_label_color, _label, _msg_color, _msg, ##__VA_ARGS__); \
|
||||
LOGGING_UNLOCK; \
|
||||
#define US_LOG_PRINTF(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
||||
US_LOGGING_LOCK; \
|
||||
US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ##__VA_ARGS__); \
|
||||
US_LOGGING_UNLOCK; \
|
||||
}
|
||||
|
||||
#define LOG_ERROR(_msg, ...) { \
|
||||
LOG_PRINTF(COLOR_RED, "ERROR", COLOR_RED, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_ERROR(x_msg, ...) { \
|
||||
US_LOG_PRINTF(US_COLOR_RED, "ERROR", US_COLOR_RED, x_msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_PERROR(_msg, ...) { \
|
||||
char _perror_buf[1024] = {0}; \
|
||||
char *_perror_ptr = errno_to_string(errno, _perror_buf, 1024); \
|
||||
LOG_ERROR(_msg ": %s", ##__VA_ARGS__, _perror_ptr); \
|
||||
#define US_LOG_PERROR(x_msg, ...) { \
|
||||
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 LOG_INFO(_msg, ...) { \
|
||||
LOG_PRINTF(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_INFO(x_msg, ...) { \
|
||||
US_LOG_PRINTF(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_INFO_NOLOCK(_msg, ...) { \
|
||||
LOG_PRINTF_NOLOCK(COLOR_GREEN, "INFO ", "", _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_INFO_NOLOCK(x_msg, ...) { \
|
||||
US_LOG_PRINTF_NOLOCK(US_COLOR_GREEN, "INFO ", "", x_msg, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define LOG_PERF(_msg, ...) { \
|
||||
if (us_log_level >= LOG_LEVEL_PERF) { \
|
||||
LOG_PRINTF(COLOR_CYAN, "PERF ", COLOR_CYAN, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_PERF(x_msg, ...) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_PERF) { \
|
||||
US_LOG_PRINTF(US_COLOR_CYAN, "PERF ", US_COLOR_CYAN, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_PERF_FPS(_msg, ...) { \
|
||||
if (us_log_level >= LOG_LEVEL_PERF) { \
|
||||
LOG_PRINTF(COLOR_YELLOW, "PERF ", COLOR_YELLOW, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_PERF_FPS(x_msg, ...) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_PERF) { \
|
||||
US_LOG_PRINTF(US_COLOR_YELLOW, "PERF ", US_COLOR_YELLOW, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_VERBOSE(_msg, ...) { \
|
||||
if (us_log_level >= LOG_LEVEL_VERBOSE) { \
|
||||
LOG_PRINTF(COLOR_BLUE, "VERB ", COLOR_BLUE, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_VERBOSE(x_msg, ...) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_VERBOSE) { \
|
||||
US_LOG_PRINTF(US_COLOR_BLUE, "VERB ", US_COLOR_BLUE, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define LOG_VERBOSE_PERROR(_msg, ...) { \
|
||||
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); \
|
||||
#define US_LOG_VERBOSE_PERROR(x_msg, ...) { \
|
||||
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 LOG_DEBUG(_msg, ...) { \
|
||||
if (us_log_level >= LOG_LEVEL_DEBUG) { \
|
||||
LOG_PRINTF(COLOR_GRAY, "DEBUG", COLOR_GRAY, _msg, ##__VA_ARGS__); \
|
||||
#define US_LOG_DEBUG(x_msg, ...) { \
|
||||
if (us_g_log_level >= US_LOG_LEVEL_DEBUG) { \
|
||||
US_LOG_PRINTF(US_COLOR_GRAY, "DEBUG", US_COLOR_GRAY, x_msg, ##__VA_ARGS__); \
|
||||
} \
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
#include "memsink.h"
|
||||
|
||||
|
||||
memsink_s *memsink_init(
|
||||
us_memsink_s *us_memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout) {
|
||||
|
||||
memsink_s *sink;
|
||||
A_CALLOC(sink, 1);
|
||||
us_memsink_s *sink;
|
||||
US_CALLOC(sink, 1);
|
||||
sink->name = name;
|
||||
sink->obj = obj;
|
||||
sink->server = server;
|
||||
@@ -36,57 +36,56 @@ memsink_s *memsink_init(
|
||||
sink->client_ttl = client_ttl;
|
||||
sink->timeout = timeout;
|
||||
sink->fd = -1;
|
||||
sink->mem = MAP_FAILED;
|
||||
atomic_init(&sink->has_clients, false);
|
||||
|
||||
LOG_INFO("Using %s-sink: %s", name, obj);
|
||||
US_LOG_INFO("Using %s-sink: %s", name, obj);
|
||||
|
||||
mode_t mask = umask(0);
|
||||
const mode_t mask = umask(0);
|
||||
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
|
||||
umask(mask);
|
||||
if (sink->fd == -1) {
|
||||
umask(mask);
|
||||
LOG_PERROR("%s-sink: Can't open shared memory", name);
|
||||
US_LOG_PERROR("%s-sink: Can't open shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (sink->server && ftruncate(sink->fd, sizeof(memsink_shared_s)) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't truncate shared memory", name);
|
||||
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s)) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't truncate shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((sink->mem = memsink_shared_map(sink->fd)) == NULL) {
|
||||
LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||
if ((sink->mem = us_memsink_shared_map(sink->fd)) == NULL) {
|
||||
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return sink;
|
||||
|
||||
error:
|
||||
memsink_destroy(sink);
|
||||
us_memsink_destroy(sink);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void memsink_destroy(memsink_s *sink) {
|
||||
if (sink->mem != MAP_FAILED) {
|
||||
if (memsink_shared_unmap(sink->mem) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
|
||||
void us_memsink_destroy(us_memsink_s *sink) {
|
||||
if (sink->mem != NULL) {
|
||||
if (us_memsink_shared_unmap(sink->mem) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
|
||||
}
|
||||
}
|
||||
if (sink->fd >= 0) {
|
||||
if (close(sink->fd) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't close shared memory fd", sink->name);
|
||||
}
|
||||
if (sink->rm && shm_unlink(sink->obj) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't remove shared memory", sink->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(sink);
|
||||
}
|
||||
|
||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
// Return true (the need to write to memsink) on any of these conditions:
|
||||
// - EWOULDBLOCK - we have an active client;
|
||||
// - Incorrect magic or version - need to first write;
|
||||
@@ -100,97 +99,97 @@ bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||
atomic_store(&sink->has_clients, true);
|
||||
return true;
|
||||
}
|
||||
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sink->mem->magic != MEMSINK_MAGIC || sink->mem->version != MEMSINK_VERSION) {
|
||||
if (sink->mem->magic != US_MEMSINK_MAGIC || sink->mem->version != US_MEMSINK_VERSION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
|
||||
const bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic());
|
||||
atomic_store(&sink->has_clients, has_clients);
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return false;
|
||||
}
|
||||
return (has_clients || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
||||
return (has_clients || !US_FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
||||
}
|
||||
|
||||
int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame) {
|
||||
assert(sink->server);
|
||||
|
||||
const long double now = get_now_monotonic();
|
||||
const long double now = us_get_now_monotonic();
|
||||
|
||||
if (frame->used > MEMSINK_MAX_DATA) {
|
||||
LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
|
||||
sink->name, frame->used, MEMSINK_MAX_DATA);
|
||||
if (frame->used > US_MEMSINK_MAX_DATA) {
|
||||
US_LOG_ERROR("%s-sink: Can't put frame: is too big (%zu > %zu)",
|
||||
sink->name, frame->used, US_MEMSINK_MAX_DATA);
|
||||
return 0; // -2
|
||||
}
|
||||
|
||||
if (flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||
LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
|
||||
if (us_flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||
US_LOG_VERBOSE("%s-sink: >>>>> Exposing new frame ...", sink->name);
|
||||
|
||||
sink->last_id = get_now_id();
|
||||
sink->last_id = us_get_now_id();
|
||||
sink->mem->id = sink->last_id;
|
||||
|
||||
memcpy(sink->mem->data, frame->data, frame->used);
|
||||
sink->mem->used = frame->used;
|
||||
FRAME_COPY_META(frame, sink->mem);
|
||||
US_FRAME_COPY_META(frame, sink->mem);
|
||||
|
||||
sink->mem->magic = MEMSINK_MAGIC;
|
||||
sink->mem->version = MEMSINK_VERSION;
|
||||
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic()));
|
||||
sink->mem->magic = US_MEMSINK_MAGIC;
|
||||
sink->mem->version = US_MEMSINK_VERSION;
|
||||
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > us_get_now_monotonic()));
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
|
||||
sink->name, get_now_monotonic() - now);
|
||||
US_LOG_VERBOSE("%s-sink: Exposed new frame; full exposition time = %.3Lf",
|
||||
sink->name, us_get_now_monotonic() - now);
|
||||
|
||||
} else if (errno == EWOULDBLOCK) {
|
||||
LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
|
||||
US_LOG_VERBOSE("%s-sink: ===== Shared memory is busy now; frame skipped", sink->name);
|
||||
|
||||
} else {
|
||||
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int memsink_client_get(memsink_s *sink, frame_s *frame) { // cppcheck-suppress unusedFunction
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame) { // cppcheck-suppress unusedFunction
|
||||
assert(!sink->server); // Client only
|
||||
|
||||
if (flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
return -2;
|
||||
}
|
||||
LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int retval = -2; // Not updated
|
||||
if (sink->mem->magic == MEMSINK_MAGIC) {
|
||||
if (sink->mem->version != MEMSINK_VERSION) {
|
||||
LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
|
||||
sink->name, sink->mem->version, MEMSINK_VERSION);
|
||||
if (sink->mem->magic == US_MEMSINK_MAGIC) {
|
||||
if (sink->mem->version != US_MEMSINK_VERSION) {
|
||||
US_LOG_ERROR("%s-sink: Protocol version mismatch: sink=%u, required=%u",
|
||||
sink->name, sink->mem->version, US_MEMSINK_VERSION);
|
||||
retval = -1;
|
||||
goto done;
|
||||
}
|
||||
if (sink->mem->id != sink->last_id) { // When updated
|
||||
sink->last_id = sink->mem->id;
|
||||
frame_set_data(frame, sink->mem->data, sink->mem->used);
|
||||
FRAME_COPY_META(sink->mem, frame);
|
||||
us_frame_set_data(frame, sink->mem->data, sink->mem->used);
|
||||
US_FRAME_COPY_META(sink->mem, frame);
|
||||
retval = 0;
|
||||
}
|
||||
sink->mem->last_client_ts = get_now_monotonic();
|
||||
sink->mem->last_client_ts = us_get_now_monotonic();
|
||||
}
|
||||
|
||||
done:
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
US_LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return -1;
|
||||
}
|
||||
return retval;
|
||||
|
||||
@@ -42,27 +42,27 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *obj;
|
||||
bool server;
|
||||
bool rm;
|
||||
unsigned client_ttl; // Only for server
|
||||
unsigned timeout;
|
||||
const char *name;
|
||||
const char *obj;
|
||||
bool server;
|
||||
bool rm;
|
||||
unsigned client_ttl; // Only for server
|
||||
unsigned timeout;
|
||||
|
||||
int fd;
|
||||
memsink_shared_s *mem;
|
||||
us_memsink_shared_s *mem;
|
||||
uint64_t last_id;
|
||||
atomic_bool has_clients; // Only for server
|
||||
} memsink_s;
|
||||
} us_memsink_s;
|
||||
|
||||
|
||||
memsink_s *memsink_init(
|
||||
us_memsink_s *us_memsink_init(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, unsigned client_ttl, unsigned timeout);
|
||||
|
||||
void memsink_destroy(memsink_s *sink);
|
||||
void us_memsink_destroy(us_memsink_s *sink);
|
||||
|
||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame);
|
||||
int memsink_server_put(memsink_s *sink, const frame_s *frame);
|
||||
bool us_memsink_server_check(us_memsink_s *sink, const us_frame_s *frame);
|
||||
int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame);
|
||||
|
||||
int memsink_client_get(memsink_s *sink, frame_s *frame);
|
||||
int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame);
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
#include <sys/mman.h>
|
||||
|
||||
|
||||
#define MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||
#define MEMSINK_VERSION ((uint32_t)2)
|
||||
#define US_MEMSINK_MAGIC ((uint64_t)0xCAFEBABECAFEBABE)
|
||||
#define US_MEMSINK_VERSION ((uint32_t)2)
|
||||
|
||||
#ifndef CFG_MEMSINK_MAX_DATA
|
||||
# define CFG_MEMSINK_MAX_DATA 33554432
|
||||
#ifndef US_CFG_MEMSINK_MAX_DATA
|
||||
# define US_CFG_MEMSINK_MAX_DATA 33554432
|
||||
#endif
|
||||
#define MEMSINK_MAX_DATA ((size_t)(CFG_MEMSINK_MAX_DATA))
|
||||
#define US_MEMSINK_MAX_DATA ((size_t)(US_CFG_MEMSINK_MAX_DATA))
|
||||
|
||||
|
||||
typedef struct {
|
||||
@@ -58,14 +58,14 @@ typedef struct {
|
||||
|
||||
long double last_client_ts;
|
||||
|
||||
uint8_t data[MEMSINK_MAX_DATA];
|
||||
} memsink_shared_s;
|
||||
uint8_t data[US_MEMSINK_MAX_DATA];
|
||||
} us_memsink_shared_s;
|
||||
|
||||
|
||||
INLINE memsink_shared_s *memsink_shared_map(int fd) {
|
||||
memsink_shared_s *mem = mmap(
|
||||
INLINE us_memsink_shared_s *us_memsink_shared_map(int fd) {
|
||||
us_memsink_shared_s *mem = mmap(
|
||||
NULL,
|
||||
sizeof(memsink_shared_s),
|
||||
sizeof(us_memsink_shared_s),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
@@ -78,7 +78,7 @@ INLINE memsink_shared_s *memsink_shared_map(int fd) {
|
||||
return mem;
|
||||
}
|
||||
|
||||
INLINE int memsink_shared_unmap(memsink_shared_s *mem) {
|
||||
INLINE int us_memsink_shared_unmap(us_memsink_shared_s *mem) {
|
||||
assert(mem != NULL);
|
||||
return munmap(mem, sizeof(memsink_shared_s));
|
||||
return munmap(mem, sizeof(us_memsink_shared_s));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "options.h"
|
||||
|
||||
|
||||
void build_short_options(const struct option opts[], char *short_opts, size_t size) {
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size) {
|
||||
memset(short_opts, 0, size);
|
||||
for (unsigned short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) {
|
||||
assert(short_index < size - 3);
|
||||
|
||||
@@ -30,4 +30,4 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
void build_short_options(const struct option opts[], char *short_opts, size_t size);
|
||||
void us_build_short_options(const struct option opts[], char *short_opts, size_t size);
|
||||
|
||||
@@ -72,26 +72,25 @@ extern char **environ;
|
||||
|
||||
|
||||
#ifdef HAS_PDEATHSIG
|
||||
INLINE int process_track_parent_death(void) {
|
||||
pid_t parent = getppid();
|
||||
INLINE int us_process_track_parent_death(void) {
|
||||
const pid_t parent = getppid();
|
||||
int signum = SIGTERM;
|
||||
# if defined(__linux__)
|
||||
int retval = prctl(PR_SET_PDEATHSIG, signum);
|
||||
const int retval = prctl(PR_SET_PDEATHSIG, signum);
|
||||
# elif defined(__FreeBSD__)
|
||||
int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
|
||||
const int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
|
||||
# else
|
||||
# error WTF?
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
LOG_PERROR("Can't set to receive SIGTERM on parent process death");
|
||||
US_LOG_PERROR("Can't set to receive SIGTERM on parent process death");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (kill(parent, 0) < 0) {
|
||||
LOG_PERROR("The parent process %d is already dead", parent);
|
||||
US_LOG_PERROR("The parent process %d is already dead", parent);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -99,21 +98,21 @@ INLINE int process_track_parent_death(void) {
|
||||
#ifdef WITH_SETPROCTITLE
|
||||
# pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
# pragma GCC diagnostic push
|
||||
INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix) {
|
||||
INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefix) {
|
||||
# pragma GCC diagnostic pop
|
||||
|
||||
char *cmdline = NULL;
|
||||
size_t allocated = 2048;
|
||||
size_t used = 0;
|
||||
|
||||
A_REALLOC(cmdline, allocated);
|
||||
US_REALLOC(cmdline, allocated);
|
||||
cmdline[0] = '\0';
|
||||
|
||||
for (int index = 0; index < argc; ++index) {
|
||||
size_t arg_len = strlen(argv[index]);
|
||||
if (used + arg_len + 16 >= allocated) {
|
||||
allocated += arg_len + 2048;
|
||||
A_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
||||
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
|
||||
}
|
||||
|
||||
strcat(cmdline, " ");
|
||||
@@ -130,18 +129,16 @@ INLINE void process_set_name_prefix(int argc, char *argv[], const char *prefix)
|
||||
}
|
||||
#endif
|
||||
|
||||
INLINE void process_notify_parent(void) {
|
||||
pid_t parent = getppid();
|
||||
|
||||
INLINE void us_process_notify_parent(void) {
|
||||
const pid_t parent = getppid();
|
||||
if (kill(parent, SIGUSR2) < 0) {
|
||||
LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
|
||||
US_LOG_PERROR("Can't send SIGUSR2 to the parent process %d", parent);
|
||||
}
|
||||
}
|
||||
|
||||
INLINE void process_suicide(void) {
|
||||
pid_t pid = getpid();
|
||||
|
||||
INLINE void us_process_suicide(void) {
|
||||
const pid_t pid = getpid();
|
||||
if (kill(pid, SIGTERM) < 0) {
|
||||
LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
|
||||
US_LOG_PERROR("Can't send SIGTERM to own pid %d", pid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,37 +41,38 @@
|
||||
|
||||
|
||||
#ifdef PTHREAD_MAX_NAMELEN_NP
|
||||
# define MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
||||
# define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
||||
#else
|
||||
# define MAX_THREAD_NAME ((size_t)16)
|
||||
# define US_MAX_THREAD_NAME ((size_t)16)
|
||||
#endif
|
||||
|
||||
#define A_THREAD_CREATE(_tid, _func, _arg) assert(!pthread_create(_tid, NULL, _func, _arg))
|
||||
#define A_THREAD_JOIN(_tid) assert(!pthread_join(_tid, NULL))
|
||||
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
||||
#define US_THREAD_JOIN(x_tid) assert(!pthread_join((x_tid), NULL))
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
# define A_THREAD_RENAME(_fmt, ...) { \
|
||||
char _new_tname_buf[MAX_THREAD_NAME] = {0}; \
|
||||
assert(snprintf(_new_tname_buf, MAX_THREAD_NAME, _fmt, ##__VA_ARGS__) > 0); \
|
||||
thread_set_name(_new_tname_buf); \
|
||||
# define US_THREAD_RENAME(x_fmt, ...) { \
|
||||
char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
||||
assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
|
||||
us_thread_set_name(m_new_tname_buf); \
|
||||
}
|
||||
#else
|
||||
# define A_THREAD_RENAME(_fmt, ...)
|
||||
# define US_THREAD_RENAME(_fmt, ...)
|
||||
#endif
|
||||
|
||||
#define A_MUTEX_INIT(_mutex) assert(!pthread_mutex_init(_mutex, NULL))
|
||||
#define A_MUTEX_DESTROY(_mutex) assert(!pthread_mutex_destroy(_mutex))
|
||||
#define A_MUTEX_LOCK(_mutex) assert(!pthread_mutex_lock(_mutex))
|
||||
#define A_MUTEX_UNLOCK(_mutex) assert(!pthread_mutex_unlock(_mutex))
|
||||
#define US_MUTEX_INIT(x_mutex) assert(!pthread_mutex_init(&(x_mutex), NULL))
|
||||
#define US_MUTEX_DESTROY(x_mutex) assert(!pthread_mutex_destroy(&(x_mutex)))
|
||||
#define US_MUTEX_LOCK(x_mutex) assert(!pthread_mutex_lock(&(x_mutex)))
|
||||
#define US_MUTEX_UNLOCK(x_mutex) assert(!pthread_mutex_unlock(&(x_mutex)))
|
||||
|
||||
#define A_COND_INIT(_cond) assert(!pthread_cond_init(_cond, NULL))
|
||||
#define A_COND_DESTROY(_cond) assert(!pthread_cond_destroy(_cond))
|
||||
#define A_COND_SIGNAL(...) assert(!pthread_cond_signal(__VA_ARGS__))
|
||||
#define A_COND_WAIT_TRUE(_var, _cond, _mutex) { while(!(_var)) assert(!pthread_cond_wait(_cond, _mutex)); }
|
||||
#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_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
|
||||
INLINE void thread_set_name(const char *name) {
|
||||
INLINE void us_thread_set_name(const char *name) {
|
||||
# if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
@@ -79,45 +80,45 @@ INLINE void thread_set_name(const char *name) {
|
||||
# elif defined(__NetBSD__)
|
||||
pthread_setname_np(pthread_self(), "%s", (void *)name);
|
||||
# else
|
||||
# error thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# error us_thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
INLINE void thread_get_name(char *name) { // Always required for logging
|
||||
INLINE void us_thread_get_name(char *name) { // Always required for logging
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
int retval = -1;
|
||||
# if defined(__linux__) || defined (__NetBSD__)
|
||||
retval = pthread_getname_np(pthread_self(), name, MAX_THREAD_NAME);
|
||||
retval = pthread_getname_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
||||
# elif \
|
||||
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|
||||
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|
||||
|| defined(__DragonFly__)
|
||||
pthread_get_name_np(pthread_self(), name, MAX_THREAD_NAME);
|
||||
pthread_get_name_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
||||
if (name[0] != '\0') {
|
||||
retval = 0;
|
||||
}
|
||||
# else
|
||||
# error thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# error us_thread_get_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
if (retval < 0) {
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
pid_t tid = syscall(SYS_gettid);
|
||||
const pid_t tid = syscall(SYS_gettid);
|
||||
#elif defined(__FreeBSD__)
|
||||
pid_t tid = syscall(SYS_thr_self);
|
||||
const pid_t tid = syscall(SYS_thr_self);
|
||||
#elif defined(__OpenBSD__)
|
||||
pid_t tid = syscall(SYS_getthrid);
|
||||
const pid_t tid = syscall(SYS_getthrid);
|
||||
#elif defined(__NetBSD__)
|
||||
pid_t tid = syscall(SYS__lwp_self);
|
||||
const pid_t tid = syscall(SYS__lwp_self);
|
||||
#elif defined(__DragonFly__)
|
||||
pid_t tid = syscall(SYS_lwp_gettid);
|
||||
const pid_t tid = syscall(SYS_lwp_gettid);
|
||||
#else
|
||||
pid_t tid = 0; // Makes cppcheck happy
|
||||
const pid_t tid = 0; // Makes cppcheck happy
|
||||
# warning gettid() not implemented
|
||||
#endif
|
||||
assert(snprintf(name, MAX_THREAD_NAME, "tid=%d", tid) > 0);
|
||||
assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
|
||||
|
||||
#ifdef WITH_PTHREAD_NP
|
||||
}
|
||||
|
||||
108
src/libs/tools.h
108
src/libs/tools.h
@@ -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!
|
||||
@@ -53,36 +59,41 @@
|
||||
#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))
|
||||
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
||||
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
||||
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
|
||||
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
||||
|
||||
#define A_ASPRINTF(_dest, _fmt, ...) assert(asprintf(&(_dest), _fmt, ##__VA_ARGS__) >= 0)
|
||||
|
||||
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
||||
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
|
||||
|
||||
|
||||
INLINE const char *bool_to_string(bool flag) {
|
||||
INLINE char *us_strdup(const char *str) {
|
||||
char *const new = strdup(str);
|
||||
assert(new != NULL);
|
||||
return new;
|
||||
}
|
||||
|
||||
INLINE const char *us_bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
INLINE size_t align_size(size_t size, size_t to) {
|
||||
INLINE size_t us_align_size(size_t size, size_t to) {
|
||||
return ((size + (to - 1)) & ~(to - 1));
|
||||
}
|
||||
|
||||
INLINE unsigned min_u(unsigned a, unsigned b) {
|
||||
INLINE unsigned us_min_u(unsigned a, unsigned b) {
|
||||
return (a < b ? a : b);
|
||||
}
|
||||
|
||||
INLINE unsigned max_u(unsigned a, unsigned b) {
|
||||
INLINE unsigned us_max_u(unsigned a, unsigned b) {
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
INLINE long long floor_ms(long double now) {
|
||||
INLINE long long us_floor_ms(long double now) {
|
||||
return (long long)now - (now < (long long)now); // floor()
|
||||
}
|
||||
|
||||
INLINE uint32_t triple_u32(uint32_t x) {
|
||||
INLINE uint32_t us_triple_u32(uint32_t x) {
|
||||
// https://nullprogram.com/blog/2018/07/31/
|
||||
x ^= x >> 17;
|
||||
x *= UINT32_C(0xED5AD4BB);
|
||||
@@ -94,7 +105,7 @@ INLINE uint32_t triple_u32(uint32_t x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
INLINE void us_get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(clk_id, &ts));
|
||||
*sec = ts.tv_sec;
|
||||
@@ -107,47 +118,47 @@ INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
}
|
||||
|
||||
#if defined(CLOCK_MONOTONIC_RAW)
|
||||
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_RAW
|
||||
#elif defined(CLOCK_MONOTONIC_FAST)
|
||||
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC_FAST
|
||||
#else
|
||||
# define X_CLOCK_MONOTONIC CLOCK_MONOTONIC
|
||||
# define _X_CLOCK_MONOTONIC CLOCK_MONOTONIC
|
||||
#endif
|
||||
|
||||
INLINE long double get_now_monotonic(void) {
|
||||
INLINE long double us_get_now_monotonic(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
get_now(X_CLOCK_MONOTONIC, &sec, &msec);
|
||||
us_get_now(_X_CLOCK_MONOTONIC, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE uint64_t get_now_monotonic_u64(void) {
|
||||
INLINE uint64_t us_get_now_monotonic_u64(void) {
|
||||
struct timespec ts;
|
||||
assert(!clock_gettime(X_CLOCK_MONOTONIC, &ts));
|
||||
assert(!clock_gettime(_X_CLOCK_MONOTONIC, &ts));
|
||||
return (uint64_t)(ts.tv_nsec / 1000) + (uint64_t)ts.tv_sec * 1000000;
|
||||
}
|
||||
|
||||
#undef X_CLOCK_MONOTONIC
|
||||
#undef _X_CLOCK_MONOTONIC
|
||||
|
||||
INLINE uint64_t get_now_id(void) {
|
||||
uint64_t now = get_now_monotonic_u64();
|
||||
return (uint64_t)triple_u32(now) | ((uint64_t)triple_u32(now + 12345) << 32);
|
||||
INLINE uint64_t us_get_now_id(void) {
|
||||
const uint64_t now = us_get_now_monotonic_u64();
|
||||
return (uint64_t)us_triple_u32(now) | ((uint64_t)us_triple_u32(now + 12345) << 32);
|
||||
}
|
||||
|
||||
INLINE long double get_now_real(void) {
|
||||
INLINE long double us_get_now_real(void) {
|
||||
time_t sec;
|
||||
long msec;
|
||||
get_now(CLOCK_REALTIME, &sec, &msec);
|
||||
us_get_now(CLOCK_REALTIME, &sec, &msec);
|
||||
return (long double)sec + ((long double)msec) / 1000;
|
||||
}
|
||||
|
||||
INLINE unsigned get_cores_available(void) {
|
||||
INLINE unsigned us_get_cores_available(void) {
|
||||
long cores_sysconf = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
cores_sysconf = (cores_sysconf < 0 ? 0 : cores_sysconf);
|
||||
return max_u(min_u(cores_sysconf, 4), 1);
|
||||
return us_max_u(us_min_u(cores_sysconf, 4), 1);
|
||||
}
|
||||
|
||||
INLINE void ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
INLINE void us_ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
ts->tv_sec = (long)ld;
|
||||
ts->tv_nsec = (ld - ts->tv_sec) * 1000000000L;
|
||||
if (ts->tv_nsec > 999999999L) {
|
||||
@@ -156,17 +167,17 @@ INLINE void ld_to_timespec(long double ld, struct timespec *ts) {
|
||||
}
|
||||
}
|
||||
|
||||
INLINE long double timespec_to_ld(const struct timespec *ts) {
|
||||
INLINE long double us_timespec_to_ld(const struct timespec *ts) {
|
||||
return ts->tv_sec + ((long double)ts->tv_nsec) / 1000000000;
|
||||
}
|
||||
|
||||
INLINE int flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
long double deadline_ts = get_now_monotonic() + timeout;
|
||||
INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
|
||||
const long double deadline_ts = us_get_now_monotonic() + timeout;
|
||||
int retval = -1;
|
||||
|
||||
while (true) {
|
||||
retval = flock(fd, LOCK_EX | LOCK_NB);
|
||||
if (retval == 0 || errno != EWOULDBLOCK || get_now_monotonic() > deadline_ts) {
|
||||
if (retval == 0 || errno != EWOULDBLOCK || us_get_now_monotonic() > deadline_ts) {
|
||||
break;
|
||||
}
|
||||
if (usleep(1000) < 0) {
|
||||
@@ -176,15 +187,34 @@ 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);
|
||||
INLINE char *us_errno_to_string(int error) {
|
||||
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';
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@
|
||||
typedef struct {
|
||||
struct jpeg_error_mgr mgr; // Default manager
|
||||
jmp_buf jmp;
|
||||
const frame_s *frame;
|
||||
const us_frame_s *frame;
|
||||
} _jpeg_error_manager_s;
|
||||
|
||||
|
||||
static void _jpeg_error_handler(j_common_ptr jpeg);
|
||||
|
||||
|
||||
int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
|
||||
assert(is_jpeg(src->format));
|
||||
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
|
||||
assert(us_is_jpeg(src->format));
|
||||
|
||||
volatile int retval = 0;
|
||||
|
||||
@@ -57,7 +57,7 @@ int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
|
||||
|
||||
jpeg_start_decompress(&jpeg);
|
||||
|
||||
frame_copy_meta(src, dest);
|
||||
us_frame_copy_meta(src, dest);
|
||||
dest->format = V4L2_PIX_FMT_RGB24;
|
||||
dest->width = jpeg.output_width;
|
||||
dest->height = jpeg.output_height;
|
||||
@@ -68,10 +68,10 @@ int unjpeg(const frame_s *src, frame_s *dest, bool decode) {
|
||||
JSAMPARRAY scanlines;
|
||||
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1);
|
||||
|
||||
frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
|
||||
us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
|
||||
while (jpeg.output_scanline < jpeg.output_height) {
|
||||
jpeg_read_scanlines(&jpeg, scanlines, 1);
|
||||
frame_append_data(dest, scanlines[0], dest->stride);
|
||||
us_frame_append_data(dest, scanlines[0], dest->stride);
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&jpeg);
|
||||
@@ -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 JPEG: %s", msg);
|
||||
US_LOG_ERROR("Can't decompress JPEG: %s", msg);
|
||||
longjmp(jpeg_error->jmp, -1);
|
||||
}
|
||||
|
||||
@@ -37,4 +37,4 @@
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
int unjpeg(const frame_s *src, frame_s *dest, bool decode);
|
||||
int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode);
|
||||
|
||||
@@ -26,17 +26,15 @@
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#ifndef CFG_XIOCTL_RETRIES
|
||||
# define CFG_XIOCTL_RETRIES 4
|
||||
#ifndef US_CFG_XIOCTL_RETRIES
|
||||
# define US_CFG_XIOCTL_RETRIES 4
|
||||
#endif
|
||||
#define XIOCTL_RETRIES ((unsigned)(CFG_XIOCTL_RETRIES))
|
||||
#define _XIOCTL_RETRIES ((unsigned)(US_CFG_XIOCTL_RETRIES))
|
||||
|
||||
|
||||
INLINE int xioctl(int fd, int request, void *arg) {
|
||||
int retries = XIOCTL_RETRIES;
|
||||
INLINE int us_xioctl(int fd, int request, void *arg) {
|
||||
int retries = _XIOCTL_RETRIES;
|
||||
int retval = -1;
|
||||
|
||||
do {
|
||||
|
||||
@@ -23,85 +23,83 @@
|
||||
#include "blank.h"
|
||||
|
||||
|
||||
static frame_s *_init_internal(void);
|
||||
static frame_s *_init_external(const char *path);
|
||||
static us_frame_s *_init_internal(void);
|
||||
static us_frame_s *_init_external(const char *path);
|
||||
|
||||
|
||||
frame_s *blank_frame_init(const char *path) {
|
||||
frame_s *blank = NULL;
|
||||
us_frame_s *us_blank_frame_init(const char *path) {
|
||||
us_frame_s *blank = NULL;
|
||||
|
||||
if (path && path[0] != '\0') {
|
||||
blank = _init_external(path);
|
||||
}
|
||||
|
||||
if (blank) {
|
||||
LOG_INFO("Using external blank placeholder: %s", path);
|
||||
if (blank != NULL) {
|
||||
US_LOG_INFO("Using external blank placeholder: %s", path);
|
||||
} else {
|
||||
blank = _init_internal();
|
||||
LOG_INFO("Using internal blank placeholder");
|
||||
US_LOG_INFO("Using internal blank placeholder");
|
||||
}
|
||||
return blank;
|
||||
}
|
||||
|
||||
static frame_s *_init_internal(void) {
|
||||
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;
|
||||
static us_frame_s *_init_internal(void) {
|
||||
us_frame_s *const blank = us_frame_init();
|
||||
us_frame_set_data(blank, US_BLANK_JPEG_DATA, US_BLANK_JPEG_DATA_SIZE);
|
||||
blank->width = US_BLANK_JPEG_WIDTH;
|
||||
blank->height = US_BLANK_JPEG_HEIGHT;
|
||||
blank->format = V4L2_PIX_FMT_JPEG;
|
||||
return blank;
|
||||
}
|
||||
|
||||
static frame_s *_init_external(const char *path) {
|
||||
static us_frame_s *_init_external(const char *path) {
|
||||
FILE *fp = NULL;
|
||||
|
||||
frame_s *blank = frame_init();
|
||||
us_frame_s *blank = us_frame_init();
|
||||
blank->format = V4L2_PIX_FMT_JPEG;
|
||||
|
||||
if ((fp = fopen(path, "rb")) == NULL) {
|
||||
LOG_PERROR("Can't open blank placeholder '%s'", path);
|
||||
US_LOG_PERROR("Can't open blank placeholder '%s'", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
# define CHUNK_SIZE ((size_t)(100 * 1024))
|
||||
while (true) {
|
||||
if (blank->used + CHUNK_SIZE >= blank->allocated) {
|
||||
frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
|
||||
us_frame_realloc_data(blank, blank->used + CHUNK_SIZE * 2);
|
||||
}
|
||||
|
||||
size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
|
||||
const size_t readed = fread(blank->data + blank->used, 1, CHUNK_SIZE, fp);
|
||||
blank->used += readed;
|
||||
|
||||
if (readed < CHUNK_SIZE) {
|
||||
if (feof(fp)) {
|
||||
break;
|
||||
} else {
|
||||
LOG_PERROR("Can't read blank placeholder");
|
||||
US_LOG_PERROR("Can't read blank placeholder");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
# undef CHUNK_SIZE
|
||||
|
||||
frame_s *decoded = frame_init();
|
||||
if (unjpeg(blank, decoded, false) < 0) {
|
||||
frame_destroy(decoded);
|
||||
us_frame_s *const decoded = us_frame_init();
|
||||
if (us_unjpeg(blank, decoded, false) < 0) {
|
||||
us_frame_destroy(decoded);
|
||||
goto error;
|
||||
}
|
||||
blank->width = decoded->width;
|
||||
blank->height = decoded->height;
|
||||
frame_destroy(decoded);
|
||||
us_frame_destroy(decoded);
|
||||
|
||||
goto ok;
|
||||
|
||||
error:
|
||||
frame_destroy(blank);
|
||||
us_frame_destroy(blank);
|
||||
blank = NULL;
|
||||
|
||||
ok:
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
US_DELETE(fp, fclose);
|
||||
|
||||
return blank;
|
||||
}
|
||||
|
||||
@@ -35,4 +35,4 @@
|
||||
#include "data/blank_jpeg.h"
|
||||
|
||||
|
||||
frame_s *blank_frame_init(const char *path);
|
||||
us_frame_s *us_blank_frame_init(const char *path);
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
#include "blank_jpeg.h"
|
||||
|
||||
|
||||
const unsigned BLANK_JPEG_WIDTH = 640;
|
||||
const unsigned BLANK_JPEG_HEIGHT = 480;
|
||||
const unsigned US_BLANK_JPEG_WIDTH = 640;
|
||||
const unsigned US_BLANK_JPEG_HEIGHT = 480;
|
||||
|
||||
const size_t BLANK_JPEG_DATA_SIZE = 13845;
|
||||
const uint8_t BLANK_JPEG_DATA[] = {
|
||||
const size_t US_BLANK_JPEG_DATA_SIZE = 13845;
|
||||
const uint8_t US_BLANK_JPEG_DATA[] = {
|
||||
0xFF, 0xD8, 0xFF, 0xE1, 0x09, 0x50, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x6E, 0x73, 0x2E, 0x61, 0x64, 0x6F, 0x62,
|
||||
0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x78, 0x61, 0x70, 0x2F, 0x31, 0x2E, 0x30, 0x2F, 0x00, 0x3C, 0x3F, 0x78, 0x70, 0x61,
|
||||
0x63, 0x6B, 0x65, 0x74, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D, 0x22, 0xEF, 0xBB, 0xBF, 0x22, 0x20, 0x69, 0x64, 0x3D,
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
extern const unsigned BLANK_JPEG_WIDTH;
|
||||
extern const unsigned BLANK_JPEG_HEIGHT;
|
||||
extern const unsigned US_BLANK_JPEG_WIDTH;
|
||||
extern const unsigned US_BLANK_JPEG_HEIGHT;
|
||||
|
||||
extern const size_t BLANK_JPEG_DATA_SIZE;
|
||||
extern const uint8_t BLANK_JPEG_DATA[];
|
||||
extern const size_t US_BLANK_JPEG_DATA_SIZE;
|
||||
extern const uint8_t US_BLANK_JPEG_DATA[];
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "index_html.h"
|
||||
|
||||
|
||||
const char *const HTML_INDEX_PAGE = " \
|
||||
const char *const US_HTML_INDEX_PAGE = " \
|
||||
<!DOCTYPE html> \
|
||||
\
|
||||
<html> \
|
||||
@@ -33,7 +33,7 @@ const char *const HTML_INDEX_PAGE = " \
|
||||
</head> \
|
||||
\
|
||||
<body> \
|
||||
<h3>µStreamer v" VERSION "</h3> \
|
||||
<h3>µStreamer v" US_VERSION "</h3> \
|
||||
<hr> \
|
||||
<ul> \
|
||||
<li> \
|
||||
|
||||
@@ -27,4 +27,4 @@
|
||||
#include "../../libs/const.h"
|
||||
|
||||
|
||||
extern const char *const HTML_INDEX_PAGE;
|
||||
extern const char *const US_HTML_INDEX_PAGE;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,79 +42,80 @@
|
||||
#include <linux/v4l2-controls.h>
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/array.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/xioctl.h"
|
||||
|
||||
|
||||
#define VIDEO_MIN_WIDTH ((unsigned)160)
|
||||
#define VIDEO_MAX_WIDTH ((unsigned)10240)
|
||||
#define US_VIDEO_MIN_WIDTH ((unsigned)160)
|
||||
#define US_VIDEO_MAX_WIDTH ((unsigned)10240)
|
||||
|
||||
#define VIDEO_MIN_HEIGHT ((unsigned)120)
|
||||
#define VIDEO_MAX_HEIGHT ((unsigned)4320)
|
||||
#define US_VIDEO_MIN_HEIGHT ((unsigned)120)
|
||||
#define US_VIDEO_MAX_HEIGHT ((unsigned)4320)
|
||||
|
||||
#define VIDEO_MAX_FPS ((unsigned)120)
|
||||
#define US_VIDEO_MAX_FPS ((unsigned)120)
|
||||
|
||||
#define STANDARD_UNKNOWN V4L2_STD_UNKNOWN
|
||||
#define STANDARDS_STR "PAL, NTSC, SECAM"
|
||||
#define US_STANDARD_UNKNOWN V4L2_STD_UNKNOWN
|
||||
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
|
||||
|
||||
#define FORMAT_UNKNOWN -1
|
||||
#define FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
|
||||
#define US_FORMAT_UNKNOWN -1
|
||||
#define US_FORMATS_STR "YUYV, UYVY, RGB565, RGB24, MJPEG, JPEG"
|
||||
|
||||
#define IO_METHOD_UNKNOWN -1
|
||||
#define IO_METHODS_STR "MMAP, USERPTR"
|
||||
#define US_IO_METHOD_UNKNOWN -1
|
||||
#define US_IO_METHODS_STR "MMAP, USERPTR"
|
||||
|
||||
|
||||
typedef struct {
|
||||
frame_s raw;
|
||||
us_frame_s raw;
|
||||
struct v4l2_buffer buf;
|
||||
int dma_fd;
|
||||
bool grabbed;
|
||||
} hw_buffer_s;
|
||||
} us_hw_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
unsigned hw_fps;
|
||||
unsigned jpeg_quality;
|
||||
size_t raw_size;
|
||||
unsigned n_bufs;
|
||||
hw_buffer_s *hw_bufs;
|
||||
bool capturing;
|
||||
bool persistent_timeout_reported;
|
||||
} device_runtime_s;
|
||||
int fd;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned format;
|
||||
unsigned stride;
|
||||
unsigned hw_fps;
|
||||
unsigned jpeg_quality;
|
||||
size_t raw_size;
|
||||
unsigned n_bufs;
|
||||
us_hw_buffer_s *hw_bufs;
|
||||
bool capturing;
|
||||
bool persistent_timeout_reported;
|
||||
} us_device_runtime_s;
|
||||
|
||||
typedef enum {
|
||||
CTL_MODE_NONE = 0,
|
||||
CTL_MODE_VALUE,
|
||||
CTL_MODE_AUTO,
|
||||
CTL_MODE_DEFAULT,
|
||||
} control_mode_e;
|
||||
} us_control_mode_e;
|
||||
|
||||
typedef struct {
|
||||
control_mode_e mode;
|
||||
int value;
|
||||
} control_s;
|
||||
us_control_mode_e mode;
|
||||
int value;
|
||||
} us_control_s;
|
||||
|
||||
typedef struct {
|
||||
control_s brightness;
|
||||
control_s contrast;
|
||||
control_s saturation;
|
||||
control_s hue;
|
||||
control_s gamma;
|
||||
control_s sharpness;
|
||||
control_s backlight_compensation;
|
||||
control_s white_balance;
|
||||
control_s gain;
|
||||
control_s color_effect;
|
||||
control_s rotate;
|
||||
control_s flip_vertical;
|
||||
control_s flip_horizontal;
|
||||
} controls_s;
|
||||
us_control_s brightness;
|
||||
us_control_s contrast;
|
||||
us_control_s saturation;
|
||||
us_control_s hue;
|
||||
us_control_s gamma;
|
||||
us_control_s sharpness;
|
||||
us_control_s backlight_compensation;
|
||||
us_control_s white_balance;
|
||||
us_control_s gain;
|
||||
us_control_s color_effect;
|
||||
us_control_s rotate;
|
||||
us_control_s flip_vertical;
|
||||
us_control_s flip_horizontal;
|
||||
} us_controls_s;
|
||||
|
||||
typedef struct {
|
||||
char *path;
|
||||
@@ -132,25 +133,25 @@ typedef struct {
|
||||
bool persistent;
|
||||
unsigned timeout;
|
||||
|
||||
controls_s ctl;
|
||||
us_controls_s ctl;
|
||||
|
||||
device_runtime_s *run;
|
||||
} device_s;
|
||||
us_device_runtime_s *run;
|
||||
} us_device_s;
|
||||
|
||||
|
||||
device_s *device_init(void);
|
||||
void device_destroy(device_s *dev);
|
||||
us_device_s *us_device_init(void);
|
||||
void us_device_destroy(us_device_s *dev);
|
||||
|
||||
int device_parse_format(const char *str);
|
||||
v4l2_std_id device_parse_standard(const char *str);
|
||||
int device_parse_io_method(const char *str);
|
||||
int us_device_parse_format(const char *str);
|
||||
v4l2_std_id us_device_parse_standard(const char *str);
|
||||
int us_device_parse_io_method(const char *str);
|
||||
|
||||
int device_open(device_s *dev);
|
||||
void device_close(device_s *dev);
|
||||
int us_device_open(us_device_s *dev);
|
||||
void us_device_close(us_device_s *dev);
|
||||
|
||||
int device_export_to_dma(device_s *dev);
|
||||
int device_switch_capturing(device_s *dev, bool enable);
|
||||
int device_select(device_s *dev, bool *has_read, bool *has_write, bool *has_error);
|
||||
int device_grab_buffer(device_s *dev, hw_buffer_s **hw);
|
||||
int device_release_buffer(device_s *dev, hw_buffer_s *hw);
|
||||
int device_consume_event(device_s *dev);
|
||||
int us_device_export_to_dma(us_device_s *dev);
|
||||
int us_device_switch_capturing(us_device_s *dev, bool enable);
|
||||
int us_device_select(us_device_s *dev, bool *has_read, bool *has_write, bool *has_error);
|
||||
int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw);
|
||||
int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw);
|
||||
int us_device_consume_event(us_device_s *dev);
|
||||
|
||||
@@ -25,112 +25,110 @@
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
const encoder_type_e type;
|
||||
const us_encoder_type_e type; // cppcheck-suppress unusedStructMember
|
||||
} _ENCODER_TYPES[] = {
|
||||
{"CPU", ENCODER_TYPE_CPU},
|
||||
{"HW", ENCODER_TYPE_HW},
|
||||
{"M2M-VIDEO", ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-IMAGE", ENCODER_TYPE_M2M_IMAGE},
|
||||
{"M2M-MJPEG", ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-JPEG", ENCODER_TYPE_M2M_IMAGE},
|
||||
{"OMX", ENCODER_TYPE_M2M_IMAGE},
|
||||
{"NOOP", ENCODER_TYPE_NOOP},
|
||||
{"CPU", US_ENCODER_TYPE_CPU},
|
||||
{"HW", US_ENCODER_TYPE_HW},
|
||||
{"M2M-VIDEO", US_ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-IMAGE", US_ENCODER_TYPE_M2M_IMAGE},
|
||||
{"M2M-MJPEG", US_ENCODER_TYPE_M2M_VIDEO},
|
||||
{"M2M-JPEG", US_ENCODER_TYPE_M2M_IMAGE},
|
||||
{"OMX", US_ENCODER_TYPE_M2M_IMAGE},
|
||||
{"NOOP", US_ENCODER_TYPE_NOOP},
|
||||
};
|
||||
|
||||
|
||||
static void *_worker_job_init(void *v_enc);
|
||||
static void _worker_job_destroy(void *v_job);
|
||||
static bool _worker_run_job(worker_s *wr);
|
||||
static bool _worker_run_job(us_worker_s *wr);
|
||||
|
||||
|
||||
#define ER(_next) enc->run->_next
|
||||
#define _ER(x_next) enc->run->x_next
|
||||
|
||||
|
||||
encoder_s *encoder_init(void) {
|
||||
encoder_runtime_s *run;
|
||||
A_CALLOC(run, 1);
|
||||
run->type = ENCODER_TYPE_CPU;
|
||||
us_encoder_s *us_encoder_init(void) {
|
||||
us_encoder_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
run->type = US_ENCODER_TYPE_CPU;
|
||||
run->quality = 80;
|
||||
A_MUTEX_INIT(&run->mutex);
|
||||
US_MUTEX_INIT(run->mutex);
|
||||
|
||||
encoder_s *enc;
|
||||
A_CALLOC(enc, 1);
|
||||
us_encoder_s *enc;
|
||||
US_CALLOC(enc, 1);
|
||||
enc->type = run->type;
|
||||
enc->n_workers = get_cores_available();
|
||||
enc->n_workers = us_get_cores_available();
|
||||
enc->run = run;
|
||||
return enc;
|
||||
}
|
||||
|
||||
void encoder_destroy(encoder_s *enc) {
|
||||
if (ER(m2ms)) {
|
||||
for (unsigned index = 0; index < ER(n_m2ms); ++index) {
|
||||
if (ER(m2ms[index])) {
|
||||
m2m_encoder_destroy(ER(m2ms[index]));
|
||||
}
|
||||
void us_encoder_destroy(us_encoder_s *enc) {
|
||||
if (_ER(m2ms) != NULL) {
|
||||
for (unsigned index = 0; index < _ER(n_m2ms); ++index) {
|
||||
US_DELETE(_ER(m2ms[index]), us_m2m_encoder_destroy)
|
||||
}
|
||||
free(ER(m2ms));
|
||||
free(_ER(m2ms));
|
||||
}
|
||||
A_MUTEX_DESTROY(&ER(mutex));
|
||||
US_MUTEX_DESTROY(_ER(mutex));
|
||||
free(enc->run);
|
||||
free(enc);
|
||||
}
|
||||
|
||||
encoder_type_e encoder_parse_type(const char *str) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
|
||||
return _ENCODER_TYPES[index].type;
|
||||
us_encoder_type_e us_encoder_parse_type(const char *str) {
|
||||
US_ARRAY_ITERATE(_ENCODER_TYPES, 0, item, {
|
||||
if (!strcasecmp(item->name, str)) {
|
||||
return item->type;
|
||||
}
|
||||
}
|
||||
return ENCODER_TYPE_UNKNOWN;
|
||||
});
|
||||
return US_ENCODER_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
const char *encoder_type_to_string(encoder_type_e type) {
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||
if (_ENCODER_TYPES[index].type == type) {
|
||||
return _ENCODER_TYPES[index].name;
|
||||
const char *us_encoder_type_to_string(us_encoder_type_e type) {
|
||||
US_ARRAY_ITERATE(_ENCODER_TYPES, 0, item, {
|
||||
if (item->type == type) {
|
||||
return item->name;
|
||||
}
|
||||
}
|
||||
});
|
||||
return _ENCODER_TYPES[0].name;
|
||||
}
|
||||
|
||||
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
|
||||
# define DR(_next) dev->run->_next
|
||||
us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *dev) {
|
||||
# define DR(x_next) dev->run->x_next
|
||||
|
||||
encoder_type_e type = (ER(cpu_forced) ? ENCODER_TYPE_CPU : enc->type);
|
||||
us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
|
||||
unsigned quality = dev->jpeg_quality;
|
||||
unsigned n_workers = min_u(enc->n_workers, DR(n_bufs));
|
||||
unsigned n_workers = us_min_u(enc->n_workers, DR(n_bufs));
|
||||
bool cpu_forced = false;
|
||||
|
||||
if (is_jpeg(DR(format)) && type != ENCODER_TYPE_HW) {
|
||||
LOG_INFO("Switching to HW encoder: the input is (M)JPEG ...");
|
||||
type = ENCODER_TYPE_HW;
|
||||
if (us_is_jpeg(DR(format)) && type != US_ENCODER_TYPE_HW) {
|
||||
US_LOG_INFO("Switching to HW encoder: the input is (M)JPEG ...");
|
||||
type = US_ENCODER_TYPE_HW;
|
||||
}
|
||||
|
||||
if (type == ENCODER_TYPE_HW) {
|
||||
if (!is_jpeg(DR(format))) {
|
||||
LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
|
||||
if (type == US_ENCODER_TYPE_HW) {
|
||||
if (!us_is_jpeg(DR(format))) {
|
||||
US_LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
|
||||
goto use_cpu;
|
||||
}
|
||||
quality = DR(jpeg_quality);
|
||||
n_workers = 1;
|
||||
|
||||
} else if (type == ENCODER_TYPE_M2M_VIDEO || type == ENCODER_TYPE_M2M_IMAGE) {
|
||||
LOG_DEBUG("Preparing M2M-%s encoder ...", (type == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
|
||||
if (ER(m2ms) == NULL) {
|
||||
A_CALLOC(ER(m2ms), n_workers);
|
||||
} else if (type == US_ENCODER_TYPE_M2M_VIDEO || type == US_ENCODER_TYPE_M2M_IMAGE) {
|
||||
US_LOG_DEBUG("Preparing M2M-%s encoder ...", (type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
|
||||
if (_ER(m2ms) == NULL) {
|
||||
US_CALLOC(_ER(m2ms), n_workers);
|
||||
}
|
||||
for (; ER(n_m2ms) < n_workers; ++ER(n_m2ms)) {
|
||||
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
char name[32];
|
||||
snprintf(name, 32, "JPEG-%u", ER(n_m2ms));
|
||||
if (type == ENCODER_TYPE_M2M_VIDEO) {
|
||||
ER(m2ms[ER(n_m2ms)]) = m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
snprintf(name, 32, "JPEG-%u", _ER(n_m2ms));
|
||||
if (type == US_ENCODER_TYPE_M2M_VIDEO) {
|
||||
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
} else {
|
||||
ER(m2ms[ER(n_m2ms)]) = m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (type == ENCODER_TYPE_NOOP) {
|
||||
} else if (type == US_ENCODER_TYPE_NOOP) {
|
||||
n_workers = 1;
|
||||
quality = 0;
|
||||
}
|
||||
@@ -138,32 +136,33 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
|
||||
goto ok;
|
||||
|
||||
use_cpu:
|
||||
type = ENCODER_TYPE_CPU;
|
||||
type = US_ENCODER_TYPE_CPU;
|
||||
quality = dev->jpeg_quality;
|
||||
|
||||
ok:
|
||||
if (type == ENCODER_TYPE_NOOP) {
|
||||
LOG_INFO("Using JPEG NOOP encoder");
|
||||
if (type == US_ENCODER_TYPE_NOOP) {
|
||||
US_LOG_INFO("Using JPEG NOOP encoder");
|
||||
} else if (quality == 0) {
|
||||
LOG_INFO("Using JPEG quality: encoder default");
|
||||
US_LOG_INFO("Using JPEG quality: encoder default");
|
||||
} else {
|
||||
LOG_INFO("Using JPEG quality: %u%%", quality);
|
||||
US_LOG_INFO("Using JPEG quality: %u%%", quality);
|
||||
}
|
||||
|
||||
A_MUTEX_LOCK(&ER(mutex));
|
||||
ER(type) = type;
|
||||
ER(quality) = quality;
|
||||
US_MUTEX_LOCK(_ER(mutex));
|
||||
_ER(type) = type;
|
||||
_ER(quality) = quality;
|
||||
if (cpu_forced) {
|
||||
ER(cpu_forced) = true;
|
||||
_ER(cpu_forced) = true;
|
||||
}
|
||||
A_MUTEX_UNLOCK(&ER(mutex));
|
||||
US_MUTEX_UNLOCK(_ER(mutex));
|
||||
|
||||
long double desired_interval = 0;
|
||||
if (dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)) {
|
||||
desired_interval = (long double)1 / dev->desired_fps;
|
||||
}
|
||||
const long double desired_interval = (
|
||||
dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)
|
||||
? (long double)1 / dev->desired_fps
|
||||
: 0
|
||||
);
|
||||
|
||||
return workers_pool_init(
|
||||
return us_workers_pool_init(
|
||||
"JPEG", "jw", n_workers, desired_interval,
|
||||
_worker_job_init, (void *)enc,
|
||||
_worker_job_destroy,
|
||||
@@ -172,64 +171,61 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
|
||||
# undef DR
|
||||
}
|
||||
|
||||
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality) {
|
||||
A_MUTEX_LOCK(&ER(mutex));
|
||||
*type = ER(type);
|
||||
*quality = ER(quality);
|
||||
A_MUTEX_UNLOCK(&ER(mutex));
|
||||
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality) {
|
||||
US_MUTEX_LOCK(_ER(mutex));
|
||||
*type = _ER(type);
|
||||
*quality = _ER(quality);
|
||||
US_MUTEX_UNLOCK(_ER(mutex));
|
||||
}
|
||||
|
||||
static void *_worker_job_init(void *v_enc) {
|
||||
encoder_job_s *job;
|
||||
A_CALLOC(job, 1);
|
||||
job->enc = (encoder_s *)v_enc;
|
||||
job->dest = frame_init();
|
||||
us_encoder_job_s *job;
|
||||
US_CALLOC(job, 1);
|
||||
job->enc = (us_encoder_s *)v_enc;
|
||||
job->dest = us_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);
|
||||
us_encoder_job_s *job = (us_encoder_job_s *)v_job;
|
||||
us_frame_destroy(job->dest);
|
||||
free(job);
|
||||
}
|
||||
|
||||
#undef ER
|
||||
static bool _worker_run_job(us_worker_s *wr) {
|
||||
us_encoder_job_s *job = (us_encoder_job_s *)wr->job;
|
||||
us_encoder_s *enc = job->enc; // Just for _ER()
|
||||
us_frame_s *src = &job->hw->raw;
|
||||
us_frame_s *dest = job->dest;
|
||||
|
||||
static bool _worker_run_job(worker_s *wr) {
|
||||
encoder_job_s *job = (encoder_job_s *)wr->job;
|
||||
frame_s *src = &job->hw->raw;
|
||||
frame_s *dest = job->dest;
|
||||
assert(_ER(type) != US_ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
# define ER(_next) job->enc->run->_next
|
||||
|
||||
assert(ER(type) != ENCODER_TYPE_UNKNOWN);
|
||||
|
||||
if (ER(type) == ENCODER_TYPE_CPU) {
|
||||
LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
|
||||
if (_ER(type) == US_ENCODER_TYPE_CPU) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
cpu_encoder_compress(src, dest, ER(quality));
|
||||
us_cpu_encoder_compress(src, dest, _ER(quality));
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_HW) {
|
||||
LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
|
||||
} else if (_ER(type) == US_ENCODER_TYPE_HW) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
hw_encoder_compress(src, dest);
|
||||
us_hw_encoder_compress(src, dest);
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_M2M_VIDEO || ER(type) == ENCODER_TYPE_M2M_IMAGE) {
|
||||
LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
|
||||
(ER(type) == ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
|
||||
if (m2m_encoder_compress(ER(m2ms[wr->number]), src, dest, false) < 0) {
|
||||
} else if (_ER(type) == US_ENCODER_TYPE_M2M_VIDEO || _ER(type) == US_ENCODER_TYPE_M2M_IMAGE) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
|
||||
(_ER(type) == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
|
||||
if (us_m2m_encoder_compress(_ER(m2ms[wr->number]), src, dest, false) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_NOOP) {
|
||||
LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u",
|
||||
} else if (_ER(type) == US_ENCODER_TYPE_NOOP) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using NOOP (do nothing): worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
usleep(5000); // Просто чтобы работала логика desired_fps
|
||||
dest->encode_end_ts = get_now_monotonic(); // frame_encoding_end()
|
||||
dest->encode_end_ts = us_get_now_monotonic(); // us_frame_encoding_end()
|
||||
}
|
||||
|
||||
LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
|
||||
US_LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
|
||||
job->dest->used,
|
||||
job->dest->encode_end_ts - job->dest->encode_begin_ts,
|
||||
wr->name,
|
||||
@@ -238,12 +234,10 @@ static bool _worker_run_job(worker_s *wr) {
|
||||
return true;
|
||||
|
||||
error:
|
||||
LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
|
||||
LOG_ERROR("Error while compressing buffer, falling back to CPU");
|
||||
A_MUTEX_LOCK(&ER(mutex));
|
||||
ER(cpu_forced) = true;
|
||||
A_MUTEX_UNLOCK(&ER(mutex));
|
||||
US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
|
||||
US_LOG_ERROR("Error while compressing buffer, falling back to CPU");
|
||||
US_MUTEX_LOCK(_ER(mutex));
|
||||
_ER(cpu_forced) = true;
|
||||
US_MUTEX_UNLOCK(_ER(mutex));
|
||||
return false;
|
||||
|
||||
# undef ER
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/array.h"
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
@@ -46,46 +47,46 @@
|
||||
#define ENCODER_TYPES_STR "CPU, HW, M2M-VIDEO, M2M-IMAGE, NOOP"
|
||||
|
||||
typedef enum {
|
||||
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
|
||||
ENCODER_TYPE_CPU,
|
||||
ENCODER_TYPE_HW,
|
||||
ENCODER_TYPE_M2M_VIDEO,
|
||||
ENCODER_TYPE_M2M_IMAGE,
|
||||
ENCODER_TYPE_NOOP,
|
||||
} encoder_type_e;
|
||||
US_ENCODER_TYPE_UNKNOWN, // Only for us_encoder_parse_type() and main()
|
||||
US_ENCODER_TYPE_CPU,
|
||||
US_ENCODER_TYPE_HW,
|
||||
US_ENCODER_TYPE_M2M_VIDEO,
|
||||
US_ENCODER_TYPE_M2M_IMAGE,
|
||||
US_ENCODER_TYPE_NOOP,
|
||||
} us_encoder_type_e;
|
||||
|
||||
typedef struct {
|
||||
encoder_type_e type;
|
||||
unsigned quality;
|
||||
bool cpu_forced;
|
||||
pthread_mutex_t mutex;
|
||||
us_encoder_type_e type;
|
||||
unsigned quality;
|
||||
bool cpu_forced;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
unsigned n_m2ms;
|
||||
m2m_encoder_s **m2ms;
|
||||
} encoder_runtime_s;
|
||||
unsigned n_m2ms;
|
||||
us_m2m_encoder_s **m2ms;
|
||||
} us_encoder_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
encoder_type_e type;
|
||||
unsigned n_workers;
|
||||
char *m2m_path;
|
||||
us_encoder_type_e type;
|
||||
unsigned n_workers;
|
||||
char *m2m_path;
|
||||
|
||||
encoder_runtime_s *run;
|
||||
} encoder_s;
|
||||
us_encoder_runtime_s *run;
|
||||
} us_encoder_s;
|
||||
|
||||
typedef struct {
|
||||
encoder_s *enc;
|
||||
hw_buffer_s *hw;
|
||||
frame_s *dest;
|
||||
} encoder_job_s;
|
||||
us_encoder_s *enc;
|
||||
us_hw_buffer_s *hw;
|
||||
us_frame_s *dest;
|
||||
} us_encoder_job_s;
|
||||
|
||||
|
||||
encoder_s *encoder_init(void);
|
||||
void encoder_destroy(encoder_s *enc);
|
||||
us_encoder_s *us_encoder_init(void);
|
||||
void us_encoder_destroy(us_encoder_s *enc);
|
||||
|
||||
encoder_type_e encoder_parse_type(const char *str);
|
||||
const char *encoder_type_to_string(encoder_type_e type);
|
||||
us_encoder_type_e us_encoder_parse_type(const char *str);
|
||||
const char *us_encoder_type_to_string(us_encoder_type_e type);
|
||||
|
||||
workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev);
|
||||
void encoder_get_runtime_params(encoder_s *enc, encoder_type_e *type, unsigned *quality);
|
||||
us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *dev);
|
||||
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality);
|
||||
|
||||
int encoder_compress(encoder_s *enc, unsigned worker_number, frame_s *src, frame_s *dest);
|
||||
int us_encoder_compress(us_encoder_s *enc, unsigned worker_number, us_frame_s *src, us_frame_s *dest);
|
||||
|
||||
@@ -31,26 +31,26 @@
|
||||
typedef struct {
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
JOCTET *buf; // Start of buffer
|
||||
frame_s *frame;
|
||||
us_frame_s *frame;
|
||||
} _jpeg_dest_manager_s;
|
||||
|
||||
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame);
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame);
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame);
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg);
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
|
||||
static void _jpeg_term_destination(j_compress_ptr jpeg);
|
||||
|
||||
|
||||
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
|
||||
void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality) {
|
||||
// This function based on compress_image_to_jpeg() from mjpg-streamer
|
||||
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
|
||||
struct jpeg_compress_struct jpeg;
|
||||
struct jpeg_error_mgr jpeg_error;
|
||||
@@ -70,8 +70,8 @@ void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
|
||||
|
||||
jpeg_start_compress(&jpeg, TRUE);
|
||||
|
||||
# define WRITE_SCANLINES(_format, _func) \
|
||||
case _format: { _func(&jpeg, src); break; }
|
||||
# define WRITE_SCANLINES(x_format, x_func) \
|
||||
case x_format: { x_func(&jpeg, src); break; }
|
||||
|
||||
switch (src->format) {
|
||||
// https://www.fourcc.org/yuv.php
|
||||
@@ -87,17 +87,17 @@ void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality) {
|
||||
jpeg_finish_compress(&jpeg);
|
||||
jpeg_destroy_compress(&jpeg);
|
||||
|
||||
frame_encoding_end(dest);
|
||||
us_frame_encoding_end(dest);
|
||||
}
|
||||
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
|
||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
|
||||
if (jpeg->dest == NULL) {
|
||||
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(_jpeg_dest_manager_s)
|
||||
)));
|
||||
)) != NULL);
|
||||
}
|
||||
|
||||
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
dest->mgr.init_destination = _jpeg_init_destination;
|
||||
dest->mgr.empty_output_buffer = _jpeg_empty_output_buffer;
|
||||
dest->mgr.term_destination = _jpeg_term_destination;
|
||||
@@ -111,11 +111,11 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, frame_s *frame) {
|
||||
#define YUV_B(_y, _u, _) (((_y) + (454 * (_u))) >> 8)
|
||||
#define NORM_COMPONENT(_x) (((_x) > 255) ? 255 : (((_x) < 0) ? 0 : (_x)))
|
||||
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
|
||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
uint8_t *line_buf;
|
||||
A_CALLOC(line_buf, frame->width * 3);
|
||||
US_CALLOC(line_buf, frame->width * 3);
|
||||
|
||||
const unsigned padding = frame_get_padding(frame);
|
||||
const unsigned padding = us_frame_get_padding(frame);
|
||||
const uint8_t *data = frame->data;
|
||||
unsigned z = 0;
|
||||
|
||||
@@ -123,13 +123,13 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const
|
||||
uint8_t *ptr = line_buf;
|
||||
|
||||
for (unsigned x = 0; x < frame->width; ++x) {
|
||||
int y = (!z ? data[0] << 8 : data[2] << 8);
|
||||
int u = data[1] - 128;
|
||||
int v = data[3] - 128;
|
||||
const int y = (!z ? data[0] << 8 : data[2] << 8);
|
||||
const int u = data[1] - 128;
|
||||
const int v = data[3] - 128;
|
||||
|
||||
int r = YUV_R(y, u, v);
|
||||
int g = YUV_G(y, u, v);
|
||||
int b = YUV_B(y, u, v);
|
||||
const int r = YUV_R(y, u, v);
|
||||
const int g = YUV_G(y, u, v);
|
||||
const int b = YUV_B(y, u, v);
|
||||
|
||||
*(ptr++) = NORM_COMPONENT(r);
|
||||
*(ptr++) = NORM_COMPONENT(g);
|
||||
@@ -149,11 +149,11 @@ static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const
|
||||
free(line_buf);
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
|
||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
uint8_t *line_buf;
|
||||
A_CALLOC(line_buf, frame->width * 3);
|
||||
US_CALLOC(line_buf, frame->width * 3);
|
||||
|
||||
const unsigned padding = frame_get_padding(frame);
|
||||
const unsigned padding = us_frame_get_padding(frame);
|
||||
const uint8_t *data = frame->data;
|
||||
unsigned z = 0;
|
||||
|
||||
@@ -161,13 +161,13 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const
|
||||
uint8_t *ptr = line_buf;
|
||||
|
||||
for (unsigned x = 0; x < frame->width; ++x) {
|
||||
int y = (!z ? data[1] << 8 : data[3] << 8);
|
||||
int u = data[0] - 128;
|
||||
int v = data[2] - 128;
|
||||
const int y = (!z ? data[1] << 8 : data[3] << 8);
|
||||
const int u = data[0] - 128;
|
||||
const int v = data[2] - 128;
|
||||
|
||||
int r = YUV_R(y, u, v);
|
||||
int g = YUV_G(y, u, v);
|
||||
int b = YUV_B(y, u, v);
|
||||
const int r = YUV_R(y, u, v);
|
||||
const int g = YUV_G(y, u, v);
|
||||
const int b = YUV_B(y, u, v);
|
||||
|
||||
*(ptr++) = NORM_COMPONENT(r);
|
||||
*(ptr++) = NORM_COMPONENT(g);
|
||||
@@ -192,18 +192,18 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const
|
||||
#undef YUV_G
|
||||
#undef YUV_R
|
||||
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
|
||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
uint8_t *line_buf;
|
||||
A_CALLOC(line_buf, frame->width * 3);
|
||||
US_CALLOC(line_buf, frame->width * 3);
|
||||
|
||||
const unsigned padding = frame_get_padding(frame);
|
||||
const unsigned padding = us_frame_get_padding(frame);
|
||||
const uint8_t *data = frame->data;
|
||||
|
||||
while (jpeg->next_scanline < frame->height) {
|
||||
uint8_t *ptr = line_buf;
|
||||
|
||||
for (unsigned x = 0; x < frame->width; ++x) {
|
||||
unsigned int two_byte = (data[1] << 8) + data[0];
|
||||
const unsigned int two_byte = (data[1] << 8) + data[0];
|
||||
|
||||
*(ptr++) = data[1] & 248; // Red
|
||||
*(ptr++) = (uint8_t)((two_byte & 2016) >> 3); // Green
|
||||
@@ -220,8 +220,8 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, cons
|
||||
free(line_buf);
|
||||
}
|
||||
|
||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const frame_s *frame) {
|
||||
const unsigned padding = frame_get_padding(frame);
|
||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
const unsigned padding = us_frame_get_padding(frame);
|
||||
uint8_t *data = frame->data;
|
||||
|
||||
while (jpeg->next_scanline < frame->height) {
|
||||
@@ -235,12 +235,12 @@ static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const
|
||||
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
|
||||
// Allocate the output buffer - it will be released when done with image
|
||||
assert((dest->buf = (JOCTET *)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
|
||||
)));
|
||||
)) != NULL);
|
||||
|
||||
dest->mgr.next_output_byte = dest->buf;
|
||||
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
||||
@@ -249,9 +249,9 @@ static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg) {
|
||||
// Called whenever local jpeg buffer fills up
|
||||
|
||||
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
|
||||
frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
us_frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
|
||||
dest->mgr.next_output_byte = dest->buf;
|
||||
dest->mgr.free_in_buffer = JPEG_OUTPUT_BUFFER_SIZE;
|
||||
@@ -263,11 +263,11 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
|
||||
// Called by jpeg_finish_compress after all data has been written.
|
||||
// Usually needs to flush buffer.
|
||||
|
||||
_jpeg_dest_manager_s *dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
|
||||
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
const size_t final = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
|
||||
|
||||
// Write any data remaining in the buffer.
|
||||
frame_append_data(dest->frame, dest->buf, final);
|
||||
us_frame_append_data(dest->frame, dest->buf, final);
|
||||
}
|
||||
|
||||
#undef JPEG_OUTPUT_BUFFER_SIZE
|
||||
|
||||
@@ -35,4 +35,4 @@
|
||||
#include "../../../libs/frame.h"
|
||||
|
||||
|
||||
void cpu_encoder_compress(const frame_s *src, frame_s *dest, unsigned quality);
|
||||
void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned quality);
|
||||
|
||||
@@ -28,21 +28,21 @@
|
||||
#include "encoder.h"
|
||||
|
||||
|
||||
void _copy_plus_huffman(const frame_s *src, frame_s *dest);
|
||||
void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest);
|
||||
static bool _is_huffman(const uint8_t *data);
|
||||
|
||||
|
||||
void hw_encoder_compress(const frame_s *src, frame_s *dest) {
|
||||
assert(is_jpeg(src->format));
|
||||
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) {
|
||||
assert(us_is_jpeg(src->format));
|
||||
_copy_plus_huffman(src, dest);
|
||||
}
|
||||
|
||||
void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
|
||||
frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
|
||||
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
|
||||
if (!_is_huffman(src->data)) {
|
||||
const uint8_t *src_ptr = src->data;
|
||||
const uint8_t *src_end = src->data + src->used;
|
||||
const uint8_t *const src_end = src->data + src->used;
|
||||
|
||||
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
|
||||
src_ptr += 1;
|
||||
@@ -54,15 +54,15 @@ void _copy_plus_huffman(const frame_s *src, frame_s *dest) {
|
||||
|
||||
const size_t paste = src_ptr - src->data;
|
||||
|
||||
frame_set_data(dest, src->data, paste);
|
||||
frame_append_data(dest, HUFFMAN_TABLE, sizeof(HUFFMAN_TABLE));
|
||||
frame_append_data(dest, src_ptr, src->used - paste);
|
||||
us_frame_set_data(dest, src->data, paste);
|
||||
us_frame_append_data(dest, US_HUFFMAN_TABLE, sizeof(US_HUFFMAN_TABLE));
|
||||
us_frame_append_data(dest, src_ptr, src->used - paste);
|
||||
|
||||
} else {
|
||||
frame_set_data(dest, src->data, src->used);
|
||||
us_frame_set_data(dest, src->data, src->used);
|
||||
}
|
||||
|
||||
frame_encoding_end(dest);
|
||||
us_frame_encoding_end(dest);
|
||||
}
|
||||
|
||||
static bool _is_huffman(const uint8_t *data) {
|
||||
|
||||
@@ -34,4 +34,4 @@
|
||||
#include "huffman.h"
|
||||
|
||||
|
||||
void hw_encoder_compress(const frame_s *src, frame_s *dest);
|
||||
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest);
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
static const uint8_t HUFFMAN_TABLE[] = {
|
||||
static const uint8_t US_HUFFMAN_TABLE[] = {
|
||||
0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
|
||||
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03,
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
#include "gpio.h"
|
||||
|
||||
|
||||
gpio_s us_gpio = {
|
||||
us_gpio_s us_g_gpio = {
|
||||
.path = "/dev/gpiochip0",
|
||||
.consumer_prefix = "ustreamer",
|
||||
|
||||
# define MAKE_OUTPUT(_role) { \
|
||||
# define MAKE_OUTPUT(x_role) { \
|
||||
.pin = -1, \
|
||||
.role = _role, \
|
||||
.role = x_role, \
|
||||
.consumer = NULL, \
|
||||
.line = NULL, \
|
||||
.state = false \
|
||||
@@ -46,82 +46,82 @@ gpio_s us_gpio = {
|
||||
};
|
||||
|
||||
|
||||
static void _gpio_output_init(gpio_output_s *output);
|
||||
static void _gpio_output_destroy(gpio_output_s *output);
|
||||
static void _gpio_output_init(us_gpio_output_s *output);
|
||||
static void _gpio_output_destroy(us_gpio_output_s *output);
|
||||
|
||||
|
||||
void gpio_init(void) {
|
||||
assert(us_gpio.chip == NULL);
|
||||
void us_gpio_init(void) {
|
||||
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
|
||||
) {
|
||||
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);
|
||||
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 {
|
||||
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 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) {
|
||||
gpiod_chip_close(us_gpio.chip);
|
||||
us_gpio.chip = NULL;
|
||||
A_MUTEX_DESTROY(&us_gpio.mutex);
|
||||
void us_gpio_destroy(void) {
|
||||
_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 gpio_inner_set(gpio_output_s *output, bool state) {
|
||||
int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
|
||||
int retval = 0;
|
||||
|
||||
assert(us_gpio.chip);
|
||||
assert(output->line);
|
||||
assert(us_g_gpio.chip != NULL);
|
||||
assert(output->line != NULL);
|
||||
assert(output->state != state); // Must be checked in macro for the performance
|
||||
A_MUTEX_LOCK(&us_gpio.mutex);
|
||||
US_MUTEX_LOCK(us_g_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); \
|
||||
US_LOG_PERROR("GPIO: Can't write value %d to line %s (will be disabled)", state, output->consumer); \
|
||||
_gpio_output_destroy(output);
|
||||
retval = -1;
|
||||
}
|
||||
|
||||
A_MUTEX_UNLOCK(&us_gpio.mutex);
|
||||
US_MUTEX_UNLOCK(us_g_gpio.mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void _gpio_output_init(gpio_output_s *output) {
|
||||
assert(us_gpio.chip);
|
||||
static void _gpio_output_init(us_gpio_output_s *output) {
|
||||
assert(us_g_gpio.chip != NULL);
|
||||
assert(output->line == NULL);
|
||||
|
||||
A_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) {
|
||||
LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer);
|
||||
_gpio_output_destroy(output);
|
||||
}
|
||||
} else {
|
||||
LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
|
||||
US_LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _gpio_output_destroy(gpio_output_s *output) {
|
||||
if (output->line) {
|
||||
static void _gpio_output_destroy(us_gpio_output_s *output) {
|
||||
if (output->line != NULL) {
|
||||
gpiod_line_release(output->line);
|
||||
output->line = NULL;
|
||||
}
|
||||
if (output->consumer) {
|
||||
if (output->consumer != NULL) {
|
||||
free(output->consumer);
|
||||
output->consumer = NULL;
|
||||
}
|
||||
|
||||
@@ -41,47 +41,47 @@ typedef struct {
|
||||
char *consumer;
|
||||
struct gpiod_line *line;
|
||||
bool state;
|
||||
} gpio_output_s;
|
||||
} us_gpio_output_s;
|
||||
|
||||
typedef struct {
|
||||
char *path;
|
||||
char *consumer_prefix;
|
||||
char *path;
|
||||
char *consumer_prefix;
|
||||
|
||||
gpio_output_s prog_running;
|
||||
gpio_output_s stream_online;
|
||||
gpio_output_s has_http_clients;
|
||||
us_gpio_output_s prog_running;
|
||||
us_gpio_output_s stream_online;
|
||||
us_gpio_output_s has_http_clients;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
struct gpiod_chip *chip;
|
||||
} gpio_s;
|
||||
} us_gpio_s;
|
||||
|
||||
|
||||
extern gpio_s us_gpio;
|
||||
extern us_gpio_s us_g_gpio;
|
||||
|
||||
|
||||
void gpio_init(void);
|
||||
void gpio_destroy(void);
|
||||
int gpio_inner_set(gpio_output_s *output, bool state);
|
||||
void us_gpio_init(void);
|
||||
void us_gpio_destroy(void);
|
||||
int us_gpio_inner_set(us_gpio_output_s *output, bool state);
|
||||
|
||||
|
||||
#define SET_STATE(_output, _state) { \
|
||||
if (_output.line && _output.state != _state) { \
|
||||
if (!gpio_inner_set(&_output, _state)) { \
|
||||
_output.state = _state; \
|
||||
#define SET_STATE(x_output, x_state) { \
|
||||
if (x_output.line && x_output.state != x_state) { \
|
||||
if (!us_gpio_inner_set(&x_output, x_state)) { \
|
||||
x_output.state = x_state; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
INLINE void gpio_set_prog_running(bool state) {
|
||||
SET_STATE(us_gpio.prog_running, state);
|
||||
INLINE void us_gpio_set_prog_running(bool state) {
|
||||
SET_STATE(us_g_gpio.prog_running, state);
|
||||
}
|
||||
|
||||
INLINE void gpio_set_stream_online(bool state) {
|
||||
SET_STATE(us_gpio.stream_online, state);
|
||||
INLINE void us_gpio_set_stream_online(bool state) {
|
||||
SET_STATE(us_g_gpio.stream_online, state);
|
||||
}
|
||||
|
||||
INLINE void gpio_set_has_http_clients(bool state) {
|
||||
SET_STATE(us_gpio.has_http_clients, state);
|
||||
INLINE void us_gpio_set_has_http_clients(bool state) {
|
||||
SET_STATE(us_g_gpio.has_http_clients, state);
|
||||
}
|
||||
|
||||
#undef SET_STATE
|
||||
|
||||
@@ -23,42 +23,42 @@
|
||||
#include "stream.h"
|
||||
|
||||
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, const char *path, unsigned bitrate, unsigned gop) {
|
||||
h264_stream_s *h264;
|
||||
A_CALLOC(h264, 1);
|
||||
us_h264_stream_s *us_h264_stream_init(us_memsink_s *sink, const char *path, unsigned bitrate, unsigned gop) {
|
||||
us_h264_stream_s *h264;
|
||||
US_CALLOC(h264, 1);
|
||||
h264->sink = sink;
|
||||
h264->tmp_src = frame_init();
|
||||
h264->dest = frame_init();
|
||||
h264->tmp_src = us_frame_init();
|
||||
h264->dest = us_frame_init();
|
||||
atomic_init(&h264->online, false);
|
||||
h264->enc = m2m_h264_encoder_init("H264", path, bitrate, gop);
|
||||
h264->enc = us_m2m_h264_encoder_init("H264", path, bitrate, gop);
|
||||
return h264;
|
||||
}
|
||||
|
||||
void h264_stream_destroy(h264_stream_s *h264) {
|
||||
m2m_encoder_destroy(h264->enc);
|
||||
frame_destroy(h264->dest);
|
||||
frame_destroy(h264->tmp_src);
|
||||
void us_h264_stream_destroy(us_h264_stream_s *h264) {
|
||||
us_m2m_encoder_destroy(h264->enc);
|
||||
us_frame_destroy(h264->dest);
|
||||
us_frame_destroy(h264->tmp_src);
|
||||
free(h264);
|
||||
}
|
||||
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, bool force_key) {
|
||||
if (!memsink_server_check(h264->sink, frame)) {
|
||||
void us_h264_stream_process(us_h264_stream_s *h264, const us_frame_s *frame, bool force_key) {
|
||||
if (!us_memsink_server_check(h264->sink, frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_jpeg(frame->format)) {
|
||||
long double now = get_now_monotonic();
|
||||
LOG_DEBUG("H264: Input frame is JPEG; decoding ...");
|
||||
if (unjpeg(frame, h264->tmp_src, true) < 0) {
|
||||
if (us_is_jpeg(frame->format)) {
|
||||
const long double now = us_get_now_monotonic();
|
||||
US_LOG_DEBUG("H264: Input frame is JPEG; decoding ...");
|
||||
if (us_unjpeg(frame, h264->tmp_src, true) < 0) {
|
||||
return;
|
||||
}
|
||||
frame = h264->tmp_src;
|
||||
LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", get_now_monotonic() - now);
|
||||
US_LOG_VERBOSE("H264: JPEG decoded; time=%.3Lf", us_get_now_monotonic() - now);
|
||||
}
|
||||
|
||||
bool online = false;
|
||||
if (!m2m_encoder_compress(h264->enc, frame, h264->dest, force_key)) {
|
||||
online = !memsink_server_put(h264->sink, h264->dest);
|
||||
if (!us_m2m_encoder_compress(h264->enc, frame, h264->dest, force_key)) {
|
||||
online = !us_memsink_server_put(h264->sink, h264->dest);
|
||||
}
|
||||
atomic_store(&h264->online, online);
|
||||
}
|
||||
|
||||
@@ -35,14 +35,14 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
memsink_s *sink;
|
||||
frame_s *tmp_src;
|
||||
frame_s *dest;
|
||||
m2m_encoder_s *enc;
|
||||
atomic_bool online;
|
||||
} h264_stream_s;
|
||||
us_memsink_s *sink;
|
||||
us_frame_s *tmp_src;
|
||||
us_frame_s *dest;
|
||||
us_m2m_encoder_s *enc;
|
||||
atomic_bool online;
|
||||
} us_h264_stream_s;
|
||||
|
||||
|
||||
h264_stream_s *h264_stream_init(memsink_s *sink, const char *path, unsigned bitrate, unsigned gop);
|
||||
void h264_stream_destroy(h264_stream_s *h264);
|
||||
void h264_stream_process(h264_stream_s *h264, const frame_s *frame, bool force_key);
|
||||
us_h264_stream_s *us_h264_stream_init(us_memsink_s *sink, const char *path, unsigned bitrate, unsigned gop);
|
||||
void us_h264_stream_destroy(us_h264_stream_s *h264);
|
||||
void us_h264_stream_process(us_h264_stream_s *h264, const us_frame_s *frame, bool force_key);
|
||||
|
||||
@@ -23,25 +23,26 @@
|
||||
#include "bev.h"
|
||||
|
||||
|
||||
char *bufferevent_my_format_reason(short what) {
|
||||
char *us_bufferevent_format_reason(short what) {
|
||||
char *reason;
|
||||
A_CALLOC(reason, 2048);
|
||||
US_CALLOC(reason, 2048);
|
||||
|
||||
char perror_buf[1024] = {0};
|
||||
char *perror_ptr = errno_to_string(EVUTIL_SOCKET_ERROR(), perror_buf, 1024); // evutil_socket_error_to_string() is not thread-safe
|
||||
// evutil_socket_error_to_string() is not thread-safe
|
||||
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(_bev, _name) { \
|
||||
if (what & _bev) { \
|
||||
# define FILL_REASON(x_bev, x_name) { \
|
||||
if (what & x_bev) { \
|
||||
if (first) { \
|
||||
first = false; \
|
||||
} else { \
|
||||
strcat(reason, ","); \
|
||||
} \
|
||||
strcat(reason, _name); \
|
||||
strcat(reason, x_name); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
@@ -32,4 +32,4 @@
|
||||
#include "../../libs/logging.h"
|
||||
|
||||
|
||||
char *bufferevent_my_format_reason(short what);
|
||||
char *us_bufferevent_format_reason(short what);
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
|
||||
|
||||
static const struct {
|
||||
const char *ext;
|
||||
const char *mime;
|
||||
const char *ext; // cppcheck-suppress unusedStructMember
|
||||
const char *mime; // cppcheck-suppress unusedStructMember
|
||||
} _MIME_TYPES[] = {
|
||||
{"html", "text/html"},
|
||||
{"htm", "text/html"},
|
||||
@@ -46,19 +46,19 @@ static const struct {
|
||||
};
|
||||
|
||||
|
||||
const char *guess_mime_type(const char *path) {
|
||||
const char *us_guess_mime_type(const char *path) {
|
||||
// FIXME: false-positive cppcheck
|
||||
char *dot = strrchr(path, '.'); // cppcheck-suppress ctunullpointer
|
||||
const char *dot = strrchr(path, '.'); // cppcheck-suppress ctunullpointer
|
||||
if (dot == NULL || strchr(dot, '/') != NULL) {
|
||||
goto misc;
|
||||
}
|
||||
|
||||
char *ext = dot + 1;
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_MIME_TYPES); ++index) {
|
||||
if (!evutil_ascii_strcasecmp(ext, _MIME_TYPES[index].ext)) {
|
||||
return _MIME_TYPES[index].mime;
|
||||
const char *ext = dot + 1;
|
||||
US_ARRAY_ITERATE(_MIME_TYPES, 0, item, {
|
||||
if (!evutil_ascii_strcasecmp(item->ext, ext)) {
|
||||
return item->mime;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
misc:
|
||||
return "application/misc";
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <event2/util.h>
|
||||
|
||||
#include "../../libs/tools.h"
|
||||
#include "../../libs/array.h"
|
||||
|
||||
|
||||
const char *guess_mime_type(const char *str);
|
||||
const char *us_guess_mime_type(const char *str);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "path.h"
|
||||
|
||||
|
||||
char *simplify_request_path(const char *str) {
|
||||
char *us_simplify_request_path(const char *str) {
|
||||
// Based on Lighttpd sources:
|
||||
// - https://github.com/lighttpd/lighttpd1.4/blob/b31e7840d5403bc640579135b7004793b9ccd6c0/src/buffer.c#L840
|
||||
// - https://github.com/lighttpd/lighttpd1.4/blob/77c01f981725512653c01cde5ca74c11633dfec4/src/t/test_buffer.c
|
||||
@@ -36,7 +36,7 @@ char *simplify_request_path(const char *str) {
|
||||
char *out;
|
||||
char *slash;
|
||||
|
||||
A_CALLOC(simplified, strlen(str) + 1);
|
||||
US_CALLOC(simplified, strlen(str) + 1);
|
||||
|
||||
if (str[0] == '\0') {
|
||||
simplified[0] = '\0';
|
||||
@@ -74,7 +74,7 @@ char *simplify_request_path(const char *str) {
|
||||
// (out <= str) still true; also now (slash < out)
|
||||
|
||||
if (ch == '/' || ch == '\0') {
|
||||
size_t toklen = out - slash;
|
||||
const size_t toklen = out - slash;
|
||||
|
||||
if (toklen == 3 && pre2 == '.' && pre1 == '.' && *slash == '/') {
|
||||
// "/../" or ("/.." at end of string)
|
||||
@@ -112,7 +112,7 @@ char *simplify_request_path(const char *str) {
|
||||
#ifdef TEST_HTTP_PATH
|
||||
|
||||
int test_simplify_request_path(const char *sample, const char *expected) {
|
||||
char *result = simplify_request_path(sample);
|
||||
char *result = us_simplify_request_path(sample);
|
||||
int retval = -!!strcmp(result, expected);
|
||||
|
||||
printf("Testing '%s' -> '%s' ... ", sample, expected);
|
||||
|
||||
@@ -31,4 +31,4 @@
|
||||
#include "../../libs/tools.h"
|
||||
|
||||
|
||||
char *simplify_request_path(const char *str);
|
||||
char *us_simplify_request_path(const char *str);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -76,30 +76,30 @@
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct stream_client_sx {
|
||||
struct server_sx *server;
|
||||
struct evhttp_request *request;
|
||||
typedef struct us_stream_client_sx {
|
||||
struct us_server_sx *server;
|
||||
struct evhttp_request *request;
|
||||
|
||||
char *key;
|
||||
bool extra_headers;
|
||||
bool advance_headers;
|
||||
bool dual_final_frames;
|
||||
bool zero_data;
|
||||
char *key;
|
||||
bool extra_headers;
|
||||
bool advance_headers;
|
||||
bool dual_final_frames;
|
||||
bool zero_data;
|
||||
|
||||
char *hostport;
|
||||
uint64_t id;
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
bool updated_prev;
|
||||
unsigned fps;
|
||||
unsigned fps_accum;
|
||||
long long fps_accum_second;
|
||||
char *hostport;
|
||||
uint64_t id;
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
bool updated_prev;
|
||||
unsigned fps;
|
||||
unsigned fps_accum;
|
||||
long long fps_accum_second;
|
||||
|
||||
LIST_STRUCT(struct stream_client_sx);
|
||||
} stream_client_s;
|
||||
US_LIST_STRUCT(struct us_stream_client_sx);
|
||||
} us_stream_client_s;
|
||||
|
||||
typedef struct {
|
||||
frame_s *frame;
|
||||
us_frame_s *frame;
|
||||
unsigned captured_fps;
|
||||
unsigned queued_fps;
|
||||
unsigned dropped;
|
||||
@@ -107,10 +107,10 @@ typedef struct {
|
||||
long double expose_cmp_ts;
|
||||
long double expose_end_ts;
|
||||
|
||||
bool notify_last_online;
|
||||
unsigned notify_last_width;
|
||||
unsigned notify_last_height;
|
||||
} exposed_s;
|
||||
bool notify_last_online;
|
||||
unsigned notify_last_width;
|
||||
unsigned notify_last_height;
|
||||
} us_exposed_s;
|
||||
|
||||
typedef struct {
|
||||
struct event_base *base;
|
||||
@@ -123,14 +123,14 @@ typedef struct {
|
||||
long double last_request_ts;
|
||||
|
||||
struct event *refresher;
|
||||
stream_s *stream;
|
||||
exposed_s *exposed;
|
||||
us_stream_s *stream;
|
||||
us_exposed_s *exposed;
|
||||
|
||||
stream_client_s *stream_clients;
|
||||
us_stream_client_s *stream_clients;
|
||||
unsigned stream_clients_count;
|
||||
} server_runtime_s;
|
||||
} us_server_runtime_s;
|
||||
|
||||
typedef struct server_sx {
|
||||
typedef struct us_server_sx {
|
||||
char *host;
|
||||
unsigned port;
|
||||
|
||||
@@ -149,6 +149,7 @@ typedef struct server_sx {
|
||||
char *passwd;
|
||||
char *static_path;
|
||||
char *allow_origin;
|
||||
char *instance_id;
|
||||
|
||||
unsigned drop_same_frames;
|
||||
unsigned fake_width;
|
||||
@@ -157,13 +158,13 @@ typedef struct server_sx {
|
||||
bool notify_parent;
|
||||
unsigned exit_on_no_clients;
|
||||
|
||||
server_runtime_s *run;
|
||||
} server_s;
|
||||
us_server_runtime_s *run;
|
||||
} us_server_s;
|
||||
|
||||
|
||||
server_s *server_init(stream_s *stream);
|
||||
void server_destroy(server_s *server);
|
||||
us_server_s *us_server_init(us_stream_s *stream);
|
||||
void us_server_destroy(us_server_s *server);
|
||||
|
||||
int server_listen(server_s *server);
|
||||
void server_loop(server_s *server);
|
||||
void server_loop_break(server_s *server);
|
||||
int us_server_listen(us_server_s *server);
|
||||
void us_server_loop(us_server_s *server);
|
||||
void us_server_loop_break(us_server_s *server);
|
||||
|
||||
@@ -23,29 +23,29 @@
|
||||
#include "static.h"
|
||||
|
||||
|
||||
char *find_static_file_path(const char *root_path, const char *request_path) {
|
||||
char *us_find_static_file_path(const char *root_path, const char *request_path) {
|
||||
char *path = NULL;
|
||||
|
||||
char *simplified_path = simplify_request_path(request_path);
|
||||
char *const simplified_path = us_simplify_request_path(request_path);
|
||||
if (simplified_path[0] == '\0') {
|
||||
LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path);
|
||||
US_LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||
sprintf(path, "%s/%s", root_path, simplified_path);
|
||||
|
||||
struct stat st;
|
||||
# define LOAD_STAT { \
|
||||
if (lstat(path, &st) < 0) { \
|
||||
LOG_VERBOSE_PERROR("HTTP: Can't stat() static path %s", path); \
|
||||
US_LOG_VERBOSE_PERROR("HTTP: Can't stat() static path %s", path); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
LOAD_STAT;
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
|
||||
US_LOG_VERBOSE("HTTP: Requested static path %s is a directory, trying %s/index.html", path, path);
|
||||
strcat(path, "/index.html");
|
||||
LOAD_STAT;
|
||||
}
|
||||
@@ -53,21 +53,19 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
|
||||
# undef LOAD_STAT
|
||||
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
LOG_VERBOSE("HTTP: Not a regular file: %s", path);
|
||||
US_LOG_VERBOSE("HTTP: Not a regular file: %s", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (access(path, R_OK) < 0) {
|
||||
LOG_VERBOSE_PERROR("HTTP: Can't access() R_OK file %s", path);
|
||||
US_LOG_VERBOSE_PERROR("HTTP: Can't access() R_OK file %s", path);
|
||||
goto error;
|
||||
}
|
||||
|
||||
goto ok;
|
||||
|
||||
error:
|
||||
if (path) {
|
||||
free(path);
|
||||
}
|
||||
US_DELETE(path, free);
|
||||
path = NULL;
|
||||
|
||||
ok:
|
||||
|
||||
@@ -35,4 +35,4 @@
|
||||
#include "path.h"
|
||||
|
||||
|
||||
char *find_static_file_path(const char *root_path, const char *request_path);
|
||||
char *us_find_static_file_path(const char *root_path, const char *request_path);
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
#include "systemd.h"
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http) {
|
||||
int fds = sd_listen_fds(1);
|
||||
evutil_socket_t us_evhttp_bind_systemd(struct evhttp *http) {
|
||||
const int fds = sd_listen_fds(1);
|
||||
if (fds < 1) {
|
||||
LOG_ERROR("No available systemd sockets");
|
||||
US_LOG_ERROR("No available systemd sockets");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http) {
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
if (evhttp_accept_socket(http, fd) < 0) {
|
||||
LOG_PERROR("Can't evhttp_accept_socket() systemd socket");
|
||||
US_LOG_PERROR("Can't evhttp_accept_socket() systemd socket");
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
|
||||
@@ -34,4 +34,4 @@
|
||||
#include "../../../libs/logging.h"
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_systemd(struct evhttp *http);
|
||||
evutil_socket_t us_evhttp_bind_systemd(struct evhttp *http);
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
#include "unix.h"
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
|
||||
evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode) {
|
||||
struct sockaddr_un addr = {0};
|
||||
|
||||
# define MAX_SUN_PATH (sizeof(addr.sun_path) - 1)
|
||||
|
||||
if (strlen(path) > MAX_SUN_PATH) {
|
||||
LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
|
||||
US_LOG_ERROR("UNIX socket path is too long; max=%zu", MAX_SUN_PATH);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -38,30 +38,30 @@ evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool
|
||||
|
||||
# undef MAX_SUN_PATH
|
||||
|
||||
evutil_socket_t fd = -1;
|
||||
assert((fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0);
|
||||
const evutil_socket_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
assert(fd >= 0);
|
||||
assert(!evutil_make_socket_nonblocking(fd));
|
||||
|
||||
if (rm && unlink(path) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
LOG_PERROR("Can't remove old UNIX socket '%s'", path);
|
||||
US_LOG_PERROR("Can't remove old UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", path);
|
||||
US_LOG_PERROR("Can't bind HTTP to UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
if (mode && chmod(path, mode) < 0) {
|
||||
LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", mode, path);
|
||||
US_LOG_PERROR("Can't set permissions %o to UNIX socket '%s'", mode, path);
|
||||
return -1;
|
||||
}
|
||||
if (listen(fd, 128) < 0) {
|
||||
LOG_PERROR("Can't listen UNIX socket '%s'", path);
|
||||
US_LOG_PERROR("Can't listen UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
if (evhttp_accept_socket(http, fd) < 0) {
|
||||
LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", path);
|
||||
US_LOG_PERROR("Can't evhttp_accept_socket() UNIX socket '%s'", path);
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
|
||||
@@ -39,4 +39,4 @@
|
||||
#include "../../libs/logging.h"
|
||||
|
||||
|
||||
evutil_socket_t evhttp_my_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);
|
||||
evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "uri.h"
|
||||
|
||||
|
||||
bool uri_get_true(struct evkeyvalq *params, const char *key) {
|
||||
bool us_uri_get_true(struct evkeyvalq *params, const char *key) {
|
||||
const char *value_str = evhttp_find_header(params, key);
|
||||
if (value_str != NULL) {
|
||||
if (
|
||||
@@ -37,8 +37,8 @@ bool uri_get_true(struct evkeyvalq *params, const char *key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char *uri_get_string(struct evkeyvalq *params, const char *key) {
|
||||
const char *value_str = evhttp_find_header(params, key);
|
||||
char *us_uri_get_string(struct evkeyvalq *params, const char *key) {
|
||||
const char *const value_str = evhttp_find_header(params, key);
|
||||
if (value_str != NULL) {
|
||||
return evhttp_encode_uri(value_str);
|
||||
}
|
||||
|
||||
@@ -29,5 +29,5 @@
|
||||
#include <event2/keyvalq_struct.h>
|
||||
|
||||
|
||||
bool uri_get_true(struct evkeyvalq *params, const char *key);
|
||||
char *uri_get_string(struct evkeyvalq *params, const char *key);
|
||||
bool us_uri_get_true(struct evkeyvalq *params, const char *key);
|
||||
char *us_uri_get_string(struct evkeyvalq *params, const char *key);
|
||||
|
||||
@@ -23,29 +23,31 @@
|
||||
#include "m2m.h"
|
||||
|
||||
|
||||
static m2m_encoder_s *_m2m_encoder_init(
|
||||
static us_m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, unsigned output_format,
|
||||
unsigned fps, unsigned bitrate, unsigned gop, unsigned quality, bool allow_dma);
|
||||
|
||||
static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame);
|
||||
static void _m2m_encoder_prepare(us_m2m_encoder_s *enc, const us_frame_s *frame);
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma);
|
||||
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
us_m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma);
|
||||
|
||||
static void _m2m_encoder_cleanup(m2m_encoder_s *enc);
|
||||
static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc);
|
||||
|
||||
static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key);
|
||||
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key);
|
||||
|
||||
|
||||
#define E_LOG_ERROR(_msg, ...) LOG_ERROR("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_PERROR(_msg, ...) LOG_PERROR("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_INFO(_msg, ...) LOG_INFO("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_VERBOSE(_msg, ...) LOG_VERBOSE("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define E_LOG_DEBUG(_msg, ...) LOG_DEBUG("%s: " _msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_INFO(x_msg, ...) US_LOG_INFO("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
#define _E_LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__)
|
||||
|
||||
#define _RUN(x_next) enc->run->x_next
|
||||
|
||||
|
||||
m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop) {
|
||||
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop) {
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100
|
||||
// По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480
|
||||
// енкодер через несколько секунд перестает производить корректные фреймы.
|
||||
@@ -53,7 +55,7 @@ m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigne
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_H264, 30, bitrate, gop, 0, true);
|
||||
}
|
||||
|
||||
m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
const double b_min = 25;
|
||||
const double b_max = 20000;
|
||||
const double step = 25;
|
||||
@@ -65,72 +67,70 @@ m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsign
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, 30, bitrate, 0, 0, true);
|
||||
}
|
||||
|
||||
m2m_encoder_s *m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality) {
|
||||
// FIXME: DMA не работает
|
||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_JPEG, 30, 0, 0, quality, false);
|
||||
}
|
||||
|
||||
void m2m_encoder_destroy(m2m_encoder_s *enc) {
|
||||
E_LOG_INFO("Destroying encoder ...");
|
||||
void us_m2m_encoder_destroy(us_m2m_encoder_s *enc) {
|
||||
_E_LOG_INFO("Destroying encoder ...");
|
||||
_m2m_encoder_cleanup(enc);
|
||||
free(enc->path);
|
||||
free(enc->name);
|
||||
free(enc);
|
||||
}
|
||||
|
||||
#define RUN(_next) enc->run->_next
|
||||
|
||||
int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key) {
|
||||
frame_encoding_begin(src, dest, (enc->output_format == V4L2_PIX_FMT_MJPEG ? V4L2_PIX_FMT_JPEG : enc->output_format));
|
||||
int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) {
|
||||
us_frame_encoding_begin(src, dest, (enc->output_format == V4L2_PIX_FMT_MJPEG ? V4L2_PIX_FMT_JPEG : enc->output_format));
|
||||
|
||||
if (
|
||||
RUN(width) != src->width
|
||||
|| RUN(height) != src->height
|
||||
|| RUN(input_format) != src->format
|
||||
|| RUN(stride) != src->stride
|
||||
|| RUN(dma) != (enc->allow_dma && src->dma_fd >= 0)
|
||||
_RUN(width) != src->width
|
||||
|| _RUN(height) != src->height
|
||||
|| _RUN(input_format) != src->format
|
||||
|| _RUN(stride) != src->stride
|
||||
|| _RUN(dma) != (enc->allow_dma && src->dma_fd >= 0)
|
||||
) {
|
||||
_m2m_encoder_prepare(enc, src);
|
||||
}
|
||||
if (!RUN(ready)) { // Already prepared but failed
|
||||
if (!_RUN(ready)) { // Already prepared but failed
|
||||
return -1;
|
||||
}
|
||||
|
||||
force_key = (enc->output_format == V4L2_PIX_FMT_H264 && (force_key || RUN(last_online) != src->online));
|
||||
force_key = (enc->output_format == V4L2_PIX_FMT_H264 && (force_key || _RUN(last_online) != src->online));
|
||||
|
||||
if (_m2m_encoder_compress_raw(enc, src, dest, force_key) < 0) {
|
||||
_m2m_encoder_cleanup(enc);
|
||||
E_LOG_ERROR("Encoder destroyed due an error (compress)");
|
||||
_E_LOG_ERROR("Encoder destroyed due an error (compress)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
frame_encoding_end(dest);
|
||||
us_frame_encoding_end(dest);
|
||||
|
||||
E_LOG_VERBOSE("Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d",
|
||||
_E_LOG_VERBOSE("Compressed new frame: size=%zu, time=%0.3Lf, force_key=%d",
|
||||
dest->used, dest->encode_end_ts - dest->encode_begin_ts, force_key);
|
||||
|
||||
RUN(last_online) = src->online;
|
||||
_RUN(last_online) = src->online;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static m2m_encoder_s *_m2m_encoder_init(
|
||||
static us_m2m_encoder_s *_m2m_encoder_init(
|
||||
const char *name, const char *path, unsigned output_format,
|
||||
unsigned fps, unsigned bitrate, unsigned gop, unsigned quality, bool allow_dma) {
|
||||
|
||||
LOG_INFO("%s: Initializing encoder ...", name);
|
||||
US_LOG_INFO("%s: Initializing encoder ...", name);
|
||||
|
||||
m2m_encoder_runtime_s *run;
|
||||
A_CALLOC(run, 1);
|
||||
us_m2m_encoder_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
run->last_online = -1;
|
||||
run->fd = -1;
|
||||
|
||||
m2m_encoder_s *enc;
|
||||
A_CALLOC(enc, 1);
|
||||
assert(enc->name = strdup(name));
|
||||
us_m2m_encoder_s *enc;
|
||||
US_CALLOC(enc, 1);
|
||||
enc->name = us_strdup(name);
|
||||
if (path == NULL) {
|
||||
assert(enc->path = strdup(output_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11"));
|
||||
enc->path = us_strdup(output_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11");
|
||||
} else {
|
||||
assert(enc->path = strdup(path));
|
||||
enc->path = us_strdup(path);
|
||||
}
|
||||
enc->output_format = output_format;
|
||||
enc->fps = fps;
|
||||
@@ -142,45 +142,45 @@ static m2m_encoder_s *_m2m_encoder_init(
|
||||
return enc;
|
||||
}
|
||||
|
||||
#define E_XIOCTL(_request, _value, _msg, ...) { \
|
||||
if (xioctl(RUN(fd), _request, _value) < 0) { \
|
||||
E_LOG_PERROR(_msg, ##__VA_ARGS__); \
|
||||
#define _E_XIOCTL(x_request, x_value, x_msg, ...) { \
|
||||
if (us_xioctl(_RUN(fd), x_request, x_value) < 0) { \
|
||||
_E_LOG_PERROR(x_msg, ##__VA_ARGS__); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
bool dma = (enc->allow_dma && frame->dma_fd >= 0);
|
||||
static void _m2m_encoder_prepare(us_m2m_encoder_s *enc, const us_frame_s *frame) {
|
||||
const bool dma = (enc->allow_dma && frame->dma_fd >= 0);
|
||||
|
||||
E_LOG_INFO("Configuring encoder: DMA=%d ...", dma);
|
||||
_E_LOG_INFO("Configuring encoder: DMA=%d ...", dma);
|
||||
|
||||
_m2m_encoder_cleanup(enc);
|
||||
|
||||
RUN(width) = frame->width;
|
||||
RUN(height) = frame->height;
|
||||
RUN(input_format) = frame->format;
|
||||
RUN(stride) = frame->stride;
|
||||
RUN(dma) = dma;
|
||||
_RUN(width) = frame->width;
|
||||
_RUN(height) = frame->height;
|
||||
_RUN(input_format) = frame->format;
|
||||
_RUN(stride) = frame->stride;
|
||||
_RUN(dma) = dma;
|
||||
|
||||
if ((RUN(fd) = open(enc->path, O_RDWR)) < 0) {
|
||||
E_LOG_PERROR("Can't open encoder device");
|
||||
if ((_RUN(fd) = open(enc->path, O_RDWR)) < 0) {
|
||||
_E_LOG_PERROR("Can't open encoder device");
|
||||
goto error;
|
||||
}
|
||||
E_LOG_DEBUG("Encoder device fd=%d opened", RUN(fd));
|
||||
_E_LOG_DEBUG("Encoder device fd=%d opened", _RUN(fd));
|
||||
|
||||
# define SET_OPTION(_cid, _value) { \
|
||||
struct v4l2_control _ctl = {0}; \
|
||||
_ctl.id = _cid; \
|
||||
_ctl.value = _value; \
|
||||
E_LOG_DEBUG("Configuring option " #_cid " ..."); \
|
||||
E_XIOCTL(VIDIOC_S_CTRL, &_ctl, "Can't set option " #_cid); \
|
||||
# define SET_OPTION(x_cid, x_value) { \
|
||||
struct v4l2_control m_ctl = {0}; \
|
||||
m_ctl.id = x_cid; \
|
||||
m_ctl.value = x_value; \
|
||||
_E_LOG_DEBUG("Configuring option " #x_cid " ..."); \
|
||||
_E_XIOCTL(VIDIOC_S_CTRL, &m_ctl, "Can't set option " #x_cid); \
|
||||
}
|
||||
|
||||
if (enc->output_format == V4L2_PIX_FMT_H264) {
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, enc->gop);
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE);
|
||||
if (RUN(width) * RUN(height) <= 1920 * 1080) { // https://forums.raspberrypi.com/viewtopic.php?t=291447#p1762296
|
||||
if (_RUN(width) * _RUN(height) <= 1920 * 1080) { // https://forums.raspberrypi.com/viewtopic.php?t=291447#p1762296
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
|
||||
} else {
|
||||
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_LEVEL, V4L2_MPEG_VIDEO_H264_LEVEL_5_1);
|
||||
@@ -199,35 +199,35 @@ static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
{
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
fmt.fmt.pix_mp.width = RUN(width);
|
||||
fmt.fmt.pix_mp.height = RUN(height);
|
||||
fmt.fmt.pix_mp.pixelformat = RUN(input_format);
|
||||
fmt.fmt.pix_mp.width = _RUN(width);
|
||||
fmt.fmt.pix_mp.height = _RUN(height);
|
||||
fmt.fmt.pix_mp.pixelformat = _RUN(input_format);
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // libcamera currently has no means to request the right colour space
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = RUN(stride);
|
||||
E_LOG_DEBUG("Configuring INPUT format ...");
|
||||
E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set INPUT format");
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = _RUN(stride);
|
||||
_E_LOG_DEBUG("Configuring INPUT format ...");
|
||||
_E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set INPUT format");
|
||||
}
|
||||
|
||||
{
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
fmt.fmt.pix_mp.width = RUN(width);
|
||||
fmt.fmt.pix_mp.height = RUN(height);
|
||||
fmt.fmt.pix_mp.width = _RUN(width);
|
||||
fmt.fmt.pix_mp.height = _RUN(height);
|
||||
fmt.fmt.pix_mp.pixelformat = enc->output_format;
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT;
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
|
||||
// fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10;
|
||||
E_LOG_DEBUG("Configuring OUTPUT format ...");
|
||||
E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
|
||||
_E_LOG_DEBUG("Configuring OUTPUT format ...");
|
||||
_E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
|
||||
if (fmt.fmt.pix_mp.pixelformat != enc->output_format) {
|
||||
char fourcc_str[8];
|
||||
E_LOG_ERROR("The OUTPUT format can't be configured as %s",
|
||||
fourcc_to_string(enc->output_format, fourcc_str, 8));
|
||||
E_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
|
||||
_E_LOG_ERROR("The OUTPUT format can't be configured as %s",
|
||||
us_fourcc_to_string(enc->output_format, fourcc_str, 8));
|
||||
_E_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@@ -237,61 +237,61 @@ static void _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
setfps.parm.output.timeperframe.numerator = 1;
|
||||
setfps.parm.output.timeperframe.denominator = enc->fps;
|
||||
E_LOG_DEBUG("Configuring INPUT FPS ...");
|
||||
E_XIOCTL(VIDIOC_S_PARM, &setfps, "Can't set INPUT FPS");
|
||||
_E_LOG_DEBUG("Configuring INPUT FPS ...");
|
||||
_E_XIOCTL(VIDIOC_S_PARM, &setfps, "Can't set INPUT FPS");
|
||||
}
|
||||
|
||||
if (_m2m_encoder_init_buffers(enc, (dma ? "INPUT-DMA" : "INPUT"), V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
|
||||
&RUN(input_bufs), &RUN(n_input_bufs), dma) < 0) {
|
||||
&_RUN(input_bufs), &_RUN(n_input_bufs), dma) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_m2m_encoder_init_buffers(enc, "OUTPUT", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
|
||||
&RUN(output_bufs), &RUN(n_output_bufs), false) < 0) {
|
||||
&_RUN(output_bufs), &_RUN(n_output_bufs), false) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
{
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||
E_LOG_DEBUG("Starting INPUT ...");
|
||||
E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start INPUT");
|
||||
_E_LOG_DEBUG("Starting INPUT ...");
|
||||
_E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start INPUT");
|
||||
|
||||
type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
E_LOG_DEBUG("Starting OUTPUT ...");
|
||||
E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start OUTPUT");
|
||||
_E_LOG_DEBUG("Starting OUTPUT ...");
|
||||
_E_XIOCTL(VIDIOC_STREAMON, &type, "Can't start OUTPUT");
|
||||
}
|
||||
|
||||
RUN(ready) = true;
|
||||
E_LOG_DEBUG("Encoder state: *** READY ***");
|
||||
_RUN(ready) = true;
|
||||
_E_LOG_DEBUG("Encoder state: *** READY ***");
|
||||
return;
|
||||
|
||||
error:
|
||||
_m2m_encoder_cleanup(enc);
|
||||
E_LOG_ERROR("Encoder destroyed due an error (prepare)");
|
||||
_E_LOG_ERROR("Encoder destroyed due an error (prepare)");
|
||||
}
|
||||
|
||||
static int _m2m_encoder_init_buffers(
|
||||
m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma) {
|
||||
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type,
|
||||
us_m2m_buffer_s **bufs_ptr, unsigned *n_bufs_ptr, bool dma) {
|
||||
|
||||
E_LOG_DEBUG("Initializing %s buffers ...", name);
|
||||
_E_LOG_DEBUG("Initializing %s buffers ...", name);
|
||||
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = type;
|
||||
req.memory = (dma ? V4L2_MEMORY_DMABUF : V4L2_MEMORY_MMAP);
|
||||
|
||||
E_LOG_DEBUG("Requesting %u %s buffers ...", req.count, name);
|
||||
E_XIOCTL(VIDIOC_REQBUFS, &req, "Can't request %s buffers", name);
|
||||
_E_LOG_DEBUG("Requesting %u %s buffers ...", req.count, name);
|
||||
_E_XIOCTL(VIDIOC_REQBUFS, &req, "Can't request %s buffers", name);
|
||||
if (req.count < 1) {
|
||||
E_LOG_ERROR("Insufficient %s buffer memory: %u", name, req.count);
|
||||
_E_LOG_ERROR("Insufficient %s buffer memory: %u", name, req.count);
|
||||
goto error;
|
||||
}
|
||||
E_LOG_DEBUG("Got %u %s buffers", req.count, name);
|
||||
_E_LOG_DEBUG("Got %u %s buffers", req.count, name);
|
||||
|
||||
if (dma) {
|
||||
*n_bufs_ptr = req.count;
|
||||
} else {
|
||||
A_CALLOC(*bufs_ptr, req.count);
|
||||
US_CALLOC(*bufs_ptr, req.count);
|
||||
for (*n_bufs_ptr = 0; *n_bufs_ptr < req.count; ++(*n_bufs_ptr)) {
|
||||
struct v4l2_buffer buf = {0};
|
||||
struct v4l2_plane plane = {0};
|
||||
@@ -301,25 +301,26 @@ static int _m2m_encoder_init_buffers(
|
||||
buf.length = 1;
|
||||
buf.m.planes = &plane;
|
||||
|
||||
E_LOG_DEBUG("Querying %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
E_XIOCTL(VIDIOC_QUERYBUF, &buf, "Can't query %s buffer=%u", name, *n_bufs_ptr);
|
||||
_E_LOG_DEBUG("Querying %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_E_XIOCTL(VIDIOC_QUERYBUF, &buf, "Can't query %s buffer=%u", name, *n_bufs_ptr);
|
||||
|
||||
E_LOG_DEBUG("Mapping %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_E_LOG_DEBUG("Mapping %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
if (((*bufs_ptr)[*n_bufs_ptr].data = mmap(
|
||||
NULL,
|
||||
plane.length,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
RUN(fd),
|
||||
_RUN(fd),
|
||||
plane.m.mem_offset
|
||||
)) == MAP_FAILED) {
|
||||
E_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
|
||||
_E_LOG_PERROR("Can't map %s buffer=%u", name, *n_bufs_ptr);
|
||||
goto error;
|
||||
}
|
||||
assert((*bufs_ptr)[*n_bufs_ptr].data != NULL);
|
||||
(*bufs_ptr)[*n_bufs_ptr].allocated = plane.length;
|
||||
|
||||
E_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
E_XIOCTL(VIDIOC_QBUF, &buf, "Can't queue %s buffer=%u", name, *n_bufs_ptr);
|
||||
_E_LOG_DEBUG("Queuing %s buffer=%u ...", name, *n_bufs_ptr);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &buf, "Can't queue %s buffer=%u", name, *n_bufs_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,13 +329,13 @@ static int _m2m_encoder_init_buffers(
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void _m2m_encoder_cleanup(m2m_encoder_s *enc) {
|
||||
if (RUN(ready)) {
|
||||
# define STOP_STREAM(_name, _type) { \
|
||||
enum v4l2_buf_type _type_var = _type; \
|
||||
E_LOG_DEBUG("Stopping %s ...", _name); \
|
||||
if (xioctl(RUN(fd), VIDIOC_STREAMOFF, &_type_var) < 0) { \
|
||||
E_LOG_PERROR("Can't stop %s", _name); \
|
||||
static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
|
||||
if (_RUN(ready)) {
|
||||
# define STOP_STREAM(x_name, x_type) { \
|
||||
enum v4l2_buf_type m_type_var = x_type; \
|
||||
_E_LOG_DEBUG("Stopping %s ...", x_name); \
|
||||
if (us_xioctl(_RUN(fd), VIDIOC_STREAMOFF, &m_type_var) < 0) { \
|
||||
_E_LOG_PERROR("Can't stop %s", x_name); \
|
||||
} \
|
||||
}
|
||||
|
||||
@@ -344,19 +345,19 @@ static void _m2m_encoder_cleanup(m2m_encoder_s *enc) {
|
||||
# undef STOP_STREAM
|
||||
}
|
||||
|
||||
# define DESTROY_BUFFERS(_name, _target) { \
|
||||
if (RUN(_target##_bufs)) { \
|
||||
for (unsigned index = 0; index < RUN(n_##_target##_bufs); ++index) { \
|
||||
if (RUN(_target##_bufs[index].allocated) > 0 && RUN(_target##_bufs[index].data) != MAP_FAILED) { \
|
||||
if (munmap(RUN(_target##_bufs[index].data), RUN(_target##_bufs[index].allocated)) < 0) { \
|
||||
E_LOG_PERROR("Can't unmap %s buffer=%u", #_name, index); \
|
||||
# define DESTROY_BUFFERS(x_name, x_target) { \
|
||||
if (_RUN(x_target##_bufs) != NULL) { \
|
||||
for (unsigned m_index = 0; m_index < _RUN(n_##x_target##_bufs); ++m_index) { \
|
||||
if (_RUN(x_target##_bufs[m_index].allocated) > 0 && _RUN(x_target##_bufs[m_index].data) != NULL) { \
|
||||
if (munmap(_RUN(x_target##_bufs[m_index].data), _RUN(x_target##_bufs[m_index].allocated)) < 0) { \
|
||||
_E_LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_index); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
free(RUN(_target##_bufs)); \
|
||||
RUN(_target##_bufs) = NULL; \
|
||||
free(_RUN(x_target##_bufs)); \
|
||||
_RUN(x_target##_bufs) = NULL; \
|
||||
} \
|
||||
RUN(n_##_target##_bufs) = 0; \
|
||||
_RUN(n_##x_target##_bufs) = 0; \
|
||||
}
|
||||
|
||||
DESTROY_BUFFERS("OUTPUT", output);
|
||||
@@ -364,30 +365,30 @@ static void _m2m_encoder_cleanup(m2m_encoder_s *enc) {
|
||||
|
||||
# undef DESTROY_BUFFERS
|
||||
|
||||
if (RUN(fd) >= 0) {
|
||||
if (close(RUN(fd)) < 0) {
|
||||
E_LOG_PERROR("Can't close encoder device");
|
||||
if (_RUN(fd) >= 0) {
|
||||
if (close(_RUN(fd)) < 0) {
|
||||
_E_LOG_PERROR("Can't close encoder device");
|
||||
}
|
||||
RUN(fd) = -1;
|
||||
_RUN(fd) = -1;
|
||||
}
|
||||
|
||||
RUN(last_online) = -1;
|
||||
RUN(ready) = false;
|
||||
_RUN(last_online) = -1;
|
||||
_RUN(ready) = false;
|
||||
|
||||
E_LOG_DEBUG("Encoder state: ~~~ NOT READY ~~~");
|
||||
_E_LOG_DEBUG("Encoder state: ~~~ NOT READY ~~~");
|
||||
}
|
||||
|
||||
static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key) {
|
||||
assert(RUN(ready));
|
||||
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) {
|
||||
assert(_RUN(ready));
|
||||
|
||||
E_LOG_DEBUG("Compressing new frame; force_key=%d ...", force_key);
|
||||
_E_LOG_DEBUG("Compressing new frame; force_key=%d ...", force_key);
|
||||
|
||||
if (force_key) {
|
||||
struct v4l2_control ctl = {0};
|
||||
ctl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME;
|
||||
ctl.value = 1;
|
||||
E_LOG_DEBUG("Forcing keyframe ...")
|
||||
E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
|
||||
_E_LOG_DEBUG("Forcing keyframe ...")
|
||||
_E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
|
||||
}
|
||||
|
||||
struct v4l2_buffer input_buf = {0};
|
||||
@@ -396,25 +397,25 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
input_buf.length = 1;
|
||||
input_buf.m.planes = &input_plane;
|
||||
|
||||
if (RUN(dma)) {
|
||||
if (_RUN(dma)) {
|
||||
input_buf.index = 0;
|
||||
input_buf.memory = V4L2_MEMORY_DMABUF;
|
||||
input_buf.field = V4L2_FIELD_NONE;
|
||||
input_plane.m.fd = src->dma_fd;
|
||||
E_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index);
|
||||
_E_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index);
|
||||
} else {
|
||||
input_buf.memory = V4L2_MEMORY_MMAP;
|
||||
E_LOG_DEBUG("Grabbing INPUT buffer ...");
|
||||
E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't grab INPUT buffer");
|
||||
if (input_buf.index >= RUN(n_input_bufs)) {
|
||||
E_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
|
||||
input_buf.index, RUN(n_input_bufs));
|
||||
_E_LOG_DEBUG("Grabbing INPUT buffer ...");
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't grab INPUT buffer");
|
||||
if (input_buf.index >= _RUN(n_input_bufs)) {
|
||||
_E_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
|
||||
input_buf.index, _RUN(n_input_bufs));
|
||||
goto error;
|
||||
}
|
||||
E_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index);
|
||||
_E_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index);
|
||||
}
|
||||
|
||||
uint64_t now = get_now_monotonic_u64();
|
||||
const uint64_t now = us_get_now_monotonic_u64();
|
||||
struct timeval ts = {
|
||||
.tv_sec = now / 1000000,
|
||||
.tv_usec = now % 1000000,
|
||||
@@ -424,31 +425,31 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
input_buf.timestamp.tv_usec = ts.tv_usec;
|
||||
input_plane.bytesused = src->used;
|
||||
input_plane.length = src->used;
|
||||
if (!RUN(dma)) {
|
||||
memcpy(RUN(input_bufs[input_buf.index].data), src->data, src->used);
|
||||
if (!_RUN(dma)) {
|
||||
memcpy(_RUN(input_bufs[input_buf.index].data), src->data, src->used);
|
||||
}
|
||||
|
||||
const char *input_name = (RUN(dma) ? "INPUT-DMA" : "INPUT");
|
||||
const char *input_name = (_RUN(dma) ? "INPUT-DMA" : "INPUT");
|
||||
|
||||
E_LOG_DEBUG("Sending%s %s buffer ...", (!RUN(dma) ? " (releasing)" : ""), input_name);
|
||||
E_XIOCTL(VIDIOC_QBUF, &input_buf, "Can't send %s buffer", input_name);
|
||||
_E_LOG_DEBUG("Sending%s %s buffer ...", (!_RUN(dma) ? " (releasing)" : ""), input_name);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &input_buf, "Can't send %s buffer", input_name);
|
||||
|
||||
// Для не-DMA отправка буфера по факту являтся освобождением этого буфера
|
||||
bool input_released = !RUN(dma);
|
||||
bool input_released = !_RUN(dma);
|
||||
|
||||
while (true) {
|
||||
struct pollfd enc_poll = {RUN(fd), POLLIN, 0};
|
||||
struct pollfd enc_poll = {_RUN(fd), POLLIN, 0};
|
||||
|
||||
E_LOG_DEBUG("Polling encoder ...");
|
||||
_E_LOG_DEBUG("Polling encoder ...");
|
||||
if (poll(&enc_poll, 1, 1000) < 0 && errno != EINTR) {
|
||||
E_LOG_PERROR("Can't poll encoder");
|
||||
_E_LOG_PERROR("Can't poll encoder");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (enc_poll.revents & POLLIN) {
|
||||
if (!input_released) {
|
||||
E_LOG_DEBUG("Releasing %s buffer=%u ...", input_name, input_buf.index);
|
||||
E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't release %s buffer=%u",
|
||||
_E_LOG_DEBUG("Releasing %s buffer=%u ...", input_name, input_buf.index);
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't release %s buffer=%u",
|
||||
input_name, input_buf.index);
|
||||
input_released = true;
|
||||
}
|
||||
@@ -459,23 +460,23 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
output_buf.memory = V4L2_MEMORY_MMAP;
|
||||
output_buf.length = 1;
|
||||
output_buf.m.planes = &output_plane;
|
||||
E_LOG_DEBUG("Fetching OUTPUT buffer ...");
|
||||
E_XIOCTL(VIDIOC_DQBUF, &output_buf, "Can't fetch OUTPUT buffer");
|
||||
_E_LOG_DEBUG("Fetching OUTPUT buffer ...");
|
||||
_E_XIOCTL(VIDIOC_DQBUF, &output_buf, "Can't fetch OUTPUT buffer");
|
||||
|
||||
bool done = false;
|
||||
if (ts.tv_sec != output_buf.timestamp.tv_sec || ts.tv_usec != output_buf.timestamp.tv_usec) {
|
||||
// Енкодер первый раз может выдать буфер с мусором и нулевым таймстампом,
|
||||
// так что нужно убедиться, что мы читаем выходной буфер, соответствующий
|
||||
// входному (с тем же таймстампом).
|
||||
E_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
|
||||
_E_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
|
||||
} else {
|
||||
frame_set_data(dest, RUN(output_bufs[output_buf.index].data), output_plane.bytesused);
|
||||
us_frame_set_data(dest, _RUN(output_bufs[output_buf.index].data), output_plane.bytesused);
|
||||
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
|
||||
done = true;
|
||||
}
|
||||
|
||||
E_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index);
|
||||
E_XIOCTL(VIDIOC_QBUF, &output_buf, "Can't release OUTPUT buffer=%u", output_buf.index);
|
||||
_E_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index);
|
||||
_E_XIOCTL(VIDIOC_QBUF, &output_buf, "Can't release OUTPUT buffer=%u", output_buf.index);
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
@@ -488,12 +489,4 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef E_XIOCTL
|
||||
|
||||
#undef RUN
|
||||
|
||||
#undef E_LOG_DEBUG
|
||||
#undef E_LOG_VERBOSE
|
||||
#undef E_LOG_INFO
|
||||
#undef E_LOG_PERROR
|
||||
#undef E_LOG_ERROR
|
||||
#undef _E_XIOCTL
|
||||
|
||||
@@ -45,24 +45,24 @@
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
size_t allocated;
|
||||
} m2m_buffer_s;
|
||||
} us_m2m_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
m2m_buffer_s *input_bufs;
|
||||
us_m2m_buffer_s *input_bufs;
|
||||
unsigned n_input_bufs;
|
||||
m2m_buffer_s *output_bufs;
|
||||
us_m2m_buffer_s *output_bufs;
|
||||
unsigned n_output_bufs;
|
||||
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned input_format;
|
||||
unsigned stride;
|
||||
bool dma;
|
||||
bool ready;
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned input_format;
|
||||
unsigned stride;
|
||||
bool dma;
|
||||
bool ready;
|
||||
|
||||
int last_online;
|
||||
} m2m_encoder_runtime_s;
|
||||
int last_online;
|
||||
} us_m2m_encoder_runtime_s;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
@@ -74,13 +74,13 @@ typedef struct {
|
||||
unsigned quality;
|
||||
bool allow_dma;
|
||||
|
||||
m2m_encoder_runtime_s *run;
|
||||
} m2m_encoder_s;
|
||||
us_m2m_encoder_runtime_s *run;
|
||||
} us_m2m_encoder_s;
|
||||
|
||||
|
||||
m2m_encoder_s *m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop);
|
||||
m2m_encoder_s *m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
m2m_encoder_s *m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
void m2m_encoder_destroy(m2m_encoder_s *enc);
|
||||
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, unsigned bitrate, unsigned gop);
|
||||
us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, unsigned quality);
|
||||
void us_m2m_encoder_destroy(us_m2m_encoder_s *enc);
|
||||
|
||||
int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest, bool force_key);
|
||||
int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key);
|
||||
|
||||
@@ -40,12 +40,9 @@
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
stream_s *stream;
|
||||
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;
|
||||
@@ -56,27 +53,25 @@ static void _block_thread_signals(void) {
|
||||
}
|
||||
|
||||
static void *_stream_loop_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("stream");
|
||||
US_THREAD_RENAME("stream");
|
||||
_block_thread_signals();
|
||||
stream_loop(_ctx->stream);
|
||||
us_stream_loop(_g_stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *_server_loop_thread(UNUSED void *arg) {
|
||||
A_THREAD_RENAME("http");
|
||||
US_THREAD_RENAME("http");
|
||||
_block_thread_signals();
|
||||
server_loop(_ctx->server);
|
||||
us_server_loop(_g_server);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _signal_handler(int signum) {
|
||||
switch (signum) {
|
||||
case SIGTERM: LOG_INFO_NOLOCK("===== Stopping by SIGTERM ====="); break;
|
||||
case SIGINT: LOG_INFO_NOLOCK("===== Stopping by SIGINT ====="); break;
|
||||
default: LOG_INFO_NOLOCK("===== Stopping by %d =====", signum); break;
|
||||
}
|
||||
stream_loop_break(_ctx->stream);
|
||||
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) {
|
||||
@@ -87,13 +82,13 @@ static void _install_signal_handlers(void) {
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGINT));
|
||||
assert(!sigaddset(&sig_act.sa_mask, SIGTERM));
|
||||
|
||||
LOG_DEBUG("Installing SIGINT handler ...");
|
||||
US_LOG_DEBUG("Installing SIGINT handler ...");
|
||||
assert(!sigaction(SIGINT, &sig_act, NULL));
|
||||
|
||||
LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
US_LOG_DEBUG("Installing SIGTERM handler ...");
|
||||
assert(!sigaction(SIGTERM, &sig_act, NULL));
|
||||
|
||||
LOG_DEBUG("Ignoring SIGPIPE ...");
|
||||
US_LOG_DEBUG("Ignoring SIGPIPE ...");
|
||||
assert(signal(SIGPIPE, SIG_IGN) != SIG_ERR);
|
||||
}
|
||||
|
||||
@@ -101,55 +96,50 @@ int main(int argc, char *argv[]) {
|
||||
assert(argc >= 0);
|
||||
int exit_code = 0;
|
||||
|
||||
LOGGING_INIT;
|
||||
A_THREAD_RENAME("main");
|
||||
US_LOGGING_INIT;
|
||||
US_THREAD_RENAME("main");
|
||||
|
||||
options_s *options = options_init(argc, argv);
|
||||
device_s *dev = device_init();
|
||||
encoder_s *enc = encoder_init();
|
||||
stream_s *stream = stream_init(dev, enc);
|
||||
server_s *server = server_init(stream);
|
||||
us_options_s *options = us_options_init(argc, argv);
|
||||
us_device_s *dev = us_device_init();
|
||||
us_encoder_s *enc = us_encoder_init();
|
||||
_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
|
||||
gpio_init();
|
||||
us_gpio_init();
|
||||
# endif
|
||||
|
||||
_install_signal_handlers();
|
||||
|
||||
_main_context_s ctx;
|
||||
ctx.stream = stream;
|
||||
ctx.server = server;
|
||||
_ctx = &ctx;
|
||||
|
||||
if ((exit_code = server_listen(server)) == 0) {
|
||||
if ((exit_code = us_server_listen(_g_server)) == 0) {
|
||||
# ifdef WITH_GPIO
|
||||
gpio_set_prog_running(true);
|
||||
us_gpio_set_prog_running(true);
|
||||
# endif
|
||||
|
||||
pthread_t stream_loop_tid;
|
||||
pthread_t server_loop_tid;
|
||||
A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL);
|
||||
A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL);
|
||||
A_THREAD_JOIN(server_loop_tid);
|
||||
A_THREAD_JOIN(stream_loop_tid);
|
||||
US_THREAD_CREATE(stream_loop_tid, _stream_loop_thread, NULL);
|
||||
US_THREAD_CREATE(server_loop_tid, _server_loop_thread, NULL);
|
||||
US_THREAD_JOIN(server_loop_tid);
|
||||
US_THREAD_JOIN(stream_loop_tid);
|
||||
}
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
gpio_set_prog_running(false);
|
||||
gpio_destroy();
|
||||
us_gpio_set_prog_running(false);
|
||||
us_gpio_destroy();
|
||||
# endif
|
||||
}
|
||||
|
||||
server_destroy(server);
|
||||
stream_destroy(stream);
|
||||
encoder_destroy(enc);
|
||||
device_destroy(dev);
|
||||
options_destroy(options);
|
||||
us_server_destroy(_g_server);
|
||||
us_stream_destroy(_g_stream);
|
||||
us_encoder_destroy(enc);
|
||||
us_device_destroy(dev);
|
||||
us_options_destroy(options);
|
||||
|
||||
if (exit_code == 0) {
|
||||
LOG_INFO("Bye-bye");
|
||||
US_LOG_INFO("Bye-bye");
|
||||
}
|
||||
LOGGING_DESTROY;
|
||||
US_LOGGING_DESTROY;
|
||||
return (exit_code < 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "options.h"
|
||||
|
||||
|
||||
enum _OPT_VALUES {
|
||||
enum _US_OPT_VALUES {
|
||||
_O_DEVICE = 'd',
|
||||
_O_INPUT = 'i',
|
||||
_O_RESOLUTION = 'r',
|
||||
@@ -82,15 +82,16 @@ enum _OPT_VALUES {
|
||||
_O_PASSWD,
|
||||
_O_STATIC,
|
||||
_O_ALLOW_ORIGIN,
|
||||
_O_INSTANCE_ID,
|
||||
_O_TCP_NODELAY,
|
||||
_O_SERVER_TIMEOUT,
|
||||
|
||||
# define ADD_SINK(_prefix) \
|
||||
_O_##_prefix, \
|
||||
_O_##_prefix##_MODE, \
|
||||
_O_##_prefix##_RM, \
|
||||
_O_##_prefix##_CLIENT_TTL, \
|
||||
_O_##_prefix##_TIMEOUT,
|
||||
# define ADD_SINK(x_prefix) \
|
||||
_O_##x_prefix, \
|
||||
_O_##x_prefix##_MODE, \
|
||||
_O_##x_prefix##_RM, \
|
||||
_O_##x_prefix##_CLIENT_TTL, \
|
||||
_O_##x_prefix##_TIMEOUT,
|
||||
ADD_SINK(SINK)
|
||||
ADD_SINK(RAW_SINK)
|
||||
ADD_SINK(H264_SINK)
|
||||
@@ -177,16 +178,17 @@ 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},
|
||||
|
||||
# define ADD_SINK(_opt, _prefix) \
|
||||
{_opt "sink", required_argument, NULL, _O_##_prefix}, \
|
||||
{_opt "sink-mode", required_argument, NULL, _O_##_prefix##_MODE}, \
|
||||
{_opt "sink-rm", no_argument, NULL, _O_##_prefix##_RM}, \
|
||||
{_opt "sink-client-ttl", required_argument, NULL, _O_##_prefix##_CLIENT_TTL}, \
|
||||
{_opt "sink-timeout", required_argument, NULL, _O_##_prefix##_TIMEOUT},
|
||||
# define ADD_SINK(x_opt, x_prefix) \
|
||||
{x_opt "sink", required_argument, NULL, _O_##x_prefix}, \
|
||||
{x_opt "sink-mode", required_argument, NULL, _O_##x_prefix##_MODE}, \
|
||||
{x_opt "sink-rm", no_argument, NULL, _O_##x_prefix##_RM}, \
|
||||
{x_opt "sink-client-ttl", required_argument, NULL, _O_##x_prefix##_CLIENT_TTL}, \
|
||||
{x_opt "sink-timeout", required_argument, NULL, _O_##x_prefix##_TIMEOUT},
|
||||
ADD_SINK("", SINK)
|
||||
ADD_SINK("raw-", RAW_SINK)
|
||||
ADD_SINK("h264-", H264_SINK)
|
||||
@@ -228,38 +230,31 @@ 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, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server);
|
||||
static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
|
||||
|
||||
|
||||
options_s *options_init(unsigned argc, char *argv[]) {
|
||||
options_s *options;
|
||||
A_CALLOC(options, 1);
|
||||
us_options_s *us_options_init(unsigned argc, char *argv[]) {
|
||||
us_options_s *options;
|
||||
US_CALLOC(options, 1);
|
||||
options->argc = argc;
|
||||
options->argv = argv;
|
||||
|
||||
A_CALLOC(options->argv_copy, argc);
|
||||
US_CALLOC(options->argv_copy, argc);
|
||||
for (unsigned index = 0; index < argc; ++index) {
|
||||
assert(options->argv_copy[index] = strdup(argv[index]));
|
||||
options->argv_copy[index] = us_strdup(argv[index]);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
void options_destroy(options_s *options) {
|
||||
# define ADD_SINK(_prefix) { \
|
||||
if (options->_prefix) { \
|
||||
memsink_destroy(options->_prefix); \
|
||||
} \
|
||||
}
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
ADD_SINK(h264_sink);
|
||||
# undef ADD_SINK
|
||||
void us_options_destroy(us_options_s *options) {
|
||||
US_DELETE(options->sink, us_memsink_destroy);
|
||||
US_DELETE(options->raw_sink, us_memsink_destroy);
|
||||
US_DELETE(options->h264_sink, us_memsink_destroy);
|
||||
|
||||
if (options->blank) {
|
||||
frame_destroy(options->blank);
|
||||
}
|
||||
US_DELETE(options->blank, us_frame_destroy);
|
||||
|
||||
for (unsigned index = 0; index < options->argc; ++index) {
|
||||
free(options->argv_copy[index]);
|
||||
@@ -270,32 +265,32 @@ void options_destroy(options_s *options) {
|
||||
}
|
||||
|
||||
|
||||
int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) {
|
||||
# define OPT_SET(_dest, _value) { \
|
||||
_dest = _value; \
|
||||
int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) {
|
||||
# define OPT_SET(x_dest, x_value) { \
|
||||
x_dest = x_value; \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_NUMBER(_name, _dest, _min, _max, _base) { \
|
||||
errno = 0; char *_end = NULL; long long _tmp = strtoll(optarg, &_end, _base); \
|
||||
if (errno || *_end || _tmp < _min || _tmp > _max) { \
|
||||
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", _name, optarg, (long long)_min, (long long)_max); \
|
||||
# define OPT_NUMBER(x_name, x_dest, x_min, x_max, x_base) { \
|
||||
errno = 0; char *m_end = NULL; const long long m_tmp = strtoll(optarg, &m_end, x_base); \
|
||||
if (errno || *m_end || m_tmp < x_min || m_tmp > x_max) { \
|
||||
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", x_name, optarg, (long long)x_min, (long long)x_max); \
|
||||
return -1; \
|
||||
} \
|
||||
_dest = _tmp; \
|
||||
x_dest = m_tmp; \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_RESOLUTION(_name, _dest_width, _dest_height, _limited) { \
|
||||
switch (_parse_resolution(optarg, &_dest_width, &_dest_height, _limited)) { \
|
||||
# define OPT_RESOLUTION(x_name, x_dest_width, x_dest_height, x_limited) { \
|
||||
switch (_parse_resolution(optarg, &x_dest_width, &x_dest_height, x_limited)) { \
|
||||
case -1: \
|
||||
printf("Invalid resolution format for '%s=%s'\n", _name, optarg); \
|
||||
printf("Invalid resolution format for '%s=%s'\n", x_name, optarg); \
|
||||
return -1; \
|
||||
case -2: \
|
||||
printf("Invalid width of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_WIDTH, VIDEO_MAX_WIDTH); \
|
||||
printf("Invalid width of '%s=%s': min=%u, max=%u\n", x_name, optarg, US_VIDEO_MIN_WIDTH, US_VIDEO_MAX_WIDTH); \
|
||||
return -1; \
|
||||
case -3: \
|
||||
printf("Invalid height of '%s=%s': min=%u, max=%u\n", _name, optarg, VIDEO_MIN_HEIGHT, VIDEO_MAX_HEIGHT); \
|
||||
printf("Invalid height of '%s=%s': min=%u, max=%u\n", x_name, optarg, US_VIDEO_MIN_HEIGHT, US_VIDEO_MAX_HEIGHT); \
|
||||
return -1; \
|
||||
case 0: break; \
|
||||
default: assert(0 && "Unknown error"); \
|
||||
@@ -303,48 +298,48 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_PARSE(_name, _dest, _func, _invalid, _available) { \
|
||||
if ((_dest = _func(optarg)) == _invalid) { \
|
||||
printf("Unknown " _name ": %s; available: %s\n", optarg, _available); \
|
||||
# define OPT_PARSE(x_name, x_dest, x_func, x_invalid, x_available) { \
|
||||
if ((x_dest = x_func(optarg)) == x_invalid) { \
|
||||
printf("Unknown " x_name ": %s; available: %s\n", optarg, x_available); \
|
||||
return -1; \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_CTL_DEFAULT_NOBREAK(_dest) { \
|
||||
dev->ctl._dest.mode = CTL_MODE_DEFAULT; \
|
||||
# define OPT_CTL_DEFAULT_NOBREAK(x_dest) { \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_DEFAULT; \
|
||||
}
|
||||
|
||||
# define OPT_CTL_MANUAL(_dest) { \
|
||||
# define OPT_CTL_MANUAL(x_dest) { \
|
||||
if (!strcasecmp(optarg, "default")) { \
|
||||
OPT_CTL_DEFAULT_NOBREAK(_dest); \
|
||||
OPT_CTL_DEFAULT_NOBREAK(x_dest); \
|
||||
} else { \
|
||||
dev->ctl._dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, dev->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_CTL_AUTO(_dest) { \
|
||||
# define OPT_CTL_AUTO(x_dest) { \
|
||||
if (!strcasecmp(optarg, "default")) { \
|
||||
OPT_CTL_DEFAULT_NOBREAK(_dest); \
|
||||
OPT_CTL_DEFAULT_NOBREAK(x_dest); \
|
||||
} else if (!strcasecmp(optarg, "auto")) { \
|
||||
dev->ctl._dest.mode = CTL_MODE_AUTO; \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_AUTO; \
|
||||
} else { \
|
||||
dev->ctl._dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#_dest, dev->ctl._dest.value, INT_MIN, INT_MAX, 0); \
|
||||
dev->ctl.x_dest.mode = CTL_MODE_VALUE; \
|
||||
OPT_NUMBER("--"#x_dest, dev->ctl.x_dest.value, INT_MIN, INT_MAX, 0); \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
|
||||
char *blank_path = NULL;
|
||||
|
||||
# define ADD_SINK(_prefix) \
|
||||
char *_prefix##_name = NULL; \
|
||||
mode_t _prefix##_mode = 0660; \
|
||||
bool _prefix##_rm = false; \
|
||||
unsigned _prefix##_client_ttl = 10; \
|
||||
unsigned _prefix##_timeout = 1;
|
||||
# define ADD_SINK(x_prefix) \
|
||||
char *x_prefix##_name = NULL; \
|
||||
mode_t x_prefix##_mode = 0660; \
|
||||
bool x_prefix##_rm = false; \
|
||||
unsigned x_prefix##_client_ttl = 10; \
|
||||
unsigned x_prefix##_timeout = 1;
|
||||
ADD_SINK(sink);
|
||||
ADD_SINK(raw_sink);
|
||||
ADD_SINK(h264_sink);
|
||||
@@ -355,27 +350,27 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
# endif
|
||||
|
||||
char short_opts[128];
|
||||
build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
us_build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
|
||||
for (int ch; (ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) {
|
||||
switch (ch) {
|
||||
case _O_DEVICE: OPT_SET(dev->path, optarg);
|
||||
case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0);
|
||||
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
|
||||
case _O_DEVICE: OPT_SET(dev->path, optarg);
|
||||
case _O_INPUT: OPT_NUMBER("--input", dev->input, 0, 128, 0);
|
||||
case _O_RESOLUTION: OPT_RESOLUTION("--resolution", dev->width, dev->height, true);
|
||||
# pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
# pragma GCC diagnostic push
|
||||
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, device_parse_format, FORMAT_UNKNOWN, FORMATS_STR);
|
||||
case _O_FORMAT: OPT_PARSE("pixel format", dev->format, us_device_parse_format, US_FORMAT_UNKNOWN, US_FORMATS_STR);
|
||||
# pragma GCC diagnostic pop
|
||||
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, device_parse_standard, STANDARD_UNKNOWN, STANDARDS_STR);
|
||||
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, device_parse_io_method, IO_METHOD_UNKNOWN, IO_METHODS_STR);
|
||||
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, VIDEO_MAX_FPS, 0);
|
||||
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0);
|
||||
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
|
||||
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
|
||||
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_bufs, 1, 32, 0);
|
||||
case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0);
|
||||
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
|
||||
case _O_ENCODER: OPT_PARSE("encoder type", enc->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
|
||||
case _O_TV_STANDARD: OPT_PARSE("TV standard", dev->standard, us_device_parse_standard, US_STANDARD_UNKNOWN, US_STANDARDS_STR);
|
||||
case _O_IO_METHOD: OPT_PARSE("IO method", dev->io_method, us_device_parse_io_method, US_IO_METHOD_UNKNOWN, US_IO_METHODS_STR);
|
||||
case _O_DESIRED_FPS: OPT_NUMBER("--desired-fps", dev->desired_fps, 0, US_VIDEO_MAX_FPS, 0);
|
||||
case _O_MIN_FRAME_SIZE: OPT_NUMBER("--min-frame-size", dev->min_frame_size, 1, 8192, 0);
|
||||
case _O_PERSISTENT: OPT_SET(dev->persistent, true);
|
||||
case _O_DV_TIMINGS: OPT_SET(dev->dv_timings, true);
|
||||
case _O_BUFFERS: OPT_NUMBER("--buffers", dev->n_bufs, 1, 32, 0);
|
||||
case _O_WORKERS: OPT_NUMBER("--workers", enc->n_workers, 1, 32, 0);
|
||||
case _O_QUALITY: OPT_NUMBER("--quality", dev->jpeg_quality, 1, 100, 0);
|
||||
case _O_ENCODER: OPT_PARSE("encoder type", enc->type, us_encoder_parse_type, US_ENCODER_TYPE_UNKNOWN, ENCODER_TYPES_STR);
|
||||
case _O_GLITCHED_RESOLUTIONS: break; // Deprecated
|
||||
case _O_BLANK: OPT_SET(blank_path, optarg);
|
||||
case _O_LAST_AS_BLANK: OPT_NUMBER("--last-as-blank", stream->last_as_blank, 0, 86400, 0);
|
||||
@@ -409,7 +404,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_WHITE_BALANCE: OPT_CTL_AUTO(white_balance);
|
||||
case _O_GAIN: OPT_CTL_AUTO(gain);
|
||||
case _O_COLOR_EFFECT: OPT_CTL_MANUAL(color_effect);
|
||||
case _O_ROTATE: OPT_CTL_MANUAL(rotate);
|
||||
case _O_ROTATE: OPT_CTL_MANUAL(rotate);
|
||||
case _O_FLIP_VERTICAL: OPT_CTL_MANUAL(flip_vertical);
|
||||
case _O_FLIP_HORIZONTAL: OPT_CTL_MANUAL(flip_horizontal);
|
||||
|
||||
@@ -424,37 +419,44 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
case _O_USER: OPT_SET(server->user, optarg);
|
||||
case _O_PASSWD: OPT_SET(server->passwd, optarg);
|
||||
case _O_STATIC: OPT_SET(server->static_path, optarg);
|
||||
case _O_DROP_SAME_FRAMES: OPT_NUMBER("--drop-same-frames", server->drop_same_frames, 0, VIDEO_MAX_FPS, 0);
|
||||
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);
|
||||
|
||||
# define ADD_SINK(_opt, _lp, _up) \
|
||||
case _O_##_up: OPT_SET(_lp##_name, optarg); \
|
||||
case _O_##_up##_MODE: OPT_NUMBER("--" #_opt "sink-mode", _lp##_mode, INT_MIN, INT_MAX, 8); \
|
||||
case _O_##_up##_RM: OPT_SET(_lp##_rm, true); \
|
||||
case _O_##_up##_CLIENT_TTL: OPT_NUMBER("--" #_opt "sink-client-ttl", _lp##_client_ttl, 1, 60, 0); \
|
||||
case _O_##_up##_TIMEOUT: OPT_NUMBER("--" #_opt "sink-timeout", _lp##_timeout, 1, 60, 0);
|
||||
# define ADD_SINK(x_opt, x_lp, x_up) \
|
||||
case _O_##x_up: OPT_SET(x_lp##_name, optarg); \
|
||||
case _O_##x_up##_MODE: OPT_NUMBER("--" #x_opt "sink-mode", x_lp##_mode, INT_MIN, INT_MAX, 8); \
|
||||
case _O_##x_up##_RM: OPT_SET(x_lp##_rm, true); \
|
||||
case _O_##x_up##_CLIENT_TTL: OPT_NUMBER("--" #x_opt "sink-client-ttl", x_lp##_client_ttl, 1, 60, 0); \
|
||||
case _O_##x_up##_TIMEOUT: OPT_NUMBER("--" #x_opt "sink-timeout", x_lp##_timeout, 1, 60, 0);
|
||||
ADD_SINK("", sink, SINK)
|
||||
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
||||
ADD_SINK("h264-", h264_sink, H264_SINK)
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 20000, 0);
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 20000, 0);
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
|
||||
# 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
|
||||
case _O_EXIT_ON_PARENT_DEATH:
|
||||
if (process_track_parent_death() < 0) {
|
||||
if (us_process_track_parent_death() < 0) {
|
||||
return -1;
|
||||
};
|
||||
break;
|
||||
@@ -465,15 +467,15 @@ 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", 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_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(VERSION); return 1;
|
||||
case _O_VERSION: puts(US_VERSION); return 1;
|
||||
case _O_FEATURES: _features(); return 1;
|
||||
|
||||
case 0: break;
|
||||
@@ -481,22 +483,24 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
}
|
||||
}
|
||||
|
||||
options->blank = blank_frame_init(blank_path);
|
||||
US_LOG_INFO("Starting PiKVM uStreamer %s ...", US_VERSION);
|
||||
|
||||
options->blank = us_blank_frame_init(blank_path);
|
||||
stream->blank = options->blank;
|
||||
|
||||
# define ADD_SINK(_label, _prefix) { \
|
||||
if (_prefix##_name && _prefix##_name[0] != '\0') { \
|
||||
options->_prefix = memsink_init( \
|
||||
_label, \
|
||||
_prefix##_name, \
|
||||
# define ADD_SINK(x_label, x_prefix) { \
|
||||
if (x_prefix##_name && x_prefix##_name[0] != '\0') { \
|
||||
options->x_prefix = us_memsink_init( \
|
||||
x_label, \
|
||||
x_prefix##_name, \
|
||||
true, \
|
||||
_prefix##_mode, \
|
||||
_prefix##_rm, \
|
||||
_prefix##_client_ttl, \
|
||||
_prefix##_timeout \
|
||||
x_prefix##_mode, \
|
||||
x_prefix##_rm, \
|
||||
x_prefix##_client_ttl, \
|
||||
x_prefix##_timeout \
|
||||
); \
|
||||
} \
|
||||
stream->_prefix = options->_prefix; \
|
||||
stream->x_prefix = options->x_prefix; \
|
||||
}
|
||||
ADD_SINK("JPEG", sink);
|
||||
ADD_SINK("RAW", raw_sink);
|
||||
@@ -505,7 +509,7 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
|
||||
# ifdef WITH_SETPROCTITLE
|
||||
if (process_name_prefix != NULL) {
|
||||
process_set_name_prefix(options->argc, options->argv, process_name_prefix);
|
||||
us_process_set_name_prefix(options->argc, options->argv, process_name_prefix);
|
||||
}
|
||||
# endif
|
||||
|
||||
@@ -526,10 +530,10 @@ static int _parse_resolution(const char *str, unsigned *width, unsigned *height,
|
||||
return -1;
|
||||
}
|
||||
if (limited) {
|
||||
if (tmp_width < VIDEO_MIN_WIDTH || tmp_width > VIDEO_MAX_WIDTH) {
|
||||
if (tmp_width < US_VIDEO_MIN_WIDTH || tmp_width > US_VIDEO_MAX_WIDTH) {
|
||||
return -2;
|
||||
}
|
||||
if (tmp_height < VIDEO_MIN_HEIGHT || tmp_height > VIDEO_MAX_HEIGHT) {
|
||||
if (tmp_height < US_VIDEO_MIN_HEIGHT || tmp_height > US_VIDEO_MAX_HEIGHT) {
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
@@ -538,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");
|
||||
@@ -570,11 +586,11 @@ static void _features(void) {
|
||||
# endif
|
||||
}
|
||||
|
||||
static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) {
|
||||
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
|
||||
static void _help(FILE *fp, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) {
|
||||
# define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
|
||||
SAY("═══════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", VERSION);
|
||||
SAY("Version: %s; license: GPLv3", US_VERSION);
|
||||
SAY("Copyright (C) 2018-2022 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Capturing options:");
|
||||
SAY("══════════════════");
|
||||
@@ -582,12 +598,12 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" -i|--input <N> ────────────────────── Input channel. Default: %u.\n", dev->input);
|
||||
SAY(" -r|--resolution <WxH> ─────────────── Initial image resolution. Default: %ux%u.\n", dev->width, dev->height);
|
||||
SAY(" -m|--format <fmt> ─────────────────── Image format.");
|
||||
SAY(" Available: %s; default: YUYV.\n", FORMATS_STR);
|
||||
SAY(" Available: %s; default: YUYV.\n", US_FORMATS_STR);
|
||||
SAY(" -a|--tv-standard <std> ────────────── Force TV standard.");
|
||||
SAY(" Available: %s; default: disabled.\n", STANDARDS_STR);
|
||||
SAY(" Available: %s; default: disabled.\n", US_STANDARDS_STR);
|
||||
SAY(" -I|--io-method <method> ───────────── Set V4L2 IO method (see kernel documentation).");
|
||||
SAY(" Changing of this parameter may increase the performance. Or not.");
|
||||
SAY(" Available: %s; default: MMAP.\n", IO_METHODS_STR);
|
||||
SAY(" Available: %s; default: MMAP.\n", US_IO_METHODS_STR);
|
||||
SAY(" -f|--desired-fps <N> ──────────────── Desired FPS. Default: maximum possible.\n");
|
||||
SAY(" -z|--min-frame-size <N> ───────────── Drop frames smaller then this limit. Useful if the device");
|
||||
SAY(" produces small-sized garbage frames. Default: %zu bytes.\n", dev->min_frame_size);
|
||||
@@ -665,15 +681,17 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
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(_name, _opt) \
|
||||
SAY(_name " sink options:"); \
|
||||
# define ADD_SINK(x_name, x_opt) \
|
||||
SAY(x_name " sink options:"); \
|
||||
SAY("══════════════════"); \
|
||||
SAY(" --" _opt "sink <name> ──────────── Use the shared memory to sink " _name " frames. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-mode <mode> ─────── Set " _name " sink permissions (like 777). Default: 660.\n"); \
|
||||
SAY(" --" _opt "sink-rm ──────────────── Remove shared memory on stop. Default: disabled.\n"); \
|
||||
SAY(" --" _opt "sink-client-ttl <sec> ── Client TTL. Default: 10.\n"); \
|
||||
SAY(" --" _opt "sink-timeout <sec> ───── Timeout for lock. Default: 1.\n");
|
||||
SAY(" --" x_opt "sink <name> ──────────── Use the shared memory to sink " x_name " frames. Default: disabled.\n"); \
|
||||
SAY(" --" x_opt "sink-mode <mode> ─────── Set " x_name " sink permissions (like 777). Default: 660.\n"); \
|
||||
SAY(" --" x_opt "sink-rm ──────────────── Remove shared memory on stop. Default: disabled.\n"); \
|
||||
SAY(" --" x_opt "sink-client-ttl <sec> ── Client TTL. Default: 10.\n"); \
|
||||
SAY(" --" x_opt "sink-timeout <sec> ───── Timeout for lock. Default: 1.\n");
|
||||
ADD_SINK("JPEG", "")
|
||||
ADD_SINK("RAW", "raw-")
|
||||
ADD_SINK("H264", "h264-")
|
||||
@@ -684,8 +702,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", 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");
|
||||
@@ -710,7 +728,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", 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");
|
||||
|
||||
@@ -50,17 +50,17 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned argc;
|
||||
char **argv;
|
||||
char **argv_copy;
|
||||
frame_s *blank;
|
||||
memsink_s *sink;
|
||||
memsink_s *raw_sink;
|
||||
memsink_s *h264_sink;
|
||||
} options_s;
|
||||
unsigned argc;
|
||||
char **argv;
|
||||
char **argv_copy;
|
||||
us_frame_s *blank;
|
||||
us_memsink_s *sink;
|
||||
us_memsink_s *raw_sink;
|
||||
us_memsink_s *h264_sink;
|
||||
} us_options_s;
|
||||
|
||||
|
||||
options_s *options_init(unsigned argc, char *argv[]);
|
||||
void options_destroy(options_s *options);
|
||||
us_options_s *us_options_init(unsigned argc, char *argv[]);
|
||||
void us_options_destroy(us_options_s *options);
|
||||
|
||||
int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server);
|
||||
int options_parse(us_options_s *options, us_device_s *dev, us_encoder_s *enc, us_stream_s *stream, us_server_s *server);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user