mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-20 16:56:30 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c30dea20a5 | ||
|
|
b31450ba41 | ||
|
|
c05457ce2f | ||
|
|
9e63076ec5 | ||
|
|
92844fc3db | ||
|
|
7bb9434850 | ||
|
|
3104a00913 | ||
|
|
28c51f3f2f | ||
|
|
5c035f21c8 | ||
|
|
52ecaf7fd3 | ||
|
|
aa007c676f | ||
|
|
1f0b49e5fe | ||
|
|
d979209096 | ||
|
|
154d8e4c2b | ||
|
|
27d42d2545 | ||
|
|
142670c374 | ||
|
|
2a9c4d7e7a |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 0.56
|
||||
current_version = 0.60
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)(\.(?P<patch>\d+)(\-(?P<release>[a-z]+))?)?
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
2
Makefile
2
Makefile
@@ -7,7 +7,7 @@ CC ?= gcc
|
||||
|
||||
# =====
|
||||
LIBS = -lm -ljpeg -pthread -levent -levent_pthreads -luuid
|
||||
override CFLAGS += -c -std=c99 -Wall -Wextra -D_GNU_SOURCE
|
||||
override CFLAGS += -c -std=c11 -Wall -Wextra -D_GNU_SOURCE
|
||||
SOURCES = $(shell ls src/*.c src/encoders/cpu/*.c src/encoders/hw/*.c)
|
||||
PROG = ustreamer
|
||||
|
||||
|
||||
2
PKGBUILD
2
PKGBUILD
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=0.56
|
||||
pkgver=0.60
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pi-kvm/ustreamer"
|
||||
|
||||
@@ -56,6 +56,8 @@ The recommended way of running µStreamer with [Auvidea B101](https://www.raspbe
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Device input format
|
||||
--encoder=omx \ # Hardware encoding with OpenMAX
|
||||
--workers=3 \ # Maximum workers for OpenMAX
|
||||
--persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected)
|
||||
--dv-timings \ # Use DV-timings
|
||||
--drop-same-frames=30 # Save that traffic
|
||||
```
|
||||
|
||||
@@ -56,6 +56,8 @@ $ ./ustreamer --help
|
||||
$ ./ustreamer \
|
||||
--format=uyvy \ # Настройка входного формата устройства
|
||||
--encoder=omx \ # Использование аппаратного кодирования с помощью OpenMAX
|
||||
--workers=3 \ # Максимум воркеров для OpenMAX
|
||||
--persistent \ # Не переинициализировать устройство при таймауте (например, когда был отключен HDMI-кабель)
|
||||
--dv-timings \ # Включение DV-таймингов
|
||||
--drop-same-frames=30 # Экономим трафик
|
||||
```
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define VERSION "0.56"
|
||||
#define VERSION "0.60"
|
||||
|
||||
79
src/device.c
79
src/device.c
@@ -95,7 +95,7 @@ struct device_t *device_init() {
|
||||
dev->height = 480;
|
||||
dev->format = V4L2_PIX_FMT_YUYV;
|
||||
dev->standard = V4L2_STD_UNKNOWN;
|
||||
dev->n_buffers = max_u(sysconf(_SC_NPROCESSORS_ONLN), 1) + 1;
|
||||
dev->n_buffers = max_u(min_u(sysconf(_SC_NPROCESSORS_ONLN), 4), 1) + 1;
|
||||
dev->n_workers = dev->n_buffers;
|
||||
dev->timeout = 1;
|
||||
dev->error_delay = 1;
|
||||
@@ -177,8 +177,8 @@ void device_close(struct device_t *dev) {
|
||||
for (unsigned index = 0; index < dev->run->n_buffers; ++index) {
|
||||
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
||||
|
||||
if (HW_BUFFER(size) > 0 && HW_BUFFER(data) != MAP_FAILED) {
|
||||
if (munmap(HW_BUFFER(data), HW_BUFFER(size)) < 0) {
|
||||
if (HW_BUFFER(allocated) > 0 && HW_BUFFER(data) != MAP_FAILED) {
|
||||
if (munmap(HW_BUFFER(data), HW_BUFFER(allocated)) < 0) {
|
||||
LOG_PERROR("Can't unmap device buffer %u", index);
|
||||
}
|
||||
}
|
||||
@@ -201,6 +201,77 @@ void device_close(struct device_t *dev) {
|
||||
}
|
||||
}
|
||||
|
||||
int device_switch_capturing(struct device_t *dev, bool enable) {
|
||||
if (enable != dev->run->capturing) {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(%s) ...", (enable ? "VIDIOC_STREAMON" : "VIDIOC_STREAMOFF"));
|
||||
if (xioctl(dev->run->fd, (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
|
||||
LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop"));
|
||||
if (enable) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
dev->run->capturing = enable;
|
||||
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int device_grab_buffer(struct device_t *dev) {
|
||||
struct v4l2_buffer buf_info;
|
||||
|
||||
MEMSET_ZERO(buf_info);
|
||||
buf_info.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_DQBUF, &buf_info) < 0) {
|
||||
LOG_PERROR("Unable to dequeue buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Got a new frame in buffer index=%u; bytesused=%u", buf_info.index, buf_info.bytesused);
|
||||
if (buf_info.index >= dev->run->n_buffers) {
|
||||
LOG_ERROR("Got invalid buffer index=%u; nbuffers=%u", buf_info.index, dev->run->n_buffers);
|
||||
return -1;
|
||||
}
|
||||
|
||||
dev->run->hw_buffers[buf_info.index].used = buf_info.bytesused;
|
||||
memcpy(&dev->run->hw_buffers[buf_info.index].buf_info, &buf_info, sizeof(struct v4l2_buffer));
|
||||
return buf_info.index;
|
||||
}
|
||||
|
||||
int device_release_buffer(struct device_t *dev, unsigned index) {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_QBUF, &dev->run->hw_buffers[index].buf_info) < 0) {
|
||||
LOG_PERROR("Unable to requeue buffer");
|
||||
return -1;
|
||||
}
|
||||
dev->run->hw_buffers[index].used = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int device_consume_event(struct device_t *dev) {
|
||||
struct v4l2_event event;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ...");
|
||||
if (!xioctl(dev->run->fd, VIDIOC_DQEVENT, &event)) {
|
||||
switch (event.type) {
|
||||
case V4L2_EVENT_SOURCE_CHANGE:
|
||||
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
|
||||
return -1;
|
||||
case V4L2_EVENT_EOS:
|
||||
LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
LOG_PERROR("Got some V4L2 device event, but where is it? ");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _device_open_check_cap(struct device_t *dev) {
|
||||
struct v4l2_capability cap;
|
||||
int input = dev->input; // Needs pointer to int for ioctl()
|
||||
@@ -397,7 +468,7 @@ static int _device_open_mmap(struct device_t *dev) {
|
||||
LOG_PERROR("Can't map device buffer %u", dev->run->n_buffers);
|
||||
return -1;
|
||||
}
|
||||
HW_BUFFER(size) = buf_info.length;
|
||||
HW_BUFFER(allocated) = buf_info.length;
|
||||
|
||||
# undef HW_BUFFER
|
||||
}
|
||||
|
||||
13
src/device.h
13
src/device.h
@@ -42,13 +42,15 @@
|
||||
|
||||
|
||||
struct hw_buffer_t {
|
||||
unsigned char *data;
|
||||
size_t size;
|
||||
unsigned char *data;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
struct v4l2_buffer buf_info;
|
||||
};
|
||||
|
||||
struct picture_t {
|
||||
unsigned char *data;
|
||||
size_t size;
|
||||
size_t used;
|
||||
size_t allocated;
|
||||
long double grab_time;
|
||||
long double encode_begin_time;
|
||||
@@ -116,3 +118,8 @@ v4l2_std_id device_parse_standard(const char *str);
|
||||
|
||||
int device_open(struct device_t *dev);
|
||||
void device_close(struct device_t *dev);
|
||||
|
||||
int device_switch_capturing(struct device_t *dev, bool enable);
|
||||
int device_grab_buffer(struct device_t *dev);
|
||||
int device_release_buffer(struct device_t *dev, unsigned index);
|
||||
int device_consume_event(struct device_t *dev);
|
||||
|
||||
@@ -81,8 +81,8 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
|
||||
|
||||
# define PICTURE(_next) dev->run->pictures[index]._next
|
||||
|
||||
PICTURE(size) = 0;
|
||||
_jpeg_set_dest_picture(&jpeg, PICTURE(data), &PICTURE(size));
|
||||
PICTURE(used) = 0;
|
||||
_jpeg_set_dest_picture(&jpeg, PICTURE(data), &PICTURE(used));
|
||||
|
||||
jpeg.image_width = dev->run->width;
|
||||
jpeg.image_height = dev->run->height;
|
||||
@@ -112,8 +112,8 @@ void cpu_encoder_compress_buffer(struct device_t *dev, unsigned index, unsigned
|
||||
// https://stackoverflow.com/questions/19857766/error-handling-in-libjpeg
|
||||
jpeg_finish_compress(&jpeg);
|
||||
jpeg_destroy_compress(&jpeg);
|
||||
assert(PICTURE(size) > 0);
|
||||
assert(PICTURE(size) <= dev->run->max_picture_size);
|
||||
assert(PICTURE(used) > 0);
|
||||
assert(PICTURE(used) <= dev->run->max_picture_size);
|
||||
}
|
||||
|
||||
static void _jpeg_set_dest_picture(j_compress_ptr jpeg, unsigned char *picture, size_t *written) {
|
||||
|
||||
@@ -69,8 +69,8 @@ void hw_encoder_compress_buffer(struct device_t *dev, unsigned index) {
|
||||
# define PICTURE(_next) dev->run->pictures[index]._next
|
||||
# define HW_BUFFER(_next) dev->run->hw_buffers[index]._next
|
||||
|
||||
assert(PICTURE(allocated) >= HW_BUFFER(size) + sizeof(HUFFMAN_TABLE));
|
||||
PICTURE(size) = _memcpy_with_huffman(PICTURE(data), HW_BUFFER(data), HW_BUFFER(size));
|
||||
assert(PICTURE(allocated) >= HW_BUFFER(used) + sizeof(HUFFMAN_TABLE));
|
||||
PICTURE(used) = _memcpy_with_huffman(PICTURE(data), HW_BUFFER(data), HW_BUFFER(used));
|
||||
|
||||
# undef HW_BUFFER
|
||||
# undef PICTURE
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
#include "encoder.h"
|
||||
|
||||
|
||||
#define INPUT_PORT 340
|
||||
#define OUTPUT_PORT 341
|
||||
static const OMX_U32 _INPUT_PORT = 340;
|
||||
static const OMX_U32 _OUTPUT_PORT = 341;
|
||||
|
||||
|
||||
static int _i_omx = 0;
|
||||
@@ -178,7 +178,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
# define OUT(_next) omx->output_buffer->_next
|
||||
|
||||
OMX_ERRORTYPE error;
|
||||
size_t slice_size = IN(nAllocLen);
|
||||
size_t slice_size = (IN(nAllocLen) < HW_BUFFER(used) ? IN(nAllocLen) : HW_BUFFER(used));
|
||||
size_t pos = 0;
|
||||
|
||||
if ((error = OMX_FillThisBuffer(omx->encoder, omx->output_buffer)) != OMX_ErrorNone) {
|
||||
@@ -186,7 +186,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
return -1;
|
||||
}
|
||||
|
||||
PICTURE(size) = 0;
|
||||
PICTURE(used) = 0;
|
||||
omx->output_available = false;
|
||||
omx->input_required = true;
|
||||
|
||||
@@ -198,9 +198,9 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
if (omx->output_available) {
|
||||
omx->output_available = false;
|
||||
|
||||
assert(PICTURE(size) + OUT(nFilledLen) <= dev->run->max_picture_size);
|
||||
memcpy(PICTURE(data) + PICTURE(size), OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
|
||||
PICTURE(size) += OUT(nFilledLen);
|
||||
assert(PICTURE(used) + OUT(nFilledLen) <= dev->run->max_picture_size);
|
||||
memcpy(PICTURE(data) + PICTURE(used), OUT(pBuffer) + OUT(nOffset), OUT(nFilledLen));
|
||||
PICTURE(used) += OUT(nFilledLen);
|
||||
|
||||
if (OUT(nFlags) & OMX_BUFFERFLAG_ENDOFFRAME) {
|
||||
OUT(nFlags) = 0;
|
||||
@@ -216,7 +216,7 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
if (omx->input_required) {
|
||||
omx->input_required = false;
|
||||
|
||||
if (pos == HW_BUFFER(size)) {
|
||||
if (pos == HW_BUFFER(used)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -226,8 +226,8 @@ int omx_encoder_compress_buffer(struct omx_encoder_t *omx, struct device_t *dev,
|
||||
|
||||
pos += slice_size;
|
||||
|
||||
if (pos + slice_size > HW_BUFFER(size)) {
|
||||
slice_size = HW_BUFFER(size) - pos;
|
||||
if (pos + slice_size > HW_BUFFER(used)) {
|
||||
slice_size = HW_BUFFER(used) - pos;
|
||||
}
|
||||
|
||||
if ((error = OMX_EmptyThisBuffer(omx->encoder, omx->input_buffer)) != OMX_ErrorNone) {
|
||||
@@ -300,7 +300,7 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
|
||||
|
||||
LOG_DEBUG("Setting up OMX JPEG input port ...");
|
||||
|
||||
if (component_get_portdef(&omx->encoder, &portdef, INPUT_PORT) < 0) {
|
||||
if (component_get_portdef(&omx->encoder, &portdef, _INPUT_PORT) < 0) {
|
||||
LOG_ERROR("... first");
|
||||
return -1;
|
||||
}
|
||||
@@ -338,17 +338,17 @@ static int _omx_setup_input(struct omx_encoder_t *omx, struct device_t *dev) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (component_get_portdef(&omx->encoder, &portdef, INPUT_PORT) < 0) {
|
||||
if (component_get_portdef(&omx->encoder, &portdef, _INPUT_PORT) < 0) {
|
||||
LOG_ERROR("... second");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (component_enable_port(&omx->encoder, INPUT_PORT) < 0) {
|
||||
if (component_enable_port(&omx->encoder, _INPUT_PORT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
omx->i_input_port_enabled = true;
|
||||
|
||||
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->input_buffer, INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
|
||||
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->input_buffer, _INPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't allocate OMX JPEG input buffer");
|
||||
return -1;
|
||||
}
|
||||
@@ -361,7 +361,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
|
||||
|
||||
LOG_DEBUG("Setting up OMX JPEG output port ...");
|
||||
|
||||
if (component_get_portdef(&omx->encoder, &portdef, OUTPUT_PORT) < 0) {
|
||||
if (component_get_portdef(&omx->encoder, &portdef, _OUTPUT_PORT) < 0) {
|
||||
LOG_ERROR("... first");
|
||||
return -1;
|
||||
}
|
||||
@@ -374,7 +374,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (component_get_portdef(&omx->encoder, &portdef, OUTPUT_PORT) < 0) {
|
||||
if (component_get_portdef(&omx->encoder, &portdef, _OUTPUT_PORT) < 0) {
|
||||
LOG_ERROR("... second");
|
||||
return -1;
|
||||
}
|
||||
@@ -395,7 +395,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
|
||||
OMX_PARAM_IJGSCALINGTYPE ijg;
|
||||
|
||||
OMX_INIT_STRUCTURE(ijg);
|
||||
ijg.nPortIndex = OUTPUT_PORT;
|
||||
ijg.nPortIndex = _OUTPUT_PORT;
|
||||
ijg.bEnabled = OMX_TRUE;
|
||||
|
||||
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamBrcmEnableIJGTableScaling, &ijg)) != OMX_ErrorNone) {
|
||||
@@ -408,7 +408,7 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
|
||||
OMX_IMAGE_PARAM_QFACTORTYPE qfactor;
|
||||
|
||||
OMX_INIT_STRUCTURE(qfactor);
|
||||
qfactor.nPortIndex = OUTPUT_PORT;
|
||||
qfactor.nPortIndex = _OUTPUT_PORT;
|
||||
qfactor.nQFactor = quality;
|
||||
|
||||
if ((error = OMX_SetParameter(omx->encoder, OMX_IndexParamQFactor, &qfactor)) != OMX_ErrorNone) {
|
||||
@@ -417,12 +417,12 @@ static int _omx_setup_output(struct omx_encoder_t *omx, unsigned quality) {
|
||||
}
|
||||
}
|
||||
|
||||
if (component_enable_port(&omx->encoder, OUTPUT_PORT) < 0) {
|
||||
if (component_enable_port(&omx->encoder, _OUTPUT_PORT) < 0) {
|
||||
return -1;
|
||||
}
|
||||
omx->i_output_port_enabled = true;
|
||||
|
||||
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->output_buffer, OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
|
||||
if ((error = OMX_AllocateBuffer(omx->encoder, &omx->output_buffer, _OUTPUT_PORT, NULL, portdef.nBufferSize)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't allocate OMX JPEG output buffer");
|
||||
return -1;
|
||||
}
|
||||
@@ -434,23 +434,23 @@ static int _omx_encoder_clear_ports(struct omx_encoder_t *omx) {
|
||||
int retcode = 0;
|
||||
|
||||
if (omx->i_output_port_enabled) {
|
||||
retcode -= component_disable_port(&omx->encoder, OUTPUT_PORT);
|
||||
retcode -= component_disable_port(&omx->encoder, _OUTPUT_PORT);
|
||||
omx->i_output_port_enabled = false;
|
||||
}
|
||||
if (omx->i_input_port_enabled) {
|
||||
retcode -= component_disable_port(&omx->encoder, INPUT_PORT);
|
||||
retcode -= component_disable_port(&omx->encoder, _INPUT_PORT);
|
||||
omx->i_input_port_enabled = false;
|
||||
}
|
||||
|
||||
if (omx->input_buffer) {
|
||||
if ((error = OMX_FreeBuffer(omx->encoder, INPUT_PORT, omx->input_buffer)) != OMX_ErrorNone) {
|
||||
if ((error = OMX_FreeBuffer(omx->encoder, _INPUT_PORT, omx->input_buffer)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't free OMX JPEG input buffer");
|
||||
// retcode -= 1;
|
||||
}
|
||||
omx->input_buffer = NULL;
|
||||
}
|
||||
if (omx->output_buffer) {
|
||||
if ((error = OMX_FreeBuffer(omx->encoder, OUTPUT_PORT, omx->output_buffer)) != OMX_ErrorNone) {
|
||||
if ((error = OMX_FreeBuffer(omx->encoder, _OUTPUT_PORT, omx->output_buffer)) != OMX_ErrorNone) {
|
||||
LOG_OMX_ERROR(error, "Can't free OMX JPEG output buffer");
|
||||
// retcode -= 1;
|
||||
}
|
||||
|
||||
54
src/http.c
54
src/http.c
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
@@ -323,18 +324,22 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
|
||||
assert((buf = evbuffer_new()));
|
||||
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture.data), EXPOSED(picture.size)));
|
||||
assert(!evbuffer_add(buf, (const void *)EXPOSED(picture.data), EXPOSED(picture.used)));
|
||||
|
||||
ADD_HEADER("Access-Control-Allow-Origin:", "*");
|
||||
ADD_HEADER("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
|
||||
ADD_HEADER("Pragma", "no-cache");
|
||||
ADD_HEADER("Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
|
||||
|
||||
# define ADD_TIME_HEADER(_key, _value) \
|
||||
{ sprintf(header_buf, "%.06Lf", _value); ADD_HEADER(_key, header_buf); }
|
||||
# define ADD_TIME_HEADER(_key, _value) { \
|
||||
sprintf(header_buf, "%.06Lf", _value); \
|
||||
ADD_HEADER(_key, header_buf); \
|
||||
}
|
||||
|
||||
# define ADD_UNSIGNED_HEADER(_key, _value) \
|
||||
{ sprintf(header_buf, "%u", _value); ADD_HEADER(_key, header_buf); }
|
||||
# define ADD_UNSIGNED_HEADER(_key, _value) { \
|
||||
sprintf(header_buf, "%u", _value); \
|
||||
ADD_HEADER(_key, header_buf); \
|
||||
}
|
||||
|
||||
ADD_TIME_HEADER("X-Timestamp", get_now_real());
|
||||
|
||||
@@ -415,13 +420,8 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
|
||||
}
|
||||
|
||||
evhttp_connection_get_peer(conn, &client_addr, &client_port);
|
||||
LOG_INFO("HTTP: Registered the new stream client: [%s]:%u; id=%s; advance_headers=%s; dual_final_frames=%s; clients now: %u",
|
||||
client_addr,
|
||||
client_port,
|
||||
client->id,
|
||||
bool_to_string(client->advance_headers),
|
||||
bool_to_string(client->dual_final_frames),
|
||||
server->run->stream_clients_count);
|
||||
LOG_INFO("HTTP: Registered the new stream client: [%s]:%u; id=%s; clients now: %u",
|
||||
client_addr, client_port, client->id, server->run->stream_clients_count);
|
||||
|
||||
buf_event = evhttp_connection_get_bufferevent(conn);
|
||||
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void *)client);
|
||||
@@ -471,8 +471,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
// по тем же причинам, по которым у нас нет Content-Length.
|
||||
|
||||
# define ADD_ADVANCE_HEADERS \
|
||||
{ assert(evbuffer_add_printf(buf, \
|
||||
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real())); }
|
||||
assert(evbuffer_add_printf(buf, \
|
||||
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, get_now_real()))
|
||||
|
||||
if (client->need_initial) {
|
||||
assert(evbuffer_add_printf(buf,
|
||||
@@ -505,7 +505,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
"Content-Length: %zu" RN
|
||||
"X-Timestamp: %.06Lf" RN
|
||||
"%s",
|
||||
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data)),
|
||||
EXPOSED(picture.used) * sizeof(*EXPOSED(picture.data)),
|
||||
get_now_real(), (client->extra_headers ? "" : RN)
|
||||
));
|
||||
if (client->extra_headers) {
|
||||
@@ -541,7 +541,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
|
||||
|
||||
assert(!evbuffer_add(buf,
|
||||
(void *)EXPOSED(picture.data),
|
||||
EXPOSED(picture.size) * sizeof(*EXPOSED(picture.data))
|
||||
EXPOSED(picture.used) * sizeof(*EXPOSED(picture.data))
|
||||
));
|
||||
assert(evbuffer_add_printf(buf, RN "--" BOUNDARY RN));
|
||||
|
||||
@@ -652,13 +652,15 @@ static void _http_exposed_refresh(UNUSED int fd, UNUSED short what, void *v_serv
|
||||
bool stream_updated = false;
|
||||
bool picture_updated = false;
|
||||
|
||||
# define UNLOCK_STREAM \
|
||||
{ server->run->stream->updated = false; A_MUTEX_UNLOCK(&server->run->stream->mutex); }
|
||||
# define UNLOCK_STREAM { \
|
||||
atomic_store(&server->run->stream->updated, false); \
|
||||
A_MUTEX_UNLOCK(&server->run->stream->mutex); \
|
||||
}
|
||||
|
||||
if (server->run->stream->updated) {
|
||||
if (atomic_load(&server->run->stream->updated)) {
|
||||
LOG_DEBUG("Refreshing HTTP exposed ...");
|
||||
A_MUTEX_LOCK(&server->run->stream->mutex);
|
||||
if (server->run->stream->picture.size > 0) { // If online
|
||||
if (server->run->stream->picture.used > 0) { // If online
|
||||
picture_updated = _expose_new_picture(server);
|
||||
UNLOCK_STREAM;
|
||||
} else {
|
||||
@@ -681,19 +683,19 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
||||
# define STREAM(_next) server->run->stream->_next
|
||||
# define EXPOSED(_next) server->run->exposed->_next
|
||||
|
||||
assert(STREAM(picture.size) > 0);
|
||||
assert(STREAM(picture.used) > 0);
|
||||
EXPOSED(captured_fps) = STREAM(captured_fps);
|
||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||
|
||||
# define MEM_STREAM_TO_EXPOSED \
|
||||
EXPOSED(picture.data), STREAM(picture.data), \
|
||||
STREAM(picture.size) * sizeof(*STREAM(picture.data))
|
||||
STREAM(picture.used) * sizeof(*STREAM(picture.data))
|
||||
|
||||
if (server->drop_same_frames) {
|
||||
if (
|
||||
EXPOSED(online)
|
||||
&& EXPOSED(dropped) < server->drop_same_frames
|
||||
&& EXPOSED(picture.size) == STREAM(picture.size)
|
||||
&& EXPOSED(picture.used) == STREAM(picture.used)
|
||||
&& !memcmp(MEM_STREAM_TO_EXPOSED)
|
||||
) {
|
||||
EXPOSED(expose_cmp_time) = get_now_monotonic();
|
||||
@@ -718,7 +720,7 @@ static bool _expose_new_picture(struct http_server_t *server) {
|
||||
|
||||
# undef MEM_STREAM_TO_EXPOSED
|
||||
|
||||
EXPOSED(picture.size) = STREAM(picture.size);
|
||||
EXPOSED(picture.used) = STREAM(picture.used);
|
||||
|
||||
EXPOSED(picture.grab_time) = STREAM(picture.grab_time);
|
||||
EXPOSED(picture.encode_begin_time) = STREAM(picture.encode_begin_time);
|
||||
@@ -746,7 +748,7 @@ static bool _expose_blank_picture(struct http_server_t *server) {
|
||||
EXPOSED(expose_begin_time) = get_now_monotonic();
|
||||
EXPOSED(expose_cmp_time) = EXPOSED(expose_begin_time);
|
||||
|
||||
if (EXPOSED(online) || EXPOSED(picture.size) == 0) {
|
||||
if (EXPOSED(online) || EXPOSED(picture.used) == 0) {
|
||||
if (EXPOSED(picture.allocated) < BLANK_JPEG_LEN) {
|
||||
A_REALLOC(EXPOSED(picture.data), BLANK_JPEG_LEN);
|
||||
EXPOSED(picture.allocated) = BLANK_JPEG_LEN;
|
||||
@@ -754,7 +756,7 @@ static bool _expose_blank_picture(struct http_server_t *server) {
|
||||
|
||||
memcpy(EXPOSED(picture.data), BLANK_JPEG_DATA, BLANK_JPEG_LEN * sizeof(*EXPOSED(picture.data)));
|
||||
|
||||
EXPOSED(picture.size) = BLANK_JPEG_LEN;
|
||||
EXPOSED(picture.used) = BLANK_JPEG_LEN;
|
||||
|
||||
EXPOSED(picture.grab_time) = 0;
|
||||
EXPOSED(picture.encode_begin_time) = 0;
|
||||
|
||||
135
src/main.c
135
src/main.c
@@ -41,8 +41,8 @@
|
||||
#include "http.h"
|
||||
|
||||
|
||||
static const char _short_opts[] = "d:i:x:y:m:a:f:z:tb:w:q:c:s:p:u:ro:e:h";
|
||||
static const struct option _long_opts[] = {
|
||||
static const char _SHORT_OPTS[] = "d:i:x:y:m:a:f:z:ntb:w:q:c:s:p:u:ro:e:lhv";
|
||||
static const struct option _LONG_OPTS[] = {
|
||||
{"device", required_argument, NULL, 'd'},
|
||||
{"input", required_argument, NULL, 'i'},
|
||||
{"width", required_argument, NULL, 'x'},
|
||||
@@ -51,14 +51,14 @@ static const struct option _long_opts[] = {
|
||||
{"tv-standard", required_argument, NULL, 'a'},
|
||||
{"desired-fps", required_argument, NULL, 'f'},
|
||||
{"min-frame-size", required_argument, NULL, 'z'},
|
||||
{"persistent", no_argument, NULL, 'n'},
|
||||
{"dv-timings", no_argument, NULL, 't'},
|
||||
{"buffers", required_argument, NULL, 'b'},
|
||||
{"workers", required_argument, NULL, 'w'},
|
||||
{"quality", required_argument, NULL, 'q'},
|
||||
{"encoder", required_argument, NULL, 'c'},
|
||||
{"device-timeout", required_argument, NULL, 1000},
|
||||
{"device-persistent", no_argument, NULL, 1001},
|
||||
{"device-error-delay", required_argument, NULL, 1002},
|
||||
{"device-error-delay", required_argument, NULL, 1001},
|
||||
|
||||
{"brightness", required_argument, NULL, 2000},
|
||||
{"brightness-auto", no_argument, NULL, 2001},
|
||||
@@ -80,7 +80,7 @@ static const struct option _long_opts[] = {
|
||||
{"unix-rm", no_argument, NULL, 'r'},
|
||||
{"unix-mode", required_argument, NULL, 'o'},
|
||||
{"drop-same-frames", required_argument, NULL, 'e'},
|
||||
{"slowdown", no_argument, NULL, 3000},
|
||||
{"slowdown", no_argument, NULL, 'l'},
|
||||
{"fake-width", required_argument, NULL, 3001},
|
||||
{"fake-height", required_argument, NULL, 3002},
|
||||
{"server-timeout", required_argument, NULL, 3003},
|
||||
@@ -90,7 +90,7 @@ static const struct option _long_opts[] = {
|
||||
{"debug", no_argument, NULL, 5002},
|
||||
{"log-level", required_argument, NULL, 5010},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"version", no_argument, NULL, 6000},
|
||||
{"version", no_argument, NULL, 'v'},
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
|
||||
@@ -106,78 +106,79 @@ static void _version(bool nl) {
|
||||
|
||||
static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
|
||||
printf("\nuStreamer - Lightweight and fast MJPG-HTTP streamer\n");
|
||||
printf("===================================================\n\n");
|
||||
printf("═══════════════════════════════════════════════════\n\n");
|
||||
printf("Version: ");
|
||||
_version(false);
|
||||
printf("; license: GPLv3\n");
|
||||
printf("Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com>\n\n");
|
||||
printf("Capturing options:\n");
|
||||
printf("------------------\n");
|
||||
printf(" -d|--device </dev/path> -- Path to V4L2 device. Default: %s.\n\n", dev->path);
|
||||
printf(" -i|--input <N> -- Input channel. Default: %u.\n\n", dev->input);
|
||||
printf(" -x|--width <N> -- Initial image width. Default: %u.\n\n", dev->width);
|
||||
printf(" -y|--height <N> -- Initial image height. Default: %u.\n\n", dev->height);
|
||||
printf(" -m|--format <fmt> -- Image format.\n");
|
||||
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
|
||||
printf(" -a|--tv-standard <std> -- Force TV standard.\n");
|
||||
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
|
||||
printf(" -f|--desired-fps <N> -- Desired FPS. Default: maximum as possible.\n\n");
|
||||
printf(" -z|--min-frame-size <N> -- Drop frames smaller then this limit.\n");
|
||||
printf(" Useful if the device produces small-sized garbage frames.\n\n");
|
||||
printf(" -t|--dv-timings -- Enable DV timings queriyng and events processing.\n");
|
||||
printf(" Supports automatic resolution changing. Default: disabled.\n\n");
|
||||
printf(" -b|--buffers <N> -- The number of buffers to receive data from the device.\n");
|
||||
printf(" Each buffer may processed using an intermediate thread.\n");
|
||||
printf(" Default: %u (number of CPU cores + 1)\n\n", dev->n_buffers);
|
||||
printf(" -w|--workers <N> -- The number of compressing threads. Default: %u (== --buffers).\n\n", dev->n_workers);
|
||||
printf(" -q|--quality <N> -- Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n\n", encoder->quality);
|
||||
printf(" -c|--encoder <type> -- Use specified encoder. It may affects to workers number.\n");
|
||||
printf(" -- Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
|
||||
printf(" --device-timeout <seconds> -- Timeout for device querying. Default: %u\n\n", dev->timeout);
|
||||
printf(" --device-persistent -- Don't re-initialize device on timeout. Default: disabled.\n\n");
|
||||
printf(" --device-error-delay <seconds> -- Delay before trying to connect to the device again\n");
|
||||
printf(" after a timeout. Default: %u\n\n", dev->error_delay);
|
||||
printf("══════════════════\n");
|
||||
printf(" -d|--device </dev/path> ──────── Path to V4L2 device. Default: %s.\n\n", dev->path);
|
||||
printf(" -i|--input <N> ───────────────── Input channel. Default: %u.\n\n", dev->input);
|
||||
printf(" -x|--width <N> ───────────────── Initial image width. Default: %u.\n\n", dev->width);
|
||||
printf(" -y|--height <N> ──────────────── Initial image height. Default: %u.\n\n", dev->height);
|
||||
printf(" -m|--format <fmt> ────────────── Image format.\n");
|
||||
printf(" Available: %s; default: YUYV.\n\n", FORMATS_STR);
|
||||
printf(" -a|--tv-standard <std> ───────── Force TV standard.\n");
|
||||
printf(" Available: %s; default: disabled.\n\n", STANDARDS_STR);
|
||||
printf(" -f|--desired-fps <N> ─────────── Desired FPS. Default: maximum as possible.\n\n");
|
||||
printf(" -z|--min-frame-size <N> ──────── Drop frames smaller then this limit.\n");
|
||||
printf(" Useful if the device produces small-sized garbage frames.\n\n");
|
||||
printf(" -n|--persistent ──────────────── Don't re-initialize device on timeout. Default: disabled.\n\n");
|
||||
printf(" -t|--dv-timings ──────────────── Enable DV timings queriyng and events processing.\n");
|
||||
printf(" Supports automatic resolution changing. Default: disabled.\n\n");
|
||||
printf(" -b|--buffers <N> ─────────────── The number of buffers to receive data from the device.\n");
|
||||
printf(" Each buffer may processed using an intermediate thread.\n");
|
||||
printf(" Default: %u (the number of CPU cores (but not more 4) + 1)\n\n", dev->n_buffers);
|
||||
printf(" -w|--workers <N> ─────────────── The number of worker threads. Default: %u (== --buffers).\n\n", dev->n_workers);
|
||||
printf(" -q|--quality <N> ─────────────── Set quality of JPEG encoding from 1 to 100 (best). Default: %u.\n\n", encoder->quality);
|
||||
printf(" -c|--encoder <type> ──────────── Use specified encoder. It may affects to workers number.\n");
|
||||
printf(" Available: %s; default: CPU.\n\n", ENCODER_TYPES_STR);
|
||||
printf(" --device-timeout <seconds> ───── Timeout for device querying. Default: %u\n\n", dev->timeout);
|
||||
printf(" --device-error-delay <seconds> ─ Delay before trying to connect to the device again\n");
|
||||
printf(" after timeout. Default: %u\n\n", dev->error_delay);
|
||||
printf("Image control options:\n");
|
||||
printf("---------------\n");
|
||||
printf(" --brightness <N> -- Set brightness. Default: no change.\n\n");
|
||||
printf(" --brightness-auto -- Enable automatic brightness control. Default: no change.\n\n");
|
||||
printf(" --contrast <N> -- Set contrast. Default: no change.\n\n");
|
||||
printf(" --saturation <N> -- Set saturation. Default: no change.\n\n");
|
||||
printf(" --hue <N> -- Set hue. Default: no change.\n\n");
|
||||
printf(" --hue-auto -- Enable automatic hue control. Default: no change.\n\n");
|
||||
printf(" --gamma <N> -- Set gamma. Default: no change.\n\n");
|
||||
printf(" --sharpness <N> -- Set sharpness. Default: no change.\n\n");
|
||||
printf(" --backlight-compensation <N> -- Set backlight compensation. Default: no change.\n\n");
|
||||
printf(" --white-balance <N> -- Set white balance. Default: no change.\n\n");
|
||||
printf(" --white-balance-auto -- Enable automatic white balance control. Default: no change.\n\n");
|
||||
printf(" --gain <N> -- Set gain. Default: no change.\n\n");
|
||||
printf(" --gain-auto -- Enable automatic gain control. Default: no change.\n\n");
|
||||
printf("══════════════════════\n");
|
||||
printf(" --brightness <N> ───────────── Set brightness. Default: no change.\n\n");
|
||||
printf(" --brightness-auto ──────────── Enable automatic brightness control. Default: no change.\n\n");
|
||||
printf(" --contrast <N> ─────────────── Set contrast. Default: no change.\n\n");
|
||||
printf(" --saturation <N> ───────────── Set saturation. Default: no change.\n\n");
|
||||
printf(" --hue <N> ──────────────────── Set hue. Default: no change.\n\n");
|
||||
printf(" --hue-auto ─────────────────── Enable automatic hue control. Default: no change.\n\n");
|
||||
printf(" --gamma <N> ────────────────── Set gamma. Default: no change.\n\n");
|
||||
printf(" --sharpness <N> ────────────── Set sharpness. Default: no change.\n\n");
|
||||
printf(" --backlight-compensation <N> ─ Set backlight compensation. Default: no change.\n\n");
|
||||
printf(" --white-balance <N> ────────── Set white balance. Default: no change.\n\n");
|
||||
printf(" --white-balance-auto ───────── Enable automatic white balance control. Default: no change.\n\n");
|
||||
printf(" --gain <N> ─────────────────── Set gain. Default: no change.\n\n");
|
||||
printf(" --gain-auto ────────────────── Enable automatic gain control. Default: no change.\n\n");
|
||||
printf("HTTP server options:\n");
|
||||
printf("--------------------\n");
|
||||
printf(" -s|--host <address> -- Listen on Hostname or IP. Default: %s\n\n", server->host);
|
||||
printf(" -p|--port <N> -- Bind to this TCP port. Default: %u\n\n", server->port);
|
||||
printf(" -u|--unix <path> -- Bind to UNIX domain socket. Default: disabled\n\n");
|
||||
printf(" -r|--unix-rm -- Try to remove old UNIX socket file before binding. Default: disabled\n\n");
|
||||
printf(" -o|--unix-mode <mode> -- Set UNIX socket file permissions (like 777). Default: disabled\n\n");
|
||||
printf(" -e|--drop-same-frames <N> -- Don't send same frames to clients, but no more than specified number.\n");
|
||||
printf("════════════════════\n");
|
||||
printf(" -s|--host <address> ──────── Listen on Hostname or IP. Default: %s\n\n", server->host);
|
||||
printf(" -p|--port <N> ────────────── Bind to this TCP port. Default: %u\n\n", server->port);
|
||||
printf(" -u|--unix <path> ─────────── Bind to UNIX domain socket. Default: disabled\n\n");
|
||||
printf(" -r|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled\n\n");
|
||||
printf(" -o|--unix-mode <mode> ────── Set UNIX socket file permissions (like 777). Default: disabled\n\n");
|
||||
printf(" -e|--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(" --slowdown -- Slowdown capturing to 1 FPS or less when no stream clients connected.\n");
|
||||
printf(" -l|--slowdown ────────────── Slowdown capturing to 1 FPS or less when no stream clients connected.\n");
|
||||
printf(" Useful to reduce CPU cosumption. Default: disabled.\n\n");
|
||||
printf(" --fake-width <N> -- Override image width for /state. Default: disabled\n\n");
|
||||
printf(" --fake-height <N> -- Override image height for /state. Default: disabled.\n\n");
|
||||
printf(" --server-timeout <seconds> -- Timeout for client connections. Default: %u\n\n", server->timeout);
|
||||
printf(" --fake-width <N> ─────────── Override image width for /state. Default: disabled\n\n");
|
||||
printf(" --fake-height <N> ────────── Override image height for /state. Default: disabled.\n\n");
|
||||
printf(" --server-timeout <seconds> ─ Timeout for client connections. Default: %u\n\n", server->timeout);
|
||||
printf("Misc options:\n");
|
||||
printf("-------------\n");
|
||||
printf(" --log-level <N> -- Verbosity level of messages from 0 (info) to 3 (debug).\n");
|
||||
printf("═════════════\n");
|
||||
printf(" --log-level <N> ─ Verbosity level of messages from 0 (info) to 3 (debug).\n");
|
||||
printf(" Enabling debugging messages can slow down the program.\n");
|
||||
printf(" Available levels: 0=info, 1=performance, 2=verbose, 3=debug.\n");
|
||||
printf(" Default: %u.\n\n", log_level);
|
||||
printf(" --perf -- Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
|
||||
printf(" --verbose -- Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
|
||||
printf(" --debug -- Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
|
||||
printf(" -h|--help -- Print this messages and exit.\n\n");
|
||||
printf(" --perf ────────── Enable performance messages (same as --log-level=1). Default: disabled.\n\n");
|
||||
printf(" --verbose ─────── Enable verbose messages and lower (same as --log-level=2). Default: disabled.\n\n");
|
||||
printf(" --debug ───────── Enable debug messages and lower (same as --log-level=3). Default: disabled.\n\n");
|
||||
printf(" -h|--help ─────── Print this text and exit.\n\n");
|
||||
printf(" -v|--version ──── Print version and exit.\n\n");
|
||||
}
|
||||
|
||||
static int _parse_options(int argc, char *argv[], struct device_t *dev, struct encoder_t *encoder, struct http_server_t *server) {
|
||||
@@ -232,7 +233,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
int ch;
|
||||
|
||||
log_level = LOG_LEVEL_INFO;
|
||||
while ((ch = getopt_long(argc, argv, _short_opts, _long_opts, &index)) >= 0) {
|
||||
while ((ch = getopt_long(argc, argv, _SHORT_OPTS, _LONG_OPTS, &index)) >= 0) {
|
||||
switch (ch) {
|
||||
case 'd': OPT_SET(dev->path, optarg);
|
||||
case 'i': OPT_UNSIGNED(dev->input, "--input", 0, 128);
|
||||
@@ -245,14 +246,14 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
case 'a': OPT_PARSE(dev->standard, device_parse_standard, STANDARD_UNKNOWN, "TV standard");
|
||||
case 'f': OPT_UNSIGNED(dev->desired_fps, "--desired-fps", 0, 30);
|
||||
case 'z': OPT_UNSIGNED(dev->min_frame_size, "--min-frame-size", 0, 8192);
|
||||
case 'n': OPT_SET(dev->persistent, true);
|
||||
case 't': OPT_SET(dev->dv_timings, true);
|
||||
case 'b': OPT_UNSIGNED(dev->n_buffers, "--buffers", 1, 32);
|
||||
case 'w': OPT_UNSIGNED(dev->n_workers, "--workers", 1, 32);
|
||||
case 'q': OPT_UNSIGNED(encoder->quality, "--quality", 1, 100);
|
||||
case 'c': OPT_PARSE(encoder->type, encoder_parse_type, ENCODER_TYPE_UNKNOWN, "encoder type");
|
||||
case 1000: OPT_UNSIGNED(dev->timeout, "--device-timeout", 1, 60);
|
||||
case 1001: OPT_SET(dev->persistent, true);
|
||||
case 1002: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 1, 60);
|
||||
case 1001: OPT_UNSIGNED(dev->error_delay, "--device-error-delay", 1, 60);
|
||||
|
||||
case 2000: OPT_CTL(brightness);
|
||||
case 2001: OPT_CTL_AUTO(brightness);
|
||||
@@ -284,7 +285,7 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
|
||||
case 5002: OPT_SET(log_level, LOG_LEVEL_DEBUG);
|
||||
case 5010: OPT_UNSIGNED(log_level, "--log-level", 0, 3);
|
||||
case 'h': _help(dev, encoder, server); return 1;
|
||||
case 6000: _version(true); return 1;
|
||||
case 'v': _version(true); return 1;
|
||||
case 0: break;
|
||||
default: _help(dev, encoder, server); return -1;
|
||||
}
|
||||
|
||||
160
src/stream.c
160
src/stream.c
@@ -20,6 +20,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
@@ -48,21 +49,19 @@ static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t
|
||||
static void *_stream_worker_thread(void *v_worker);
|
||||
static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool_t *pool);
|
||||
|
||||
static int _stream_control(struct device_t *dev, bool enable);
|
||||
static int _stream_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
|
||||
static int _stream_release_buffer(struct device_t *dev, struct v4l2_buffer *buf_info);
|
||||
static int _stream_handle_event(struct device_t *dev);
|
||||
|
||||
|
||||
struct stream_t *stream_init(struct device_t *dev, struct encoder_t *encoder) {
|
||||
struct process_t *proc;
|
||||
struct stream_t *stream;
|
||||
|
||||
A_CALLOC(proc, 1);
|
||||
atomic_init(&proc->stop, false);
|
||||
atomic_init(&proc->slowdown, false);
|
||||
|
||||
A_CALLOC(stream, 1);
|
||||
stream->dev = dev;
|
||||
stream->encoder = encoder;
|
||||
atomic_init(&stream->updated, false);
|
||||
A_MUTEX_INIT(&stream->mutex);
|
||||
stream->proc = proc;
|
||||
return stream;
|
||||
@@ -76,11 +75,10 @@ void stream_destroy(struct stream_t *stream) {
|
||||
|
||||
void stream_loop(struct stream_t *stream) {
|
||||
struct workers_pool_t pool;
|
||||
bool workers_stop;
|
||||
|
||||
MEMSET_ZERO(pool);
|
||||
atomic_init(&pool.workers_stop, false);
|
||||
pool.encoder = stream->encoder;
|
||||
pool.workers_stop = &workers_stop;
|
||||
|
||||
LOG_INFO("Using V4L2 device: %s", stream->dev->path);
|
||||
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
||||
@@ -99,7 +97,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
|
||||
LOG_INFO("Capturing ...");
|
||||
|
||||
while (!stream->proc->stop) {
|
||||
while (!atomic_load(&stream->proc->stop)) {
|
||||
int free_worker_number = -1;
|
||||
|
||||
SEP_DEBUG('-');
|
||||
@@ -109,7 +107,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
A_COND_WAIT_TRUE(pool.free_workers, &pool.free_workers_cond, &pool.free_workers_mutex);
|
||||
A_MUTEX_UNLOCK(&pool.free_workers_mutex);
|
||||
|
||||
if (oldest_worker && !oldest_worker->has_job && oldest_worker->buf_index >= 0) {
|
||||
if (oldest_worker && !atomic_load(&oldest_worker->has_job) && oldest_worker->buf_index >= 0) {
|
||||
if (oldest_worker->job_failed) {
|
||||
break;
|
||||
}
|
||||
@@ -123,7 +121,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
} else {
|
||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
||||
if (
|
||||
!pool.workers[number].has_job && (
|
||||
!atomic_load(&pool.workers[number].has_job) && (
|
||||
free_worker_number == -1
|
||||
|| pool.workers[free_worker_number].job_start_time < pool.workers[number].job_start_time
|
||||
)
|
||||
@@ -134,16 +132,16 @@ void stream_loop(struct stream_t *stream) {
|
||||
}
|
||||
|
||||
assert(free_worker_number >= 0);
|
||||
assert(!pool.workers[free_worker_number].has_job);
|
||||
assert(!atomic_load(&pool.workers[free_worker_number].has_job));
|
||||
|
||||
LOG_PERF("----- Raw frame dropped; worker = %u", free_worker_number);
|
||||
}
|
||||
|
||||
if (stream->proc->stop) {
|
||||
if (atomic_load(&stream->proc->stop)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (stream->proc->slowdown) {
|
||||
if (atomic_load(&stream->proc->slowdown)) {
|
||||
usleep(1000000);
|
||||
}
|
||||
|
||||
@@ -188,22 +186,23 @@ void stream_loop(struct stream_t *stream) {
|
||||
if (FD_ISSET(stream->dev->run->fd, &read_fds)) {
|
||||
LOG_DEBUG("Frame is ready");
|
||||
|
||||
struct v4l2_buffer buf_info;
|
||||
int buf_index;
|
||||
long double now = get_now_monotonic();
|
||||
long long now_second = floor_ms(now);
|
||||
|
||||
if (_stream_grab_buffer(stream->dev, &buf_info) < 0) {
|
||||
if ((buf_index = device_grab_buffer(stream->dev)) < 0) {
|
||||
break;
|
||||
}
|
||||
stream->dev->run->pictures[buf_info.index].grab_time = now;
|
||||
stream->dev->run->pictures[buf_index].grab_time = now;
|
||||
|
||||
// Workaround for broken, corrupted frames:
|
||||
// Under low light conditions corrupted frames may get captured.
|
||||
// The good thing is such frames are quite small compared to the regular pictures.
|
||||
// For example a VGA (640x480) webcam picture is normally >= 8kByte large,
|
||||
// corrupted frames are smaller.
|
||||
if (buf_info.bytesused < stream->dev->min_frame_size) {
|
||||
LOG_DEBUG("Dropping too small frame sized %u bytes, assuming it as broken", buf_info.bytesused);
|
||||
if (stream->dev->run->hw_buffers[buf_index].used < stream->dev->min_frame_size) {
|
||||
LOG_DEBUG("Dropping too small frame sized %zu bytes, assuming it as broken",
|
||||
stream->dev->run->hw_buffers[buf_index].used);
|
||||
goto pass_frame;
|
||||
}
|
||||
|
||||
@@ -231,8 +230,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
|
||||
# define FREE_WORKER(_next) pool.workers[free_worker_number]._next
|
||||
|
||||
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_info.index);
|
||||
FREE_WORKER(buf_info) = buf_info;
|
||||
LOG_DEBUG("Grabbed a new frame to buffer %u", buf_index);
|
||||
|
||||
if (!oldest_worker) {
|
||||
oldest_worker = &pool.workers[free_worker_number];
|
||||
@@ -251,8 +249,8 @@ void stream_loop(struct stream_t *stream) {
|
||||
last_worker->order_next = NULL;
|
||||
|
||||
A_MUTEX_LOCK(&FREE_WORKER(has_job_mutex));
|
||||
FREE_WORKER(buf_index) = buf_info.index;
|
||||
FREE_WORKER(has_job) = true;
|
||||
FREE_WORKER(buf_index) = buf_index;
|
||||
atomic_store(&FREE_WORKER(has_job), true);
|
||||
A_MUTEX_UNLOCK(&FREE_WORKER(has_job_mutex));
|
||||
A_COND_SIGNAL(&FREE_WORKER(has_job_cond));
|
||||
|
||||
@@ -266,7 +264,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
|
||||
pass_frame:
|
||||
|
||||
if (_stream_release_buffer(stream->dev, &buf_info) < 0) {
|
||||
if (device_release_buffer(stream->dev, buf_index) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -280,7 +278,7 @@ void stream_loop(struct stream_t *stream) {
|
||||
|
||||
if (FD_ISSET(stream->dev->run->fd, &error_fds)) {
|
||||
LOG_INFO("Got V4L2 event");
|
||||
if (_stream_handle_event(stream->dev) < 0) {
|
||||
if (device_consume_event(stream->dev) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -288,25 +286,25 @@ void stream_loop(struct stream_t *stream) {
|
||||
}
|
||||
|
||||
A_MUTEX_LOCK(&stream->mutex);
|
||||
stream->picture.size = 0; // On stream offline
|
||||
stream->picture.used = 0; // On stream offline
|
||||
free(stream->picture.data);
|
||||
stream->width = 0;
|
||||
stream->height = 0;
|
||||
stream->updated = true;
|
||||
atomic_store(&stream->updated, true);
|
||||
A_MUTEX_UNLOCK(&stream->mutex);
|
||||
}
|
||||
|
||||
_stream_destroy_workers(stream, &pool);
|
||||
_stream_control(stream->dev, false);
|
||||
device_switch_capturing(stream->dev, false);
|
||||
device_close(stream->dev);
|
||||
}
|
||||
|
||||
void stream_loop_break(struct stream_t *stream) {
|
||||
stream->proc->stop = 1;
|
||||
atomic_store(&stream->proc->stop, true);
|
||||
}
|
||||
|
||||
void stream_switch_slowdown(struct stream_t *stream, bool slowdown) {
|
||||
stream->proc->slowdown = slowdown;
|
||||
atomic_store(&stream->proc->slowdown, slowdown);
|
||||
}
|
||||
|
||||
static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index) {
|
||||
@@ -314,10 +312,10 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
|
||||
|
||||
A_MUTEX_LOCK(&stream->mutex);
|
||||
|
||||
stream->picture.size = PICTURE(size);
|
||||
stream->picture.used = PICTURE(used);
|
||||
stream->picture.allocated = PICTURE(allocated);
|
||||
|
||||
memcpy(stream->picture.data, PICTURE(data), stream->picture.size * sizeof(*stream->picture.data));
|
||||
memcpy(stream->picture.data, PICTURE(data), stream->picture.used * sizeof(*stream->picture.data));
|
||||
|
||||
stream->picture.grab_time = PICTURE(grab_time);
|
||||
stream->picture.encode_begin_time = PICTURE(encode_begin_time);
|
||||
@@ -325,7 +323,7 @@ static void _stream_expose_picture(struct stream_t *stream, unsigned buf_index)
|
||||
|
||||
stream->width = stream->dev->run->width;
|
||||
stream->height = stream->dev->run->height;
|
||||
stream->updated = true;
|
||||
atomic_store(&stream->updated, true);
|
||||
|
||||
A_MUTEX_UNLOCK(&stream->mutex);
|
||||
|
||||
@@ -365,8 +363,8 @@ static long double _stream_get_fluency_delay(struct device_t *dev, struct worker
|
||||
static int _stream_init_loop(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||
int retval = -1;
|
||||
|
||||
LOG_DEBUG("%s: *stream->proc->stop = %d", __FUNCTION__, stream->proc->stop);
|
||||
while (!stream->proc->stop) {
|
||||
LOG_DEBUG("%s: stream->proc->stop = %d", __FUNCTION__, atomic_load(&stream->proc->stop));
|
||||
while (!atomic_load(&stream->proc->stop)) {
|
||||
if ((retval = _stream_init(stream, pool)) < 0) {
|
||||
LOG_INFO("Sleeping %u seconds before new stream init ...", stream->dev->error_delay);
|
||||
sleep(stream->dev->error_delay);
|
||||
@@ -381,13 +379,13 @@ static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||
SEP_INFO('=');
|
||||
|
||||
_stream_destroy_workers(stream, pool);
|
||||
_stream_control(stream->dev, false);
|
||||
device_switch_capturing(stream->dev, false);
|
||||
device_close(stream->dev);
|
||||
|
||||
if (device_open(stream->dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (_stream_control(stream->dev, true) < 0) {
|
||||
if (device_switch_capturing(stream->dev, true) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -405,7 +403,7 @@ static int _stream_init(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||
static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t *pool) {
|
||||
LOG_INFO("Spawning %u workers ...", stream->dev->n_workers);
|
||||
|
||||
*pool->workers_stop = false;
|
||||
atomic_store(&pool->workers_stop, false);
|
||||
A_CALLOC(pool->workers, stream->dev->n_workers);
|
||||
|
||||
A_MUTEX_INIT(&pool->free_workers_mutex);
|
||||
@@ -417,11 +415,12 @@ static void _stream_init_workers(struct stream_t *stream, struct workers_pool_t
|
||||
pool->free_workers += 1;
|
||||
|
||||
A_MUTEX_INIT(&WORKER(has_job_mutex));
|
||||
atomic_init(&WORKER(has_job), false);
|
||||
A_COND_INIT(&WORKER(has_job_cond));
|
||||
|
||||
WORKER(number) = number;
|
||||
WORKER(proc_stop) = (sig_atomic_t *volatile)&(stream->proc->stop);
|
||||
WORKER(workers_stop) = pool->workers_stop;
|
||||
WORKER(proc_stop) = &stream->proc->stop;
|
||||
WORKER(workers_stop) = &pool->workers_stop;
|
||||
|
||||
WORKER(free_workers_mutex) = &pool->free_workers_mutex;
|
||||
WORKER(free_workers) = &pool->free_workers;
|
||||
@@ -441,13 +440,13 @@ static void *_stream_worker_thread(void *v_worker) {
|
||||
|
||||
LOG_DEBUG("Hello! I am a worker #%u ^_^", worker->number);
|
||||
|
||||
while (!*worker->proc_stop && !*worker->workers_stop) {
|
||||
while (!atomic_load(worker->proc_stop) && !atomic_load(worker->workers_stop)) {
|
||||
LOG_DEBUG("Worker %u waiting for a new job ...", worker->number);
|
||||
A_MUTEX_LOCK(&worker->has_job_mutex);
|
||||
A_COND_WAIT_TRUE(worker->has_job, &worker->has_job_cond, &worker->has_job_mutex);
|
||||
A_COND_WAIT_TRUE(atomic_load(&worker->has_job), &worker->has_job_cond, &worker->has_job_mutex);
|
||||
A_MUTEX_UNLOCK(&worker->has_job_mutex);
|
||||
|
||||
if (!*worker->workers_stop) {
|
||||
if (!atomic_load(worker->workers_stop)) {
|
||||
# define PICTURE(_next) worker->dev->run->pictures[worker->buf_index]._next
|
||||
|
||||
LOG_DEBUG("Worker %u compressing JPEG from buffer %u ...", worker->number, worker->buf_index);
|
||||
@@ -458,9 +457,9 @@ static void *_stream_worker_thread(void *v_worker) {
|
||||
}
|
||||
PICTURE(encode_end_time) = get_now_monotonic();
|
||||
|
||||
if (_stream_release_buffer(worker->dev, &worker->buf_info) == 0) {
|
||||
if (device_release_buffer(worker->dev, worker->buf_index) == 0) {
|
||||
worker->job_start_time = PICTURE(encode_begin_time);
|
||||
worker->has_job = false;
|
||||
atomic_store(&worker->has_job, false);
|
||||
|
||||
long double last_comp_time = PICTURE(encode_end_time) - worker->job_start_time;
|
||||
|
||||
@@ -469,10 +468,10 @@ static void *_stream_worker_thread(void *v_worker) {
|
||||
A_MUTEX_UNLOCK(&worker->last_comp_time_mutex);
|
||||
|
||||
LOG_VERBOSE("Compressed JPEG size=%zu; time=%0.3Lf; worker=%u; buffer=%u",
|
||||
PICTURE(size), last_comp_time, worker->number, worker->buf_index);
|
||||
PICTURE(used), last_comp_time, worker->number, worker->buf_index);
|
||||
} else {
|
||||
worker->job_failed = true;
|
||||
worker->has_job = false;
|
||||
atomic_store(&worker->has_job, false);
|
||||
}
|
||||
|
||||
# undef PICTURE
|
||||
@@ -492,12 +491,12 @@ static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool
|
||||
if (pool->workers) {
|
||||
LOG_INFO("Destroying workers ...");
|
||||
|
||||
*pool->workers_stop = true;
|
||||
atomic_store(&pool->workers_stop, true);
|
||||
for (unsigned number = 0; number < stream->dev->n_workers; ++number) {
|
||||
# define WORKER(_next) pool->workers[number]._next
|
||||
|
||||
A_MUTEX_LOCK(&WORKER(has_job_mutex));
|
||||
WORKER(has_job) = true; // Final job: die
|
||||
atomic_store(&WORKER(has_job), true); // Final job: die
|
||||
A_MUTEX_UNLOCK(&WORKER(has_job_mutex));
|
||||
A_COND_SIGNAL(&WORKER(has_job_cond));
|
||||
|
||||
@@ -516,68 +515,3 @@ static void _stream_destroy_workers(struct stream_t *stream, struct workers_pool
|
||||
pool->free_workers = 0;
|
||||
pool->workers = NULL;
|
||||
}
|
||||
|
||||
static int _stream_control(struct device_t *dev, bool enable) {
|
||||
if (enable != dev->run->capturing) {
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(%s) ...", (enable ? "VIDIOC_STREAMON" : "VIDIOC_STREAMOFF"));
|
||||
if (xioctl(dev->run->fd, (enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF), &type) < 0) {
|
||||
LOG_PERROR("Unable to %s capturing", (enable ? "start" : "stop"));
|
||||
if (enable) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
dev->run->capturing = enable;
|
||||
LOG_INFO("Capturing %s", (enable ? "started" : "stopped"));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _stream_grab_buffer(struct device_t *dev, struct v4l2_buffer *buf_info) {
|
||||
MEMSET_ZERO_PTR(buf_info);
|
||||
buf_info->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf_info->memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_DQBUF) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_DQBUF, buf_info) < 0) {
|
||||
LOG_PERROR("Unable to dequeue buffer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Got a new frame in buffer index=%u; bytesused=%u", buf_info->index, buf_info->bytesused);
|
||||
if (buf_info->index >= dev->run->n_buffers) {
|
||||
LOG_ERROR("Got invalid buffer index=%u; nbuffers=%u", buf_info->index, dev->run->n_buffers);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _stream_release_buffer(struct device_t *dev, struct v4l2_buffer *buf_info) {
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_QBUF) ...");
|
||||
if (xioctl(dev->run->fd, VIDIOC_QBUF, buf_info) < 0) {
|
||||
LOG_PERROR("Unable to requeue buffer");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _stream_handle_event(struct device_t *dev) {
|
||||
struct v4l2_event event;
|
||||
|
||||
LOG_DEBUG("Calling ioctl(VIDIOC_DQEVENT) ...");
|
||||
if (!xioctl(dev->run->fd, VIDIOC_DQEVENT, &event)) {
|
||||
switch (event.type) {
|
||||
case V4L2_EVENT_SOURCE_CHANGE:
|
||||
LOG_INFO("Got V4L2_EVENT_SOURCE_CHANGE: source changed");
|
||||
return -1;
|
||||
case V4L2_EVENT_EOS:
|
||||
LOG_INFO("Got V4L2_EVENT_EOS: end of stream (ignored)");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
LOG_PERROR("Got some V4L2 device event, but where is it? ");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
17
src/stream.h
17
src/stream.h
@@ -23,7 +23,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <signal.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -34,16 +34,15 @@
|
||||
struct worker_t {
|
||||
pthread_t tid;
|
||||
unsigned number;
|
||||
sig_atomic_t *volatile proc_stop;
|
||||
bool *workers_stop;
|
||||
atomic_bool *proc_stop;
|
||||
atomic_bool *workers_stop;
|
||||
|
||||
pthread_mutex_t last_comp_time_mutex;
|
||||
long double last_comp_time;
|
||||
|
||||
pthread_mutex_t has_job_mutex;
|
||||
int buf_index;
|
||||
struct v4l2_buffer buf_info;
|
||||
bool has_job;
|
||||
atomic_bool has_job;
|
||||
bool job_failed;
|
||||
long double job_start_time;
|
||||
pthread_cond_t has_job_cond;
|
||||
@@ -61,7 +60,7 @@ struct worker_t {
|
||||
|
||||
struct workers_pool_t {
|
||||
struct worker_t *workers;
|
||||
bool *workers_stop;
|
||||
atomic_bool workers_stop;
|
||||
|
||||
pthread_mutex_t free_workers_mutex;
|
||||
unsigned free_workers;
|
||||
@@ -71,8 +70,8 @@ struct workers_pool_t {
|
||||
};
|
||||
|
||||
struct process_t {
|
||||
sig_atomic_t volatile stop;
|
||||
bool slowdown;
|
||||
atomic_bool stop;
|
||||
atomic_bool slowdown;
|
||||
};
|
||||
|
||||
struct stream_t {
|
||||
@@ -80,7 +79,7 @@ struct stream_t {
|
||||
unsigned width;
|
||||
unsigned height;
|
||||
unsigned captured_fps;
|
||||
bool updated;
|
||||
atomic_bool updated;
|
||||
pthread_mutex_t mutex;
|
||||
|
||||
struct process_t *proc;
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
#define A_CALLOC(_dest, _nmemb) assert((_dest = calloc(_nmemb, sizeof(*(_dest)))))
|
||||
#define A_REALLOC(_dest, _nmemb) assert((_dest = realloc(_dest, _nmemb * sizeof(*(_dest)))))
|
||||
#define MEMSET_ZERO(_obj) memset(&(_obj), 0, sizeof(_obj))
|
||||
#define MEMSET_ZERO_PTR(_ptr) memset(_ptr, 0, sizeof(*(_ptr)))
|
||||
|
||||
#define ARRAY_LEN(_array) (sizeof(_array) / sizeof(_array[0]))
|
||||
|
||||
@@ -63,12 +62,16 @@ INLINE char *bool_to_string(bool flag) {
|
||||
return (flag ? "true" : "false");
|
||||
}
|
||||
|
||||
INLINE unsigned min_u(unsigned a, unsigned b) {
|
||||
return (a < b ? a : b);
|
||||
}
|
||||
|
||||
INLINE unsigned max_u(unsigned a, unsigned b) {
|
||||
return (a > b ? a : b);
|
||||
}
|
||||
|
||||
INLINE long long floor_ms(long double now) {
|
||||
return (long long) now - (now < (long long) now); // floor()
|
||||
return (long long)now - (now < (long long)now); // floor()
|
||||
}
|
||||
|
||||
INLINE void get_now(clockid_t clk_id, time_t *sec, long *msec) {
|
||||
|
||||
Reference in New Issue
Block a user