mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-26 19:56:33 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0e070be5b | ||
|
|
aa05c470b3 | ||
|
|
13af11a3a6 | ||
|
|
41330940c6 | ||
|
|
46e630d2f6 | ||
|
|
63d87f0526 | ||
|
|
b578e9897e | ||
|
|
b2ebcf99c8 | ||
|
|
6a6b910869 | ||
|
|
4e8acf371f | ||
|
|
c4cb8288c7 |
@@ -1,7 +1,7 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 5.48
|
current_version = 5.51
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}
|
{major}.{minor}
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ Add `-e EDID=1` to set HDMI EDID before starting ustreamer. Use together with `-
|
|||||||
-----
|
-----
|
||||||
# Raspberry Pi Camera Example
|
# Raspberry Pi Camera Example
|
||||||
|
|
||||||
Example usage for the Raspberry Pi v3 camera (required `libcamerify` which is located in `libcamera-tools` on Raspbian):
|
Example usage for the Raspberry Pi v3 camera (required `libcamerify` which is located in `libcamera-tools` and `libcamera-v4l2` (install both) on Raspbian):
|
||||||
```
|
```
|
||||||
$ sudo modprobe bcm2835-v4l2
|
$ sudo modprobe bcm2835-v4l2
|
||||||
$ libcamerify ./ustreamer --host :: --encoder=m2m-image
|
$ libcamerify ./ustreamer --host :: --encoder=m2m-image
|
||||||
@@ -180,7 +180,7 @@ $ modprobe bcm2835-v4l2 max_video_width=2592 max_video_height=1944
|
|||||||
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
|
µStreamer supports bandwidth-efficient streaming using [H.264 compression](https://en.wikipedia.org/wiki/Advanced_Video_Coding) and the Janus WebRTC server. See the [Janus integration guide](docs/h264.md) for full details.
|
||||||
|
|
||||||
## Nginx
|
## Nginx
|
||||||
When uStreamer is behind an Nginx proxy, it's buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
When uStreamer is behind an Nginx proxy, its buffering behavior introduces latency into the video stream. It's possible to disable Nginx's buffering to eliminate the additional latency:
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
location /stream {
|
location /stream {
|
||||||
|
|||||||
@@ -99,7 +99,9 @@ static atomic_bool _g_key_required = false;
|
|||||||
janus_plugin *create(void);
|
janus_plugin *create(void);
|
||||||
|
|
||||||
|
|
||||||
static void *_video_rtp_thread(UNUSED void *arg) {
|
static void *_video_rtp_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
US_THREAD_RENAME("us_video_rtp");
|
US_THREAD_RENAME("us_video_rtp");
|
||||||
atomic_store(&_g_video_rtp_tid_created, true);
|
atomic_store(&_g_video_rtp_tid_created, true);
|
||||||
|
|
||||||
@@ -116,7 +118,9 @@ static void *_video_rtp_thread(UNUSED void *arg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *_video_sink_thread(UNUSED void *arg) {
|
static void *_video_sink_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
US_THREAD_RENAME("us_video_sink");
|
US_THREAD_RENAME("us_video_sink");
|
||||||
atomic_store(&_g_video_sink_tid_created, true);
|
atomic_store(&_g_video_sink_tid_created, true);
|
||||||
|
|
||||||
@@ -178,7 +182,9 @@ static void *_video_sink_thread(UNUSED void *arg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *_audio_thread(UNUSED void *arg) {
|
static void *_audio_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
US_THREAD_RENAME("us_audio");
|
US_THREAD_RENAME("us_audio");
|
||||||
atomic_store(&_g_audio_tid_created, true);
|
atomic_store(&_g_audio_tid_created, true);
|
||||||
assert(_g_config->audio_dev_name != NULL);
|
assert(_g_config->audio_dev_name != NULL);
|
||||||
@@ -344,7 +350,8 @@ static json_t *_plugin_query_session(janus_plugin_session *session) {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _set_transmit(janus_plugin_session *session, UNUSED const char *msg, bool transmit) {
|
static void _set_transmit(janus_plugin_session *session, const char *msg, bool transmit) {
|
||||||
|
(void)msg;
|
||||||
_IF_DISABLED({ return; });
|
_IF_DISABLED({ return; });
|
||||||
_LOCK_ALL;
|
_LOCK_ALL;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
@@ -503,7 +510,9 @@ static struct janus_plugin_result *_plugin_handle_message(
|
|||||||
# undef FREE_MSG_JSEP
|
# undef FREE_MSG_JSEP
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _plugin_incoming_rtcp(UNUSED janus_plugin_session *handle, UNUSED janus_plugin_rtcp *packet) {
|
static void _plugin_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet) {
|
||||||
|
(void)handle;
|
||||||
|
(void)packet;
|
||||||
if (packet->video && janus_rtcp_has_pli(packet->buffer, packet->length)) {
|
if (packet->video && janus_rtcp_has_pli(packet->buffer, packet->length)) {
|
||||||
// US_JLOG_INFO("main", "Got video PLI");
|
// US_JLOG_INFO("main", "Got video PLI");
|
||||||
atomic_store(&_g_key_required, true);
|
atomic_store(&_g_key_required, true);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" Manpage for ustreamer-dump.
|
.\" Manpage for ustreamer-dump.
|
||||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||||
.TH USTREAMER-DUMP 1 "version 5.48" "January 2021"
|
.TH USTREAMER-DUMP 1 "version 5.51" "January 2021"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" Manpage for ustreamer.
|
.\" Manpage for ustreamer.
|
||||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||||
.TH USTREAMER 1 "version 5.48" "November 2020"
|
.TH USTREAMER 1 "version 5.51" "November 2020"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||||
@@ -52,7 +52,7 @@ Initial image resolution. Default: 640x480.
|
|||||||
.TP
|
.TP
|
||||||
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
|
.BR \-m\ \fIfmt ", " \-\-format\ \fIfmt
|
||||||
Image format.
|
Image format.
|
||||||
Available: YUYV, UYVY, RGB565, RGB24, JPEG; default: YUYV.
|
Available: YUYV, YVYU, UYVY, RGB565, RGB24, JPEG; default: YUYV.
|
||||||
.TP
|
.TP
|
||||||
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
|
.BR \-a\ \fIstd ", " \-\-tv\-standard\ \fIstd
|
||||||
Force TV standard.
|
Force TV standard.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
pkgname=ustreamer
|
pkgname=ustreamer
|
||||||
pkgver=5.48
|
pkgver=5.51
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
pkgdesc="Lightweight and fast MJPEG-HTTP streamer"
|
||||||
url="https://github.com/pikvm/ustreamer"
|
url="https://github.com/pikvm/ustreamer"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=ustreamer
|
PKG_NAME:=ustreamer
|
||||||
PKG_VERSION:=5.48
|
PKG_VERSION:=5.51
|
||||||
PKG_RELEASE:=1
|
PKG_RELEASE:=1
|
||||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ def _find_sources(suffix: str) -> list[str]:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
setup(
|
setup(
|
||||||
name="ustreamer",
|
name="ustreamer",
|
||||||
version="5.48",
|
version="5.51",
|
||||||
description="uStreamer tools",
|
description="uStreamer tools",
|
||||||
author="Maxim Devaev",
|
author="Maxim Devaev",
|
||||||
author_email="mdevaev@gmail.com",
|
author_email="mdevaev@gmail.com",
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ static int _MemsinkObject_init(_MemsinkObject *self, PyObject *args, PyObject *k
|
|||||||
|
|
||||||
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
|
static PyObject *_MemsinkObject_repr(_MemsinkObject *self) {
|
||||||
char repr[1024];
|
char repr[1024];
|
||||||
snprintf(repr, 1023, "<Memsink(%s)>", self->obj);
|
US_SNPRINTF(repr, 1023, "<Memsink(%s)>", self->obj);
|
||||||
return Py_BuildValue("s", repr);
|
return Py_BuildValue("s", repr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define US_VERSION_MAJOR 5
|
#define US_VERSION_MAJOR 5
|
||||||
#define US_VERSION_MINOR 48
|
#define US_VERSION_MINOR 51
|
||||||
|
|
||||||
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
#define US_MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||||
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
#define US_MAKE_VERSION1(_major, _minor) US_MAKE_VERSION2(_major, _minor)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ unsigned us_frame_get_padding(const us_frame_s *frame) {
|
|||||||
unsigned bytes_per_pixel = 0;
|
unsigned bytes_per_pixel = 0;
|
||||||
switch (frame->format) {
|
switch (frame->format) {
|
||||||
case V4L2_PIX_FMT_YUYV:
|
case V4L2_PIX_FMT_YUYV:
|
||||||
|
case V4L2_PIX_FMT_YVYU:
|
||||||
case V4L2_PIX_FMT_UYVY:
|
case V4L2_PIX_FMT_UYVY:
|
||||||
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break;
|
case V4L2_PIX_FMT_RGB565: bytes_per_pixel = 2; break;
|
||||||
case V4L2_PIX_FMT_BGR24:
|
case V4L2_PIX_FMT_BGR24:
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ extern pthread_mutex_t us_g_log_mutex;
|
|||||||
|
|
||||||
|
|
||||||
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
#define US_LOG_PRINTF_NOLOCK(x_label_color, x_label, x_msg_color, x_msg, ...) { \
|
||||||
char m_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
char m_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
|
||||||
us_thread_get_name(m_tname_buf); \
|
us_thread_get_name(m_tname_buf); \
|
||||||
if (us_g_log_colored) { \
|
if (us_g_log_colored) { \
|
||||||
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
|
fprintf(stderr, US_COLOR_GRAY "-- " x_label_color x_label US_COLOR_GRAY \
|
||||||
|
|||||||
@@ -41,9 +41,9 @@
|
|||||||
|
|
||||||
|
|
||||||
#ifdef PTHREAD_MAX_NAMELEN_NP
|
#ifdef PTHREAD_MAX_NAMELEN_NP
|
||||||
# define US_MAX_THREAD_NAME ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
# define US_THREAD_NAME_SIZE ((size_t)(PTHREAD_MAX_NAMELEN_NP))
|
||||||
#else
|
#else
|
||||||
# define US_MAX_THREAD_NAME ((size_t)16)
|
# define US_THREAD_NAME_SIZE ((size_t)16)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
#define US_THREAD_CREATE(x_tid, x_func, x_arg) assert(!pthread_create(&(x_tid), NULL, (x_func), (x_arg)))
|
||||||
@@ -51,8 +51,8 @@
|
|||||||
|
|
||||||
#ifdef WITH_PTHREAD_NP
|
#ifdef WITH_PTHREAD_NP
|
||||||
# define US_THREAD_RENAME(x_fmt, ...) { \
|
# define US_THREAD_RENAME(x_fmt, ...) { \
|
||||||
char m_new_tname_buf[US_MAX_THREAD_NAME] = {0}; \
|
char m_new_tname_buf[US_THREAD_NAME_SIZE] = {0}; \
|
||||||
assert(snprintf(m_new_tname_buf, US_MAX_THREAD_NAME, (x_fmt), ##__VA_ARGS__) > 0); \
|
US_SNPRINTF(m_new_tname_buf, (US_THREAD_NAME_SIZE - 1), (x_fmt), ##__VA_ARGS__); \
|
||||||
us_thread_set_name(m_new_tname_buf); \
|
us_thread_set_name(m_new_tname_buf); \
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@@ -89,12 +89,12 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
|||||||
#ifdef WITH_PTHREAD_NP
|
#ifdef WITH_PTHREAD_NP
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
# if defined(__linux__) || defined (__NetBSD__)
|
# if defined(__linux__) || defined (__NetBSD__)
|
||||||
retval = pthread_getname_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
retval = pthread_getname_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
|
||||||
# elif \
|
# elif \
|
||||||
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|
(defined(__FreeBSD__) && defined(__FreeBSD_version) && __FreeBSD_version >= 1103500) \
|
||||||
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|
|| (defined(__OpenBSD__) && defined(OpenBSD) && OpenBSD >= 201905) \
|
||||||
|| defined(__DragonFly__)
|
|| defined(__DragonFly__)
|
||||||
pthread_get_name_np(pthread_self(), name, US_MAX_THREAD_NAME);
|
pthread_get_name_np(pthread_self(), name, US_THREAD_NAME_SIZE - 1);
|
||||||
if (name[0] != '\0') {
|
if (name[0] != '\0') {
|
||||||
retval = 0;
|
retval = 0;
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ INLINE void us_thread_get_name(char *name) { // Always required for logging
|
|||||||
const pid_t tid = 0; // Makes cppcheck happy
|
const pid_t tid = 0; // Makes cppcheck happy
|
||||||
# warning gettid() not implemented
|
# warning gettid() not implemented
|
||||||
#endif
|
#endif
|
||||||
assert(snprintf(name, US_MAX_THREAD_NAME, "tid=%d", tid) > 0);
|
US_SNPRINTF(name, (US_THREAD_NAME_SIZE - 1), "tid=%d", tid);
|
||||||
|
|
||||||
#ifdef WITH_PTHREAD_NP
|
#ifdef WITH_PTHREAD_NP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <locale.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
@@ -57,14 +56,14 @@
|
|||||||
#define RN "\r\n"
|
#define RN "\r\n"
|
||||||
|
|
||||||
#define INLINE inline __attribute__((always_inline))
|
#define INLINE inline __attribute__((always_inline))
|
||||||
#define UNUSED __attribute__((unused))
|
|
||||||
|
|
||||||
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
#define US_CALLOC(x_dest, x_nmemb) assert(((x_dest) = calloc((x_nmemb), sizeof(*(x_dest)))) != NULL)
|
||||||
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
#define US_REALLOC(x_dest, x_nmemb) assert(((x_dest) = realloc((x_dest), (x_nmemb) * sizeof(*(x_dest)))) != NULL)
|
||||||
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
|
#define US_DELETE(x_dest, x_free) { if (x_dest) { x_free(x_dest); } }
|
||||||
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
#define US_MEMSET_ZERO(x_obj) memset(&(x_obj), 0, sizeof(x_obj))
|
||||||
|
|
||||||
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) >= 0)
|
#define US_SNPRINTF(x_dest, x_size, x_fmt, ...) assert(snprintf((x_dest), (x_size), (x_fmt), ##__VA_ARGS__) > 0)
|
||||||
|
#define US_ASPRINTF(x_dest, x_fmt, ...) assert(asprintf(&(x_dest), (x_fmt), ##__VA_ARGS__) > 0)
|
||||||
|
|
||||||
|
|
||||||
INLINE char *us_strdup(const char *str) {
|
INLINE char *us_strdup(const char *str) {
|
||||||
@@ -178,15 +177,16 @@ INLINE int us_flock_timedwait_monotonic(int fd, long double timeout) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
INLINE char *us_errno_to_string(int error) {
|
INLINE char *us_errno_to_string(int error) {
|
||||||
locale_t locale = newlocale(LC_MESSAGES_MASK, "C", NULL);
|
char buf[2048];
|
||||||
char *buf;
|
const size_t max_len = sizeof(buf) - 1;
|
||||||
if (locale) {
|
# if (_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE
|
||||||
buf = us_strdup(strerror_l(error, locale));
|
if (strerror_r(error, buf, max_len) != 0) {
|
||||||
freelocale(locale);
|
US_SNPRINTF(buf, max_len, "Errno = %d", error);
|
||||||
} else {
|
|
||||||
buf = us_strdup("!!! newlocale() error !!!");
|
|
||||||
}
|
}
|
||||||
return buf;
|
return us_strdup(buf);
|
||||||
|
# else
|
||||||
|
return us_strdup(strerror_r(error, buf, max_len));
|
||||||
|
# endif
|
||||||
}
|
}
|
||||||
|
|
||||||
INLINE char *us_signum_to_string(int signum) {
|
INLINE char *us_signum_to_string(int signum) {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ static const struct {
|
|||||||
const unsigned format; // cppcheck-suppress unusedStructMember
|
const unsigned format; // cppcheck-suppress unusedStructMember
|
||||||
} _FORMATS[] = {
|
} _FORMATS[] = {
|
||||||
{"YUYV", V4L2_PIX_FMT_YUYV},
|
{"YUYV", V4L2_PIX_FMT_YUYV},
|
||||||
|
{"YVYU", V4L2_PIX_FMT_YVYU},
|
||||||
{"UYVY", V4L2_PIX_FMT_UYVY},
|
{"UYVY", V4L2_PIX_FMT_UYVY},
|
||||||
{"RGB565", V4L2_PIX_FMT_RGB565},
|
{"RGB565", V4L2_PIX_FMT_RGB565},
|
||||||
{"RGB24", V4L2_PIX_FMT_RGB24},
|
{"RGB24", V4L2_PIX_FMT_RGB24},
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
|
#define US_STANDARDS_STR "PAL, NTSC, SECAM"
|
||||||
|
|
||||||
#define US_FORMAT_UNKNOWN -1
|
#define US_FORMAT_UNKNOWN -1
|
||||||
#define US_FORMATS_STR "YUYV, UYVY, RGB565, RGB24, BGR24, MJPEG, JPEG"
|
#define US_FORMATS_STR "YUYV, YVYU, UYVY, RGB565, RGB24, BGR24, MJPEG, JPEG"
|
||||||
|
|
||||||
#define US_IO_METHOD_UNKNOWN -1
|
#define US_IO_METHOD_UNKNOWN -1
|
||||||
#define US_IO_METHODS_STR "MMAP, USERPTR"
|
#define US_IO_METHODS_STR "MMAP, USERPTR"
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ us_workers_pool_s *us_encoder_workers_pool_init(us_encoder_s *enc, us_device_s *
|
|||||||
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
|
for (; _ER(n_m2ms) < n_workers; ++_ER(n_m2ms)) {
|
||||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||||
char name[32];
|
char name[32];
|
||||||
snprintf(name, 32, "JPEG-%u", _ER(n_m2ms));
|
US_SNPRINTF(name, 31, "JPEG-%u", _ER(n_m2ms));
|
||||||
if (type == US_ENCODER_TYPE_M2M_VIDEO) {
|
if (type == US_ENCODER_TYPE_M2M_VIDEO) {
|
||||||
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
_ER(m2ms[_ER(n_m2ms)]) = us_m2m_mjpeg_encoder_init(name, enc->m2m_path, quality);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ typedef struct {
|
|||||||
|
|
||||||
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
|
static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame);
|
||||||
|
|
||||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const 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_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
|
||||||
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||||
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
static void _jpeg_write_scanlines_rgb24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||||
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
static void _jpeg_write_scanlines_bgr24(struct jpeg_compress_struct *jpeg, const us_frame_s *frame);
|
||||||
@@ -71,21 +70,17 @@ void us_cpu_encoder_compress(const us_frame_s *src, us_frame_s *dest, unsigned q
|
|||||||
|
|
||||||
jpeg_start_compress(&jpeg, TRUE);
|
jpeg_start_compress(&jpeg, TRUE);
|
||||||
|
|
||||||
# define WRITE_SCANLINES(x_format, x_func) \
|
|
||||||
case x_format: { x_func(&jpeg, src); break; }
|
|
||||||
|
|
||||||
switch (src->format) {
|
switch (src->format) {
|
||||||
// https://www.fourcc.org/yuv.php
|
// https://www.fourcc.org/yuv.php
|
||||||
WRITE_SCANLINES(V4L2_PIX_FMT_YUYV, _jpeg_write_scanlines_yuyv);
|
case V4L2_PIX_FMT_YUYV:
|
||||||
WRITE_SCANLINES(V4L2_PIX_FMT_UYVY, _jpeg_write_scanlines_uyvy);
|
case V4L2_PIX_FMT_YVYU:
|
||||||
WRITE_SCANLINES(V4L2_PIX_FMT_RGB565, _jpeg_write_scanlines_rgb565);
|
case V4L2_PIX_FMT_UYVY: _jpeg_write_scanlines_yuv(&jpeg, src); break;
|
||||||
WRITE_SCANLINES(V4L2_PIX_FMT_RGB24, _jpeg_write_scanlines_rgb24);
|
case V4L2_PIX_FMT_RGB565: _jpeg_write_scanlines_rgb565(&jpeg, src); break;
|
||||||
WRITE_SCANLINES(V4L2_PIX_FMT_BGR24, _jpeg_write_scanlines_bgr24);
|
case V4L2_PIX_FMT_RGB24: _jpeg_write_scanlines_rgb24(&jpeg, src); break;
|
||||||
default: assert(0 && "Unsupported input format for CPU encoder");
|
case V4L2_PIX_FMT_BGR24: _jpeg_write_scanlines_bgr24(&jpeg, src); break;
|
||||||
|
default: assert(0 && "Unsupported input format for CPU encoder"); return;
|
||||||
}
|
}
|
||||||
|
|
||||||
# undef WRITE_SCANLINES
|
|
||||||
|
|
||||||
jpeg_finish_compress(&jpeg);
|
jpeg_finish_compress(&jpeg);
|
||||||
jpeg_destroy_compress(&jpeg);
|
jpeg_destroy_compress(&jpeg);
|
||||||
|
|
||||||
@@ -108,40 +103,7 @@ static void _jpeg_set_dest_frame(j_compress_ptr jpeg, us_frame_s *frame) {
|
|||||||
frame->used = 0;
|
frame->used = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _jpeg_write_scanlines_yuyv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
static void _jpeg_write_scanlines_yuv(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
||||||
uint8_t *line_buf;
|
|
||||||
US_CALLOC(line_buf, frame->width * 3);
|
|
||||||
|
|
||||||
const unsigned padding = us_frame_get_padding(frame);
|
|
||||||
const uint8_t *data = frame->data;
|
|
||||||
|
|
||||||
while (jpeg->next_scanline < frame->height) {
|
|
||||||
uint8_t *ptr = line_buf;
|
|
||||||
|
|
||||||
for (unsigned x = 0; x < frame->width; ++x) {
|
|
||||||
// See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-yuyv.html
|
|
||||||
const bool is_odd_pixel = x & 1;
|
|
||||||
const uint8_t y = data[is_odd_pixel ? 2 : 0];
|
|
||||||
const uint8_t u = data[1];
|
|
||||||
const uint8_t v = data[3];
|
|
||||||
|
|
||||||
ptr[0] = y;
|
|
||||||
ptr[1] = u;
|
|
||||||
ptr[2] = v;
|
|
||||||
ptr += 3;
|
|
||||||
|
|
||||||
data += (is_odd_pixel ? 4: 0);
|
|
||||||
}
|
|
||||||
data += padding;
|
|
||||||
|
|
||||||
JSAMPROW scanlines[1] = {line_buf};
|
|
||||||
jpeg_write_scanlines(jpeg, scanlines, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(line_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const us_frame_s *frame) {
|
|
||||||
uint8_t *line_buf;
|
uint8_t *line_buf;
|
||||||
US_CALLOC(line_buf, frame->width * 3);
|
US_CALLOC(line_buf, frame->width * 3);
|
||||||
|
|
||||||
@@ -154,9 +116,23 @@ static void _jpeg_write_scanlines_uyvy(struct jpeg_compress_struct *jpeg, const
|
|||||||
for (unsigned x = 0; x < frame->width; ++x) {
|
for (unsigned x = 0; x < frame->width; ++x) {
|
||||||
// See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-uyvy.html
|
// See also: https://www.kernel.org/doc/html/v4.8/media/uapi/v4l/pixfmt-uyvy.html
|
||||||
const bool is_odd_pixel = x & 1;
|
const bool is_odd_pixel = x & 1;
|
||||||
const uint8_t y = data[is_odd_pixel ? 3 : 1];
|
uint8_t y, u, v;
|
||||||
const uint8_t u = data[0];
|
if (frame->format == V4L2_PIX_FMT_YUYV) {
|
||||||
const uint8_t v = data[2];
|
y = data[is_odd_pixel ? 2 : 0];
|
||||||
|
u = data[1];
|
||||||
|
v = data[3];
|
||||||
|
} else if (frame->format == V4L2_PIX_FMT_YVYU) {
|
||||||
|
y = data[is_odd_pixel ? 2 : 0];
|
||||||
|
u = data[3];
|
||||||
|
v = data[1];
|
||||||
|
} else if (frame->format == V4L2_PIX_FMT_UYVY) {
|
||||||
|
y = data[is_odd_pixel ? 3 : 1];
|
||||||
|
u = data[0];
|
||||||
|
v = data[2];
|
||||||
|
} else {
|
||||||
|
assert(0 && "Unsupported pixel format");
|
||||||
|
return; // Makes linter happy
|
||||||
|
}
|
||||||
|
|
||||||
ptr[0] = y;
|
ptr[0] = y;
|
||||||
ptr[1] = u;
|
ptr[1] = u;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ char *us_bufferevent_format_reason(short what) {
|
|||||||
char *const perror_str = us_errno_to_string(EVUTIL_SOCKET_ERROR());
|
char *const perror_str = us_errno_to_string(EVUTIL_SOCKET_ERROR());
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
strcat(reason, perror_str);
|
strncat(reason, perror_str, 1023);
|
||||||
free(perror_str);
|
free(perror_str);
|
||||||
strcat(reason, " (");
|
strcat(reason, " (");
|
||||||
|
|
||||||
|
|||||||
@@ -502,12 +502,12 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
|||||||
char header_buf[256];
|
char header_buf[256];
|
||||||
|
|
||||||
# define ADD_TIME_HEADER(x_key, x_value) { \
|
# define ADD_TIME_HEADER(x_key, x_value) { \
|
||||||
snprintf(header_buf, 255, "%.06Lf", x_value); \
|
US_SNPRINTF(header_buf, 255, "%.06Lf", x_value); \
|
||||||
ADD_HEADER(x_key, header_buf); \
|
ADD_HEADER(x_key, header_buf); \
|
||||||
}
|
}
|
||||||
|
|
||||||
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \
|
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \
|
||||||
snprintf(header_buf, 255, "%u", x_value); \
|
US_SNPRINTF(header_buf, 255, "%u", x_value); \
|
||||||
ADD_HEADER(x_key, header_buf); \
|
ADD_HEADER(x_key, header_buf); \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,10 +667,12 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
|
||||||
"Pragma: no-cache" RN
|
"Pragma: no-cache" RN
|
||||||
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
|
||||||
"Set-Cookie: stream_client=%s/%" PRIx64 "; path=/; max-age=30" RN
|
"Set-Cookie: stream_client%s%s=%s/%" PRIx64 "; path=/; max-age=30" RN
|
||||||
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY RN
|
||||||
RN
|
RN
|
||||||
"--" BOUNDARY RN,
|
"--" BOUNDARY RN,
|
||||||
|
(server->instance_id[0] == '\0' ? "" : "_"),
|
||||||
|
server->instance_id,
|
||||||
(client->key != NULL ? client->key : "0"),
|
(client->key != NULL ? client->key : "0"),
|
||||||
client->id
|
client->id
|
||||||
);
|
);
|
||||||
@@ -745,7 +747,10 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
|||||||
# undef BOUNDARY
|
# undef BOUNDARY
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UNUSED short what, void *v_client) {
|
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_client) {
|
||||||
|
(void)buf_event;
|
||||||
|
(void)what;
|
||||||
|
|
||||||
us_stream_client_s *const client = (us_stream_client_s *)v_client;
|
us_stream_client_s *const client = (us_stream_client_s *)v_client;
|
||||||
us_server_s *const server = client->server;
|
us_server_s *const server = client->server;
|
||||||
|
|
||||||
@@ -823,7 +828,10 @@ static void _http_queue_send_stream(us_server_s *server, bool stream_updated, bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_request_watcher(UNUSED int fd, UNUSED short what, void *v_server) {
|
static void _http_request_watcher(int fd, short what, void *v_server) {
|
||||||
|
(void)fd;
|
||||||
|
(void)what;
|
||||||
|
|
||||||
us_server_s *server = (us_server_s *)v_server;
|
us_server_s *server = (us_server_s *)v_server;
|
||||||
const long double now = us_get_now_monotonic();
|
const long double now = us_get_now_monotonic();
|
||||||
|
|
||||||
@@ -837,7 +845,10 @@ static void _http_request_watcher(UNUSED int fd, UNUSED short what, void *v_serv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _http_refresher(UNUSED int fd, UNUSED short what, void *v_server) {
|
static void _http_refresher(int fd, short what, void *v_server) {
|
||||||
|
(void)fd;
|
||||||
|
(void)what;
|
||||||
|
|
||||||
us_server_s *server = (us_server_s *)v_server;
|
us_server_s *server = (us_server_s *)v_server;
|
||||||
bool stream_updated = false;
|
bool stream_updated = false;
|
||||||
bool frame_updated = false;
|
bool frame_updated = false;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ char *us_find_static_file_path(const char *root_path, const char *request_path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
US_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||||
sprintf(path, "%s/%s", root_path, simplified_path);
|
assert(sprintf(path, "%s/%s", root_path, simplified_path) > 0);
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
# define LOAD_STAT { \
|
# define LOAD_STAT { \
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
|||||||
@@ -52,14 +52,16 @@ static void _block_thread_signals(void) {
|
|||||||
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
assert(!pthread_sigmask(SIG_BLOCK, &mask, NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *_stream_loop_thread(UNUSED void *arg) {
|
static void *_stream_loop_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
US_THREAD_RENAME("stream");
|
US_THREAD_RENAME("stream");
|
||||||
_block_thread_signals();
|
_block_thread_signals();
|
||||||
us_stream_loop(_g_stream);
|
us_stream_loop(_g_stream);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *_server_loop_thread(UNUSED void *arg) {
|
static void *_server_loop_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
US_THREAD_RENAME("http");
|
US_THREAD_RENAME("http");
|
||||||
_block_thread_signals();
|
_block_thread_signals();
|
||||||
us_server_loop(_g_server);
|
us_server_loop(_g_server);
|
||||||
|
|||||||
Reference in New Issue
Block a user