Compare commits

...

17 Commits
v0.10 ... v0.12

Author SHA1 Message Date
Devaev Maxim
05b18fb68d Bump version: 0.11 → 0.12 2018-09-30 03:17:57 +03:00
Maxim Devaev
32120ae12d Update README.md 2018-09-30 03:17:11 +03:00
Maxim Devaev
386396dc84 Update README.md 2018-09-30 03:11:05 +03:00
Devaev Maxim
3c086c2129 refactoring 2018-09-30 02:52:17 +03:00
Devaev Maxim
fb9a8e31b1 --drop-same-frames to reduce the outgoing traffic 2018-09-30 02:49:11 +03:00
Devaev Maxim
0c192ddcfb Bump version: 0.10 → 0.11 2018-09-29 07:33:24 +03:00
Devaev Maxim
38b4246b9b refactoring 2018-09-29 07:33:11 +03:00
Maxim Devaev
a46ca0f702 Update README.md 2018-09-29 03:17:37 +03:00
Maxim Devaev
ddf290e811 Update README.md 2018-09-29 03:04:38 +03:00
Maxim Devaev
98b37cb48f Update README.md 2018-09-29 03:00:34 +03:00
Maxim Devaev
d7ab090a69 Update README.md 2018-09-28 20:29:42 +03:00
Maxim Devaev
1b160eb177 Update README.md 2018-09-28 20:18:20 +03:00
Maxim Devaev
fad5bec927 Update README.md 2018-09-28 19:32:27 +03:00
Maxim Devaev
ac036a04b5 Update README.md 2018-09-28 18:56:28 +03:00
Maxim Devaev
cf5d4b1d2a Update README.md 2018-09-28 18:52:58 +03:00
Maxim Devaev
102edd6880 Update README.md 2018-09-28 18:48:22 +03:00
Maxim Devaev
1e87410035 Create README.md 2018-09-28 18:06:05 +03:00
11 changed files with 192 additions and 56 deletions

View File

@@ -1,7 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 0.10
current_version = 0.12
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
serialize =
{major}.{minor}

View File

@@ -3,7 +3,7 @@
pkgname=ustreamer
pkgver=0.10
pkgver=0.12
pkgrel=1
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
url="https://github.com/pi-kvm/ustreamer"

79
README.md Normal file
View File

@@ -0,0 +1,79 @@
# µStreamer
µStreamer - это маленький и очень быстрый сервер, который позволяет организовать трансляцию видео в формате [MJPG](https://en.wikipedia.org/wiki/Motion_JPEG) с любого устройства V4L2 в сеть. Этот формат нативно поддерживается всеми современными браузерами и большинством приложений для просмотра видео (mplayer, VLC и так далее). µStreamer был разработан в рамках проекта [Pi-KVM](https://github.com/pi-kvm) специально для стриминга с устройств видеозахвата [VGA](https://www.amazon.com/dp/B0126O0RDC) и [HDMI](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/) с максимально возможным разрешением и FPS, которые только позволяет железо.
Функционально µStreamer очень похож на [mjpg-streamer](https://github.com/jacksonliam/mjpg-streamer) при использовании им плагинов ```input_uvc.so``` и ```output_http.so```, однако имеет ряд серьезных отличий. Основные приведены в этой таблице:
| **Фича** | **µStreamer** | **mjpg-streamer** |
|----------|---------------|-------------------|
| Многопоточное кодирование JPEG | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Аппаратное кодирование с помощью [OpenMAX IL](https://www.khronos.org/openmaxil) на Raspberry Pi | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поведение при физическом отключении устройства<br>от сервера во время работы | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Транслирует черный экран<br>с надписью ```NO SIGNAL```,<br>пока устройство не будет подключено снова | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Необратимо зависает <sup>1</sup> |
| Поддержка [DV-таймингов](https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/dv-timings.html) - возможности изменения <br>параметров разрешения трансляции на лету<br>по сигналу источника (устройства видеозахвата) | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>1</sup> |
| Возможность пропуска фреймов при передаче<br>статического изображения по HTTP<br>для экономии трафика | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть <sup>2</sup> | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Дебаг-логи без перекомпиляции,<br>логгирование статистики производительности,<br>возможность получения параметров<br>трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет |
| Поддерживаемые входные форматы устройств | ![#ffaa00](https://placehold.it/15/ffaa00/000000?text=+) YUYV, UYVY,<br>RGB565, ~~MJPG~~ <sup>3</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) YUYV, UYVY,<br>RGB565, MJPG |
| Поддержка контролов веб-камер (фокус,<br> движение сервами) и всяких настроек,<br> типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
| Возможность сервить файлы встроенным<br>HTTP-сервером, настройки авторизации | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет <sup>4</sup> | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть |
Сносочки:
* ```1``` Для mjpg-streamer существует [мой патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), предотвращающий зависание при отключении устройства и добавляющий поддержку DV-таймингов, однако трансляция при этом все равно прерывается. В данный момент этот патч не принят в апстрим, и я даже не гарантирую его стопроцентную работоспособность. Код mjpg-streamer очень плохо структурирован и чрезвычайно запутан, и я мог что-то упустить. Собственно, это одна из причин, почему µStreamer написан с нуля.
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако увеличивает загрузку процессора и добавляет небольшую задержку. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
* ```3``` Поскольку µStreamer писался в первую очередь для устройств видеозахвата, в нем реализованы только те форматы, которые для них были нужны. MJPG в контексте входных данных означает, что устройство умеет самостоятельно сжимать картинку в JPEG и отдавать ее программе, что позволяет значительно снизить загрузку процессора и избавить его от необходимости кодировать картинку софтом. Этот формат поддерживается большинством веб-камер, но не поддерживается ни одним из встреченных мной устройств видеозахвата; его не умеет ни [Auvidea B101](https://auvidea.com/b101-hdmi-to-csi-2-bridge-15-pin-fpc/), ни [EasyCap UTV 007](https://www.amazon.com/dp/B0126O0RDC). Нет никаких технических сложностей добавить поддержку аппаратного MJPG источника, но у меня просто пока не дошли до этого руки.
* ```4``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
-----
# TL;DR
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
-----
# Сборка
Для сборки вам понадобятся ```make```, ```gcc```, ```libevent``` с поддержкой ```pthreads``` и ```libjpeg8```/```libjpeg-turbo```.
На Raspberry Pi програма автоматически собирается с поддержкой OpenMAX, если обнаружит нужные хедеры в ```/opt/vc/include```.
```
$ git clone --depth=1 https://github.com/pi-kvm/ustreamer
$ cd ustreamer
$ make
$ ./ustreamer --help
```
Для Arch Linux в AUR есть готовый пакет: https://aur.archlinux.org/packages/ustreamer
-----
# Использование
Будучи запущенным без аргументов, ```ustremaer``` попробует открыть устройство ```/dev/video0``` с разрешением 640x480 и начать трансляцию на ```http://localhost:8080```. Это поведение может быть изменено с помощью опций ```--device```, ```--host``` и ```--port```. Пример вещания на всю сеть по 80 порту:
```
# ./ustreamer --device=/dev/video1 --host=0.0.0.0 --port=80
```
Рекомендуемый способ запуска µStreamer для работы с [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) на Raspberry Pi:
```bash
$ ./ustreamer \
--format=uyvy \ # Настройка входного формата устройства
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
--dv-timings # Включение DV-таймингов
```
За полным списком опций обращайтесь ко встроенной справке: ```ustreamer --help```.
-----
# Лицензия
Copyright (C) 2018 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
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/.

View File

@@ -21,4 +21,4 @@
#pragma once
#define VERSION "0.10"
#define VERSION "0.12"

View File

@@ -39,7 +39,7 @@
static const struct {
const char *name;
const v4l2_std_id standard;
} STANDARDS[] = {
} _STANDARDS[] = {
{"UNKNOWN", V4L2_STD_UNKNOWN},
{"PAL", V4L2_STD_PAL},
{"NTSC", V4L2_STD_NTSC},
@@ -49,7 +49,7 @@ static const struct {
static const struct {
const char *name;
const unsigned format;
} FORMATS[] = {
} _FORMATS[] = {
{"YUYV", V4L2_PIX_FMT_YUYV},
{"UYVY", V4L2_PIX_FMT_UYVY},
{"RGB565", V4L2_PIX_FMT_RGB565},
@@ -96,18 +96,18 @@ void device_destroy(struct device_t *dev) {
}
int device_parse_format(const char *const str) {
for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) {
if (!strcasecmp(str, FORMATS[index].name)) {
return FORMATS[index].format;
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
if (!strcasecmp(str, _FORMATS[index].name)) {
return _FORMATS[index].format;
}
}
return FORMAT_UNKNOWN;
}
v4l2_std_id device_parse_standard(const char *const str) {
for (unsigned index = 1; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) {
if (!strcasecmp(str, STANDARDS[index].name)) {
return STANDARDS[index].standard;
for (unsigned index = 1; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
if (!strcasecmp(str, _STANDARDS[index].name)) {
return _STANDARDS[index].standard;
}
}
return STANDARD_UNKNOWN;
@@ -434,19 +434,19 @@ static const char *_format_to_string_auto(char *buf, const size_t size, const un
}
static const char *_format_to_string_null(const unsigned format) {
for (unsigned index = 0; index < sizeof(FORMATS) / sizeof(FORMATS[0]); ++index) {
if (format == FORMATS[index].format) {
return FORMATS[index].name;
for (unsigned index = 0; index < sizeof(_FORMATS) / sizeof(_FORMATS[0]); ++index) {
if (format == _FORMATS[index].format) {
return _FORMATS[index].name;
}
}
return NULL;
}
static const char *_standard_to_string(v4l2_std_id standard) {
for (unsigned index = 0; index < sizeof(STANDARDS) / sizeof(STANDARDS[0]); ++index) {
if (standard == STANDARDS[index].standard) {
return STANDARDS[index].name;
for (unsigned index = 0; index < sizeof(_STANDARDS) / sizeof(_STANDARDS[0]); ++index) {
if (standard == _STANDARDS[index].standard) {
return _STANDARDS[index].name;
}
}
return STANDARDS[0].name;
return _STANDARDS[0].name;
}

View File

@@ -37,7 +37,7 @@
static const struct {
const char *name;
const enum encoder_type_t type;
} ENCODER_TYPES[] = {
} _ENCODER_TYPES[] = {
{"CPU", ENCODER_TYPE_CPU},
# ifdef OMX_ENCODER
{"OMX", ENCODER_TYPE_OMX},
@@ -91,9 +91,9 @@ void encoder_destroy(struct encoder_t *encoder) {
}
enum encoder_type_t encoder_parse_type(const char *const str) {
for (unsigned index = 0; index < sizeof(ENCODER_TYPES) / sizeof(ENCODER_TYPES[0]); ++index) {
if (!strcasecmp(str, ENCODER_TYPES[index].name)) {
return ENCODER_TYPES[index].type;
for (unsigned index = 0; index < sizeof(_ENCODER_TYPES) / sizeof(_ENCODER_TYPES[0]); ++index) {
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
return _ENCODER_TYPES[index].type;
}
}
return ENCODER_TYPE_UNKNOWN;

View File

@@ -52,24 +52,24 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
static void _http_exposed_refresh(int fd, short event, void *v_server);
static void _http_queue_send_stream(struct http_server_t *server);
static void _http_queue_send_stream(struct http_server_t *server, const bool updated);
static void _expose_new_picture(struct http_server_t *server);
static void _expose_blank_picture(struct http_server_t *server);
static bool _expose_new_picture(struct http_server_t *server);
static bool _expose_blank_picture(struct http_server_t *server);
struct http_server_t *http_server_init(struct stream_t *stream) {
struct http_server_runtime_t *run;
struct http_server_t *server;
struct exposed_t *exposed;
struct timeval refresh_interval;
A_CALLOC(exposed, 1);
A_CALLOC(run, 1);
run->stream = stream;
run->exposed = exposed;
run->refresh_interval.tv_sec = 0;
run->refresh_interval.tv_usec = 30000; // ~30 refreshes per second
run->drop_same_frames_blank = 10;
A_CALLOC(server, 1);
server->host = "localhost";
@@ -89,8 +89,11 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)exposed));
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
refresh_interval.tv_sec = 0;
refresh_interval.tv_usec = 30000; // ~30 refreshes per second
assert((run->refresh = event_new(run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
assert(!event_add(run->refresh, &run->refresh_interval));
assert(!event_add(run->refresh, &refresh_interval));
return server;
}
@@ -108,12 +111,16 @@ void http_server_destroy(struct http_server_t *server) {
}
int http_server_listen(struct http_server_t *server) {
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
LOG_DEBUG("Binding HTTP to [%s]:%d ...", server->host, server->port);
evhttp_set_timeout(server->run->http, server->timeout);
if (evhttp_bind_socket(server->run->http, server->host, server->port) != 0) {
LOG_PERROR("Can't listen HTTP on [%s]:%d", server->host, server->port)
return -1;
}
LOG_INFO("Listening HTTP on [%s]:%d", server->host, server->port);
return 0;
}
@@ -229,6 +236,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
client->server = server;
client->request = request;
client->need_initial = true;
client->need_first_frame = true;
if (server->run->stream_clients == NULL) {
server->run->stream_clients = client;
@@ -321,23 +329,26 @@ static void _http_callback_stream_error(UNUSED struct bufferevent *buf_event, UN
free(client);
}
static void _http_queue_send_stream(struct http_server_t *server) {
static void _http_queue_send_stream(struct http_server_t *server, const bool updated) {
struct stream_client_t *client;
struct evhttp_connection *conn;
struct bufferevent *buf_event;
for (client = server->run->stream_clients; client != NULL; client = client->next) {
conn = evhttp_request_get_connection(client->request);
if (conn != NULL) {
if (conn != NULL && (updated || client->need_first_frame)) {
buf_event = evhttp_connection_get_bufferevent(conn);
bufferevent_setcb(buf_event, NULL, _http_callback_stream_write, _http_callback_stream_error, (void *)client);
bufferevent_enable(buf_event, EV_READ|EV_WRITE);
client->need_first_frame = false;
}
}
}
static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_server) {
struct http_server_t *server = (struct http_server_t *)v_server;
bool updated = false;
bool queue_send = false;
#define LOCK_STREAM \
A_PTHREAD_M_LOCK(&server->run->stream->mutex);
@@ -349,41 +360,66 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
LOG_DEBUG("Refreshing HTTP exposed ...");
LOCK_STREAM;
if (server->run->stream->picture.size > 0) { // If online
_expose_new_picture(server);
updated = _expose_new_picture(server);
UNLOCK_STREAM;
} else {
UNLOCK_STREAM;
_expose_blank_picture(server);
updated = _expose_blank_picture(server);
}
_http_queue_send_stream(server);
queue_send = true;
} else if (!server->run->exposed->online) {
LOG_DEBUG("Refreshing HTTP exposed (BLANK) ...");
_http_queue_send_stream(server);
updated = _expose_blank_picture(server);
queue_send = true;
}
if (queue_send) {
_http_queue_send_stream(server, updated);
}
# undef LOCK_STREAM
# undef UNLOCK_STREAM
}
void _expose_new_picture(struct http_server_t *server) {
static bool _expose_new_picture(struct http_server_t *server) {
assert(server->run->stream->picture.size > 0);
server->run->exposed->fps = server->run->stream->fps;
# define MEM_STREAM_TO_EXPOSED \
server->run->exposed->picture.data, server->run->stream->picture.data, \
server->run->stream->picture.size * sizeof(*server->run->exposed->picture.data)
if (server->drop_same_frames) {
if (
server->run->exposed->online
&& server->run->exposed->dropped < server->drop_same_frames
&& server->run->exposed->picture.size == server->run->stream->picture.size
&& !memcmp(MEM_STREAM_TO_EXPOSED)
) {
LOG_PERF("HTTP: dropped same frame number %u", server->run->exposed->dropped);
++server->run->exposed->dropped;
return false; // Not updated
}
}
if (server->run->exposed->picture.allocated < server->run->stream->picture.allocated) {
A_REALLOC(server->run->exposed->picture.data, server->run->stream->picture.allocated);
server->run->exposed->picture.allocated = server->run->stream->picture.allocated;
}
memcpy(
server->run->exposed->picture.data, server->run->stream->picture.data,
server->run->stream->picture.size * sizeof(*server->run->exposed->picture.data)
);
memcpy(MEM_STREAM_TO_EXPOSED);
# undef MEM_STREAM_TO_EXPOSED
server->run->exposed->picture.size = server->run->stream->picture.size;
server->run->exposed->width = server->run->stream->width;
server->run->exposed->height = server->run->stream->height;
server->run->exposed->fps = server->run->stream->fps;
server->run->exposed->online = true;
server->run->exposed->dropped = 0;
return true; // Updated
}
void _expose_blank_picture(struct http_server_t *server) {
static bool _expose_blank_picture(struct http_server_t *server) {
if (server->run->exposed->online || server->run->exposed->picture.size == 0) {
if (server->run->exposed->picture.allocated < BLANK_JPG_SIZE) {
A_REALLOC(server->run->exposed->picture.data, BLANK_JPG_SIZE);
@@ -400,5 +436,16 @@ void _expose_blank_picture(struct http_server_t *server) {
server->run->exposed->height = BLANK_JPG_HEIGHT;
server->run->exposed->fps = 0;
server->run->exposed->online = false;
goto updated;
}
if (server->run->exposed->dropped < server->run->drop_same_frames_blank) {
LOG_PERF("HTTP: dropped same frame (BLANK) number %u", server->run->exposed->dropped);
++server->run->exposed->dropped;
return false; // Not updated
}
updated:
server->run->exposed->dropped = 0;
return true; // Updated
}

View File

@@ -20,7 +20,6 @@
#include <stdbool.h>
#include <time.h>
#include <event2/event.h>
#include <event2/http.h>
@@ -33,6 +32,7 @@ struct stream_client_t {
struct http_server_t *server;
struct evhttp_request *request;
bool need_initial;
bool need_first_frame;
struct stream_client_t *prev;
struct stream_client_t *next;
@@ -44,6 +44,7 @@ struct exposed_t {
unsigned height;
unsigned fps;
bool online;
unsigned dropped;
};
struct http_server_runtime_t {
@@ -52,14 +53,14 @@ struct http_server_runtime_t {
struct event *refresh;
struct stream_t *stream;
struct exposed_t *exposed;
struct timeval refresh_interval;
struct stream_client_t *stream_clients;
unsigned drop_same_frames_blank;
};
struct http_server_t {
char *host;
unsigned port;
unsigned drop_same_frames;
unsigned fake_width;
unsigned fake_height;
unsigned timeout;

View File

@@ -38,10 +38,7 @@
#include "encoder.h"
#define JPEG_OUTPUT_BUFFER_SIZE 4096
struct mjpg_destination_mgr {
struct _mjpg_destination_mgr {
struct jpeg_destination_mgr mgr; // Default manager
JOCTET *buffer; // Start of buffer
unsigned char *outbuffer_cursor;
@@ -114,15 +111,15 @@ void jpeg_encoder_compress_buffer(struct device_t *dev, const unsigned index, co
}
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, unsigned long *written) {
struct mjpg_destination_mgr *dest;
struct _mjpg_destination_mgr *dest;
if (jpeg->dest == NULL) {
assert((jpeg->dest = (struct jpeg_destination_mgr *)(*jpeg->mem->alloc_small)(
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct mjpg_destination_mgr)
(j_common_ptr) jpeg, JPOOL_PERMANENT, sizeof(struct _mjpg_destination_mgr)
)));
}
dest = (struct mjpg_destination_mgr *) jpeg->dest;
dest = (struct _mjpg_destination_mgr *) 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;
@@ -222,8 +219,10 @@ static void _jpeg_write_scanlines_rgb565(struct jpeg_compress_struct *jpeg,
}
}
#define JPEG_OUTPUT_BUFFER_SIZE 4096
static void _jpeg_init_destination(j_compress_ptr jpeg) {
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
// Allocate the output buffer - it will be released when done with image
assert((dest->buffer = (JOCTET *)(*jpeg->mem->alloc_small)(
@@ -237,7 +236,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
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
memcpy(dest->outbuffer_cursor, dest->buffer, JPEG_OUTPUT_BUFFER_SIZE);
dest->outbuffer_cursor += JPEG_OUTPUT_BUFFER_SIZE;
@@ -253,7 +252,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
struct mjpg_destination_mgr *dest = (struct mjpg_destination_mgr *) jpeg->dest;
struct _mjpg_destination_mgr *dest = (struct _mjpg_destination_mgr *) jpeg->dest;
size_t data_count = JPEG_OUTPUT_BUFFER_SIZE - dest->mgr.free_in_buffer;
// Write any data remaining in the buffer
@@ -261,3 +260,5 @@ static void _jpeg_term_destination(j_compress_ptr jpeg) {
dest->outbuffer_cursor += data_count;
*dest->written += data_count;
}
#undef JPEG_OUTPUT_BUFFER_SIZE

View File

@@ -40,7 +40,7 @@
#include "http.h"
static const char _short_opts[] = "d:x:y:f:a:e:z:tn:w:q:c:s:p:h";
static const char _short_opts[] = "d:x:y:f:a:e:z:tn:w:q:c:s:p:r:h";
static const struct option _long_opts[] = {
{"device", required_argument, NULL, 'd'},
{"width", required_argument, NULL, 'x'},
@@ -62,6 +62,7 @@ static const struct option _long_opts[] = {
{"host", required_argument, NULL, 's'},
{"port", required_argument, NULL, 'p'},
{"drop-same-frames", required_argument, NULL, 'r'},
{"fake-width", required_argument, NULL, 2000},
{"fake-height", required_argument, NULL, 2001},
{"server-timeout", required_argument, NULL, 2002},
@@ -111,6 +112,10 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf("--------------------\n");
printf(" --host <address> -- Listen on Hostname or IP. Default: %s\n\n", server->host);
printf(" --port <N> -- Bind to this TCP port. Default: %d\n\n", server->port);
printf(" --drop-same-frames <N> -- Don't send same frames to clients, but no more than specified number.\n");
printf(" It can significantly reduce the outgoing traffic, but will increase\n");
printf(" the CPU loading. Don't use this option with analog signal sources\n");
printf(" or webcams, it's useless. Default: disabled.\n\n");
printf(" --fake-width <N> -- Override image width for /ping. Default: disabled\n\n");
printf(" --fake-height <N> -- Override image height for /ping. Default: disabled.\n\n");
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %d\n\n", server->timeout);
@@ -136,7 +141,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
# define OPT_UNSIGNED(_dest, _name, _min, _max) \
{ errno = 0; int _tmp = strtol(optarg, NULL, 0); \
if (errno || _tmp < _min || _tmp > _max) \
{ printf("Invalid value for '%s=%u'; minimal=%u; maximum=%u\n", _name, _tmp, _min, _max); return -1; } \
{ printf("Invalid value for '%s=%u'; min=%u; max=%u\n", _name, _tmp, _min, _max); return -1; } \
_dest = _tmp; break; }
# define OPT_PARSE(_dest, _func, _invalid, _name) \
@@ -173,6 +178,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
case 's': server->host = optarg; break;
case 'p': OPT_UNSIGNED(server->port, "--port", 1, 65535);
case 'r': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
case 2000: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
case 2001: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
case 2002: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);

View File

@@ -40,6 +40,8 @@
int component_enable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
int component_disable_port(OMX_HANDLETYPE *component, const OMX_U32 port);
int component_get_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef, const OMX_U32 port);
int component_set_portdef(OMX_HANDLETYPE *component, OMX_PARAM_PORTDEFINITIONTYPE *portdef);
int component_set_state(OMX_HANDLETYPE *component, const OMX_STATETYPE state);