mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74bf710bb6 | ||
|
|
1d3b428a75 | ||
|
|
749bc5caf7 | ||
|
|
3b7cbc62c4 | ||
|
|
dff49d8e7b | ||
|
|
b23883e65f | ||
|
|
c2e30c7fc4 | ||
|
|
37216250b8 | ||
|
|
dc82894038 | ||
|
|
f3a350148e | ||
|
|
5e49f50e22 |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 3.19
|
||||
current_version = 3.23
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
@@ -38,8 +38,7 @@ You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support,
|
||||
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Add `libraspberrypi-dev` for `WITH_OMX=1` and `libgpiod` for `WITH_GPIO=1`.
|
||||
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev libbsd-dev`.
|
||||
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg-dev libjpeg62-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libbsd0`.
|
||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||
|
||||
On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX=1``` to ```make```. To enable GPIO support install [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) and pass option ```WITH_GPIO=1```. If the compiler reports about a missing function ```pthread_get_name_np()``` (or similar), add option ```WITH_PTHREAD_NP=0``` (it's enabled by default). For the similar error with ```setproctitle()``` add option ```WITH_SETPROCTITLE=0```.
|
||||
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
|
||||
* Arch: `sudo pacman -S libevent libjpeg-turbo libutil-linux libbsd`.
|
||||
* Raspbian: `sudo apt install libevent-dev libjpeg8-dev libbsd-dev`. Добавьте `libraspberrypi-dev` для сборки с `WITH_OMX=1` и `libgpiod` для `WITH_GPIO=1`.
|
||||
* Debian: `sudo apt install build-essential libevent-dev libjpeg62-turbo-dev libbsd-dev`.
|
||||
* Ubuntu 20.04 x86_64: `sudo apt install build-essential libevent-dev libjpeg-dev libjpeg62-dev libbsd-dev make gcc libjpeg8 libjpeg-turbo8 libbsd0`.
|
||||
* Debian/Ubuntu: `sudo apt install build-essential libevent-dev libjpeg-dev libbsd-dev`.
|
||||
|
||||
На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX=1```. Для включения сборки с поддержкой GPIO установите [libgpiod](https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about) и добавьте параметр ```WITH_GPIO=1```. Если при сборке компилятор ругается на отсутствие функции ```pthread_get_name_np()``` или другой подобной, добавьте параметр ```WITH_PTHREAD_NP=0``` (по умолчанию он включен). При аналогичной ошибке с функцией ```setproctitle()``` добавьте параметр ```WITH_SETPROCTITLE=0```.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 3.19" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 3.23" "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 3.19" "November 2020"
|
||||
.TH USTREAMER 1 "version 3.23" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPG video from any V4L2 device to the network
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=3.19
|
||||
pkgver=3.23
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=3.19
|
||||
PKG_VERSION:=3.23
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from distutils.core import setup
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="3.19",
|
||||
version="3.23",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
@@ -50,7 +50,6 @@ typedef struct {
|
||||
memsink_shared_s *mem;
|
||||
|
||||
tmp_frame_s *tmp_frame;
|
||||
PyObject *dict_frame; // PyDict
|
||||
} MemsinkObject;
|
||||
|
||||
|
||||
@@ -67,10 +66,6 @@ static void MemsinkObject_destroy_internals(MemsinkObject *self) {
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
}
|
||||
if (self->dict_frame != NULL) {
|
||||
Py_DECREF(self->dict_frame);
|
||||
self->dict_frame = NULL;
|
||||
}
|
||||
if (self->tmp_frame) {
|
||||
if (TMP(data)) {
|
||||
free(TMP(data));
|
||||
@@ -108,10 +103,6 @@ static int MemsinkObject_init(MemsinkObject *self, PyObject *args, PyObject *kwa
|
||||
TMP(allocated) = 512 * 1024;
|
||||
A_REALLOC(TMP(data), TMP(allocated));
|
||||
|
||||
if ((self->dict_frame = PyDict_New()) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if ((self->fd = shm_open(self->obj, O_RDWR, 0)) == -1) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
@@ -274,14 +265,17 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
|
||||
PyDict_Clear(self->dict_frame);
|
||||
PyObject *dict_frame = PyDict_New();
|
||||
if (dict_frame == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
# define SET_VALUE(_key, _maker) { \
|
||||
PyObject *_tmp = _maker; \
|
||||
if (_tmp == NULL) { \
|
||||
return NULL; \
|
||||
} \
|
||||
if (PyDict_SetItemString(self->dict_frame, _key, _tmp) < 0) { \
|
||||
if (PyDict_SetItemString(dict_frame, _key, _tmp) < 0) { \
|
||||
Py_DECREF(_tmp); \
|
||||
return NULL; \
|
||||
} \
|
||||
@@ -303,8 +297,7 @@ static PyObject *MemsinkObject_wait_frame(MemsinkObject *self, PyObject *Py_UNUS
|
||||
# undef SET_NUMBER
|
||||
# undef SET_VALUE
|
||||
|
||||
Py_INCREF(self->dict_frame);
|
||||
return self->dict_frame;
|
||||
return dict_frame;
|
||||
}
|
||||
|
||||
static PyObject *MemsinkObject_is_opened(MemsinkObject *self, PyObject *Py_UNUSED(ignored)) {
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef VERSION
|
||||
# define VERSION "3.19"
|
||||
# define VERSION "3.23"
|
||||
#endif
|
||||
|
||||
@@ -39,13 +39,15 @@
|
||||
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
||||
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||
|
||||
#define A_ASPRINTF(_dest, _fmt, ...) assert(asprintf(&(_dest), _fmt, ##__VA_ARGS__) >= 0)
|
||||
|
||||
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
||||
|
||||
#define INLINE inline __attribute__((always_inline))
|
||||
#define UNUSED __attribute__((unused))
|
||||
|
||||
|
||||
INLINE char *bool_to_string(bool flag) {
|
||||
INLINE const char *bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
|
||||
@@ -217,9 +217,7 @@ static void *_worker_job_init(worker_s *wr, void *v_enc) {
|
||||
A_CALLOC(job, 1);
|
||||
job->enc = (encoder_s *)v_enc;
|
||||
|
||||
const size_t dest_role_len = strlen(wr->name) + 16;
|
||||
A_CALLOC(job->dest_role, dest_role_len);
|
||||
snprintf(job->dest_role, dest_role_len, "%s_dest", wr->name);
|
||||
A_ASPRINTF(job->dest_role, "%s_dest", wr->name);
|
||||
job->dest = frame_init(job->dest_role);
|
||||
|
||||
return (void *)job;
|
||||
|
||||
@@ -102,8 +102,7 @@ static void _gpio_output_init(gpio_output_s *output) {
|
||||
assert(gpio.chip);
|
||||
assert(output->line == NULL);
|
||||
|
||||
A_CALLOC(output->consumer, strlen(gpio.consumer_prefix) + strlen(output->role) + 16);
|
||||
sprintf(output->consumer, "%s::%s", gpio.consumer_prefix, output->role);
|
||||
A_ASPRINTF(output->consumer, "%s::%s", gpio.consumer_prefix, output->role);
|
||||
|
||||
if (output->pin >= 0) {
|
||||
if ((output->line = gpiod_chip_get_line(gpio.chip, output->pin)) != NULL) {
|
||||
|
||||
@@ -31,8 +31,10 @@ h264_stream_s *h264_stream_init(memsink_s *sink, unsigned bitrate, unsigned gop)
|
||||
h264->dest = frame_init("h264_dest");
|
||||
atomic_init(&h264->online, false);
|
||||
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L210
|
||||
if ((h264->enc = h264_encoder_init(bitrate, gop, 0)) == NULL) {
|
||||
// FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100
|
||||
// По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480
|
||||
// енкодер через несколько секунд перестает производить корректные фреймы.
|
||||
if ((h264->enc = h264_encoder_init(bitrate, gop, 30)) == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ static void _http_queue_send_stream(server_s *server, bool stream_updated, bool
|
||||
|
||||
static bool _expose_new_frame(server_s *server);
|
||||
|
||||
static char *_http_get_client_hostport(struct evhttp_request *request);
|
||||
|
||||
|
||||
#define RUN(_next) server->run->_next
|
||||
#define STREAM(_next) RUN(stream->_next)
|
||||
@@ -94,6 +96,7 @@ void server_destroy(server_s *server) {
|
||||
for (stream_client_s *client = RUN(stream_clients); client != NULL;) {
|
||||
stream_client_s *next = client->next;
|
||||
free(client->key);
|
||||
free(client->hostport);
|
||||
free(client);
|
||||
client = next;
|
||||
}
|
||||
@@ -142,16 +145,14 @@ int server_listen(server_s *server) {
|
||||
evhttp_set_timeout(RUN(http), server->timeout);
|
||||
|
||||
if (server->user[0] != '\0') {
|
||||
char *raw_token;
|
||||
char *encoded_token = NULL;
|
||||
|
||||
A_CALLOC(raw_token, strlen(server->user) + strlen(server->passwd) + 16);
|
||||
sprintf(raw_token, "%s:%s", server->user, server->passwd);
|
||||
char *raw_token;
|
||||
A_ASPRINTF(raw_token, "%s:%s", server->user, server->passwd);
|
||||
base64_encode((uint8_t *)raw_token, strlen(raw_token), &encoded_token, NULL);
|
||||
free(raw_token);
|
||||
|
||||
A_CALLOC(RUN(auth_token), strlen(encoded_token) + 16);
|
||||
sprintf(RUN(auth_token), "Basic %s", encoded_token);
|
||||
A_ASPRINTF(RUN(auth_token), "Basic %s", encoded_token);
|
||||
free(encoded_token);
|
||||
|
||||
LOG_INFO("Using HTTP basic auth");
|
||||
@@ -473,6 +474,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
# undef PARSE_PARAM
|
||||
evhttp_clear_headers(¶ms);
|
||||
|
||||
client->hostport = _http_get_client_hostport(request);
|
||||
client->id = get_now_id();
|
||||
|
||||
if (RUN(stream_clients) == NULL) {
|
||||
@@ -493,23 +495,18 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
# endif
|
||||
}
|
||||
|
||||
char *client_addr;
|
||||
unsigned short client_port;
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
|
||||
LOG_INFO("HTTP: Registered client: [%s]:%u, id=%" PRIx64 "; clients now: %u",
|
||||
client_addr, client_port, client->id, RUN(stream_clients_count));
|
||||
|
||||
LOG_INFO("HTTP: Registered client: %s, id=%" PRIx64 "; clients now: %u",
|
||||
client->hostport, client->id, RUN(stream_clients_count));
|
||||
|
||||
struct bufferevent *buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
if (server->tcp_nodelay && !RUN(unix_fd)) {
|
||||
evutil_socket_t fd;
|
||||
int on = 1;
|
||||
|
||||
LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client [%s]:%u ...", client_addr, client_port);
|
||||
LOG_DEBUG("HTTP: Setting up TCP_NODELAY to the client %s ...", client->hostport);
|
||||
assert((fd = bufferevent_getfd(buf_event)) >= 0);
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)) != 0) {
|
||||
LOG_PERROR("HTTP: Can't set TCP_NODELAY to the client [%s]:%u", client_addr, client_port);
|
||||
LOG_PERROR("HTTP: Can't set TCP_NODELAY to the client %s", client->hostport);
|
||||
}
|
||||
}
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||
@@ -668,17 +665,10 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
# endif
|
||||
}
|
||||
|
||||
char *client_addr = "???";
|
||||
unsigned short client_port = 0;
|
||||
LOG_INFO("HTTP: Disconnected client: %s, id=%" PRIx64 ", %s; clients now: %u",
|
||||
client->hostport, client->id, reason, RUN(stream_clients_count));
|
||||
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(client->request);
|
||||
if (conn) {
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
}
|
||||
|
||||
LOG_INFO("HTTP: Disconnected client: [%s]:%u, id=%" PRIx64 ", %s; clients now: %u",
|
||||
client_addr, client_port, client->id, reason, RUN(stream_clients_count));
|
||||
|
||||
if (conn) {
|
||||
evhttp_connection_free(conn);
|
||||
}
|
||||
@@ -692,6 +682,7 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
|
||||
client->next->prev = client->prev;
|
||||
}
|
||||
free(client->key);
|
||||
free(client->hostport);
|
||||
free(client);
|
||||
|
||||
free(reason);
|
||||
@@ -834,3 +825,37 @@ static bool _expose_new_frame(server_s *server) {
|
||||
#undef VID
|
||||
#undef STREAM
|
||||
#undef RUN
|
||||
|
||||
static char *_http_get_client_hostport(struct evhttp_request *request) {
|
||||
char *addr = NULL;
|
||||
unsigned short port = 0;
|
||||
struct evhttp_connection *conn = evhttp_request_get_connection(request);
|
||||
if (conn) {
|
||||
char *peer;
|
||||
evhttp_connection_get_peer(conn, &peer, &port);
|
||||
assert(addr = strdup(peer));
|
||||
}
|
||||
|
||||
const char *xff = evhttp_find_header(evhttp_request_get_input_headers(request), "X-Forwarded-For");
|
||||
if (xff) {
|
||||
if (addr) {
|
||||
free(addr);
|
||||
}
|
||||
assert(addr = strndup(xff, 1024));
|
||||
for (unsigned index = 0; addr[index]; ++index) {
|
||||
if (addr[index] == ',') {
|
||||
addr[index] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addr == NULL) {
|
||||
assert(addr = strdup("???"));
|
||||
}
|
||||
|
||||
char *hostport;
|
||||
A_ASPRINTF(hostport, "[%s]:%u", addr, port);
|
||||
free(addr);
|
||||
return hostport;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ typedef struct stream_client_sx {
|
||||
bool dual_final_frames;
|
||||
bool zero_data;
|
||||
|
||||
char *hostport;
|
||||
uint64_t id;
|
||||
bool need_initial;
|
||||
bool need_first_frame;
|
||||
|
||||
@@ -32,7 +32,7 @@ char *find_static_file_path(const char *root_path, const char *request_path) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16);
|
||||
A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 16); // + reserved for /index.html
|
||||
sprintf(path, "%s/%s", root_path, simplified_path);
|
||||
|
||||
struct stat st;
|
||||
|
||||
@@ -49,14 +49,11 @@ workers_pool_s *workers_pool_init(
|
||||
A_MUTEX_INIT(&pool->free_workers_mutex);
|
||||
A_COND_INIT(&pool->free_workers_cond);
|
||||
|
||||
const size_t wr_name_len = strlen(wr_prefix) + 64;
|
||||
|
||||
for (unsigned number = 0; number < pool->n_workers; ++number) {
|
||||
# define WR(_next) pool->workers[number]._next
|
||||
|
||||
WR(number) = number;
|
||||
A_CALLOC(WR(name), wr_name_len);
|
||||
snprintf(WR(name), wr_name_len, "%s-%u", wr_prefix, number);
|
||||
A_ASPRINTF(WR(name), "%s-%u", wr_prefix, number);
|
||||
|
||||
A_MUTEX_INIT(&WR(has_job_mutex));
|
||||
atomic_init(&WR(has_job), false);
|
||||
|
||||
Reference in New Issue
Block a user