diff --git a/Makefile b/Makefile index 2ec1847..20b050c 100644 --- a/Makefile +++ b/Makefile @@ -30,9 +30,9 @@ ifneq ($(call optbool,$(WITH_OMX_ENCODER)),) endif -ifneq ($(call optbool,$(WITH_WORKERS_GPIO_DEBUG)),) +ifneq ($(call optbool,$(WITH_GPIO)),) LIBS += -lwiringPi - override CFLAGS += -DWITH_WORKERS_GPIO_DEBUG + override CFLAGS += -DWITH_GPIO endif diff --git a/README.md b/README.md index dd57374..6a0354d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ | Streaming via UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | | Debug logs without recompiling,
performance statistics log,
access to HTTP broadcast parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | | Option to serve files
with a built-in HTTP server | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Regular files only | +| Signaling about the stream state to GPIO
on Raspberry Pi using [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | | Access to webcam controls (focus, servos)
and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | Footnotes: @@ -31,7 +32,7 @@ If you're going to live-stream from your backyard webcam and need to control it, # Building You'll need ```make```, ```gcc```, ```libevent``` with ```pthreads``` support, ```libjpeg8```/```libjpeg-turbo``` and ```libuuid```. -On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX_ENCODER=1``` to ```make```. +On Raspberry Pi you can build the program with OpenMAX IL. To do this pass option ```WITH_OMX_ENCODER=1``` to ```make```. To enable GPIO support install [wiringPi](http://wiringpi.com) and pass option ```WITH_GPIO=1```. ``` $ git clone --depth=1 https://github.com/pi-kvm/ustreamer @@ -40,7 +41,7 @@ $ make $ ./ustreamer --help ``` -AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```. +AUR has a package for Arch Linux: https://aur.archlinux.org/packages/ustreamer. It should compile automatically with OpenMAX IL on Raspberry Pi, if the corresponding headers are present in ```/opt/vc/include```. Same with GPIO. FreeBSD port: https://www.freshports.org/multimedia/ustreamer. ----- diff --git a/README.ru.md b/README.ru.md index 23793e6..862f4af 100644 --- a/README.ru.md +++ b/README.ru.md @@ -16,6 +16,7 @@ | Стрим через UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | | Дебаг-логи без перекомпиляции,
логгирование статистики производительности,
возможность получения параметров
трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | | Возможность сервить файлы встроенным
HTTP-сервером | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) Нет каталогов | +| Вывод сигналов о состоянии стрима на GPIO
на Raspberry Pi с помощью [wiringPi](http://wiringpi.com) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | | Поддержка контролов веб-камер (фокус,
движение сервами) и всяких настроек,
типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | Сносочки: @@ -31,7 +32,7 @@ # Сборка Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads```, ```libjpeg8```/```libjpeg-turbo``` и ```libuuid```. -На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX_ENCODER=1```. +На Raspberry Pi программу можно собрать с поддержкой OpenMAX IL. Для этого передайте ```make``` параметр ```WITH_OMX_ENCODER=1```. Для включения сборки с поддержкой GPIO установите [wiringPi](http://wiringpi.com) и добавьте параметр ```WITH_GPIO=1```. ``` $ git clone --depth=1 https://github.com/pi-kvm/ustreamer @@ -40,7 +41,7 @@ $ make $ ./ustreamer --help ``` -Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```. +Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer. На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX IL, если обнаружит нужные хедеры в ```/opt/vc/include```. То же самое и с GPIO. Порт для FreeBSD: https://www.freshports.org/multimedia/ustreamer. ----- diff --git a/pkg/arch/PKGBUILD b/pkg/arch/PKGBUILD index 37e92d8..27f108b 100644 --- a/pkg/arch/PKGBUILD +++ b/pkg/arch/PKGBUILD @@ -11,6 +11,7 @@ license=(GPL) arch=(i686 x86_64 armv6h armv7h) depends=(libjpeg libevent libutil-linux) # optional: raspberrypi-firmware for OMX JPEG encoder +# optional: wiringpi for GPIO support makedepends=(gcc make) source=(${pkgname}::"git+https://github.com/pi-kvm/ustreamer#commit=v${pkgver}") md5sums=(SKIP) @@ -23,7 +24,8 @@ build() { cd $pkgname-build local _options="" - [ -d /opt/vc/include ] && _options="$_options WITH_OMX_ENCODER=1" + [ -e /opt/vc/include/IL/OMX_Core.h ] && _options="$_options WITH_OMX_ENCODER=1" + [ -e /usr/include/wiringPi.h ] && _options="$_options WITH_GPIO=1" make $_options CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" $MAKEFLAGS } diff --git a/src/gpio.h b/src/gpio.h new file mode 100644 index 0000000..be5e7f5 --- /dev/null +++ b/src/gpio.h @@ -0,0 +1,92 @@ +/***************************************************************************** +# # +# uStreamer - Lightweight and fast MJPG-HTTP streamer. # +# # +# Copyright (C) 2018 Maxim Devaev # +# # +# 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 . # +# # +*****************************************************************************/ + + +#pragma once + +#include + +#include + +#include "logging.h" + + +int gpio_pin_prog_running; +int gpio_pin_stream_online; +int gpio_pin_has_http_clients; +int gpio_pin_workers_busy_at; + + +#define GPIO_INIT { \ + gpio_pin_prog_running = -1; \ + gpio_pin_stream_online = -1; \ + gpio_pin_has_http_clients = -1; \ + gpio_pin_workers_busy_at = -1; \ + } + +#define GPIO_INIT_PIN(_role, _offset) { \ + if (gpio_pin_##_role >= 0) { \ + pinMode(gpio_pin_##_role + _offset, OUTPUT); \ + digitalWrite(gpio_pin_##_role + _offset, LOW); \ + if (_offset == 0) { \ + LOG_INFO("GPIO: Using pin %d as %s", gpio_pin_##_role, #_role); \ + } else { \ + LOG_INFO("GPIO: Using pin %d+%d as %s", gpio_pin_##_role, _offset, #_role); \ + } \ + } \ + } + +#define GPIO_INIT_PINOUT { \ + if ( \ + gpio_pin_prog_running >= 0 \ + || gpio_pin_stream_online >= 0 \ + || gpio_pin_has_http_clients >= 0 \ + || gpio_pin_workers_busy_at >= 0 \ + ) { \ + LOG_INFO("GPIO: Using wiringPi"); \ + if (wiringPiSetupGpio() < 0) { \ + LOG_PERROR("GPIO: Can't initialize wiringPi"); \ + exit(1); \ + } else { \ + GPIO_INIT_PIN(prog_running, 0); \ + GPIO_INIT_PIN(stream_online, 0); \ + GPIO_INIT_PIN(has_http_clients, 0); \ + GPIO_INIT_PIN(workers_busy_at, 0); \ + } \ + } \ + } + +#define GPIO_SET_STATE(_role, _offset, _state) { \ + if (gpio_pin_##_role >= 0) { \ + if (_offset == 0) { \ + LOG_DEBUG("GPIO: Writing %d to pin %d (%s)", _state, gpio_pin_##_role, #_role); \ + } else { \ + LOG_DEBUG("GPIO: Writing %d to pin %d+%d (%s)", _state, gpio_pin_##_role, _offset, #_role); \ + } \ + digitalWrite(gpio_pin_##_role + _offset, _state); \ + } \ + } + +#define GPIO_SET_LOW(_role) GPIO_SET_STATE(_role, 0, LOW) +#define GPIO_SET_HIGH(_role) GPIO_SET_STATE(_role, 0, HIGH) + +#define GPIO_SET_LOW_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, LOW) +#define GPIO_SET_HIGH_AT(_role, _offset) GPIO_SET_STATE(_role, _offset, HIGH) diff --git a/src/http/server.c b/src/http/server.c index 54f1fed..b9b737a 100644 --- a/src/http/server.c +++ b/src/http/server.c @@ -53,6 +53,9 @@ #include "../logging.h" #include "../encoder.h" #include "../stream.h" +#ifdef WITH_GPIO +# include "../gpio.h" +#endif #include "blank.h" #include "base64.h" @@ -564,8 +567,14 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server } server->run->stream_clients_count += 1; - if (server->slowdown && server->run->stream_clients_count == 1) { - stream_switch_slowdown(server->run->stream, false); + if (server->run->stream_clients_count == 1) { + if (server->slowdown) { + stream_switch_slowdown(server->run->stream, false); + } + +# ifdef WITH_GPIO + GPIO_SET_HIGH(has_http_clients); +# endif } evhttp_connection_get_peer(conn, &client_addr, &client_port); @@ -717,8 +726,15 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN # define RUN(_next) client->server->run->_next RUN(stream_clients_count) -= 1; - if (client->server->slowdown && RUN(stream_clients_count) <= 0) { - stream_switch_slowdown(RUN(stream), true); + + if (RUN(stream_clients_count) <= 0) { + if (client->server->slowdown) { + stream_switch_slowdown(RUN(stream), true); + } + +# ifdef WITH_GPIO + GPIO_SET_LOW(has_http_clients); +# endif } conn = evhttp_request_get_connection(client->request); diff --git a/src/main.c b/src/main.c index b6979b1..0fcbead 100644 --- a/src/main.c +++ b/src/main.c @@ -32,10 +32,6 @@ #include -#ifdef WITH_WORKERS_GPIO_DEBUG -# include -#endif - #include "config.h" #include "tools.h" #include "logging.h" @@ -43,6 +39,9 @@ #include "encoder.h" #include "stream.h" #include "http/server.h" +#ifdef WITH_GPIO +# include "gpio.h" +#endif static const char _SHORT_OPTS[] = "d:i:x:y:m:a:f:z:ntb:w:q:c:s:p:u:ro:k:e:lhv"; @@ -93,6 +92,13 @@ static const struct option _LONG_OPTS[] = { {"fake-height", required_argument, NULL, 3004}, {"server-timeout", required_argument, NULL, 3005}, +#ifdef WITH_GPIO + {"gpio-prog-running", required_argument, NULL, 4000}, + {"gpio-stream-online", required_argument, NULL, 4001}, + {"gpio-has-http-clients", required_argument, NULL, 4002}, + {"gpio-workers-busy-at", required_argument, NULL, 4003}, +#endif + {"perf", no_argument, NULL, 5000}, {"verbose", no_argument, NULL, 5001}, {"debug", no_argument, NULL, 5002}, @@ -183,6 +189,15 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s printf(" --fake-width ─────────── Override image width for /state. Default: disabled.\n\n"); printf(" --fake-height ────────── Override image height for /state. Default: disabled.\n\n"); printf(" --server-timeout ─ Timeout for client connections. Default: %u.\n\n", server->timeout); +#ifdef WITH_GPIO + printf("GPIO options:\n"); + printf("═════════════\n"); + printf(" --gpio-prog-running ───── Set 1 on GPIO pin while uStreamer is running. Default: disabled.\n\n"); + printf(" --gpio-stream-online ──── Set 1 while streaming. Default: disabled\n\n"); + printf(" --gpio-has-http-clients ─ Set 1 while stream has at least one client. Default: disabled.\n\n"); + printf(" --gpio-workers-busy-at ── Set 1 on (pin + N) while worker with number N has a job.\n"); + printf(" The workers numbering starts from zero. Default: disabled\n\n"); +#endif printf("Misc options:\n"); printf("═════════════\n"); printf(" --log-level ─ Verbosity level of messages from 0 (info) to 3 (debug).\n"); @@ -299,6 +314,13 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e case 3004: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200); case 3005: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60); +# ifdef WITH_GPIO + case 4000: OPT_UNSIGNED(gpio_pin_prog_running, "--gpio-prog-running", 0, 256); + case 4001: OPT_UNSIGNED(gpio_pin_stream_online, "--gpio-stream-online", 0, 256); + case 4002: OPT_UNSIGNED(gpio_pin_has_http_clients, "--gpio-has-http-clients", 0, 256); + case 4003: OPT_UNSIGNED(gpio_pin_workers_busy_at, "--gpio-workers-busy-at", 0, 256); +# endif + case 5000: OPT_SET(log_level, LOG_LEVEL_PERF); case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE); case 5002: OPT_SET(log_level, LOG_LEVEL_DEBUG); @@ -381,13 +403,8 @@ int main(int argc, char *argv[]) { LOGGING_INIT; -# ifdef WITH_WORKERS_GPIO_DEBUG - if (wiringPiSetupGpio() < 0) { - LOG_PERROR("Can't initialize wiringPi GPIO"); - return 1; - } else { - LOG_INFO("Using wiringPi to debug using GPIO"); - } +# ifdef WITH_GPIO + GPIO_INIT; # endif dev = device_init(); @@ -396,6 +413,10 @@ int main(int argc, char *argv[]) { server = http_server_init(stream); if ((exit_code = _parse_options(argc, argv, dev, encoder, server)) == 0) { +# ifdef WITH_GPIO + GPIO_INIT_PINOUT; +# endif + _install_signal_handlers(); pthread_t stream_loop_tid; @@ -407,6 +428,10 @@ int main(int argc, char *argv[]) { _ctx = &ctx; if ((exit_code = http_server_listen(server)) == 0) { +# ifdef WITH_GPIO + GPIO_SET_HIGH(prog_running); +# endif + A_THREAD_CREATE(&stream_loop_tid, _stream_loop_thread, NULL); A_THREAD_CREATE(&server_loop_tid, _server_loop_thread, NULL); A_THREAD_JOIN(server_loop_tid); @@ -419,6 +444,10 @@ int main(int argc, char *argv[]) { encoder_destroy(encoder); device_destroy(dev); +# ifdef WITH_GPIO + GPIO_SET_LOW(prog_running); +# endif + LOGGING_DESTROY; return (exit_code < 0 ? 1 : 0); } diff --git a/src/stream.c b/src/stream.c index 8236194..45e0d6e 100644 --- a/src/stream.c +++ b/src/stream.c @@ -38,12 +38,8 @@ #include "xioctl.h" #include "device.h" #include "encoder.h" - -#ifdef WITH_WORKERS_GPIO_DEBUG -# include -# ifndef WORKERS_GPIO_DEBUG_START_PIN -# define WORKERS_GPIO_DEBUG_START_PIN 5 -# endif +#ifdef WITH_GPIO +# include "gpio.h" #endif @@ -99,7 +95,7 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index, static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream); static void _workers_pool_destroy(struct _workers_pool_t *pool); -static void *__worker_thread(void *v_worker); +static void *_worker_thread(void *v_worker); static struct _worker_t *_workers_pool_wait(struct _workers_pool_t *pool); static void _workers_pool_assign(struct _workers_pool_t *pool, struct _worker_t *ready_worker, unsigned buf_index); @@ -187,11 +183,16 @@ void stream_loop(struct stream_t *stream) { } } else if (selected == 0) { +# ifdef WITH_GPIO + GPIO_SET_LOW(stream_online); +# endif + if (stream->dev->persistent) { if (!persistent_timeout_reported) { LOG_ERROR("Mainloop select() timeout, polling ...") persistent_timeout_reported = true; } + continue; } else { LOG_ERROR("Mainloop select() timeout"); @@ -204,6 +205,10 @@ void stream_loop(struct stream_t *stream) { if (has_read) { LOG_DEBUG("Frame is ready"); +# ifdef WITH_GPIO + GPIO_SET_HIGH(stream_online); +# endif + int buf_index; long double now = get_now_monotonic(); long long now_second = floor_ms(now); @@ -283,6 +288,10 @@ void stream_loop(struct stream_t *stream) { _workers_pool_destroy(pool); device_switch_capturing(stream->dev, false); device_close(stream->dev); + +# ifdef WITH_GPIO + GPIO_SET_LOW(stream_online); +# endif } } @@ -389,7 +398,7 @@ static struct _workers_pool_t *_workers_pool_init(struct stream_t *stream) { WORKER(dev) = stream->dev; WORKER(encoder) = stream->encoder; - A_THREAD_CREATE(&WORKER(tid), __worker_thread, (void *)&(pool->workers[number])); + A_THREAD_CREATE(&WORKER(tid), _worker_thread, (void *)&(pool->workers[number])); pool->free_workers += 1; @@ -424,25 +433,21 @@ static void _workers_pool_destroy(struct _workers_pool_t *pool) { free(pool); } -static void *__worker_thread(void *v_worker) { +static void *_worker_thread(void *v_worker) { struct _worker_t *worker = (struct _worker_t *)v_worker; LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number); -# ifdef WITH_WORKERS_GPIO_DEBUG -# define WORKER_GPIO_DEBUG_BUSY digitalWrite(WORKERS_GPIO_DEBUG_START_PIN + worker->number, HIGH) -# define WORKER_GPIO_DEBUG_FREE digitalWrite(WORKERS_GPIO_DEBUG_START_PIN + worker->number, LOW) - pinMode(WORKERS_GPIO_DEBUG_START_PIN + worker->number, OUTPUT); - WORKER_GPIO_DEBUG_FREE; -# else -# define WORKER_GPIO_DEBUG_BUSY -# define WORKER_GPIO_DEBUG_FREE +# ifdef WITH_GPIO + GPIO_INIT_PIN(workers_busy_at, worker->number); # endif while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) { LOG_DEBUG("Worker %u waiting for a new job ...", worker->number); - WORKER_GPIO_DEBUG_FREE; +# ifdef WITH_GPIO + GPIO_SET_LOW_AT(workers_busy_at, worker->number); +# endif A_MUTEX_LOCK(&worker->has_job_mutex); A_COND_WAIT_TRUE(atomic_load(&worker->has_job), &worker->has_job_cond, &worker->has_job_mutex); @@ -453,7 +458,9 @@ static void *__worker_thread(void *v_worker) { LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index); - WORKER_GPIO_DEBUG_BUSY; +# ifdef WITH_GPIO + GPIO_SET_HIGH_AT(workers_busy_at, worker->number); +# endif if (encoder_compress_buffer(worker->encoder, worker->dev, worker->number, worker->buf_index) < 0) { worker->job_failed = false; @@ -482,7 +489,9 @@ static void *__worker_thread(void *v_worker) { } LOG_DEBUG("Bye-bye (worker %u)", worker->number); - WORKER_GPIO_DEBUG_FREE; +# ifdef WITH_GPIO + GPIO_SET_LOW_AT(workers_busy_at, worker->number); +# endif return NULL; }