mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25d87d5fa8 | ||
|
|
e8a7fb32ac | ||
|
|
9d5eb8bacb | ||
|
|
353e58d7ca | ||
|
|
6c24c9ea61 | ||
|
|
dfeefe5a1c | ||
|
|
aae090ab4e | ||
|
|
18038799f0 | ||
|
|
fab4c47f17 | ||
|
|
c40b3ee225 | ||
|
|
fca69db680 | ||
|
|
0d974a5faf | ||
|
|
1ed39790ba | ||
|
|
75a193f997 | ||
|
|
65c652e624 | ||
|
|
ae2f270f50 | ||
|
|
0a639eabca | ||
|
|
9ec59143dd | ||
|
|
e059a21ef9 | ||
|
|
074ce86f67 | ||
|
|
b8b67de5cf | ||
|
|
5f3198e72f | ||
|
|
3a3889d02c | ||
|
|
88203f9c53 | ||
|
|
24aca349a3 | ||
|
|
a9e0cb49e9 | ||
|
|
4ec3f11935 | ||
|
|
14e9d9f7af | ||
|
|
580ca68291 | ||
|
|
37f3f093dc | ||
|
|
70fa6548fe | ||
|
|
f8a703f166 | ||
|
|
3f69dd785f | ||
|
|
8e6c374acf | ||
|
|
caf9ed7bfe | ||
|
|
94b1224456 | ||
|
|
c8201df720 | ||
|
|
e0f09f65a1 | ||
|
|
4e1f62bfac | ||
|
|
b0b881f199 | ||
|
|
a21f527bce | ||
|
|
d64077c2d5 | ||
|
|
83f12baa61 | ||
|
|
b6fac2608d | ||
|
|
e6ebc12505 | ||
|
|
8c92ab6f47 | ||
|
|
7dc492d875 | ||
|
|
d43014346d | ||
|
|
bcd447963c | ||
|
|
eec6cfd0d4 | ||
|
|
f177300e69 | ||
|
|
7015a26a63 | ||
|
|
290282b6b6 | ||
|
|
a339ff5d06 | ||
|
|
8d4e9a6ca0 | ||
|
|
f0f5fcd67f | ||
|
|
02fabc7b45 | ||
|
|
bdf55396e5 | ||
|
|
976f466038 | ||
|
|
b79b4fd55e | ||
|
|
6d77f5334f | ||
|
|
25957de017 | ||
|
|
847f34e10c | ||
|
|
0ab8e0d05e | ||
|
|
ac88996a8c | ||
|
|
408157c82b | ||
|
|
7356dea737 | ||
|
|
87a75a816a | ||
|
|
b6a2332207 | ||
|
|
34c0dcb1ce | ||
|
|
283f31a5a6 | ||
|
|
2f1264c916 | ||
|
|
69e7cbf746 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 6.3
|
||||
current_version = 6.12
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,8 +3,9 @@
|
||||
/pkg/arch/pkg/
|
||||
/pkg/arch/src/
|
||||
/src/build/
|
||||
/python/build/
|
||||
/python/dist/
|
||||
/python/ustreamer.egg-info/
|
||||
/python/root/
|
||||
/janus/build/
|
||||
/ustreamer
|
||||
/ustreamer-*
|
||||
|
||||
10
Makefile
10
Makefile
@@ -9,6 +9,8 @@ PY ?= python3
|
||||
CFLAGS ?= -O3
|
||||
LDFLAGS ?=
|
||||
|
||||
R_DESTDIR = $(if $(DESTDIR),$(shell realpath "$(DESTDIR)"),)
|
||||
|
||||
export
|
||||
|
||||
_LINTERS_IMAGE ?= ustreamer-linters
|
||||
@@ -43,7 +45,7 @@ apps:
|
||||
|
||||
python:
|
||||
$(MAKE) -C python
|
||||
$(ECHO) ln -sf python/build/lib.*/*.so .
|
||||
$(ECHO) ln -sf python/root/usr/lib/python*/site-packages/*.so .
|
||||
|
||||
|
||||
janus:
|
||||
@@ -59,10 +61,10 @@ endif
|
||||
ifneq ($(call optbool,$(WITH_JANUS)),)
|
||||
$(MAKE) -C janus install
|
||||
endif
|
||||
mkdir -p $(DESTDIR)$(MANPREFIX)/man1
|
||||
mkdir -p $(R_DESTDIR)$(MANPREFIX)/man1
|
||||
for man in $(shell ls man); do \
|
||||
install -m644 man/$$man $(DESTDIR)$(MANPREFIX)/man1/$$man; \
|
||||
gzip -f $(DESTDIR)$(MANPREFIX)/man1/$$man; \
|
||||
install -m644 man/$$man $(R_DESTDIR)$(MANPREFIX)/man1/$$man; \
|
||||
gzip -f $(R_DESTDIR)$(MANPREFIX)/man1/$$man; \
|
||||
done
|
||||
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ v4l2 utilities provide the tools to manage USB webcam setting and information. S
|
||||
|
||||
-----
|
||||
# License
|
||||
Copyright (C) 2018-2023 by Maxim Devaev mdevaev@gmail.com
|
||||
Copyright (C) 2018-2024 by 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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
DESTDIR ?=
|
||||
R_DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
CC ?= gcc
|
||||
@@ -42,8 +42,8 @@ $(_BUILD)/%.o: %.c
|
||||
|
||||
|
||||
install: $(_PLUGIN)
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/lib/ustreamer/janus
|
||||
install -m755 $(_PLUGIN) $(DESTDIR)$(PREFIX)/lib/ustreamer/janus/$(PLUGIN)
|
||||
mkdir -p $(R_DESTDIR)$(PREFIX)/lib/ustreamer/janus
|
||||
install -m755 $(_PLUGIN) $(R_DESTDIR)$(PREFIX)/lib/ustreamer/janus/$(PLUGIN)
|
||||
|
||||
|
||||
clean:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -22,6 +22,24 @@
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdatomic.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <speex/speex_resampler.h>
|
||||
#include <opus/opus.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/errors.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/array.h"
|
||||
#include "uslibs/ring.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
#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))
|
||||
@@ -31,7 +49,7 @@
|
||||
// - 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_BUF8(_hz) (_HZ_TO_BUF16(_hz) * sizeof(s16))
|
||||
|
||||
#define _MIN_PCM_HZ 8000
|
||||
#define _MAX_PCM_HZ 192000
|
||||
@@ -41,13 +59,13 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
int16_t data[_MAX_BUF16];
|
||||
s16 data[_MAX_BUF16];
|
||||
} _pcm_buffer_s;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[_MAX_BUF8]; // Worst case
|
||||
size_t used;
|
||||
uint64_t pts;
|
||||
u8 data[_MAX_BUF8]; // Worst case
|
||||
uz used;
|
||||
u64 pts;
|
||||
} _enc_buffer_s;
|
||||
|
||||
|
||||
@@ -71,7 +89,7 @@ bool us_audio_probe(const char *name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz) {
|
||||
us_audio_s *us_audio_init(const char *name, uint pcm_hz) {
|
||||
us_audio_s *audio;
|
||||
US_CALLOC(audio, 1);
|
||||
audio->pcm_hz = pcm_hz;
|
||||
@@ -162,18 +180,18 @@ void us_audio_destroy(us_audio_s *audio) {
|
||||
free(audio);
|
||||
}
|
||||
|
||||
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts) {
|
||||
int us_audio_get_encoded(us_audio_s *audio, u8 *data, uz *size, u64 *pts) {
|
||||
if (atomic_load(&audio->stop)) {
|
||||
return -1;
|
||||
}
|
||||
const int ri = us_ring_consumer_acquire(audio->enc_ring, 0.1);
|
||||
if (ri < 0) {
|
||||
return -2;
|
||||
return US_ERROR_NO_DATA;
|
||||
}
|
||||
const _enc_buffer_s *const buf = audio->enc_ring->items[ri];
|
||||
if (*size < buf->used) {
|
||||
us_ring_consumer_release(audio->enc_ring, ri);
|
||||
return -3;
|
||||
return US_ERROR_NO_DATA;
|
||||
}
|
||||
memcpy(data, buf->data, buf->used);
|
||||
*size = buf->used;
|
||||
@@ -197,8 +215,8 @@ static _enc_buffer_s *_enc_buffer_init(void) {
|
||||
static void *_pcm_thread(void *v_audio) {
|
||||
US_THREAD_SETTLE("us_a_pcm");
|
||||
|
||||
us_audio_s *const audio = (us_audio_s *)v_audio;
|
||||
uint8_t in[_MAX_BUF8];
|
||||
us_audio_s *const audio = v_audio;
|
||||
u8 in[_MAX_BUF8];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
const int frames = snd_pcm_readi(audio->pcm, in, audio->pcm_frames);
|
||||
@@ -227,8 +245,8 @@ static void *_pcm_thread(void *v_audio) {
|
||||
static void *_encoder_thread(void *v_audio) {
|
||||
US_THREAD_SETTLE("us_a_enc");
|
||||
|
||||
us_audio_s *const audio = (us_audio_s *)v_audio;
|
||||
int16_t in_res[_MAX_BUF16];
|
||||
us_audio_s *const audio = v_audio;
|
||||
s16 in_res[_MAX_BUF16];
|
||||
|
||||
while (!atomic_load(&audio->stop)) {
|
||||
const int in_ri = us_ring_consumer_acquire(audio->pcm_ring, 0.1);
|
||||
@@ -237,11 +255,11 @@ static void *_encoder_thread(void *v_audio) {
|
||||
}
|
||||
_pcm_buffer_s *const in = audio->pcm_ring->items[in_ri];
|
||||
|
||||
int16_t *in_ptr;
|
||||
s16 *in_ptr;
|
||||
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);
|
||||
u32 in_count = audio->pcm_frames;
|
||||
u32 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 {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -22,50 +22,40 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <speex/speex_resampler.h>
|
||||
#include <opus/opus.h>
|
||||
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/array.h"
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/ring.h"
|
||||
#include "uslibs/threading.h"
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
snd_pcm_t *pcm;
|
||||
unsigned pcm_hz;
|
||||
unsigned pcm_frames;
|
||||
size_t pcm_size;
|
||||
uint pcm_hz;
|
||||
uint pcm_frames;
|
||||
uz pcm_size;
|
||||
snd_pcm_hw_params_t *pcm_params;
|
||||
SpeexResamplerState *res;
|
||||
OpusEncoder *enc;
|
||||
|
||||
us_ring_s *pcm_ring;
|
||||
us_ring_s *enc_ring;
|
||||
uint32_t pts;
|
||||
us_ring_s *pcm_ring;
|
||||
us_ring_s *enc_ring;
|
||||
u32 pts;
|
||||
|
||||
pthread_t pcm_tid;
|
||||
pthread_t enc_tid;
|
||||
bool tids_created;
|
||||
atomic_bool stop;
|
||||
pthread_t pcm_tid;
|
||||
pthread_t enc_tid;
|
||||
bool tids_created;
|
||||
atomic_bool stop;
|
||||
} us_audio_s;
|
||||
|
||||
|
||||
bool us_audio_probe(const char *name);
|
||||
|
||||
us_audio_s *us_audio_init(const char *name, unsigned pcm_hz);
|
||||
us_audio_s *us_audio_init(const char *name, uint pcm_hz);
|
||||
void us_audio_destroy(us_audio_s *audio);
|
||||
|
||||
int us_audio_get_encoded(us_audio_s *audio, uint8_t *data, size_t *size, uint64_t *pts);
|
||||
int us_audio_get_encoded(us_audio_s *audio, u8 *data, uz *size, u64 *pts);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -51,6 +51,7 @@ us_janus_client_s *us_janus_client_init(janus_callbacks *gw, janus_plugin_sessio
|
||||
client->session = session;
|
||||
atomic_init(&client->transmit, false);
|
||||
atomic_init(&client->transmit_audio, false);
|
||||
atomic_init(&client->video_orient, 0);
|
||||
|
||||
atomic_init(&client->stop, false);
|
||||
|
||||
@@ -103,7 +104,7 @@ static void *_audio_thread(void *v_client) {
|
||||
}
|
||||
|
||||
static void *_common_thread(void *v_client, bool video) {
|
||||
us_janus_client_s *const client = (us_janus_client_s *)v_client;
|
||||
us_janus_client_s *const client = v_client;
|
||||
us_ring_s *const ring = (video ? client->video_ring : client->audio_ring);
|
||||
assert(ring != NULL); // Audio may be NULL
|
||||
|
||||
@@ -131,6 +132,7 @@ static void *_common_thread(void *v_client, bool video) {
|
||||
# endif
|
||||
};
|
||||
janus_plugin_rtp_extensions_reset(&packet.extensions);
|
||||
|
||||
/*if (rtp->zero_playout_delay) {
|
||||
// https://github.com/pikvm/pikvm/issues/784
|
||||
packet.extensions.min_delay = 0;
|
||||
@@ -142,6 +144,13 @@ static void *_common_thread(void *v_client, bool video) {
|
||||
packet.extensions.max_delay = 300; // == 3s, i.e. 10ms granularity
|
||||
}*/
|
||||
|
||||
if (rtp.video) {
|
||||
const uint video_orient = atomic_load(&client->video_orient);
|
||||
if (video_orient != 0) {
|
||||
packet.extensions.video_rotation = video_orient;
|
||||
}
|
||||
}
|
||||
|
||||
client->gw->relay_rtp(client->session, &packet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -34,11 +34,12 @@
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
typedef struct us_janus_client_sx {
|
||||
typedef struct {
|
||||
janus_callbacks *gw;
|
||||
janus_plugin_session *session;
|
||||
atomic_bool transmit;
|
||||
atomic_bool transmit_audio;
|
||||
atomic_uint video_orient;
|
||||
|
||||
pthread_t video_tid;
|
||||
pthread_t audio_tid;
|
||||
@@ -47,7 +48,7 @@ typedef struct us_janus_client_sx {
|
||||
us_ring_s *video_ring;
|
||||
us_ring_s *audio_ring;
|
||||
|
||||
US_LIST_STRUCT(struct us_janus_client_sx);
|
||||
US_LIST_DECLARE;
|
||||
} us_janus_client_s;
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/errors.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
@@ -54,11 +55,11 @@ int us_memsink_fd_wait_frame(int fd, us_memsink_shared_s *mem, u64 last_id) {
|
||||
}
|
||||
usleep(1000); // lock_polling
|
||||
} while (now_ts < deadline_ts);
|
||||
return -2;
|
||||
return US_ERROR_NO_DATA;
|
||||
}
|
||||
|
||||
int us_memsink_fd_get_frame(int fd, us_memsink_shared_s *mem, us_frame_s *frame, u64 *frame_id, bool key_required) {
|
||||
us_frame_set_data(frame, mem->data, mem->used);
|
||||
us_frame_set_data(frame, us_memsink_get_data(mem), mem->used);
|
||||
US_FRAME_COPY_META(mem, frame);
|
||||
*frame_id = mem->id;
|
||||
mem->last_client_ts = us_get_now_monotonic();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/const.h"
|
||||
#include "uslibs/errors.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/threading.h"
|
||||
#include "uslibs/list.h"
|
||||
@@ -136,12 +137,18 @@ static void *_video_sink_thread(void *arg) {
|
||||
int fd = -1;
|
||||
us_memsink_shared_s *mem = NULL;
|
||||
|
||||
const uz data_size = us_memsink_calculate_size(_g_config->video_sink_name);
|
||||
if (data_size == 0) {
|
||||
US_ONCE({ US_JLOG_ERROR("video", "Invalid memsink object suffix"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
if ((fd = shm_open(_g_config->video_sink_name, O_RDWR, 0)) <= 0) {
|
||||
US_ONCE({ US_JLOG_PERROR("video", "Can't open memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
|
||||
if ((mem = us_memsink_shared_map(fd)) == NULL) {
|
||||
if ((mem = us_memsink_shared_map(fd, data_size)) == NULL) {
|
||||
US_ONCE({ US_JLOG_PERROR("video", "Can't map memsink"); });
|
||||
goto close_memsink;
|
||||
}
|
||||
@@ -172,13 +179,16 @@ static void *_video_sink_thread(void *arg) {
|
||||
if (ri >= 0 && frame->key) {
|
||||
atomic_store(&_g_key_required, false);
|
||||
}
|
||||
} else if (waited != -2) {
|
||||
} else if (waited != US_ERROR_NO_DATA) {
|
||||
goto close_memsink;
|
||||
}
|
||||
}
|
||||
|
||||
close_memsink:
|
||||
US_DELETE(mem, us_memsink_shared_unmap);
|
||||
if (mem != NULL) {
|
||||
us_memsink_shared_unmap(mem, data_size);
|
||||
mem = NULL;
|
||||
}
|
||||
US_CLOSE_FD(fd);
|
||||
US_JLOG_INFO("video", "Memsink closed");
|
||||
sleep(1); // error_delay
|
||||
@@ -453,12 +463,25 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
|
||||
} else if (!strcmp(request_str, "watch")) {
|
||||
bool with_audio = false;
|
||||
uint video_orient = 0;
|
||||
{
|
||||
json_t *const params = json_object_get(msg, "params");
|
||||
if (params != NULL) {
|
||||
json_t *const audio = json_object_get(params, "audio");
|
||||
if (audio != NULL && json_is_boolean(audio)) {
|
||||
with_audio = (_g_rtpa != NULL && json_boolean_value(audio));
|
||||
{
|
||||
json_t *const obj = json_object_get(params, "audio");
|
||||
if (obj != NULL && json_is_boolean(obj)) {
|
||||
with_audio = (_g_rtpa != NULL && json_boolean_value(obj));
|
||||
}
|
||||
}
|
||||
{
|
||||
json_t *const obj = json_object_get(params, "orientation");
|
||||
if (obj != NULL && json_is_integer(obj)) {
|
||||
video_orient = json_integer_value(obj);
|
||||
switch (video_orient) {
|
||||
case 90: case 180: case 270: break;
|
||||
default: video_orient = 0; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,6 +521,7 @@ static struct janus_plugin_result *_plugin_handle_message(
|
||||
US_LIST_ITERATE(_g_clients, client, {
|
||||
if (client->session == session) {
|
||||
atomic_store(&client->transmit_audio, with_audio);
|
||||
atomic_store(&client->video_orient, video_orient);
|
||||
}
|
||||
has_listeners = (has_listeners || atomic_load(&client->transmit_audio));
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# This source file is partially based on this code: #
|
||||
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -57,7 +57,7 @@ void us_rtp_write_header(us_rtp_s *rtp, u32 pts, bool marked) {
|
||||
++rtp->seq;
|
||||
|
||||
# define WRITE_BE_U32(x_offset, x_value) \
|
||||
*((u32 *)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
|
||||
*((u32*)(rtp->datagram + x_offset)) = __builtin_bswap32(x_value)
|
||||
WRITE_BE_U32(0, word0);
|
||||
WRITE_BE_U32(4, pts);
|
||||
WRITE_BE_U32(8, rtp->ssrc);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# This source file is partially based on this code: #
|
||||
# - https://github.com/catid/kvm/blob/master/kvm_pipeline/src #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -71,6 +71,7 @@ char *us_rtpv_make_sdp(us_rtpv_s *rtpv) {
|
||||
"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
|
||||
"a=extmap:2 urn:3gpp:video-orientation" RN
|
||||
"a=sendonly" RN,
|
||||
pl, pl, pl, pl,
|
||||
pl, pl, pl,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
1
janus/src/uslibs/errors.h
Symbolic link
1
janus/src/uslibs/errors.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/errors.h
|
||||
1
janus/src/uslibs/memsinksh.c
Symbolic link
1
janus/src/uslibs/memsinksh.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsinksh.c
|
||||
@@ -1,44 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef long long sll;
|
||||
typedef ssize_t sz;
|
||||
typedef int8_t s8;
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t s64;
|
||||
|
||||
typedef unsigned uint;
|
||||
typedef unsigned long long ull;
|
||||
typedef size_t uz;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef long double ldf;
|
||||
1
janus/src/uslibs/types.h
Symbolic link
1
janus/src/uslibs/types.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/types.h
|
||||
@@ -3,7 +3,7 @@ envlist = cppcheck, flake8, pylint, mypy, vulture, htmlhint
|
||||
skipsdist = true
|
||||
|
||||
[testenv]
|
||||
basepython = python3.11
|
||||
basepython = python3.12
|
||||
changedir = /src
|
||||
|
||||
[testenv:cppcheck]
|
||||
@@ -25,7 +25,7 @@ commands = cppcheck \
|
||||
allowlist_externals = bash
|
||||
commands = bash -c 'flake8 --config=linters/flake8.ini tools/*.py' python/*.py
|
||||
deps =
|
||||
flake8==5.0.4
|
||||
flake8
|
||||
flake8-quotes
|
||||
|
||||
[testenv:pylint]
|
||||
@@ -33,6 +33,7 @@ allowlist_externals = bash
|
||||
commands = bash -c 'pylint --rcfile=linters/pylint.ini --output-format=colorized --reports=no tools/*.py python/*.py'
|
||||
deps =
|
||||
pylint
|
||||
setuptools
|
||||
|
||||
[testenv:mypy]
|
||||
allowlist_externals = bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 6.3" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 6.12" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 6.3" "November 2020"
|
||||
.TH USTREAMER 1 "version 6.12" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
@@ -96,8 +96,6 @@ HW ─ Use pre-encoded MJPEG frames directly from camera hardware.
|
||||
M2M-VIDEO ─ GPU-accelerated MJPEG encoding.
|
||||
|
||||
M2M-IMAGE ─ GPU-accelerated JPEG encoding.
|
||||
|
||||
NOOP ─ Don't compress MJPEG stream (do nothing).
|
||||
.TP
|
||||
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
|
||||
It doesn't do anything. Still here for compatibility.
|
||||
@@ -212,25 +210,25 @@ Timeout for client connections. Default: 10.
|
||||
.SS "JPEG sink options"
|
||||
With shared memory sink you can write a stream to a file. See \fBustreamer-dump\fR(1) for more info.
|
||||
.TP
|
||||
.BR \-\-sink\ \fIname
|
||||
Use the specified shared memory object to sink JPEG frames. Default: disabled.
|
||||
.BR \-\-jpeg\-sink\ \fIname
|
||||
Use the specified shared memory object to sink JPEG frames. The name should end with a suffix ".jpeg" or ":jpeg". Default: disabled.
|
||||
.TP
|
||||
.BR \-\-sink\-mode\ \fImode
|
||||
.BR \-\-jpeg\-sink\-mode\ \fImode
|
||||
Set JPEG sink permissions (like 777). Default: 660.
|
||||
.TP
|
||||
.BR \-\-sink\-rm
|
||||
.BR \-\-jpeg\-sink\-rm
|
||||
Remove shared memory on stop. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-sink\-client\-ttl\ \fIsec
|
||||
.BR \-\-jpeg\-sink\-client\-ttl\ \fIsec
|
||||
Client TTL. Default: 10.
|
||||
.TP
|
||||
.BR \-\-sink\-timeout\ \fIsec
|
||||
.BR \-\-jpeg\-sink\-timeout\ \fIsec
|
||||
Timeout for lock. Default: 1.
|
||||
|
||||
.SS "H264 sink options"
|
||||
.TP
|
||||
.BR \-\-h264\-sink\ \fIname
|
||||
Use the specified shared memory object to sink H264 frames. Default: disabled.
|
||||
Use the specified shared memory object to sink H264 frames. The name should end with a suffix ".h264" or ":h264". Default: disabled.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\-mode\ \fImode
|
||||
Set H264 sink permissions (like 777). Default: 660.
|
||||
@@ -253,6 +251,22 @@ Interval between keyframes. Default: 30.
|
||||
.BR \-\-h264\-m2m\-device\ \fI/dev/path
|
||||
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
|
||||
|
||||
.SS "RAW sink options"
|
||||
.TP
|
||||
.BR \-\-raw\-sink\ \fIname
|
||||
Use the specified shared memory object to sink RAW frames. The name should end with a suffix ".raw" or ":raw". Default: disabled.
|
||||
.TP
|
||||
.BR \-\-raw\-sink\-mode\ \fImode
|
||||
Set RAW sink permissions (like 777). Default: 660.
|
||||
.TP
|
||||
.BR \-\-raw\-sink\-rm
|
||||
Remove shared memory on stop. Default: disabled.
|
||||
.TP
|
||||
.BR \-\-raw\-sink\-client\-ttl\ \fIsec
|
||||
Client TTL. Default: 10.
|
||||
.TP
|
||||
.BR \-\-raw\-sink\-timeout\ \fIsec
|
||||
Timeout for lock. Default: 1.
|
||||
|
||||
.SS "Process options"
|
||||
.TP
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=6.3
|
||||
pkgver=6.12
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
@@ -19,7 +19,7 @@ _options="WITH_GPIO=1 WITH_SYSTEMD=1"
|
||||
if [ -e /usr/bin/python3 ]; then
|
||||
_options="$_options WITH_PYTHON=1"
|
||||
depends+=(python)
|
||||
makedepends+=(python-setuptools)
|
||||
makedepends+=(python-setuptools python-pip python-build python-wheel)
|
||||
fi
|
||||
if [ -e /usr/include/janus/plugins/plugin.h ];then
|
||||
depends+=(janus-gateway alsa-lib opus)
|
||||
@@ -28,12 +28,6 @@ if [ -e /usr/include/janus/plugins/plugin.h ];then
|
||||
fi
|
||||
|
||||
|
||||
# LD does not link mmal with this option
|
||||
# This DOESN'T affect setup.py
|
||||
LDFLAGS="${LDFLAGS//--as-needed/}"
|
||||
export LDFLAGS="${LDFLAGS//,,/,}"
|
||||
|
||||
|
||||
build() {
|
||||
cd "$srcdir"
|
||||
rm -rf $pkgname-build
|
||||
|
||||
@@ -24,7 +24,7 @@ RUN apk add --no-cache \
|
||||
WORKDIR /ustreamer
|
||||
COPY --from=build /build/ustreamer/src/ustreamer.bin ustreamer
|
||||
|
||||
RUN wget https://raw.githubusercontent.com/pikvm/kvmd/master/configs/kvmd/edid/v3-hdmi.hex -O /edid.hex
|
||||
RUN wget https://raw.githubusercontent.com/pikvm/kvmd/master/configs/kvmd/edid/v2.hex -O /edid.hex
|
||||
COPY pkg/docker/entry.sh /
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=6.3
|
||||
PKG_VERSION:=6.12
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
2
python/MANIFEST.in
Normal file
2
python/MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
||||
include setup.py
|
||||
recursive-include src *.c *h
|
||||
@@ -1,20 +1,23 @@
|
||||
-include ../config.mk
|
||||
|
||||
DESTDIR ?=
|
||||
R_DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
PY ?= python3
|
||||
|
||||
|
||||
# =====
|
||||
all:
|
||||
all: root
|
||||
root: $(shell find src -type f,l)
|
||||
$(info == PY_BUILD ustreamer-*.so)
|
||||
$(ECHO) $(PY) setup.py build
|
||||
rm -rf root
|
||||
$(ECHO) $(PY) -m build --skip-dependency-check --no-isolation
|
||||
$(ECHO) $(PY) -m pip install dist/*.whl --ignore-installed --root=./root
|
||||
|
||||
|
||||
install:
|
||||
$(PY) setup.py install --prefix=$(PREFIX) --root=$(if $(DESTDIR),$(DESTDIR),/)
|
||||
$(PY) -m pip install dist/*.whl --ignore-installed --prefix=$(PREFIX) --root=$(if $(R_DESTDIR),$(R_DESTDIR),/)
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf build ustreamer.egg-info
|
||||
rm -rf root dist ustreamer.egg-info
|
||||
|
||||
@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="6.3",
|
||||
version="6.12",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
@@ -29,7 +29,6 @@ if __name__ == "__main__":
|
||||
extra_compile_args=["-std=c17", "-D_GNU_SOURCE"],
|
||||
undef_macros=["NDEBUG"],
|
||||
sources=_find_sources(".c"),
|
||||
depends=_find_sources(".h"),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
1
python/src/uslibs/errors.h
Symbolic link
1
python/src/uslibs/errors.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/errors.h
|
||||
1
python/src/uslibs/memsinksh.c
Symbolic link
1
python/src/uslibs/memsinksh.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/memsinksh.c
|
||||
@@ -1,44 +0,0 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef long long sll;
|
||||
typedef ssize_t sz;
|
||||
typedef int8_t s8;
|
||||
typedef int16_t s16;
|
||||
typedef int32_t s32;
|
||||
typedef int64_t s64;
|
||||
|
||||
typedef unsigned uint;
|
||||
typedef unsigned long long ull;
|
||||
typedef size_t uz;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
typedef long double ldf;
|
||||
1
python/src/uslibs/types.h
Symbolic link
1
python/src/uslibs/types.h
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../src/libs/types.h
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <Python.h>
|
||||
|
||||
#include "uslibs/types.h"
|
||||
#include "uslibs/errors.h"
|
||||
#include "uslibs/tools.h"
|
||||
#include "uslibs/frame.h"
|
||||
#include "uslibs/memsinksh.h"
|
||||
@@ -26,6 +27,7 @@ typedef struct {
|
||||
double lock_timeout;
|
||||
double wait_timeout;
|
||||
double drop_same_frames;
|
||||
uz data_size;
|
||||
|
||||
int fd;
|
||||
us_memsink_shared_s *mem;
|
||||
@@ -37,7 +39,10 @@ typedef struct {
|
||||
|
||||
|
||||
static void _MemsinkObject_destroy_internals(_MemsinkObject *self) {
|
||||
US_DELETE(self->mem, us_memsink_shared_unmap);
|
||||
if (self->mem != NULL) {
|
||||
us_memsink_shared_unmap(self->mem, self->data_size);
|
||||
self->mem = NULL;
|
||||
}
|
||||
US_CLOSE_FD(self->fd);
|
||||
US_DELETE(self->frame, us_frame_destroy);
|
||||
}
|
||||
@@ -64,13 +69,18 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
|
||||
SET_DOUBLE(drop_same_frames, >= 0);
|
||||
# undef SET_DOUBLE
|
||||
|
||||
if ((self->data_size = us_memsink_calculate_size(self->obj)) == 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "Invalid memsink object suffix");
|
||||
return -1;
|
||||
}
|
||||
|
||||
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 = us_memsink_shared_map(self->fd)) == NULL) {
|
||||
if ((self->mem = us_memsink_shared_map(self->fd, self->data_size)) == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
@@ -99,11 +109,11 @@ static PyObject *_MemsinkObject_close(_MemsinkObject *self, PyObject *Py_UNUSED(
|
||||
|
||||
static PyObject *_MemsinkObject_enter(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
Py_INCREF(self);
|
||||
return (PyObject *)self;
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_exit(_MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
return PyObject_CallMethod((PyObject *)self, "close", "");
|
||||
return PyObject_CallMethod((PyObject*)self, "close", "");
|
||||
}
|
||||
|
||||
static int _wait_frame(_MemsinkObject *self) {
|
||||
@@ -139,7 +149,7 @@ static int _wait_frame(_MemsinkObject *self) {
|
||||
if (
|
||||
US_FRAME_COMPARE_GEOMETRY(self->mem, self->frame)
|
||||
&& (self->frame_ts + self->drop_same_frames > now_ts)
|
||||
&& !memcmp(self->frame->data, mem->data, mem->used)
|
||||
&& !memcmp(self->frame->data, us_memsink_get_data(mem), mem->used)
|
||||
) {
|
||||
self->frame_id = mem->id;
|
||||
goto retry;
|
||||
@@ -166,9 +176,9 @@ static int _wait_frame(_MemsinkObject *self) {
|
||||
if (PyErr_CheckSignals() < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
} while (now_ts < deadline_ts);
|
||||
return -2;
|
||||
|
||||
return US_ERROR_NO_DATA;
|
||||
}
|
||||
|
||||
static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args, PyObject *kwargs) {
|
||||
@@ -185,12 +195,12 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
|
||||
|
||||
switch (_wait_frame(self)) {
|
||||
case 0: break;
|
||||
case -2: Py_RETURN_NONE;
|
||||
case US_ERROR_NO_DATA: Py_RETURN_NONE;
|
||||
default: return NULL;
|
||||
}
|
||||
|
||||
us_memsink_shared_s *mem = self->mem;
|
||||
us_frame_set_data(self->frame, mem->data, mem->used);
|
||||
us_frame_set_data(self->frame, us_memsink_get_data(mem), mem->used);
|
||||
US_FRAME_COPY_META(self->mem, self->frame);
|
||||
self->frame_id = mem->id;
|
||||
self->frame_ts = us_get_now_monotonic();
|
||||
@@ -230,7 +240,7 @@ static PyObject *_MemsinkObject_wait_frame(_MemsinkObject *self, PyObject *args,
|
||||
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 *)self->frame->data, self->frame->used));
|
||||
SET_VALUE("data", PyBytes_FromStringAndSize((const char*)self->frame->data, self->frame->used));
|
||||
|
||||
# undef SET_NUMBER
|
||||
# undef SET_VALUE
|
||||
@@ -305,7 +315,7 @@ PyMODINIT_FUNC PyInit_ustreamer(void) {
|
||||
|
||||
Py_INCREF(&_MemsinkType);
|
||||
|
||||
if (PyModule_AddObject(module, "Memsink", (PyObject *)&_MemsinkType) < 0) {
|
||||
if (PyModule_AddObject(module, "Memsink", (PyObject*)&_MemsinkType) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
45
src/Makefile
45
src/Makefile
@@ -1,4 +1,4 @@
|
||||
DESTDIR ?=
|
||||
R_DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
CC ?= gcc
|
||||
@@ -12,11 +12,11 @@ _DUMP = ustreamer-dump.bin
|
||||
_V4P = ustreamer-v4p.bin
|
||||
|
||||
_CFLAGS = -MD -c -std=c17 -Wall -Wextra -D_GNU_SOURCE $(CFLAGS)
|
||||
_LDFLAGS = $(LDFLAGS)
|
||||
|
||||
_COMMON_LIBS = -lm -ljpeg -pthread -lrt -latomic
|
||||
_USTR_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic -levent -levent_pthreads
|
||||
_DUMP_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic
|
||||
_V4P_LDFLAGS = $(LDFLAGS) -lm -ljpeg -pthread -lrt -latomic
|
||||
|
||||
_USTR_LIBS = $(_COMMON_LIBS) -levent -levent_pthreads
|
||||
_USTR_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
ustreamer/*.c \
|
||||
@@ -27,15 +27,14 @@ _USTR_SRCS = $(shell ls \
|
||||
ustreamer/*.c \
|
||||
)
|
||||
|
||||
_DUMP_LIBS = $(_COMMON_LIBS)
|
||||
_DUMP_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
dump/*.c \
|
||||
)
|
||||
|
||||
_V4P_LIBS = $(_COMMON_LIBS)
|
||||
_V4P_SRCS = $(shell ls \
|
||||
libs/*.c \
|
||||
libs/drm/*.c \
|
||||
v4p/*.c \
|
||||
)
|
||||
|
||||
@@ -52,16 +51,16 @@ endef
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_GPIO)),)
|
||||
_USTR_LIBS += -lgpiod
|
||||
override _CFLAGS += -DWITH_GPIO $(shell pkg-config --atleast-version=2 libgpiod 2> /dev/null && echo -DHAVE_GPIOD2)
|
||||
_USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
|
||||
override _USTR_LDFLAGS += -lgpiod
|
||||
override _USTR_SRCS += $(shell ls ustreamer/gpio/*.c)
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(call optbool,$(WITH_SYSTEMD)),)
|
||||
_USTR_LIBS += -lsystemd
|
||||
override _CFLAGS += -DWITH_SYSTEMD
|
||||
_USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
|
||||
override _USTR_LDFLAGS += -lsystemd
|
||||
override _USTR_SRCS += $(shell ls ustreamer/http/systemd/*.c)
|
||||
endif
|
||||
|
||||
|
||||
@@ -73,10 +72,10 @@ endif
|
||||
|
||||
WITH_SETPROCTITLE ?= 1
|
||||
ifneq ($(call optbool,$(WITH_SETPROCTITLE)),)
|
||||
ifeq ($(shell uname -s | tr A-Z a-z),linux)
|
||||
_USTR_LIBS += -lbsd
|
||||
endif
|
||||
override _CFLAGS += -DWITH_SETPROCTITLE
|
||||
ifeq ($(shell uname -s | tr A-Z a-z),linux)
|
||||
override _USTR_LDFLAGS += -lbsd
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
@@ -84,8 +83,10 @@ WITH_V4P ?= 0
|
||||
ifneq ($(call optbool,$(WITH_V4P)),)
|
||||
override _TARGETS += $(_V4P)
|
||||
override _OBJS += $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
|
||||
override _CFLAGS += $(shell pkg-config --cflags libdrm)
|
||||
_V4P_LDFLAGS = $(shell pkg-config --libs libdrm)
|
||||
override _CFLAGS += -DWITH_V4P $(shell pkg-config --cflags libdrm)
|
||||
override _V4P_LDFLAGS += $(shell pkg-config --libs libdrm)
|
||||
override _USTR_SRCS += $(shell ls libs/drm/*.c)
|
||||
override _USTR_LDFLAGS += $(shell pkg-config --libs libdrm)
|
||||
endif
|
||||
|
||||
|
||||
@@ -94,29 +95,31 @@ all: $(_TARGETS)
|
||||
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||
mkdir -p $(R_DESTDIR)$(PREFIX)/bin
|
||||
for i in $(subst .bin,,$(_TARGETS)); do \
|
||||
install -m755 $$i.bin $(DESTDIR)$(PREFIX)/bin/$$i; \
|
||||
install -m755 $$i.bin $(R_DESTDIR)$(PREFIX)/bin/$$i; \
|
||||
done
|
||||
|
||||
|
||||
install-strip: install
|
||||
for i in $(subst .bin,,$(_TARGETS)); do strip $(DESTDIR)$(PREFIX)/bin/$$i; done
|
||||
for i in $(subst .bin,,$(_TARGETS)); do \
|
||||
strip $(R_DESTDIR)$(PREFIX)/bin/$$i; \
|
||||
done
|
||||
|
||||
|
||||
$(_USTR): $(_USTR_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_USTR_LIBS)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_USTR_LDFLAGS)
|
||||
|
||||
|
||||
$(_DUMP): $(_DUMP_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_DUMP_LIBS)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_DUMP_LDFLAGS)
|
||||
|
||||
|
||||
$(_V4P): $(_V4P_SRCS:%.c=$(_BUILD)/%.o)
|
||||
$(info == LD $@)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_LDFLAGS) $(_V4P_LDFLAGS) $(_V4P_LIBS)
|
||||
$(ECHO) $(CC) $^ -o $@ $(_V4P_LDFLAGS)
|
||||
|
||||
|
||||
$(_BUILD)/%.o: %.c
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -47,7 +47,7 @@ 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) {
|
||||
us_output_file_s *output = (us_output_file_s *)v_output;
|
||||
us_output_file_s *output = v_output;
|
||||
if (output->json) {
|
||||
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated);
|
||||
fprintf(output->fp,
|
||||
@@ -66,7 +66,7 @@ void us_output_file_write(void *v_output, const us_frame_s *frame) {
|
||||
}
|
||||
|
||||
void us_output_file_destroy(void *v_output) {
|
||||
us_output_file_s *output = (us_output_file_s *)v_output;
|
||||
us_output_file_s *output = v_output;
|
||||
US_DELETE(output->base64_data, free);
|
||||
if (output->fp && output->fp != stdout) {
|
||||
if (fclose(output->fp) < 0) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -31,10 +31,12 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include "../libs/const.h"
|
||||
#include "../libs/errors.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
#include "../libs/fpsi.h"
|
||||
#include "../libs/signal.h"
|
||||
#include "../libs/options.h"
|
||||
|
||||
@@ -182,7 +184,7 @@ int main(int argc, char *argv[]) {
|
||||
_output_context_s ctx = {0};
|
||||
|
||||
if (output_path && output_path[0] != '\0') {
|
||||
if ((ctx.v_output = (void *)us_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 = us_output_file_write;
|
||||
@@ -220,26 +222,22 @@ static int _dump_sink(
|
||||
const useconds_t interval_us = interval * 1000000;
|
||||
|
||||
us_frame_s *frame = us_frame_init();
|
||||
us_fpsi_s *fpsi = us_fpsi_init("SINK", false);
|
||||
us_memsink_s *sink = NULL;
|
||||
|
||||
if ((sink = us_memsink_init("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
|
||||
if ((sink = us_memsink_init_opened("input", sink_name, false, 0, false, 0, sink_timeout)) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
unsigned fps = 0;
|
||||
unsigned fps_accum = 0;
|
||||
long long fps_second = 0;
|
||||
|
||||
long double last_ts = 0;
|
||||
|
||||
while (!_g_stop) {
|
||||
bool key_requested;
|
||||
const int error = us_memsink_client_get(sink, frame, &key_requested, key_required);
|
||||
if (error == 0) {
|
||||
const int got = us_memsink_client_get(sink, frame, &key_requested, key_required);
|
||||
if (got == 0) {
|
||||
key_required = false;
|
||||
|
||||
const long double now = us_get_now_monotonic();
|
||||
const long long now_second = us_floor_ms(now);
|
||||
|
||||
char fourcc_str[8];
|
||||
US_LOG_VERBOSE("Frame: %s - %ux%u -- online=%d, key=%d, kr=%d, gop=%u, latency=%.3Lf, backlog=%.3Lf, size=%zu",
|
||||
@@ -253,13 +251,7 @@ static int _dump_sink(
|
||||
US_LOG_DEBUG(" stride=%u, grab_ts=%.3Lf, encode_begin_ts=%.3Lf, encode_end_ts=%.3Lf",
|
||||
frame->stride, 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;
|
||||
US_LOG_PERF_FPS("A new second has come; captured_fps=%u", fps);
|
||||
}
|
||||
fps_accum += 1;
|
||||
us_fpsi_update(fpsi, true, NULL);
|
||||
|
||||
if (ctx->v_output != NULL) {
|
||||
ctx->write(ctx->v_output, frame);
|
||||
@@ -275,7 +267,7 @@ static int _dump_sink(
|
||||
if (interval_us > 0) {
|
||||
usleep(interval_us);
|
||||
}
|
||||
} else if (error == -2) {
|
||||
} else if (got == US_ERROR_NO_DATA) {
|
||||
usleep(1000);
|
||||
} else {
|
||||
goto error;
|
||||
@@ -286,6 +278,7 @@ static int _dump_sink(
|
||||
|
||||
error:
|
||||
US_DELETE(sink, us_memsink_destroy);
|
||||
us_fpsi_destroy(fpsi);
|
||||
us_frame_destroy(frame);
|
||||
US_LOG_INFO("Bye-bye");
|
||||
return retval;
|
||||
@@ -296,7 +289,7 @@ static void _help(FILE *fp) {
|
||||
SAY("\nuStreamer-dump - Dump uStreamer's memory sink to file");
|
||||
SAY("═════════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", US_VERSION);
|
||||
SAY("Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
SAY("Example:");
|
||||
SAY("════════");
|
||||
SAY(" ustreamer-dump --sink test --output - \\");
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -49,7 +49,7 @@ typedef struct {
|
||||
int dma_fd;
|
||||
bool grabbed;
|
||||
atomic_int refs;
|
||||
} us_hw_buffer_s;
|
||||
} us_capture_hwbuf_s;
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
@@ -62,13 +62,13 @@ typedef struct {
|
||||
uint jpeg_quality;
|
||||
uz raw_size;
|
||||
uint n_bufs;
|
||||
us_hw_buffer_s *hw_bufs;
|
||||
us_capture_hwbuf_s *bufs;
|
||||
bool dma;
|
||||
enum v4l2_buf_type capture_type;
|
||||
bool capture_mplane;
|
||||
bool streamon;
|
||||
int open_error_reported;
|
||||
} us_device_runtime_s;
|
||||
int open_error_once;
|
||||
} us_capture_runtime_s;
|
||||
|
||||
typedef enum {
|
||||
CTL_MODE_NONE = 0,
|
||||
@@ -104,6 +104,8 @@ typedef struct {
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
|
||||
bool format_swap_rgb;
|
||||
uint jpeg_quality;
|
||||
v4l2_std_id standard;
|
||||
enum v4l2_memory io_method;
|
||||
@@ -116,22 +118,22 @@ typedef struct {
|
||||
bool persistent;
|
||||
uint timeout;
|
||||
us_controls_s ctl;
|
||||
us_device_runtime_s *run;
|
||||
} us_device_s;
|
||||
us_capture_runtime_s *run;
|
||||
} us_capture_s;
|
||||
|
||||
|
||||
us_device_s *us_device_init(void);
|
||||
void us_device_destroy(us_device_s *dev);
|
||||
us_capture_s *us_capture_init(void);
|
||||
void us_capture_destroy(us_capture_s *cap);
|
||||
|
||||
int us_device_parse_format(const char *str);
|
||||
int us_device_parse_standard(const char *str);
|
||||
int us_device_parse_io_method(const char *str);
|
||||
int us_capture_parse_format(const char *str);
|
||||
int us_capture_parse_standard(const char *str);
|
||||
int us_capture_parse_io_method(const char *str);
|
||||
|
||||
int us_device_open(us_device_s *dev);
|
||||
void us_device_close(us_device_s *dev);
|
||||
int us_capture_open(us_capture_s *cap);
|
||||
void us_capture_close(us_capture_s *cap);
|
||||
|
||||
int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw);
|
||||
int us_device_release_buffer(us_device_s *dev, us_hw_buffer_s *hw);
|
||||
int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw);
|
||||
int us_capture_hwbuf_release(us_capture_s *cap, us_capture_hwbuf_s *hw);
|
||||
|
||||
void us_device_buffer_incref(us_hw_buffer_s *hw);
|
||||
void us_device_buffer_decref(us_hw_buffer_s *hw);
|
||||
void us_capture_hwbuf_incref(us_capture_hwbuf_s *hw);
|
||||
void us_capture_hwbuf_decref(us_capture_hwbuf_s *hw);
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
|
||||
#define US_VERSION_MAJOR 6
|
||||
#define US_VERSION_MINOR 3
|
||||
#define US_VERSION_MINOR 12
|
||||
|
||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -37,17 +37,19 @@
|
||||
#include <drm_fourcc.h>
|
||||
#include <libdrm/drm.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/frametext.h"
|
||||
#include "../types.h"
|
||||
#include "../errors.h"
|
||||
#include "../tools.h"
|
||||
#include "../logging.h"
|
||||
#include "../frame.h"
|
||||
#include "../frametext.h"
|
||||
#include "../capture.h"
|
||||
|
||||
|
||||
static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void *v_buf);
|
||||
static int _drm_check_status(us_drm_s *drm);
|
||||
static void _drm_ensure_dpms_power(us_drm_s *drm, bool on);
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev);
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_capture_s *cap);
|
||||
static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz);
|
||||
|
||||
static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz);
|
||||
@@ -57,11 +59,11 @@ static const char *_connector_type_to_string(u32 type);
|
||||
static float _get_refresh_rate(const drmModeModeInfo *mode);
|
||||
|
||||
|
||||
#define _D_LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _D_LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _D_LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _D_LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _D_LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
|
||||
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
|
||||
|
||||
|
||||
us_drm_s *us_drm_init(void) {
|
||||
@@ -70,6 +72,7 @@ us_drm_s *us_drm_init(void) {
|
||||
run->fd = -1;
|
||||
run->status_fd = -1;
|
||||
run->dpms_state = -1;
|
||||
run->opened = -1;
|
||||
run->has_vsync = true;
|
||||
run->exposing_dma_fd = -1;
|
||||
run->ft = us_frametext_init();
|
||||
@@ -78,8 +81,9 @@ us_drm_s *us_drm_init(void) {
|
||||
US_CALLOC(drm, 1);
|
||||
// drm->path = "/dev/dri/card0";
|
||||
drm->path = "/dev/dri/by-path/platform-gpu-card";
|
||||
drm->port = "HDMI-A-1";
|
||||
drm->port = "HDMI-A-2"; // OUT2 on PiKVM V4 Plus
|
||||
drm->timeout = 5;
|
||||
drm->blank_after = 5;
|
||||
drm->run = run;
|
||||
return drm;
|
||||
}
|
||||
@@ -90,44 +94,45 @@ void us_drm_destroy(us_drm_s *drm) {
|
||||
US_DELETE(drm, free); // cppcheck-suppress uselessAssignmentPtrArg
|
||||
}
|
||||
|
||||
int us_drm_open(us_drm_s *drm, const us_device_s *dev) {
|
||||
int us_drm_open(us_drm_s *drm, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd < 0);
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: goto unplugged;
|
||||
case US_ERROR_NO_DEVICE: goto unplugged;
|
||||
default: goto error;
|
||||
}
|
||||
|
||||
_D_LOG_INFO("Configuring DRM device for %s ...", (dev == NULL ? "STUB" : "DMA"));
|
||||
_LOG_INFO("Using passthrough: %s[%s]", drm->path, drm->port);
|
||||
_LOG_INFO("Configuring DRM device for %s ...", (cap == NULL ? "STUB" : "DMA"));
|
||||
|
||||
if ((run->fd = open(drm->path, O_RDWR | O_CLOEXEC | O_NONBLOCK)) < 0) {
|
||||
_D_LOG_PERROR("Can't open DRM device");
|
||||
_LOG_PERROR("Can't open DRM device");
|
||||
goto error;
|
||||
}
|
||||
_D_LOG_DEBUG("DRM device fd=%d opened", run->fd);
|
||||
_LOG_DEBUG("DRM device fd=%d opened", run->fd);
|
||||
|
||||
int stub = 0; // Open the real device with DMA
|
||||
if (dev == NULL) {
|
||||
if (cap == NULL) {
|
||||
stub = US_DRM_STUB_USER;
|
||||
} else if (dev->run->format != V4L2_PIX_FMT_RGB24) {
|
||||
} else if (cap->run->format != V4L2_PIX_FMT_RGB24 && cap->run->format != V4L2_PIX_FMT_BGR24) {
|
||||
stub = US_DRM_STUB_BAD_FORMAT;
|
||||
char fourcc_str[8];
|
||||
us_fourcc_to_string(dev->run->format, fourcc_str, 8);
|
||||
_D_LOG_ERROR("Input format %s is not supported, forcing to STUB ...", fourcc_str);
|
||||
us_fourcc_to_string(cap->run->format, fourcc_str, 8);
|
||||
_LOG_ERROR("Input format %s is not supported, forcing to STUB ...", fourcc_str);
|
||||
}
|
||||
|
||||
# define CHECK_CAP(x_cap) { \
|
||||
_D_LOG_DEBUG("Checking %s ...", #x_cap); \
|
||||
_LOG_DEBUG("Checking %s ...", #x_cap); \
|
||||
u64 m_check; \
|
||||
if (drmGetCap(run->fd, x_cap, &m_check) < 0) { \
|
||||
_D_LOG_PERROR("Can't check " #x_cap); \
|
||||
_LOG_PERROR("Can't check " #x_cap); \
|
||||
goto error; \
|
||||
} \
|
||||
if (!m_check) { \
|
||||
_D_LOG_ERROR(#x_cap " is not supported"); \
|
||||
_LOG_ERROR(#x_cap " is not supported"); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
@@ -137,48 +142,49 @@ int us_drm_open(us_drm_s *drm, const us_device_s *dev) {
|
||||
}
|
||||
# undef CHECK_CAP
|
||||
|
||||
const uint width = (stub > 0 ? 0 : dev->run->width);
|
||||
const uint height = (stub > 0 ? 0 : dev->run->height);
|
||||
const uint hz = (stub > 0 ? 0 : dev->run->hz);
|
||||
const uint width = (stub > 0 ? 0 : cap->run->width);
|
||||
const uint height = (stub > 0 ? 0 : cap->run->height);
|
||||
const uint hz = (stub > 0 ? 0 : cap->run->hz);
|
||||
switch (_drm_find_sink(drm, width, height, hz)) {
|
||||
case 0: break;
|
||||
case -2: goto unplugged;
|
||||
case US_ERROR_NO_DEVICE: goto unplugged;
|
||||
default: goto error;
|
||||
}
|
||||
if ((stub == 0) && (width != run->mode.hdisplay || height < run->mode.vdisplay)) {
|
||||
// We'll try to show something instead of nothing if height != vdisplay
|
||||
stub = US_DRM_STUB_BAD_RESOLUTION;
|
||||
_D_LOG_ERROR("There is no appropriate modes for the capture, forcing to STUB ...");
|
||||
_LOG_ERROR("There is no appropriate modes for the capture, forcing to STUB ...");
|
||||
}
|
||||
|
||||
if (_drm_init_buffers(drm, (stub > 0 ? NULL : dev)) < 0) {
|
||||
if (_drm_init_buffers(drm, (stub > 0 ? NULL : cap)) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
|
||||
_D_LOG_DEBUG("Setting up CRTC ...");
|
||||
_LOG_DEBUG("Setting up CRTC ...");
|
||||
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) {
|
||||
_D_LOG_PERROR("Can't set CRTC");
|
||||
_LOG_PERROR("Can't set CRTC");
|
||||
goto error;
|
||||
}
|
||||
|
||||
run->opened_for_stub = (stub > 0);
|
||||
_LOG_INFO("Opened for %s ...", (stub > 0 ? "STUB" : "DMA"));
|
||||
run->exposing_dma_fd = -1;
|
||||
run->unplugged_reported = false;
|
||||
_D_LOG_INFO("Opened for %s ...", (run->opened_for_stub ? "STUB" : "DMA"));
|
||||
return stub;
|
||||
run->blank_at_ts = 0;
|
||||
run->opened = stub;
|
||||
run->once = 0;
|
||||
return run->opened;
|
||||
|
||||
error:
|
||||
us_drm_close(drm);
|
||||
return -1;
|
||||
return run->opened; // -1 after us_drm_close()
|
||||
|
||||
unplugged:
|
||||
if (!run->unplugged_reported) {
|
||||
_D_LOG_ERROR("Display is not plugged");
|
||||
run->unplugged_reported = true;
|
||||
}
|
||||
US_ONCE_FOR(run->once, __LINE__, {
|
||||
_LOG_ERROR("Display is not plugged");
|
||||
});
|
||||
us_drm_close(drm);
|
||||
return -2;
|
||||
run->opened = US_ERROR_NO_DEVICE;
|
||||
return run->opened;
|
||||
}
|
||||
|
||||
void us_drm_close(us_drm_s *drm) {
|
||||
@@ -193,33 +199,33 @@ void us_drm_close(us_drm_s *drm) {
|
||||
}
|
||||
|
||||
if (run->saved_crtc != NULL) {
|
||||
_D_LOG_DEBUG("Restoring CRTC ...");
|
||||
_LOG_DEBUG("Restoring CRTC ...");
|
||||
if (drmModeSetCrtc(run->fd,
|
||||
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
|
||||
run->saved_crtc->x, run->saved_crtc->y,
|
||||
&run->conn_id, 1, &run->saved_crtc->mode
|
||||
) < 0 && errno != ENOENT) {
|
||||
_D_LOG_PERROR("Can't restore CRTC");
|
||||
_LOG_PERROR("Can't restore CRTC");
|
||||
}
|
||||
drmModeFreeCrtc(run->saved_crtc);
|
||||
run->saved_crtc = NULL;
|
||||
}
|
||||
|
||||
if (run->bufs != NULL) {
|
||||
_D_LOG_DEBUG("Releasing buffers ...");
|
||||
_LOG_DEBUG("Releasing buffers ...");
|
||||
for (uint n_buf = 0; n_buf < run->n_bufs; ++n_buf) {
|
||||
us_drm_buffer_s *const buf = &run->bufs[n_buf];
|
||||
if (buf->fb_added && drmModeRmFB(run->fd, buf->id) < 0) {
|
||||
_D_LOG_PERROR("Can't remove buffer=%u", n_buf);
|
||||
_LOG_PERROR("Can't remove buffer=%u", n_buf);
|
||||
}
|
||||
if (buf->dumb_created) {
|
||||
struct drm_mode_destroy_dumb destroy = {.handle = buf->handle};
|
||||
if (drmIoctl(run->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy) < 0) {
|
||||
_D_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
|
||||
_LOG_PERROR("Can't destroy dumb buffer=%u", n_buf);
|
||||
}
|
||||
}
|
||||
if (buf->data != NULL && munmap(buf->data, buf->allocated)) {
|
||||
_D_LOG_PERROR("Can't unmap buffer=%u", n_buf);
|
||||
_LOG_PERROR("Can't unmap buffer=%u", n_buf);
|
||||
}
|
||||
}
|
||||
US_DELETE(run->bufs, free);
|
||||
@@ -232,19 +238,48 @@ void us_drm_close(us_drm_s *drm) {
|
||||
|
||||
run->crtc_id = 0;
|
||||
run->dpms_state = -1;
|
||||
run->opened = -1;
|
||||
run->has_vsync = true;
|
||||
run->stub_n_buf = 0;
|
||||
|
||||
if (say) {
|
||||
_D_LOG_INFO("Closed");
|
||||
_LOG_INFO("Closed");
|
||||
}
|
||||
}
|
||||
|
||||
int us_drm_ensure_no_signal(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
assert(run->opened > 0);
|
||||
|
||||
const ldf now_ts = us_get_now_monotonic();
|
||||
if (run->blank_at_ts == 0) {
|
||||
run->blank_at_ts = now_ts + drm->blank_after;
|
||||
}
|
||||
const ldf saved_ts = run->blank_at_ts; // us_drm*() rewrites it to 0
|
||||
|
||||
int retval;
|
||||
if (now_ts <= run->blank_at_ts) {
|
||||
retval = us_drm_wait_for_vsync(drm);
|
||||
if (retval == 0) {
|
||||
retval = us_drm_expose_stub(drm, US_DRM_STUB_NO_SIGNAL, NULL);
|
||||
}
|
||||
} else {
|
||||
US_ONCE_FOR(run->once, __LINE__, {
|
||||
_LOG_INFO("Turning off the display by timeout ...");
|
||||
});
|
||||
retval = us_drm_dpms_power_off(drm);
|
||||
}
|
||||
run->blank_at_ts = saved_ts;
|
||||
return retval;
|
||||
}
|
||||
|
||||
int us_drm_dpms_power_off(us_drm_s *drm) {
|
||||
assert(drm->run->fd >= 0);
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: return 0; // Unplugged, nice
|
||||
case US_ERROR_NO_DEVICE: return 0; // Unplugged, nice
|
||||
// Во время переключения DPMS монитор моргает один раз состоянием disconnected,
|
||||
// а потом почему-то снова оказывается connected. Так что просто считаем,
|
||||
// что отсоединенный монитор на этом этапе - это нормально.
|
||||
@@ -258,10 +293,11 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
run->blank_at_ts = 0;
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: return -2;
|
||||
case US_ERROR_NO_DEVICE: return US_ERROR_NO_DEVICE;
|
||||
default: return -1;
|
||||
}
|
||||
_drm_ensure_dpms_power(drm, true);
|
||||
@@ -275,13 +311,13 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(run->fd, &fds);
|
||||
|
||||
_D_LOG_DEBUG("Calling select() for VSync ...");
|
||||
_LOG_DEBUG("Calling select() for VSync ...");
|
||||
const int result = select(run->fd + 1, &fds, NULL, NULL, &timeout);
|
||||
if (result < 0) {
|
||||
_D_LOG_PERROR("Can't select(%d) device for VSync", run->fd);
|
||||
_LOG_PERROR("Can't select(%d) device for VSync", run->fd);
|
||||
return -1;
|
||||
} else if (result == 0) {
|
||||
_D_LOG_ERROR("Device timeout while waiting VSync");
|
||||
_LOG_ERROR("Device timeout while waiting VSync");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -289,9 +325,9 @@ int us_drm_wait_for_vsync(us_drm_s *drm) {
|
||||
.version = DRM_EVENT_CONTEXT_VERSION,
|
||||
.page_flip_handler = _drm_vsync_callback,
|
||||
};
|
||||
_D_LOG_DEBUG("Handling DRM event (maybe VSync) ...");
|
||||
_LOG_DEBUG("Handling DRM event (maybe VSync) ...");
|
||||
if (drmHandleEvent(run->fd, &ctx) < 0) {
|
||||
_D_LOG_PERROR("Can't handle DRM event");
|
||||
_LOG_PERROR("Can't handle DRM event");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -305,18 +341,19 @@ static void _drm_vsync_callback(int fd, uint n_frame, uint sec, uint usec, void
|
||||
us_drm_buffer_s *const buf = v_buf;
|
||||
*buf->ctx.has_vsync = true;
|
||||
*buf->ctx.exposing_dma_fd = -1;
|
||||
_D_LOG_DEBUG("Got VSync signal");
|
||||
_LOG_DEBUG("Got VSync signal");
|
||||
}
|
||||
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev) {
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
assert(run->fd >= 0);
|
||||
assert(run->opened_for_stub);
|
||||
assert(run->opened > 0);
|
||||
run->blank_at_ts = 0;
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: return -2;
|
||||
case US_ERROR_NO_DEVICE: return US_ERROR_NO_DEVICE;
|
||||
default: return -1;
|
||||
}
|
||||
_drm_ensure_dpms_power(drm, true);
|
||||
@@ -324,24 +361,19 @@ int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev
|
||||
# define DRAW_MSG(x_msg) us_frametext_draw(run->ft, (x_msg), run->mode.hdisplay, run->mode.vdisplay)
|
||||
switch (stub) {
|
||||
case US_DRM_STUB_BAD_RESOLUTION: {
|
||||
assert(dev != NULL);
|
||||
assert(cap != NULL);
|
||||
char msg[1024];
|
||||
US_SNPRINTF(msg, 1023,
|
||||
"=== PiKVM ==="
|
||||
"\n \n< UNSUPPORTED RESOLUTION >"
|
||||
"\n \n< %ux%up%.02f >"
|
||||
"\n \nby this display",
|
||||
dev->run->width, dev->run->height, dev->run->hz);
|
||||
cap->run->width, cap->run->height, cap->run->hz);
|
||||
DRAW_MSG(msg);
|
||||
break;
|
||||
};
|
||||
case US_DRM_STUB_BAD_FORMAT:
|
||||
DRAW_MSG(
|
||||
"=== PiKVM ==="
|
||||
"\n \n< UNSUPPORTED CAPTURE FORMAT >"
|
||||
"\n \nIt shouldn't happen ever."
|
||||
"\n \nPlease check the logs and report a bug:"
|
||||
"\n \n- https://github.com/pikvm/pikvm -");
|
||||
DRAW_MSG("=== PiKVM ===\n \n< UNSUPPORTED CAPTURE FORMAT >");
|
||||
break;
|
||||
case US_DRM_STUB_NO_SIGNAL:
|
||||
DRAW_MSG("=== PiKVM ===\n \n< NO SIGNAL >");
|
||||
@@ -359,48 +391,49 @@ int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev
|
||||
|
||||
run->has_vsync = false;
|
||||
|
||||
_D_LOG_DEBUG("Copying STUB frame ...")
|
||||
_LOG_DEBUG("Copying STUB frame ...")
|
||||
memcpy(buf->data, run->ft->frame->data, US_MIN(run->ft->frame->used, buf->allocated));
|
||||
|
||||
_D_LOG_DEBUG("Exposing STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
_LOG_DEBUG("Exposing STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
const int retval = drmModePageFlip(
|
||||
run->fd, run->crtc_id, buf->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
|
||||
buf);
|
||||
if (retval < 0) {
|
||||
_D_LOG_PERROR("Can't expose STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
_LOG_PERROR("Can't expose STUB framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
}
|
||||
_D_LOG_DEBUG("Exposed STUB framebuffer n_buf=%u", run->stub_n_buf);
|
||||
_LOG_DEBUG("Exposed STUB framebuffer n_buf=%u", run->stub_n_buf);
|
||||
|
||||
run->stub_n_buf = (run->stub_n_buf + 1) % run->n_bufs;
|
||||
return retval;
|
||||
}
|
||||
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw) {
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_capture_hwbuf_s *hw) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
us_drm_buffer_s *const buf = &run->bufs[hw->buf.index];
|
||||
|
||||
assert(run->fd >= 0);
|
||||
assert(!run->opened_for_stub);
|
||||
assert(run->opened == 0);
|
||||
run->blank_at_ts = 0;
|
||||
|
||||
switch (_drm_check_status(drm)) {
|
||||
case 0: break;
|
||||
case -2: return -2;
|
||||
case US_ERROR_NO_DEVICE: return US_ERROR_NO_DEVICE;
|
||||
default: return -1;
|
||||
}
|
||||
_drm_ensure_dpms_power(drm, true);
|
||||
|
||||
run->has_vsync = false;
|
||||
|
||||
_D_LOG_DEBUG("Exposing DMA framebuffer n_buf=%u ...", hw->buf.index);
|
||||
_LOG_DEBUG("Exposing DMA framebuffer n_buf=%u ...", hw->buf.index);
|
||||
const int retval = drmModePageFlip(
|
||||
run->fd, run->crtc_id, buf->id,
|
||||
DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_PAGE_FLIP_ASYNC,
|
||||
buf);
|
||||
if (retval < 0) {
|
||||
_D_LOG_PERROR("Can't expose DMA framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
_LOG_PERROR("Can't expose DMA framebuffer n_buf=%u ...", run->stub_n_buf);
|
||||
}
|
||||
_D_LOG_DEBUG("Exposed DMA framebuffer n_buf=%u", run->stub_n_buf);
|
||||
_LOG_DEBUG("Exposed DMA framebuffer n_buf=%u", run->stub_n_buf);
|
||||
run->exposing_dma_fd = hw->dma_fd;
|
||||
return retval;
|
||||
}
|
||||
@@ -409,36 +442,36 @@ static int _drm_check_status(us_drm_s *drm) {
|
||||
us_drm_runtime_s *run = drm->run;
|
||||
|
||||
if (run->status_fd < 0) {
|
||||
_D_LOG_DEBUG("Trying to find status file ...");
|
||||
_LOG_DEBUG("Trying to find status file ...");
|
||||
struct stat st;
|
||||
if (stat(drm->path, &st) < 0) {
|
||||
_D_LOG_PERROR("Can't stat() DRM device");
|
||||
_LOG_PERROR("Can't stat() DRM device");
|
||||
goto error;
|
||||
}
|
||||
const uint mi = minor(st.st_rdev);
|
||||
_D_LOG_DEBUG("DRM device minor(st_rdev)=%u", mi);
|
||||
_LOG_DEBUG("DRM device minor(st_rdev)=%u", mi);
|
||||
|
||||
char path[128];
|
||||
US_SNPRINTF(path, 127, "/sys/class/drm/card%u-%s/status", mi, drm->port);
|
||||
_D_LOG_DEBUG("Opening status file %s ...", path);
|
||||
_LOG_DEBUG("Opening status file %s ...", path);
|
||||
if ((run->status_fd = open(path, O_RDONLY | O_CLOEXEC)) < 0) {
|
||||
_D_LOG_PERROR("Can't open status file: %s", path);
|
||||
_LOG_PERROR("Can't open status file: %s", path);
|
||||
goto error;
|
||||
}
|
||||
_D_LOG_DEBUG("Status file fd=%d opened", run->status_fd);
|
||||
_LOG_DEBUG("Status file fd=%d opened", run->status_fd);
|
||||
}
|
||||
|
||||
char status_ch;
|
||||
if (read(run->status_fd, &status_ch, 1) != 1) {
|
||||
_D_LOG_PERROR("Can't read status file");
|
||||
_LOG_PERROR("Can't read status file");
|
||||
goto error;
|
||||
}
|
||||
if (lseek(run->status_fd, 0, SEEK_SET) != 0) {
|
||||
_D_LOG_PERROR("Can't rewind status file");
|
||||
_LOG_PERROR("Can't rewind status file");
|
||||
goto error;
|
||||
}
|
||||
_D_LOG_DEBUG("Current display status: %c", status_ch);
|
||||
return (status_ch == 'd' ? -2 : 0);
|
||||
_LOG_DEBUG("Current display status: %c", status_ch);
|
||||
return (status_ch == 'd' ? US_ERROR_NO_DEVICE : 0);
|
||||
|
||||
error:
|
||||
US_CLOSE_FD(run->status_fd);
|
||||
@@ -448,24 +481,26 @@ error:
|
||||
static void _drm_ensure_dpms_power(us_drm_s *drm, bool on) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
if (run->dpms_id > 0 && run->dpms_state != (int)on) {
|
||||
_D_LOG_INFO("Changing DPMS power mode: %d -> %u ...", run->dpms_state, on);
|
||||
_LOG_INFO("Changing DPMS power mode: %d -> %u ...", run->dpms_state, on);
|
||||
if (drmModeConnectorSetProperty(
|
||||
run->fd, run->conn_id, run->dpms_id,
|
||||
(on ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF)
|
||||
) < 0) {
|
||||
_D_LOG_PERROR("Can't set DPMS power=%u (ignored)", on);
|
||||
_LOG_PERROR("Can't set DPMS power=%u (ignored)", on);
|
||||
}
|
||||
}
|
||||
run->dpms_state = (int)on;
|
||||
}
|
||||
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
static int _drm_init_buffers(us_drm_s *drm, const us_capture_s *cap) {
|
||||
us_drm_runtime_s *const run = drm->run;
|
||||
|
||||
const uint n_bufs = (dev == NULL ? 4 : dev->run->n_bufs);
|
||||
const char *name = (dev == NULL ? "STUB" : "DMA");
|
||||
const uint n_bufs = (cap == NULL ? 4 : cap->run->n_bufs);
|
||||
const char *name = (cap == NULL ? "STUB" : "DMA");
|
||||
|
||||
_D_LOG_DEBUG("Initializing %u %s buffers ...", n_bufs, name);
|
||||
_LOG_DEBUG("Initializing %u %s buffers ...", n_bufs, name);
|
||||
|
||||
uint format = DRM_FORMAT_RGB888;
|
||||
|
||||
US_CALLOC(run->bufs, n_bufs);
|
||||
for (run->n_bufs = 0; run->n_bufs < n_bufs; ++run->n_bufs) {
|
||||
@@ -479,14 +514,14 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
u32 strides[4] = {0};
|
||||
u32 offsets[4] = {0};
|
||||
|
||||
if (dev == NULL) {
|
||||
if (cap == NULL) {
|
||||
struct drm_mode_create_dumb create = {
|
||||
.width = run->mode.hdisplay,
|
||||
.height = run->mode.vdisplay,
|
||||
.bpp = 24,
|
||||
};
|
||||
if (drmIoctl(run->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) {
|
||||
_D_LOG_PERROR("Can't create %s buffer=%u", name, n_buf);
|
||||
_LOG_PERROR("Can't create %s buffer=%u", name, n_buf);
|
||||
return -1;
|
||||
}
|
||||
buf->handle = create.handle;
|
||||
@@ -494,7 +529,7 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
|
||||
struct drm_mode_map_dumb map = {.handle = create.handle};
|
||||
if (drmIoctl(run->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) {
|
||||
_D_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
|
||||
_LOG_PERROR("Can't prepare dumb buffer=%u to mapping", n_buf);
|
||||
return -1;
|
||||
}
|
||||
if ((buf->data = mmap(
|
||||
@@ -502,7 +537,7 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
run->fd, map.offset
|
||||
)) == MAP_FAILED) {
|
||||
_D_LOG_PERROR("Can't map buffer=%u", n_buf);
|
||||
_LOG_PERROR("Can't map buffer=%u", n_buf);
|
||||
return -1;
|
||||
}
|
||||
memset(buf->data, 0, create.size);
|
||||
@@ -512,20 +547,25 @@ static int _drm_init_buffers(us_drm_s *drm, const us_device_s *dev) {
|
||||
strides[0] = create.pitch;
|
||||
|
||||
} else {
|
||||
if (drmPrimeFDToHandle(run->fd, dev->run->hw_bufs[n_buf].dma_fd, &buf->handle) < 0) {
|
||||
_D_LOG_PERROR("Can't import DMA buffer=%u from capture device", n_buf);
|
||||
if (drmPrimeFDToHandle(run->fd, cap->run->bufs[n_buf].dma_fd, &buf->handle) < 0) {
|
||||
_LOG_PERROR("Can't import DMA buffer=%u from capture device", n_buf);
|
||||
return -1;
|
||||
}
|
||||
handles[0] = buf->handle;
|
||||
strides[0] = dev->run->stride;
|
||||
strides[0] = cap->run->stride;
|
||||
|
||||
switch (cap->run->format) {
|
||||
case V4L2_PIX_FMT_RGB24: format = (DRM_FORMAT_BIG_ENDIAN ? DRM_FORMAT_BGR888 : DRM_FORMAT_RGB888); break;
|
||||
case V4L2_PIX_FMT_BGR24: format = (DRM_FORMAT_BIG_ENDIAN ? DRM_FORMAT_RGB888 : DRM_FORMAT_BGR888); break;
|
||||
}
|
||||
}
|
||||
|
||||
if (drmModeAddFB2(
|
||||
run->fd,
|
||||
run->mode.hdisplay, run->mode.vdisplay, DRM_FORMAT_RGB888,
|
||||
run->mode.hdisplay, run->mode.vdisplay, format,
|
||||
handles, strides, offsets, &buf->id, 0
|
||||
)) {
|
||||
_D_LOG_PERROR("Can't setup buffer=%u", n_buf);
|
||||
_LOG_PERROR("Can't setup buffer=%u", n_buf);
|
||||
return -1;
|
||||
}
|
||||
buf->fb_added = true;
|
||||
@@ -538,22 +578,22 @@ static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
|
||||
|
||||
run->crtc_id = 0;
|
||||
|
||||
_D_LOG_DEBUG("Trying to find the appropriate sink ...");
|
||||
_LOG_DEBUG("Trying to find the appropriate sink ...");
|
||||
|
||||
drmModeRes *res = drmModeGetResources(run->fd);
|
||||
if (res == NULL) {
|
||||
_D_LOG_PERROR("Can't get resources info");
|
||||
_LOG_PERROR("Can't get resources info");
|
||||
goto done;
|
||||
}
|
||||
if (res->count_connectors <= 0) {
|
||||
_D_LOG_ERROR("Can't find any connectors");
|
||||
_LOG_ERROR("Can't find any connectors");
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (int ci = 0; ci < res->count_connectors; ++ci) {
|
||||
drmModeConnector *conn = drmModeGetConnector(run->fd, res->connectors[ci]);
|
||||
if (conn == NULL) {
|
||||
_D_LOG_PERROR("Can't get connector index=%d", ci);
|
||||
_LOG_PERROR("Can't get connector index=%d", ci);
|
||||
goto done;
|
||||
}
|
||||
|
||||
@@ -565,37 +605,37 @@ static int _drm_find_sink(us_drm_s *drm, uint width, uint height, float hz) {
|
||||
drmModeFreeConnector(conn);
|
||||
continue;
|
||||
}
|
||||
_D_LOG_INFO("Using connector %s: conn_type=%d, conn_type_id=%d",
|
||||
_LOG_INFO("Using connector %s: conn_type=%d, conn_type_id=%d",
|
||||
drm->port, conn->connector_type, conn->connector_type_id);
|
||||
|
||||
if (conn->connection != DRM_MODE_CONNECTED) {
|
||||
_D_LOG_ERROR("Connector for port %s has !DRM_MODE_CONNECTED", drm->port);
|
||||
_LOG_ERROR("Connector for port %s has !DRM_MODE_CONNECTED", drm->port);
|
||||
drmModeFreeConnector(conn);
|
||||
goto done;
|
||||
}
|
||||
|
||||
drmModeModeInfo *best;
|
||||
if ((best = _find_best_mode(conn, width, height, hz)) == NULL) {
|
||||
_D_LOG_ERROR("Can't find any appropriate display modes");
|
||||
_LOG_ERROR("Can't find any appropriate display modes");
|
||||
drmModeFreeConnector(conn);
|
||||
goto unplugged;
|
||||
}
|
||||
_D_LOG_INFO("Using best mode: %ux%up%.02f",
|
||||
_LOG_INFO("Using best mode: %ux%up%.02f",
|
||||
best->hdisplay, best->vdisplay, _get_refresh_rate(best));
|
||||
|
||||
if ((run->dpms_id = _find_dpms(run->fd, conn)) > 0) {
|
||||
_D_LOG_INFO("Using DPMS: id=%u", run->dpms_id);
|
||||
_LOG_INFO("Using DPMS: id=%u", run->dpms_id);
|
||||
} else {
|
||||
_D_LOG_INFO("Using DPMS: None");
|
||||
_LOG_INFO("Using DPMS: None");
|
||||
}
|
||||
|
||||
u32 taken_crtcs = 0; // Unused here
|
||||
if ((run->crtc_id = _find_crtc(run->fd, res, conn, &taken_crtcs)) == 0) {
|
||||
_D_LOG_ERROR("Can't find CRTC");
|
||||
_LOG_ERROR("Can't find CRTC");
|
||||
drmModeFreeConnector(conn);
|
||||
goto done;
|
||||
}
|
||||
_D_LOG_INFO("Using CRTC: id=%u", run->crtc_id);
|
||||
_LOG_INFO("Using CRTC: id=%u", run->crtc_id);
|
||||
|
||||
run->conn_id = conn->connector_id;
|
||||
memcpy(&run->mode, best, sizeof(drmModeModeInfo));
|
||||
@@ -610,7 +650,7 @@ done:
|
||||
|
||||
unplugged:
|
||||
drmModeFreeResources(res);
|
||||
return -2;
|
||||
return US_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
static drmModeModeInfo *_find_best_mode(drmModeConnector *conn, uint width, uint height, float hz) {
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -25,10 +25,10 @@
|
||||
|
||||
#include <xf86drmMode.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/frametext.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../types.h"
|
||||
#include "../frame.h"
|
||||
#include "../frametext.h"
|
||||
#include "../capture.h"
|
||||
|
||||
|
||||
typedef enum {
|
||||
@@ -63,11 +63,14 @@ typedef struct {
|
||||
uint n_bufs;
|
||||
drmModeCrtc *saved_crtc;
|
||||
int dpms_state;
|
||||
bool opened_for_stub;
|
||||
int opened;
|
||||
|
||||
bool has_vsync;
|
||||
int exposing_dma_fd;
|
||||
uint stub_n_buf;
|
||||
bool unplugged_reported;
|
||||
ldf blank_at_ts;
|
||||
|
||||
int once;
|
||||
us_frametext_s *ft;
|
||||
} us_drm_runtime_s;
|
||||
|
||||
@@ -75,6 +78,7 @@ typedef struct {
|
||||
char *path;
|
||||
char *port;
|
||||
uint timeout;
|
||||
uint blank_after;
|
||||
|
||||
us_drm_runtime_s *run;
|
||||
} us_drm_s;
|
||||
@@ -83,10 +87,11 @@ typedef struct {
|
||||
us_drm_s *us_drm_init(void);
|
||||
void us_drm_destroy(us_drm_s *drm);
|
||||
|
||||
int us_drm_open(us_drm_s *drm, const us_device_s *dev);
|
||||
int us_drm_open(us_drm_s *drm, const us_capture_s *cap);
|
||||
void us_drm_close(us_drm_s *drm);
|
||||
|
||||
int us_drm_dpms_power_off(us_drm_s *drm);
|
||||
int us_drm_wait_for_vsync(us_drm_s *drm);
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_device_s *dev);
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_hw_buffer_s *hw);
|
||||
int us_drm_expose_stub(us_drm_s *drm, us_drm_stub_e stub, const us_capture_s *cap);
|
||||
int us_drm_expose_dma(us_drm_s *drm, const us_capture_hwbuf_s *hw);
|
||||
int us_drm_ensure_no_signal(us_drm_s *drm);
|
||||
27
src/libs/errors.h
Normal file
27
src/libs/errors.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2024 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
|
||||
|
||||
#define US_ERROR_COMMON -1
|
||||
#define US_ERROR_NO_DEVICE -2
|
||||
#define US_ERROR_NO_DATA -3
|
||||
112
src/libs/fpsi.c
Normal file
112
src/libs/fpsi.c
Normal file
@@ -0,0 +1,112 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "fpsi.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "tools.h"
|
||||
#include "threading.h"
|
||||
#include "logging.h"
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
us_fpsi_s *us_fpsi_init(const char *name, bool with_meta) {
|
||||
us_fpsi_s *fpsi;
|
||||
US_CALLOC(fpsi, 1);
|
||||
fpsi->name = us_strdup(name);
|
||||
fpsi->with_meta = with_meta;
|
||||
atomic_init(&fpsi->state_sec_ts, 0);
|
||||
atomic_init(&fpsi->state, 0);
|
||||
return fpsi;
|
||||
}
|
||||
|
||||
void us_fpsi_destroy(us_fpsi_s *fpsi) {
|
||||
free(fpsi->name);
|
||||
free(fpsi);
|
||||
}
|
||||
|
||||
void us_fpsi_frame_to_meta(const us_frame_s *frame, us_fpsi_meta_s *meta) {
|
||||
meta->width = frame->width;
|
||||
meta->height = frame->height;
|
||||
meta->online = frame->online;
|
||||
}
|
||||
|
||||
void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
|
||||
if (meta != NULL) {
|
||||
assert(fpsi->with_meta);
|
||||
} else {
|
||||
assert(!fpsi->with_meta);
|
||||
}
|
||||
|
||||
const sll now_sec_ts = us_floor_ms(us_get_now_monotonic());
|
||||
if (atomic_load(&fpsi->state_sec_ts) != now_sec_ts) {
|
||||
US_LOG_PERF_FPS("FPS: %s: %u", fpsi->name, fpsi->accum);
|
||||
|
||||
// Fast mutex-less store method
|
||||
ull state = (ull)fpsi->accum & 0xFFFF;
|
||||
if (fpsi->with_meta) {
|
||||
assert(meta != NULL);
|
||||
state |= (ull)(meta->width & 0xFFFF) << 16;
|
||||
state |= (ull)(meta->height & 0xFFFF) << 32;
|
||||
state |= (ull)(meta->online ? 1 : 0) << 48;
|
||||
}
|
||||
atomic_store(&fpsi->state, state); // Сначала инфа
|
||||
atomic_store(&fpsi->state_sec_ts, now_sec_ts); // Потом время, это важно
|
||||
fpsi->accum = 0;
|
||||
}
|
||||
if (bump) {
|
||||
++fpsi->accum;
|
||||
}
|
||||
}
|
||||
|
||||
uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
|
||||
if (meta != NULL) {
|
||||
assert(fpsi->with_meta);
|
||||
} else {
|
||||
assert(!fpsi->with_meta);
|
||||
}
|
||||
|
||||
// Между чтением инфы и времени может быть гонка,
|
||||
// но это неважно. Если время свежее, до данные тоже
|
||||
// будут свежмими, обратный случай не так важен.
|
||||
const sll now_sec_ts = us_floor_ms(us_get_now_monotonic());
|
||||
const sll state_sec_ts = atomic_load(&fpsi->state_sec_ts); // Сначала время
|
||||
const ull state = atomic_load(&fpsi->state); // Потом инфа
|
||||
|
||||
uint current = state & 0xFFFF;
|
||||
if (fpsi->with_meta) {
|
||||
assert(meta != NULL);
|
||||
meta->width = (state >> 16) & 0xFFFF;
|
||||
meta->height = (state >> 32) & 0xFFFF;
|
||||
meta->online = (state >> 48) & 1;
|
||||
}
|
||||
|
||||
if (state_sec_ts != now_sec_ts && (state_sec_ts + 1) != now_sec_ts) {
|
||||
// Только текущая или прошлая секунда
|
||||
current = 0;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -24,23 +24,28 @@
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/memsink.h"
|
||||
|
||||
#include "m2m.h"
|
||||
#include "types.h"
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
us_memsink_s *sink;
|
||||
bool key_requested;
|
||||
us_frame_s *tmp_src;
|
||||
us_frame_s *dest;
|
||||
us_m2m_encoder_s *enc;
|
||||
atomic_bool online;
|
||||
} us_h264_stream_s;
|
||||
uint width;
|
||||
uint height;
|
||||
bool online;
|
||||
} us_fpsi_meta_s;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
bool with_meta;
|
||||
uint accum;
|
||||
atomic_llong state_sec_ts;
|
||||
atomic_ullong state;
|
||||
} us_fpsi_s;
|
||||
|
||||
|
||||
us_h264_stream_s *us_h264_stream_init(us_memsink_s *sink, const char *path, uint bitrate, uint 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);
|
||||
us_fpsi_s *us_fpsi_init(const char *name, bool with_meta);
|
||||
void us_fpsi_destroy(us_fpsi_s *fpsi);
|
||||
|
||||
void us_fpsi_frame_to_meta(const us_frame_s *frame, us_fpsi_meta_s *meta);
|
||||
void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta);
|
||||
uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta);
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -26,54 +26,58 @@
|
||||
#include "tools.h"
|
||||
|
||||
|
||||
#define US_FRAME_META_DECLARE \
|
||||
uint width; \
|
||||
uint height; \
|
||||
uint format; \
|
||||
uint 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; \
|
||||
uint gop; \
|
||||
\
|
||||
ldf grab_ts; \
|
||||
ldf encode_begin_ts; \
|
||||
ldf encode_end_ts;
|
||||
|
||||
|
||||
typedef struct {
|
||||
u8 *data;
|
||||
uz used;
|
||||
uz allocated;
|
||||
int dma_fd;
|
||||
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
uint 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;
|
||||
uint gop;
|
||||
|
||||
ldf grab_ts;
|
||||
ldf encode_begin_ts;
|
||||
ldf encode_end_ts;
|
||||
US_FRAME_META_DECLARE;
|
||||
} us_frame_s;
|
||||
|
||||
|
||||
#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->gop = x_src->gop; \
|
||||
(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)->gop = (x_src)->gop; \
|
||||
\
|
||||
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; \
|
||||
(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; \
|
||||
}
|
||||
|
||||
#define US_FRAME_COMPARE_GEOMETRY(x_a, x_b) ( \
|
||||
/* Compare the used size and significant meta (no timings) */ \
|
||||
x_a->used == x_b->used \
|
||||
(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 \
|
||||
&& x_a->gop == x_b->gop \
|
||||
&& (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 \
|
||||
&& (x_a)->gop == (x_b)->gop \
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -25,9 +25,9 @@
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
#define US_LIST_STRUCT(...) \
|
||||
__VA_ARGS__ *prev; \
|
||||
__VA_ARGS__ *next;
|
||||
#define US_LIST_DECLARE \
|
||||
void *prev; \
|
||||
void *next;
|
||||
|
||||
#define US_LIST_ITERATE(x_first, x_item, ...) { \
|
||||
for (__typeof__(x_first) x_item = x_first; x_item;) { \
|
||||
@@ -42,10 +42,11 @@
|
||||
x_first = x_item; \
|
||||
} else { \
|
||||
__typeof__(x_first) m_last = x_first; \
|
||||
for (; m_last->next; m_last = m_last->next); \
|
||||
for (; m_last->next != NULL; m_last = m_last->next); \
|
||||
x_item->prev = m_last; \
|
||||
m_last->next = x_item; \
|
||||
} \
|
||||
x_item->next = NULL; \
|
||||
}
|
||||
|
||||
#define US_LIST_APPEND_C(x_first, x_item, x_count) { \
|
||||
@@ -57,11 +58,15 @@
|
||||
if (x_item->prev == NULL) { \
|
||||
x_first = x_item->next; \
|
||||
} else { \
|
||||
x_item->prev->next = x_item->next; \
|
||||
__typeof__(x_first) m_prev = x_item->prev; \
|
||||
m_prev->next = x_item->next; \
|
||||
} \
|
||||
if (x_item->next != NULL) { \
|
||||
x_item->next->prev = x_item->prev; \
|
||||
__typeof__(x_first) m_next = x_item->next; \
|
||||
m_next->prev = x_item->prev; \
|
||||
} \
|
||||
x_item->prev = NULL; \
|
||||
x_item->next = NULL; \
|
||||
}
|
||||
|
||||
#define US_LIST_REMOVE_C(x_first, x_item, x_count) { \
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -33,13 +33,14 @@
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "errors.h"
|
||||
#include "tools.h"
|
||||
#include "logging.h"
|
||||
#include "frame.h"
|
||||
#include "memsinksh.h"
|
||||
|
||||
|
||||
us_memsink_s *us_memsink_init(
|
||||
us_memsink_s *us_memsink_init_opened(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, uint client_ttl, uint timeout) {
|
||||
|
||||
@@ -56,6 +57,11 @@ us_memsink_s *us_memsink_init(
|
||||
|
||||
US_LOG_INFO("Using %s-sink: %s", name, obj);
|
||||
|
||||
if ((sink->data_size = us_memsink_calculate_size(obj)) == 0) {
|
||||
US_LOG_ERROR("%s-sink: Invalid object suffix", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
const mode_t mask = umask(0);
|
||||
sink->fd = shm_open(sink->obj, (server ? O_RDWR | O_CREAT : O_RDWR), mode);
|
||||
umask(mask);
|
||||
@@ -65,12 +71,12 @@ us_memsink_s *us_memsink_init(
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s)) < 0) {
|
||||
if (sink->server && ftruncate(sink->fd, sizeof(us_memsink_shared_s) + sink->data_size) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't truncate shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((sink->mem = us_memsink_shared_map(sink->fd)) == NULL) {
|
||||
if ((sink->mem = us_memsink_shared_map(sink->fd, sink->data_size)) == NULL) {
|
||||
US_LOG_PERROR("%s-sink: Can't mmap shared memory", name);
|
||||
goto error;
|
||||
}
|
||||
@@ -83,7 +89,7 @@ error:
|
||||
|
||||
void us_memsink_destroy(us_memsink_s *sink) {
|
||||
if (sink->mem != NULL) {
|
||||
if (us_memsink_shared_unmap(sink->mem) < 0) {
|
||||
if (us_memsink_shared_unmap(sink->mem, sink->data_size) < 0) {
|
||||
US_LOG_PERROR("%s-sink: Can't unmap shared memory", sink->name);
|
||||
}
|
||||
}
|
||||
@@ -160,10 +166,10 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
|
||||
|
||||
const ldf now = us_get_now_monotonic();
|
||||
|
||||
if (frame->used > US_MEMSINK_MAX_DATA) {
|
||||
if (frame->used > sink->data_size) {
|
||||
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
|
||||
sink->name, frame->used, sink->data_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (us_flock_timedwait_monotonic(sink->fd, 1) == 0) {
|
||||
@@ -177,7 +183,7 @@ int us_memsink_server_put(us_memsink_s *sink, const us_frame_s *frame, bool *key
|
||||
*key_requested = sink->mem->key_requested;
|
||||
}
|
||||
|
||||
memcpy(sink->mem->data, frame->data, frame->used);
|
||||
memcpy(us_memsink_get_data(sink->mem), frame->data, frame->used);
|
||||
sink->mem->used = frame->used;
|
||||
US_FRAME_COPY_META(frame, sink->mem);
|
||||
|
||||
@@ -208,7 +214,7 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_reque
|
||||
|
||||
if (us_flock_timedwait_monotonic(sink->fd, sink->timeout) < 0) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
return -2;
|
||||
return US_ERROR_NO_DATA;
|
||||
}
|
||||
US_LOG_PERROR("%s-sink: Can't lock memory", sink->name);
|
||||
return -1;
|
||||
@@ -217,7 +223,7 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_reque
|
||||
int retval = 0;
|
||||
|
||||
if (sink->mem->magic != US_MEMSINK_MAGIC) {
|
||||
retval = -2; // Not updated
|
||||
retval = US_ERROR_NO_DATA; // Not updated
|
||||
goto done;
|
||||
}
|
||||
if (sink->mem->version != US_MEMSINK_VERSION) {
|
||||
@@ -231,12 +237,12 @@ int us_memsink_client_get(us_memsink_s *sink, us_frame_s *frame, bool *key_reque
|
||||
sink->mem->last_client_ts = us_get_now_monotonic();
|
||||
|
||||
if (sink->mem->id == sink->last_readed_id) {
|
||||
retval = -2; // Not updated
|
||||
retval = US_ERROR_NO_DATA; // Not updated
|
||||
goto done;
|
||||
}
|
||||
|
||||
sink->last_readed_id = sink->mem->id;
|
||||
us_frame_set_data(frame, sink->mem->data, sink->mem->used);
|
||||
us_frame_set_data(frame, us_memsink_get_data(sink->mem), sink->mem->used);
|
||||
US_FRAME_COPY_META(sink->mem, frame);
|
||||
if (key_requested != NULL) { // We don't need it for non-H264 sinks
|
||||
*key_requested = sink->mem->key_requested;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -34,6 +34,7 @@
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *obj;
|
||||
uz data_size;
|
||||
bool server;
|
||||
bool rm;
|
||||
uint client_ttl; // Only for server
|
||||
@@ -49,7 +50,7 @@ typedef struct {
|
||||
} us_memsink_s;
|
||||
|
||||
|
||||
us_memsink_s *us_memsink_init(
|
||||
us_memsink_s *us_memsink_init_opened(
|
||||
const char *name, const char *obj, bool server,
|
||||
mode_t mode, bool rm, uint client_ttl, uint timeout);
|
||||
|
||||
|
||||
72
src/libs/memsinksh.c
Normal file
72
src/libs/memsinksh.c
Normal file
@@ -0,0 +1,72 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include "memsinksh.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
|
||||
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size) {
|
||||
us_memsink_shared_s *mem = mmap(
|
||||
NULL,
|
||||
sizeof(us_memsink_shared_s) + data_size,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
fd, 0);
|
||||
if (mem == MAP_FAILED) {
|
||||
return NULL;
|
||||
}
|
||||
assert(mem != NULL);
|
||||
return mem;
|
||||
}
|
||||
|
||||
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size) {
|
||||
assert(mem != NULL);
|
||||
return munmap(mem, sizeof(us_memsink_shared_s) + data_size);
|
||||
}
|
||||
|
||||
uz us_memsink_calculate_size(const char *obj) {
|
||||
const char *ptr = strrchr(obj, ':');
|
||||
if (ptr == NULL) {
|
||||
ptr = strrchr(obj, '.');
|
||||
}
|
||||
if (ptr != NULL) {
|
||||
ptr += 1;
|
||||
if (!strcasecmp(ptr, "jpeg")) {
|
||||
return 4 * 1024 * 1024;
|
||||
} else if (!strcasecmp(ptr, "h264")) {
|
||||
return 2 * 1024 * 1024;
|
||||
} else if (!strcasecmp(ptr, "raw")) {
|
||||
return 1920 * 1200 * 3; // RGB
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u8 *us_memsink_get_data(us_memsink_shared_s *mem) {
|
||||
return (u8*)(mem) + sizeof(us_memsink_shared_s);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -22,63 +22,29 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "frame.h"
|
||||
|
||||
|
||||
#define US_MEMSINK_MAGIC ((u64)0xCAFEBABECAFEBABE)
|
||||
#define US_MEMSINK_VERSION ((u32)4)
|
||||
|
||||
#ifndef US_CFG_MEMSINK_MAX_DATA
|
||||
# define US_CFG_MEMSINK_MAX_DATA 33554432
|
||||
#endif
|
||||
#define US_MEMSINK_MAX_DATA ((uz)(US_CFG_MEMSINK_MAX_DATA))
|
||||
#define US_MEMSINK_VERSION ((u32)7)
|
||||
|
||||
|
||||
typedef struct {
|
||||
u64 magic;
|
||||
u32 version;
|
||||
|
||||
u64 id;
|
||||
|
||||
uz used;
|
||||
uint width;
|
||||
uint height;
|
||||
uint format;
|
||||
uint stride;
|
||||
bool online;
|
||||
bool key;
|
||||
uint gop;
|
||||
|
||||
ldf grab_ts;
|
||||
ldf encode_begin_ts;
|
||||
ldf encode_end_ts;
|
||||
|
||||
ldf last_client_ts;
|
||||
bool key_requested;
|
||||
|
||||
u8 data[US_MEMSINK_MAX_DATA];
|
||||
US_FRAME_META_DECLARE;
|
||||
} us_memsink_shared_s;
|
||||
|
||||
|
||||
INLINE us_memsink_shared_s *us_memsink_shared_map(int fd) {
|
||||
us_memsink_shared_s *mem = mmap(
|
||||
NULL,
|
||||
sizeof(us_memsink_shared_s),
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
0
|
||||
);
|
||||
if (mem == MAP_FAILED) {
|
||||
return NULL;
|
||||
}
|
||||
assert(mem != NULL);
|
||||
return mem;
|
||||
}
|
||||
us_memsink_shared_s *us_memsink_shared_map(int fd, uz data_size);
|
||||
int us_memsink_shared_unmap(us_memsink_shared_s *mem, uz data_size);
|
||||
|
||||
INLINE int us_memsink_shared_unmap(us_memsink_shared_s *mem) {
|
||||
assert(mem != NULL);
|
||||
return munmap(mem, sizeof(us_memsink_shared_s));
|
||||
}
|
||||
uz us_memsink_calculate_size(const char *obj);
|
||||
u8 *us_memsink_get_data(us_memsink_shared_s *mem);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -58,7 +58,7 @@ int us_tc358743_xioctl_get_audio_hz(int fd, uint *audio_hz) {
|
||||
US_MEMSET_ZERO(ctl);
|
||||
ctl.id = TC358743_CID_AUDIO_SAMPLING_RATE;
|
||||
if (us_xioctl(fd, VIDIOC_G_CTRL, &ctl) < 0) {
|
||||
return -2;
|
||||
return -1;
|
||||
}
|
||||
*audio_hz = ctl.value;
|
||||
return 0;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -84,7 +84,7 @@ INLINE void us_thread_set_name(const char *name) {
|
||||
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
pthread_set_name_np(pthread_self(), name);
|
||||
# elif defined(__NetBSD__)
|
||||
pthread_setname_np(pthread_self(), "%s", (void *)name);
|
||||
pthread_setname_np(pthread_self(), "%s", (void*)name);
|
||||
# else
|
||||
# error us_thread_set_name() not implemented, you can disable it using WITH_PTHREAD_NP=0
|
||||
# endif
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -72,14 +72,16 @@
|
||||
(m_a > m_b ? m_a : m_b); \
|
||||
})
|
||||
|
||||
#define US_ONCE(...) { \
|
||||
const int m_reported = __LINE__; \
|
||||
if (m_reported != once) { \
|
||||
#define US_ONCE_FOR(x_once, x_value, ...) { \
|
||||
const int m_reported = (x_value); \
|
||||
if (m_reported != (x_once)) { \
|
||||
__VA_ARGS__; \
|
||||
once = m_reported; \
|
||||
(x_once) = m_reported; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define US_ONCE(...) US_ONCE_FOR(once, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
|
||||
INLINE char *us_strdup(const char *str) {
|
||||
char *const new = strdup(str);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
|
||||
typedef long long sll;
|
||||
typedef ssize_t sz;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -22,6 +22,26 @@
|
||||
|
||||
#include "encoder.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/array.h"
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/capture.h"
|
||||
|
||||
#include "workers.h"
|
||||
#include "m2m.h"
|
||||
|
||||
#include "encoders/cpu/encoder.h"
|
||||
#include "encoders/hw/encoder.h"
|
||||
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
@@ -34,7 +54,7 @@ static const struct {
|
||||
{"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},
|
||||
{"NOOP", US_ENCODER_TYPE_CPU},
|
||||
};
|
||||
|
||||
|
||||
@@ -43,9 +63,6 @@ static void _worker_job_destroy(void *v_job);
|
||||
static bool _worker_run_job(us_worker_s *wr);
|
||||
|
||||
|
||||
#define _ER(x_next) enc->run->x_next
|
||||
|
||||
|
||||
us_encoder_s *us_encoder_init(void) {
|
||||
us_encoder_runtime_s *run;
|
||||
US_CALLOC(run, 1);
|
||||
@@ -62,14 +79,15 @@ us_encoder_s *us_encoder_init(void) {
|
||||
}
|
||||
|
||||
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)
|
||||
us_encoder_runtime_s *const run = enc->run;
|
||||
if (run->m2ms != NULL) {
|
||||
for (uint index = 0; index < run->n_m2ms; ++index) {
|
||||
US_DELETE(run->m2ms[index], us_m2m_encoder_destroy);
|
||||
}
|
||||
free(_ER(m2ms));
|
||||
free(run->m2ms);
|
||||
}
|
||||
US_MUTEX_DESTROY(_ER(mutex));
|
||||
free(enc->run);
|
||||
US_MUTEX_DESTROY(run->mutex);
|
||||
free(run);
|
||||
free(enc);
|
||||
}
|
||||
|
||||
@@ -91,86 +109,70 @@ const char *us_encoder_type_to_string(us_encoder_type_e type) {
|
||||
return _ENCODER_TYPES[0].name;
|
||||
}
|
||||
|
||||
void us_encoder_open(us_encoder_s *enc, us_device_s *dev) {
|
||||
assert(enc->run->pool == NULL);
|
||||
void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
|
||||
us_encoder_runtime_s *const run = enc->run;
|
||||
us_capture_runtime_s *const cr = cap->run;
|
||||
|
||||
# define DR(x_next) dev->run->x_next
|
||||
assert(run->pool == NULL);
|
||||
|
||||
us_encoder_type_e type = (_ER(cpu_forced) ? US_ENCODER_TYPE_CPU : enc->type);
|
||||
unsigned quality = dev->jpeg_quality;
|
||||
unsigned n_workers = US_MIN(enc->n_workers, DR(n_bufs));
|
||||
bool cpu_forced = false;
|
||||
us_encoder_type_e type = enc->type;
|
||||
uint quality = cap->jpeg_quality;
|
||||
uint n_workers = US_MIN(enc->n_workers, cr->n_bufs);
|
||||
|
||||
if (us_is_jpeg(DR(format)) && type != US_ENCODER_TYPE_HW) {
|
||||
if (us_is_jpeg(cr->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 == US_ENCODER_TYPE_HW) {
|
||||
if (!us_is_jpeg(DR(format))) {
|
||||
if (us_is_jpeg(cr->format)) {
|
||||
quality = cr->jpeg_quality;
|
||||
n_workers = 1;
|
||||
} else {
|
||||
US_LOG_INFO("Switching to CPU encoder: the input format is not (M)JPEG ...");
|
||||
goto use_cpu;
|
||||
type = US_ENCODER_TYPE_CPU;
|
||||
quality = cap->jpeg_quality;
|
||||
}
|
||||
quality = DR(jpeg_quality);
|
||||
n_workers = 1;
|
||||
|
||||
} 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);
|
||||
if (run->m2ms == NULL) {
|
||||
US_CALLOC(run->m2ms, n_workers);
|
||||
}
|
||||
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
|
||||
for (; run->n_m2ms < n_workers; ++run->n_m2ms) {
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
char name[32];
|
||||
US_SNPRINTF(name, 31, "JPEG-%u", _ER(n_m2ms));
|
||||
US_SNPRINTF(name, 31, "JPEG-%u", run->n_m2ms);
|
||||
if (type == US_ENCODER_TYPE_M2M_VIDEO) {
|
||||
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
run->m2ms[run->n_m2ms] = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
} else {
|
||||
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
run->m2ms[run->n_m2ms] = us_m2m_jpeg_encoder_init(name, enc->m2m_path, quality);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (type == US_ENCODER_TYPE_NOOP) {
|
||||
n_workers = 1;
|
||||
quality = 0;
|
||||
}
|
||||
|
||||
goto ok;
|
||||
if (quality == 0) {
|
||||
US_LOG_INFO("Using JPEG quality: encoder default");
|
||||
} else {
|
||||
US_LOG_INFO("Using JPEG quality: %u%%", quality);
|
||||
}
|
||||
|
||||
use_cpu:
|
||||
type = US_ENCODER_TYPE_CPU;
|
||||
quality = dev->jpeg_quality;
|
||||
US_MUTEX_LOCK(run->mutex);
|
||||
run->type = type;
|
||||
run->quality = quality;
|
||||
US_MUTEX_UNLOCK(run->mutex);
|
||||
|
||||
ok:
|
||||
if (type == US_ENCODER_TYPE_NOOP) {
|
||||
US_LOG_INFO("Using JPEG NOOP encoder");
|
||||
} else if (quality == 0) {
|
||||
US_LOG_INFO("Using JPEG quality: encoder default");
|
||||
} else {
|
||||
US_LOG_INFO("Using JPEG quality: %u%%", quality);
|
||||
}
|
||||
const ldf desired_interval = (
|
||||
cap->desired_fps > 0 && (cap->desired_fps < cap->run->hw_fps || cap->run->hw_fps == 0)
|
||||
? (ldf)1 / cap->desired_fps
|
||||
: 0
|
||||
);
|
||||
|
||||
US_MUTEX_LOCK(_ER(mutex));
|
||||
_ER(type) = type;
|
||||
_ER(quality) = quality;
|
||||
if (cpu_forced) {
|
||||
_ER(cpu_forced) = true;
|
||||
}
|
||||
US_MUTEX_UNLOCK(_ER(mutex));
|
||||
|
||||
const long double desired_interval = (
|
||||
dev->desired_fps > 0 && (dev->desired_fps < dev->run->hw_fps || dev->run->hw_fps == 0)
|
||||
? (long double)1 / dev->desired_fps
|
||||
: 0
|
||||
);
|
||||
|
||||
enc->run->pool = us_workers_pool_init(
|
||||
"JPEG", "jw", n_workers, desired_interval,
|
||||
_worker_job_init, (void *)enc,
|
||||
_worker_job_destroy,
|
||||
_worker_run_job);
|
||||
|
||||
# undef DR
|
||||
enc->run->pool = us_workers_pool_init(
|
||||
"JPEG", "jw", n_workers, desired_interval,
|
||||
_worker_job_init, (void*)enc,
|
||||
_worker_job_destroy,
|
||||
_worker_run_job);
|
||||
}
|
||||
|
||||
void us_encoder_close(us_encoder_s *enc) {
|
||||
@@ -178,57 +180,51 @@ void us_encoder_close(us_encoder_s *enc) {
|
||||
US_DELETE(enc->run->pool, us_workers_pool_destroy);
|
||||
}
|
||||
|
||||
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));
|
||||
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, uint *quality) {
|
||||
us_encoder_runtime_s *const run = enc->run;
|
||||
US_MUTEX_LOCK(run->mutex);
|
||||
*type = run->type;
|
||||
*quality = run->quality;
|
||||
US_MUTEX_UNLOCK(run->mutex);
|
||||
}
|
||||
|
||||
static void *_worker_job_init(void *v_enc) {
|
||||
us_encoder_job_s *job;
|
||||
US_CALLOC(job, 1);
|
||||
job->enc = (us_encoder_s *)v_enc;
|
||||
job->enc = (us_encoder_s*)v_enc;
|
||||
job->dest = us_frame_init();
|
||||
return (void *)job;
|
||||
return (void*)job;
|
||||
}
|
||||
|
||||
static void _worker_job_destroy(void *v_job) {
|
||||
us_encoder_job_s *job = (us_encoder_job_s *)v_job;
|
||||
us_encoder_job_s *job = v_job;
|
||||
us_frame_destroy(job->dest);
|
||||
free(job);
|
||||
}
|
||||
|
||||
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()
|
||||
const us_frame_s *src = &job->hw->raw;
|
||||
us_frame_s *dest = job->dest;
|
||||
us_encoder_job_s *const job = wr->job;
|
||||
us_encoder_runtime_s *const run = job->enc->run;
|
||||
const us_frame_s *const src = &job->hw->raw;
|
||||
us_frame_s *const dest = job->dest;
|
||||
|
||||
if (_ER(type) == US_ENCODER_TYPE_CPU) {
|
||||
if (run->type == US_ENCODER_TYPE_CPU) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
us_cpu_encoder_compress(src, dest, _ER(quality));
|
||||
us_cpu_encoder_compress(src, dest, run->quality);
|
||||
|
||||
} else if (_ER(type) == US_ENCODER_TYPE_HW) {
|
||||
} else if (run->type == US_ENCODER_TYPE_HW) {
|
||||
US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u",
|
||||
wr->name, job->hw->buf.index);
|
||||
us_hw_encoder_compress(src, dest);
|
||||
|
||||
} else if (_ER(type) == US_ENCODER_TYPE_M2M_VIDEO || _ER(type) == US_ENCODER_TYPE_M2M_IMAGE) {
|
||||
} else if (run->type == US_ENCODER_TYPE_M2M_VIDEO || run->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) {
|
||||
(run->type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index);
|
||||
if (us_m2m_encoder_compress(run->m2ms[wr->number], src, dest, false) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
} 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);
|
||||
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
|
||||
usleep(5000); // Просто чтобы работала логика desired_fps
|
||||
dest->encode_end_ts = us_get_now_monotonic(); // us_frame_encoding_end()
|
||||
|
||||
} else {
|
||||
assert(0 && "Unknown encoder type");
|
||||
}
|
||||
@@ -238,14 +234,9 @@ static bool _worker_run_job(us_worker_s *wr) {
|
||||
job->dest->encode_end_ts - job->dest->encode_begin_ts,
|
||||
wr->name,
|
||||
job->hw->buf.index);
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
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;
|
||||
error:
|
||||
US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -22,45 +22,32 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "../libs/tools.h"
|
||||
#include "../libs/array.h"
|
||||
#include "../libs/threading.h"
|
||||
#include "../libs/logging.h"
|
||||
#include "../libs/types.h"
|
||||
#include "../libs/frame.h"
|
||||
#include "../libs/device.h"
|
||||
#include "../libs/capture.h"
|
||||
|
||||
#include "workers.h"
|
||||
#include "m2m.h"
|
||||
|
||||
#include "encoders/cpu/encoder.h"
|
||||
#include "encoders/hw/encoder.h"
|
||||
|
||||
#define ENCODER_TYPES_STR "CPU, HW, M2M-VIDEO, M2M-IMAGE"
|
||||
|
||||
#define ENCODER_TYPES_STR "CPU, HW, M2M-VIDEO, M2M-IMAGE, NOOP"
|
||||
|
||||
typedef enum {
|
||||
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 {
|
||||
us_encoder_type_e type;
|
||||
unsigned quality;
|
||||
bool cpu_forced;
|
||||
uint quality;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
unsigned n_m2ms;
|
||||
uint n_m2ms;
|
||||
us_m2m_encoder_s **m2ms;
|
||||
|
||||
us_workers_pool_s *pool;
|
||||
@@ -68,16 +55,16 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
us_encoder_type_e type;
|
||||
unsigned n_workers;
|
||||
uint n_workers;
|
||||
char *m2m_path;
|
||||
|
||||
us_encoder_runtime_s *run;
|
||||
} us_encoder_s;
|
||||
|
||||
typedef struct {
|
||||
us_encoder_s *enc;
|
||||
us_hw_buffer_s *hw;
|
||||
us_frame_s *dest;
|
||||
us_encoder_s *enc;
|
||||
us_capture_hwbuf_s *hw;
|
||||
us_frame_s *dest;
|
||||
} us_encoder_job_s;
|
||||
|
||||
|
||||
@@ -87,7 +74,7 @@ void us_encoder_destroy(us_encoder_s *enc);
|
||||
int us_encoder_parse_type(const char *str);
|
||||
const char *us_encoder_type_to_string(us_encoder_type_e type);
|
||||
|
||||
void us_encoder_open(us_encoder_s *enc, us_device_s *dev);
|
||||
void us_encoder_open(us_encoder_s *enc, us_capture_s *cap);
|
||||
void us_encoder_close(us_encoder_s *enc);
|
||||
|
||||
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, unsigned *quality);
|
||||
void us_encoder_get_runtime_params(us_encoder_s *enc, us_encoder_type_e *type, uint *quality);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||
# Copyright (C) 2007 Tom Stöveken #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
@@ -29,8 +29,8 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
JOCTET *buf; // Start of buffer
|
||||
struct jpeg_destination_mgr mgr; // Default manager
|
||||
JOCTET *buf; // Start of buffer
|
||||
us_frame_s *frame;
|
||||
} _jpeg_dest_manager_s;
|
||||
|
||||
@@ -40,7 +40,10 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
|
||||
static void _jpeg_write_scanlines_yuv(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);
|
||||
#ifndef JCS_EXTENSIONS
|
||||
#warning JCS_EXT_BGR is not supported, please use libjpeg-turbo
|
||||
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||
#endif
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg);
|
||||
static boolean _jpeg_empty_output_buffer(j_compress_ptr jpeg);
|
||||
@@ -63,7 +66,15 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
|
||||
jpeg.image_width = src->width;
|
||||
jpeg.image_height = src->height;
|
||||
jpeg.input_components = 3;
|
||||
jpeg.in_color_space = ((src->format == V4L2_PIX_FMT_YUYV || src->format == V4L2_PIX_FMT_UYVY) ? JCS_YCbCr : JCS_RGB);
|
||||
switch (src->format) {
|
||||
case V4L2_PIX_FMT_YUYV:
|
||||
case V4L2_PIX_FMT_YVYU:
|
||||
case V4L2_PIX_FMT_UYVY: jpeg.in_color_space = JCS_YCbCr; break;
|
||||
# ifdef JCS_EXTENSIONS
|
||||
case V4L2_PIX_FMT_BGR24: jpeg.in_color_space = JCS_EXT_BGR; break;
|
||||
# endif
|
||||
default: jpeg.in_color_space = JCS_RGB; break;
|
||||
}
|
||||
|
||||
jpeg_set_defaults(&jpeg);
|
||||
jpeg_set_quality(&jpeg, quality, TRUE);
|
||||
@@ -77,7 +88,13 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
|
||||
case V4L2_PIX_FMT_UYVY: _jpeg_write_scanlines_yuv(&jpeg, src); break;
|
||||
case V4L2_PIX_FMT_RGB565: _jpeg_write_scanlines_rgb565(&jpeg, src); break;
|
||||
case V4L2_PIX_FMT_RGB24: _jpeg_write_scanlines_rgb24(&jpeg, src); break;
|
||||
case V4L2_PIX_FMT_BGR24: _jpeg_write_scanlines_bgr24(&jpeg, src); break;
|
||||
case V4L2_PIX_FMT_BGR24:
|
||||
# ifdef JCS_EXTENSIONS
|
||||
_jpeg_write_scanlines_rgb24(&jpeg, src); // Use native JCS_EXT_BGR
|
||||
# else
|
||||
_jpeg_write_scanlines_bgr24(&jpeg, src);
|
||||
# endif
|
||||
break;
|
||||
default: assert(0 && "Unsupported input format for CPU encoder"); return;
|
||||
}
|
||||
|
||||
@@ -89,12 +106,12 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
|
||||
|
||||
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)(
|
||||
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 *const 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;
|
||||
@@ -191,6 +208,7 @@ static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef JCS_EXTENSIONS
|
||||
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||
uint8_t *line_buf;
|
||||
US_CALLOC(line_buf, frame->width * 3);
|
||||
@@ -217,14 +235,15 @@ static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const
|
||||
|
||||
free(line_buf);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define JPEG_OUTPUT_BUFFER_SIZE ((size_t)4096)
|
||||
|
||||
static void _jpeg_init_destination(j_compress_ptr jpeg) {
|
||||
_jpeg_dest_manager_s *const 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)(
|
||||
assert((dest->buf = (JOCTET*)(*jpeg->mem->alloc_small)(
|
||||
(j_common_ptr) jpeg, JPOOL_IMAGE, JPEG_OUTPUT_BUFFER_SIZE * sizeof(JOCTET)
|
||||
)) != NULL);
|
||||
|
||||
@@ -235,7 +254,7 @@ 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 *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
_jpeg_dest_manager_s *const dest = (_jpeg_dest_manager_s*)jpeg->dest;
|
||||
|
||||
us_frame_append_data(dest->frame, dest->buf, JPEG_OUTPUT_BUFFER_SIZE);
|
||||
|
||||
@@ -249,7 +268,7 @@ 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 *const dest = (_jpeg_dest_manager_s *)jpeg->dest;
|
||||
_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.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||
# Copyright (C) 2007 Tom Stöveken #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# Copyright (C) 2005-2006 Laurent Pinchart & Michel Xhaard #
|
||||
# Copyright (C) 2006 Gabriel A. Devenyi #
|
||||
# Copyright (C) 2007 Tom Stöveken #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# #
|
||||
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# Copyright (C) 2018-2024 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 #
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user