refactoring

This commit is contained in:
Maxim Devaev 2026-01-22 12:51:17 +02:00
parent efbb2aa7ba
commit a6f111f7cf
46 changed files with 786 additions and 613 deletions

View File

@ -107,11 +107,14 @@ void us_au_pcm_destroy(us_au_pcm_s *pcm) {
void us_au_pcm_mix(us_au_pcm_s *dest, us_au_pcm_s *src) { void us_au_pcm_mix(us_au_pcm_s *dest, us_au_pcm_s *src) {
const uz size = src->frames * US_RTP_OPUS_CH * 2; // 2 for 16 bit const uz size = src->frames * US_RTP_OPUS_CH * 2; // 2 for 16 bit
if (src->frames == 0) { if (src->frames == 0) {
return; return;
} else if (dest->frames == 0) { } else if (dest->frames == 0) {
memcpy(dest->data, src->data, size); memcpy(dest->data, src->data, size);
dest->frames = src->frames; dest->frames = src->frames;
} else if (dest->frames == src->frames) { } else if (dest->frames == src->frames) {
// https://stackoverflow.com/questions/12089662 // https://stackoverflow.com/questions/12089662
for (uz index = 0; index < size; ++index) { for (uz index = 0; index < size; ++index) {

View File

@ -33,7 +33,7 @@ max-line-length = 160
[BASIC] [BASIC]
# Good variable names which should always be accepted, separated by a comma # Good variable names which should always be accepted, separated by a comma
good-names = _, __, x, y, ws, make-html-h, make-ico-h good-names = _, __, i, x, y, ws, make-html-h, make-ico-h
# Regular expression matching correct method names # Regular expression matching correct method names
method-rgx = [a-z_][a-z0-9_]{2,50}$ method-rgx = [a-z_][a-z0-9_]{2,50}$

View File

@ -1,3 +1,25 @@
/*****************************************************************************
# #
# uStreamer - Lightweight and fast MJPEG-HTTP streamer. #
# #
# Copyright (C) 2018-2024 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/>. #
# #
*****************************************************************************/
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>

View File

@ -24,56 +24,73 @@
us_output_file_s *us_output_file_init(const char *path, bool json) { us_output_file_s *us_output_file_init(const char *path, bool json) {
us_output_file_s *output; us_output_file_s *out;
US_CALLOC(output, 1); US_CALLOC(out, 1);
if (!strcmp(path, "-")) { if (!strcmp(path, "-")) {
US_LOG_INFO("Using output: <stdout>"); US_LOG_INFO("Using output: <stdout>");
output->fp = stdout; out->fp = stdout;
} else { } else {
US_LOG_INFO("Using output: %s", path); US_LOG_INFO("Using output: %s", path);
if ((output->fp = fopen(path, "wb")) == NULL) { if ((out->fp = fopen(path, "wb")) == NULL) {
US_LOG_PERROR("Can't open output file"); US_LOG_PERROR("Can't open output file");
goto error; goto error;
} }
} }
output->json = json; out->json = json;
return output; return out;
error: error:
us_output_file_destroy(output); us_output_file_destroy(out);
return NULL; return NULL;
} }
void us_output_file_write(void *v_output, const us_frame_s *frame) { void us_output_file_write(void *v_out, const us_frame_s *frame) {
us_output_file_s *output = v_output; us_output_file_s *out = v_out;
if (output->json) { if (out->json) {
us_base64_encode(frame->data, frame->used, &output->base64_data, &output->base64_allocated); us_base64_encode(frame->data, frame->used, &out->base64_data, &out->base64_allocated);
fprintf(output->fp, fprintf(
"{\"size\": %zu, \"width\": %u, \"height\": %u," out->fp,
" \"format\": %u, \"stride\": %u, \"online\": %u, \"key\": %u, \"gop\": %u," "{\"size\": %zu,"
" \"grab_begin_ts\": %.3Lf, \"grab_end_ts\": %.3Lf," " \"width\": %u,"
" \"encode_begin_ts\": %.3Lf, \"encode_end_ts\": %.3Lf," " \"height\": %u,"
" \"format\": %u,"
" \"stride\": %u,"
" \"online\": %u,"
" \"key\": %u,"
" \"gop\": %u,"
" \"grab_begin_ts\": %.3Lf,"
" \"grab_end_ts\": %.3Lf,"
" \"encode_begin_ts\": %.3Lf,"
" \"encode_end_ts\": %.3Lf,"
" \"data\": \"%s\"}\n", " \"data\": \"%s\"}\n",
frame->used, frame->width, frame->height, frame->used,
frame->format, frame->stride, frame->online, frame->key, frame->gop, frame->width,
frame->grab_begin_ts, frame->grab_end_ts, frame->height,
frame->encode_begin_ts, frame->encode_end_ts, frame->format,
output->base64_data); frame->stride,
frame->online,
frame->key,
frame->gop,
frame->grab_begin_ts,
frame->grab_end_ts,
frame->encode_begin_ts,
frame->encode_end_ts,
out->base64_data);
} else { } else {
fwrite(frame->data, 1, frame->used, output->fp); fwrite(frame->data, 1, frame->used, out->fp);
} }
fflush(output->fp); fflush(out->fp);
} }
void us_output_file_destroy(void *v_output) { void us_output_file_destroy(void *v_out) {
us_output_file_s *output = v_output; us_output_file_s *out = v_out;
US_DELETE(output->base64_data, free); US_DELETE(out->base64_data, free);
if (output->fp && output->fp != stdout) { if (out->fp && out->fp != stdout) {
if (fclose(output->fp) < 0) { if (fclose(out->fp) < 0) {
US_LOG_PERROR("Can't close output file"); US_LOG_PERROR("Can't close output file");
} }
} }
free(output); free(out);
} }

View File

@ -45,5 +45,5 @@ typedef struct {
us_output_file_s *us_output_file_init(const char *path, bool json); us_output_file_s *us_output_file_init(const char *path, bool json);
void us_output_file_write(void *v_output, const us_frame_s *frame); void us_output_file_write(void *v_out, const us_frame_s *frame);
void us_output_file_destroy(void *v_output); void us_output_file_destroy(void *v_out);

View File

@ -90,17 +90,19 @@ volatile bool _g_stop = false;
typedef struct { typedef struct {
void *v_output; void *v_out;
void (*write)(void *v_output, const us_frame_s *frame); void (*write)(void *v_out, const us_frame_s *frame);
void (*destroy)(void *v_output); void (*destroy)(void *v_out);
} _output_context_s; } _output_context_s;
static void _signal_handler(int signum); static void _signal_handler(int signum);
static int _dump_sink( static int _dump_sink(
const char *sink_name, unsigned sink_timeout, const char *sink_name,
long long count, long double interval, unsigned sink_timeout,
long long count,
long double interval,
bool key_required, bool key_required,
_output_context_s *ctx); _output_context_s *ctx);
@ -113,8 +115,8 @@ int main(int argc, char *argv[]) {
const char *sink_name = NULL; const char *sink_name = NULL;
unsigned sink_timeout = 1; unsigned sink_timeout = 1;
const char *output_path = NULL; const char *out_path = NULL;
bool output_json = false; bool out_json = false;
long long count = 0; long long count = 0;
long double interval = 0; long double interval = 0;
bool key_required = false; bool key_required = false;
@ -151,8 +153,8 @@ int main(int argc, char *argv[]) {
switch (ch) { switch (ch) {
case _O_SINK: OPT_SET(sink_name, optarg); case _O_SINK: OPT_SET(sink_name, optarg);
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0); case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
case _O_OUTPUT: OPT_SET(output_path, optarg); case _O_OUTPUT: OPT_SET(out_path, optarg);
case _O_OUTPUT_JSON: OPT_SET(output_json, true); case _O_OUTPUT_JSON: OPT_SET(out_json, true);
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0); case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60); case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
case _O_KEY_REQUIRED: OPT_SET(key_required, true); case _O_KEY_REQUIRED: OPT_SET(key_required, true);
@ -183,8 +185,8 @@ int main(int argc, char *argv[]) {
_output_context_s ctx = {0}; _output_context_s ctx = {0};
if (output_path && output_path[0] != '\0') { if (out_path && out_path[0] != '\0') {
if ((ctx.v_output = (void*)us_output_file_init(output_path, output_json)) == NULL) { if ((ctx.v_out = (void*)us_output_file_init(out_path, out_json)) == NULL) {
return 1; return 1;
} }
ctx.write = us_output_file_write; ctx.write = us_output_file_write;
@ -193,8 +195,8 @@ int main(int argc, char *argv[]) {
us_install_signals_handler(_signal_handler, false); us_install_signals_handler(_signal_handler, false);
const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, key_required, &ctx)); const int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, key_required, &ctx));
if (ctx.v_output && ctx.destroy) { if (ctx.v_out && ctx.destroy) {
ctx.destroy(ctx.v_output); ctx.destroy(ctx.v_out);
} }
return retval; return retval;
} }
@ -208,10 +210,13 @@ static void _signal_handler(int signum) {
} }
static int _dump_sink( static int _dump_sink(
const char *sink_name, unsigned sink_timeout, const char *sink_name,
long long count, long double interval, unsigned sink_timeout,
long long count,
long double interval,
bool key_required, bool key_required,
_output_context_s *ctx) { _output_context_s *ctx
) {
int retval = -1; int retval = -1;
@ -259,8 +264,8 @@ static int _dump_sink(
us_fpsi_update(fpsi, true, NULL); us_fpsi_update(fpsi, true, NULL);
if (ctx->v_output != NULL) { if (ctx->v_out != NULL) {
ctx->write(ctx->v_output, frame); ctx->write(ctx->v_out, frame);
} }
if (count >= 0) { if (count >= 0) {

View File

@ -30,8 +30,8 @@
#define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \ #define US_ARRAY_ITERATE(x_array, x_start, x_item_ptr, ...) { \
const int m_len = US_ARRAY_LEN(x_array); \ const int m_len = US_ARRAY_LEN(x_array); \
assert(x_start <= m_len); \ assert(x_start <= m_len); \
for (int m_index = x_start; m_index < m_len; ++m_index) { \ for (int m_i = x_start; m_i < m_len; ++m_i) { \
__typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_index]; \ __typeof__((x_array)[0]) *const x_item_ptr = &x_array[m_i]; \
__VA_ARGS__ \ __VA_ARGS__ \
} \ } \
} }

View File

@ -54,8 +54,8 @@ void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated) {
} }
} }
for (uint data_index = 0, encoded_index = 0; data_index < size;) { for (uint data_i = 0, encoded_i = 0; data_i < size;) {
# define OCTET(_name) uint _name = (data_index < size ? (u8)data[data_index++] : 0) # define OCTET(_name) uint _name = (data_i < size ? (u8)data[data_i++] : 0)
OCTET(octet_a); OCTET(octet_a);
OCTET(octet_b); OCTET(octet_b);
OCTET(octet_c); OCTET(octet_c);
@ -63,7 +63,7 @@ void us_base64_encode(const u8 *data, uz size, char **encoded, uz *allocated) {
const uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; const uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
# define ENCODE(_offset) (*encoded)[encoded_index++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F] # define ENCODE(_offset) (*encoded)[encoded_i++] = _ENCODING_TABLE[(triple >> _offset * 6) & 0x3F]
ENCODE(3); ENCODE(3);
ENCODE(2); ENCODE(2);
ENCODE(1); ENCODE(1);

View File

@ -103,12 +103,21 @@ static int _capture_open_export_to_dma(us_capture_s *cap);
static int _capture_apply_resolution(us_capture_s *cap, uint width, uint height, float hz); static int _capture_apply_resolution(us_capture_s *cap, uint width, uint height, float hz);
static void _capture_apply_controls(const us_capture_s *cap); static void _capture_apply_controls(const us_capture_s *cap);
static int _capture_query_control( static int _capture_query_control(
const us_capture_s *cap, struct v4l2_queryctrl *query, const us_capture_s *cap,
const char *name, uint cid, bool quiet); struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet);
static void _capture_set_control( static void _capture_set_control(
const us_capture_s *cap, const struct v4l2_queryctrl *query, const us_capture_s *cap,
const char *name, uint cid, int value, bool quiet); const struct v4l2_queryctrl *query,
const char *name,
uint cid,
int value,
bool quiet);
static const char *_format_to_string_nullable(uint format); static const char *_format_to_string_nullable(uint format);
static const char *_format_to_string_supported(uint format); static const char *_format_to_string_supported(uint format);
@ -306,15 +315,15 @@ void us_capture_close(us_capture_s *cap) {
if (run->bufs != NULL) { if (run->bufs != NULL) {
say = true; say = true;
_LOG_DEBUG("Releasing HW buffers ..."); _LOG_DEBUG("Releasing HW buffers ...");
for (uint index = 0; index < run->n_bufs; ++index) { for (uint i = 0; i < run->n_bufs; ++i) {
us_capture_hwbuf_s *hw = &run->bufs[index]; us_capture_hwbuf_s *hw = &run->bufs[i];
US_CLOSE_FD(hw->dma_fd); US_CLOSE_FD(hw->dma_fd);
if (cap->io_method == V4L2_MEMORY_MMAP) { if (cap->io_method == V4L2_MEMORY_MMAP) {
if (hw->raw.allocated > 0 && hw->raw.data != NULL) { if (hw->raw.allocated > 0 && hw->raw.data != NULL) {
if (munmap(hw->raw.data, hw->raw.allocated) < 0) { if (munmap(hw->raw.data, hw->raw.allocated) < 0) {
_LOG_PERROR("Can't unmap HW buffer=%u", index); _LOG_PERROR("Can't unmap HW buffer=%u", i);
} }
} }
} else { // V4L2_MEMORY_USERPTR } else { // V4L2_MEMORY_USERPTR
@ -451,7 +460,8 @@ int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw) {
(*hw)->raw.grab_end_ts = us_get_now_monotonic(); (*hw)->raw.grab_end_ts = us_get_now_monotonic();
_LOG_DEBUG("Grabbed HW buffer=%u: bytesused=%u, grab_begin_ts=%.3Lf, grab_end_ts=%.3Lf, latency=%.3Lf, skipped=%u", _LOG_DEBUG("Grabbed HW buffer=%u: bytesused=%u, grab_begin_ts=%.3Lf, grab_end_ts=%.3Lf, latency=%.3Lf, skipped=%u",
buf.index, buf.bytesused, buf.index,
buf.bytesused,
(*hw)->raw.grab_begin_ts, (*hw)->raw.grab_begin_ts,
(*hw)->raw.grab_end_ts, (*hw)->raw.grab_end_ts,
(*hw)->raw.grab_end_ts - (*hw)->raw.grab_begin_ts, (*hw)->raw.grab_end_ts - (*hw)->raw.grab_begin_ts,
@ -462,14 +472,14 @@ int us_capture_hwbuf_grab(us_capture_s *cap, us_capture_hwbuf_s **hw) {
int us_capture_hwbuf_release(const us_capture_s *cap, us_capture_hwbuf_s *hw) { int us_capture_hwbuf_release(const us_capture_s *cap, us_capture_hwbuf_s *hw) {
assert(atomic_load(&hw->refs) == 0); assert(atomic_load(&hw->refs) == 0);
const uint index = hw->buf.index; const uint i = hw->buf.index;
_LOG_DEBUG("Releasing HW buffer=%u ...", index); _LOG_DEBUG("Releasing HW buffer=%u ...", i);
if (us_xioctl(cap->run->fd, VIDIOC_QBUF, &hw->buf) < 0) { if (us_xioctl(cap->run->fd, VIDIOC_QBUF, &hw->buf) < 0) {
_LOG_PERROR("Can't release HW buffer=%u", index); _LOG_PERROR("Can't release HW buffer=%u", i);
return -1; return -1;
} }
hw->grabbed = false; hw->grabbed = false;
_LOG_DEBUG("HW buffer=%u released", index); _LOG_DEBUG("HW buffer=%u released", i);
return 0; return 0;
} }
@ -850,31 +860,31 @@ static void _capture_open_hw_fps(us_capture_s *cap) { // cppcheck-suppress const
return; return;
} }
# define SETFPS_TPF(x_next) setfps.parm.capture.timeperframe.x_next # define TPF(x_next) setfps.parm.capture.timeperframe.x_next
US_MEMSET_ZERO(setfps); US_MEMSET_ZERO(setfps);
setfps.type = run->capture_type; setfps.type = run->capture_type;
SETFPS_TPF(numerator) = 1; TPF(numerator) = 1;
SETFPS_TPF(denominator) = -1; // Request maximum possible FPS TPF(denominator) = -1; // Request maximum possible FPS
if (us_xioctl(run->fd, VIDIOC_S_PARM, &setfps) < 0) { if (us_xioctl(run->fd, VIDIOC_S_PARM, &setfps) < 0) {
_LOG_PERROR("Can't set HW FPS"); _LOG_PERROR("Can't set HW FPS");
return; return;
} }
if (SETFPS_TPF(numerator) != 1) { if (TPF(numerator) != 1) {
_LOG_ERROR("Invalid HW FPS numerator: %u != 1", SETFPS_TPF(numerator)); _LOG_ERROR("Invalid HW FPS numerator: %u != 1", TPF(numerator));
return; return;
} }
if (SETFPS_TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай if (TPF(denominator) == 0) { // Не знаю, бывает ли так, но пускай на всякий случай
_LOG_ERROR("Invalid HW FPS denominator: 0"); _LOG_ERROR("Invalid HW FPS denominator: 0");
return; return;
} }
_LOG_INFO("Using HW FPS: %u/%u", SETFPS_TPF(numerator), SETFPS_TPF(denominator)); _LOG_INFO("Using HW FPS: %u/%u", TPF(numerator), TPF(denominator));
# undef SETFPS_TPF # undef TPF
} }
static void _capture_open_jpeg_quality(us_capture_s *cap) { static void _capture_open_jpeg_quality(us_capture_s *cap) {
@ -1017,12 +1027,12 @@ static int _capture_open_io_method_userptr(us_capture_s *cap) {
static int _capture_open_queue_buffers(us_capture_s *cap) { static int _capture_open_queue_buffers(us_capture_s *cap) {
us_capture_runtime_s *const run = cap->run; us_capture_runtime_s *const run = cap->run;
for (uint index = 0; index < run->n_bufs; ++index) { for (uint i = 0; i < run->n_bufs; ++i) {
struct v4l2_buffer buf = {0}; struct v4l2_buffer buf = {0};
struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0};
buf.type = run->capture_type; buf.type = run->capture_type;
buf.memory = cap->io_method; buf.memory = cap->io_method;
buf.index = index; buf.index = i;
if (run->capture_mplane) { if (run->capture_mplane) {
buf.m.planes = planes; buf.m.planes = planes;
buf.length = 1; buf.length = 1;
@ -1031,11 +1041,11 @@ static int _capture_open_queue_buffers(us_capture_s *cap) {
if (cap->io_method == V4L2_MEMORY_USERPTR) { if (cap->io_method == V4L2_MEMORY_USERPTR) {
// I am not sure, may be this is incorrect for mplane device, // I am not sure, may be this is incorrect for mplane device,
// but i don't have one which supports V4L2_MEMORY_USERPTR // but i don't have one which supports V4L2_MEMORY_USERPTR
buf.m.userptr = (unsigned long)run->bufs[index].raw.data; buf.m.userptr = (unsigned long)run->bufs[i].raw.data;
buf.length = run->bufs[index].raw.allocated; buf.length = run->bufs[i].raw.allocated;
} }
_LOG_DEBUG("Calling us_xioctl(VIDIOC_QBUF) for buffer=%u ...", index); _LOG_DEBUG("Calling us_xioctl(VIDIOC_QBUF) for buffer=%u ...", i);
if (us_xioctl(run->fd, VIDIOC_QBUF, &buf) < 0) { if (us_xioctl(run->fd, VIDIOC_QBUF, &buf) < 0) {
_LOG_PERROR("Can't VIDIOC_QBUF"); _LOG_PERROR("Can't VIDIOC_QBUF");
return -1; return -1;
@ -1047,23 +1057,23 @@ static int _capture_open_queue_buffers(us_capture_s *cap) {
static int _capture_open_export_to_dma(us_capture_s *cap) { static int _capture_open_export_to_dma(us_capture_s *cap) {
us_capture_runtime_s *const run = cap->run; us_capture_runtime_s *const run = cap->run;
for (uint index = 0; index < run->n_bufs; ++index) { for (uint i = 0; i < run->n_bufs; ++i) {
struct v4l2_exportbuffer exp = { struct v4l2_exportbuffer exp = {
.type = run->capture_type, .type = run->capture_type,
.index = index, .index = i,
}; };
_LOG_DEBUG("Exporting device buffer=%u to DMA ...", index); _LOG_DEBUG("Exporting device buffer=%u to DMA ...", i);
if (us_xioctl(run->fd, VIDIOC_EXPBUF, &exp) < 0) { if (us_xioctl(run->fd, VIDIOC_EXPBUF, &exp) < 0) {
_LOG_PERROR("Can't export device buffer=%u to DMA", index); _LOG_PERROR("Can't export device buffer=%u to DMA", i);
goto error; goto error;
} }
run->bufs[index].dma_fd = exp.fd; run->bufs[i].dma_fd = exp.fd;
} }
return 0; return 0;
error: error:
for (uint index = 0; index < run->n_bufs; ++index) { for (uint i = 0; i < run->n_bufs; ++i) {
US_CLOSE_FD(run->bufs[index].dma_fd); US_CLOSE_FD(run->bufs[i].dma_fd);
} }
return -1; return -1;
} }
@ -1142,9 +1152,12 @@ static void _capture_apply_controls(const us_capture_s *cap) {
} }
static int _capture_query_control( static int _capture_query_control(
const us_capture_s *cap, struct v4l2_queryctrl *query, const us_capture_s *cap,
const char *name, uint cid, bool quiet) { struct v4l2_queryctrl *query,
const char *name,
uint cid,
bool quiet
) {
// cppcheck-suppress redundantPointerOp // cppcheck-suppress redundantPointerOp
US_MEMSET_ZERO(*query); US_MEMSET_ZERO(*query);
query->id = cid; query->id = cid;
@ -1159,9 +1172,13 @@ static int _capture_query_control(
} }
static void _capture_set_control( static void _capture_set_control(
const us_capture_s *cap, const struct v4l2_queryctrl *query, const us_capture_s *cap,
const char *name, uint cid, int value, bool quiet) { const struct v4l2_queryctrl *query,
const char *name,
uint cid,
int value,
bool quiet
) {
if (value < query->minimum || value > query->maximum || value % query->step != 0) { if (value < query->minimum || value > query->maximum || value % query->step != 0) {
if (!quiet) { if (!quiet) {
_LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u", _LOG_ERROR("Invalid value %d of control %s: min=%d, max=%d, default=%d, step=%u",

View File

@ -61,11 +61,11 @@ static const char *_connector_type_to_string(u32 type);
static float _get_refresh_rate(const drmModeModeInfo *mode); static float _get_refresh_rate(const drmModeModeInfo *mode);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_INFO(x_msg, ...) US_LOG_INFO("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("DRM: " x_msg, ##__VA_ARGS__)
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__) #define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("DRM: " x_msg, ##__VA_ARGS__)
us_drm_s *us_drm_init(void) { us_drm_s *us_drm_init(void) {
@ -164,7 +164,12 @@ int us_drm_open(us_drm_s *drm, const us_capture_s *cap) {
run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id); run->saved_crtc = drmModeGetCrtc(run->fd, run->crtc_id);
_LOG_DEBUG("Setting up CRTC ..."); _LOG_DEBUG("Setting up CRTC ...");
if (drmModeSetCrtc(run->fd, run->crtc_id, run->bufs[0].id, 0, 0, &run->conn_id, 1, &run->mode) < 0) { if (drmModeSetCrtc(
run->fd,
run->crtc_id, run->bufs[0].id,
0, 0, // X, Y
&run->conn_id, 1, &run->mode
) < 0) {
_LOG_PERROR("Can't set CRTC"); _LOG_PERROR("Can't set CRTC");
goto error; goto error;
} }
@ -202,7 +207,8 @@ void us_drm_close(us_drm_s *drm) {
if (run->saved_crtc != NULL) { if (run->saved_crtc != NULL) {
_LOG_DEBUG("Restoring CRTC ..."); _LOG_DEBUG("Restoring CRTC ...");
if (drmModeSetCrtc(run->fd, if (drmModeSetCrtc(
run->fd,
run->saved_crtc->crtc_id, run->saved_crtc->buffer_id, run->saved_crtc->crtc_id, run->saved_crtc->buffer_id,
run->saved_crtc->x, run->saved_crtc->y, run->saved_crtc->x, run->saved_crtc->y,
&run->conn_id, 1, &run->saved_crtc->mode &run->conn_id, 1, &run->saved_crtc->mode

View File

@ -38,7 +38,7 @@ us_fpsi_s *us_fpsi_init(const char *name, bool with_meta) {
US_CALLOC(fpsi, 1); US_CALLOC(fpsi, 1);
fpsi->name = us_strdup(name); fpsi->name = us_strdup(name);
fpsi->with_meta = with_meta; fpsi->with_meta = with_meta;
atomic_init(&fpsi->state_sec_ts, 0); atomic_init(&fpsi->state_ts, 0);
atomic_init(&fpsi->state, 0); atomic_init(&fpsi->state, 0);
return fpsi; return fpsi;
} }
@ -61,8 +61,8 @@ void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
assert(!fpsi->with_meta); assert(!fpsi->with_meta);
} }
const sll now_sec_ts = us_floor_ms(us_get_now_monotonic()); const sll now_ts = us_floor_ms(us_get_now_monotonic());
if (atomic_load(&fpsi->state_sec_ts) != now_sec_ts) { if (atomic_load(&fpsi->state_ts) != now_ts) {
US_LOG_PERF_FPS("FPS: %s: %u", fpsi->name, fpsi->accum); US_LOG_PERF_FPS("FPS: %s: %u", fpsi->name, fpsi->accum);
// Fast mutex-less store method // Fast mutex-less store method
@ -74,7 +74,7 @@ void us_fpsi_update(us_fpsi_s *fpsi, bool bump, const us_fpsi_meta_s *meta) {
state |= (ull)(meta->online ? 1 : 0) << 48; state |= (ull)(meta->online ? 1 : 0) << 48;
} }
atomic_store(&fpsi->state, state); // Сначала инфа atomic_store(&fpsi->state, state); // Сначала инфа
atomic_store(&fpsi->state_sec_ts, now_sec_ts); // Потом время, это важно atomic_store(&fpsi->state_ts, now_ts); // Потом время, это важно
fpsi->accum = 0; fpsi->accum = 0;
} }
if (bump) { if (bump) {
@ -90,8 +90,8 @@ uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
// Между чтением инфы и времени может быть гонка, // Между чтением инфы и времени может быть гонка,
// но это неважно. Если время свежее, до данные тоже // но это неважно. Если время свежее, до данные тоже
// будут свежмими, обратный случай не так важен. // будут свежмими, обратный случай не так важен.
const sll now_sec_ts = us_floor_ms(us_get_now_monotonic()); const sll now_ts = us_floor_ms(us_get_now_monotonic());
const sll state_sec_ts = atomic_load(&fpsi->state_sec_ts); // Сначала время const sll state_ts = atomic_load(&fpsi->state_ts); // Сначала время
const ull state = atomic_load(&fpsi->state); // Потом инфа const ull state = atomic_load(&fpsi->state); // Потом инфа
uint current = state & 0xFFFF; uint current = state & 0xFFFF;
@ -101,7 +101,7 @@ uint us_fpsi_get(us_fpsi_s *fpsi, us_fpsi_meta_s *meta) {
meta->online = (state >> 48) & 1; meta->online = (state >> 48) & 1;
} }
if (state_sec_ts != now_sec_ts && (state_sec_ts + 1) != now_sec_ts) { if (state_ts != now_ts && (state_ts + 1) != now_ts) {
// Только текущая или прошлая секунда // Только текущая или прошлая секунда
current = 0; current = 0;
} }

View File

@ -38,7 +38,7 @@ typedef struct {
char *name; char *name;
bool with_meta; bool with_meta;
uint accum; uint accum;
atomic_llong state_sec_ts; atomic_llong state_ts;
atomic_ullong state; atomic_ullong state;
} us_fpsi_s; } us_fpsi_s;

View File

@ -34,9 +34,12 @@
static void _frametext_draw_line( static void _frametext_draw_line(
us_frametext_s *ft, const char *line, us_frametext_s *ft,
uint scale_x, uint scale_y, const char *line,
uint start_x, uint start_y); uint scale_x,
uint scale_y,
uint start_x,
uint start_y);
us_frametext_s *us_frametext_init(void) { us_frametext_s *us_frametext_init(void) {
@ -156,10 +159,13 @@ empty:
} }
void _frametext_draw_line( void _frametext_draw_line(
us_frametext_s *ft, const char *line, us_frametext_s *ft,
uint scale_x, uint scale_y, const char *line,
uint start_x, uint start_y) { uint scale_x,
uint scale_y,
uint start_x,
uint start_y
) {
us_frame_s *const frame = ft->frame; us_frame_s *const frame = ft->frame;
const size_t len = strlen(line); const size_t len = strlen(line);

View File

@ -75,7 +75,7 @@ extern pthread_mutex_t us_g_log_mutex;
#define US_SEP_INFO(x_ch) { \ #define US_SEP_INFO(x_ch) { \
US_LOGGING_LOCK; \ US_LOGGING_LOCK; \
for (int m_count = 0; m_count < 80; ++m_count) { \ for (int m_i = 0; m_i < 80; ++m_i) { \
fputc((x_ch), stderr); \ fputc((x_ch), stderr); \
} \ } \
fputc('\n', stderr); \ fputc('\n', stderr); \

View File

@ -32,14 +32,14 @@
void us_build_short_options(const struct option opts[], char *short_opts, uz size) { void us_build_short_options(const struct option opts[], char *short_opts, uz size) {
memset(short_opts, 0, size); memset(short_opts, 0, size);
for (uint short_index = 0, opt_index = 0; opts[opt_index].name != NULL; ++opt_index) { for (uint short_i = 0, opt_i = 0; opts[opt_i].name != NULL; ++opt_i) {
assert(short_index < size - 3); assert(short_i < size - 3);
if (isalpha(opts[opt_index].val)) { if (isalpha(opts[opt_i].val)) {
short_opts[short_index] = opts[opt_index].val; short_opts[short_i] = opts[opt_i].val;
++short_index; ++short_i;
if (opts[opt_index].has_arg == required_argument) { if (opts[opt_i].has_arg == required_argument) {
short_opts[short_index] = ':'; short_opts[short_i] = ':';
++short_index; ++short_i;
} }
} }
} }

View File

@ -71,13 +71,13 @@ INLINE int us_process_track_parent_death(void) {
const pid_t parent = getppid(); const pid_t parent = getppid();
int signum = SIGTERM; int signum = SIGTERM;
# if defined(__linux__) # if defined(__linux__)
const int retval = prctl(PR_SET_PDEATHSIG, signum); const int result = prctl(PR_SET_PDEATHSIG, signum);
# elif defined(__FreeBSD__) # elif defined(__FreeBSD__)
const int retval = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum); const int result = procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum);
# else # else
# error WTF? # error WTF?
# endif # endif
if (retval < 0) { if (result < 0) {
US_LOG_PERROR("Can't set to receive SIGTERM on parent process death"); US_LOG_PERROR("Can't set to receive SIGTERM on parent process death");
return -1; return -1;
} }
@ -103,15 +103,15 @@ INLINE void us_process_set_name_prefix(int argc, char *argv[], const char *prefi
US_REALLOC(cmdline, allocated); US_REALLOC(cmdline, allocated);
cmdline[0] = '\0'; cmdline[0] = '\0';
for (int index = 0; index < argc; ++index) { for (int i = 0; i < argc; ++i) {
uz arg_len = strlen(argv[index]); uz arg_len = strlen(argv[i]);
if (used + arg_len + 16 >= allocated) { if (used + arg_len + 16 >= allocated) {
allocated += arg_len + 2048; allocated += arg_len + 2048;
US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert) US_REALLOC(cmdline, allocated); // cppcheck-suppress memleakOnRealloc // False-positive (ok with assert)
} }
strcat(cmdline, " "); strcat(cmdline, " ");
strcat(cmdline, argv[index]); strcat(cmdline, argv[i]);
used = strlen(cmdline); // Не считаем вручную, так надежнее used = strlen(cmdline); // Не считаем вручную, так надежнее
} }

View File

@ -34,27 +34,27 @@
us_queue_s *us_queue_init(uint capacity) { us_queue_s *us_queue_init(uint capacity) {
us_queue_s *queue; us_queue_s *q;
US_CALLOC(queue, 1); US_CALLOC(q, 1);
US_CALLOC(queue->items, capacity); US_CALLOC(q->items, capacity);
queue->capacity = capacity; q->capacity = capacity;
US_MUTEX_INIT(queue->mutex); US_MUTEX_INIT(q->mutex);
pthread_condattr_t attrs; pthread_condattr_t attrs;
assert(!pthread_condattr_init(&attrs)); assert(!pthread_condattr_init(&attrs));
assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC)); assert(!pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC));
assert(!pthread_cond_init(&queue->full_cond, &attrs)); assert(!pthread_cond_init(&q->full_cond, &attrs));
assert(!pthread_cond_init(&queue->empty_cond, &attrs)); assert(!pthread_cond_init(&q->empty_cond, &attrs));
assert(!pthread_condattr_destroy(&attrs)); assert(!pthread_condattr_destroy(&attrs));
return queue; return q;
} }
void us_queue_destroy(us_queue_s *queue) { void us_queue_destroy(us_queue_s *q) {
US_COND_DESTROY(queue->empty_cond); US_COND_DESTROY(q->empty_cond);
US_COND_DESTROY(queue->full_cond); US_COND_DESTROY(q->full_cond);
US_MUTEX_DESTROY(queue->mutex); US_MUTEX_DESTROY(q->mutex);
free(queue->items); free(q->items);
free(queue); free(q);
} }
#define _WAIT_OR_UNLOCK(x_var, x_cond) { \ #define _WAIT_OR_UNLOCK(x_var, x_cond) { \
@ -62,51 +62,51 @@ void us_queue_destroy(us_queue_s *queue) {
assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \ assert(!clock_gettime(CLOCK_MONOTONIC, &m_ts)); \
us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \ us_ld_to_timespec(us_timespec_to_ld(&m_ts) + timeout, &m_ts); \
while (x_var) { \ while (x_var) { \
const int err = pthread_cond_timedwait(&(x_cond), &queue->mutex, &m_ts); \ const int err = pthread_cond_timedwait(&(x_cond), &q->mutex, &m_ts); \
if (err == ETIMEDOUT) { \ if (err == ETIMEDOUT) { \
US_MUTEX_UNLOCK(queue->mutex); \ US_MUTEX_UNLOCK(q->mutex); \
return -1; \ return -1; \
} \ } \
assert(!err); \ assert(!err); \
} \ } \
} }
int us_queue_put(us_queue_s *queue, void *item, ldf timeout) { int us_queue_put(us_queue_s *q, void *item, ldf timeout) {
US_MUTEX_LOCK(queue->mutex); US_MUTEX_LOCK(q->mutex);
if (timeout == 0) { if (timeout == 0) {
if (queue->size == queue->capacity) { if (q->size == q->capacity) {
US_MUTEX_UNLOCK(queue->mutex); US_MUTEX_UNLOCK(q->mutex);
return -1; return -1;
} }
} else { } else {
_WAIT_OR_UNLOCK(queue->size == queue->capacity, queue->full_cond); _WAIT_OR_UNLOCK(q->size == q->capacity, q->full_cond);
} }
queue->items[queue->in] = item; q->items[q->in] = item;
++queue->size; ++q->size;
++queue->in; ++q->in;
queue->in %= queue->capacity; q->in %= q->capacity;
US_MUTEX_UNLOCK(queue->mutex); US_MUTEX_UNLOCK(q->mutex);
US_COND_BROADCAST(queue->empty_cond); US_COND_BROADCAST(q->empty_cond);
return 0; return 0;
} }
int us_queue_get(us_queue_s *queue, void **item, ldf timeout) { int us_queue_get(us_queue_s *q, void **item, ldf timeout) {
US_MUTEX_LOCK(queue->mutex); US_MUTEX_LOCK(q->mutex);
_WAIT_OR_UNLOCK(queue->size == 0, queue->empty_cond); _WAIT_OR_UNLOCK(q->size == 0, q->empty_cond);
*item = queue->items[queue->out]; *item = q->items[q->out];
--queue->size; --q->size;
++queue->out; ++q->out;
queue->out %= queue->capacity; q->out %= q->capacity;
US_MUTEX_UNLOCK(queue->mutex); US_MUTEX_UNLOCK(q->mutex);
US_COND_BROADCAST(queue->full_cond); US_COND_BROADCAST(q->full_cond);
return 0; return 0;
} }
#undef _WAIT_OR_UNLOCK #undef _WAIT_OR_UNLOCK
bool us_queue_is_empty(us_queue_s *queue) { bool us_queue_is_empty(us_queue_s *q) {
US_MUTEX_LOCK(queue->mutex); US_MUTEX_LOCK(q->mutex);
const uint size = queue->size; const uint size = q->size;
US_MUTEX_UNLOCK(queue->mutex); US_MUTEX_UNLOCK(q->mutex);
return (bool)(queue->capacity - size); return (bool)(q->capacity - size);
} }

View File

@ -43,22 +43,22 @@ typedef struct {
} us_queue_s; } us_queue_s;
#define US_QUEUE_DELETE_WITH_ITEMS(x_queue, x_free_item) { \ #define US_QUEUE_DELETE_WITH_ITEMS(x_q, x_free_item) { \
if (x_queue) { \ if (x_q) { \
while (!us_queue_is_empty(x_queue)) { \ while (!us_queue_is_empty(x_q)) { \
void *m_ptr; \ void *m_ptr; \
if (!us_queue_get(x_queue, &m_ptr, 0)) { \ if (!us_queue_get(x_q, &m_ptr, 0)) { \
US_DELETE(m_ptr, x_free_item); \ US_DELETE(m_ptr, x_free_item); \
} \ } \
} \ } \
us_queue_destroy(x_queue); \ us_queue_destroy(x_q); \
} \ } \
} }
us_queue_s *us_queue_init(uint capacity); us_queue_s *us_queue_init(uint capacity);
void us_queue_destroy(us_queue_s *queue); void us_queue_destroy(us_queue_s *q);
int us_queue_put(us_queue_s *queue, void *item, ldf timeout); int us_queue_put(us_queue_s *q, void *item, ldf timeout);
int us_queue_get(us_queue_s *queue, void **item, ldf timeout); int us_queue_get(us_queue_s *q, void **item, ldf timeout);
bool us_queue_is_empty(us_queue_s *queue); bool us_queue_is_empty(us_queue_s *q);

View File

@ -29,8 +29,8 @@
#include "queue.h" #include "queue.h"
int _acquire(us_ring_s *ring, us_queue_s *queue, ldf timeout); int _acquire(us_ring_s *ring, us_queue_s *q, ldf timeout);
void _release(us_ring_s *ring, us_queue_s *queue, uint index); void _release(us_ring_s *ring, us_queue_s *q, uint ri);
us_ring_s *us_ring_init(uint capacity) { us_ring_s *us_ring_init(uint capacity) {
@ -41,9 +41,9 @@ us_ring_s *us_ring_init(uint capacity) {
ring->capacity = capacity; ring->capacity = capacity;
ring->producer = us_queue_init(capacity); ring->producer = us_queue_init(capacity);
ring->consumer = us_queue_init(capacity); ring->consumer = us_queue_init(capacity);
for (uint index = 0; index < capacity; ++index) { for (uint ri = 0; ri < capacity; ++ri) {
ring->places[index] = index; // XXX: Just to avoid casting between pointer and uint ring->places[ri] = ri; // XXX: Just to avoid casting between pointer and uint
assert(!us_queue_put(ring->producer, (void*)(ring->places + index), 0)); assert(!us_queue_put(ring->producer, (void*)(ring->places + ri), 0));
} }
return ring; return ring;
} }
@ -60,27 +60,27 @@ int us_ring_producer_acquire(us_ring_s *ring, ldf timeout) {
return _acquire(ring, ring->producer, timeout); return _acquire(ring, ring->producer, timeout);
} }
void us_ring_producer_release(us_ring_s *ring, uint index) { void us_ring_producer_release(us_ring_s *ring, uint ri) {
_release(ring, ring->consumer, index); _release(ring, ring->consumer, ri);
} }
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout) { int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout) {
return _acquire(ring, ring->consumer, timeout); return _acquire(ring, ring->consumer, timeout);
} }
void us_ring_consumer_release(us_ring_s *ring, uint index) { void us_ring_consumer_release(us_ring_s *ring, uint ri) {
_release(ring, ring->producer, index); _release(ring, ring->producer, ri);
} }
int _acquire(us_ring_s *ring, us_queue_s *queue, ldf timeout) { int _acquire(us_ring_s *ring, us_queue_s *q, ldf timeout) {
(void)ring; (void)ring;
uint *place; uint *place;
if (us_queue_get(queue, (void**)&place, timeout) < 0) { if (us_queue_get(q, (void**)&place, timeout) < 0) {
return -1; return -1;
} }
return *place; return *place;
} }
void _release(us_ring_s *ring, us_queue_s *queue, uint index) { void _release(us_ring_s *ring, us_queue_s *q, uint ri) {
assert(!us_queue_put(queue, (void*)(ring->places + index), 0)); assert(!us_queue_put(q, (void*)(ring->places + ri), 0));
} }

View File

@ -38,15 +38,15 @@ typedef struct {
#define US_RING_INIT_WITH_ITEMS(x_ring, x_capacity, x_init_item) { \ #define US_RING_INIT_WITH_ITEMS(x_ring, x_capacity, x_init_item) { \
(x_ring) = us_ring_init(x_capacity); \ (x_ring) = us_ring_init(x_capacity); \
for (uz m_index = 0; m_index < (x_ring)->capacity; ++m_index) { \ for (uz m_ri = 0; m_ri < (x_ring)->capacity; ++m_ri) { \
(x_ring)->items[m_index] = x_init_item(); \ (x_ring)->items[m_ri] = x_init_item(); \
} \ } \
} }
#define US_RING_DELETE_WITH_ITEMS(x_ring, x_destroy_item) { \ #define US_RING_DELETE_WITH_ITEMS(x_ring, x_destroy_item) { \
if (x_ring) { \ if (x_ring) { \
for (uz m_index = 0; m_index < (x_ring)->capacity; ++m_index) { \ for (uz m_ri = 0; m_ri < (x_ring)->capacity; ++m_ri) { \
x_destroy_item((x_ring)->items[m_index]); \ x_destroy_item((x_ring)->items[m_ri]); \
} \ } \
us_ring_destroy(x_ring); \ us_ring_destroy(x_ring); \
} \ } \
@ -57,7 +57,7 @@ us_ring_s *us_ring_init(uint capacity);
void us_ring_destroy(us_ring_s *ring); void us_ring_destroy(us_ring_s *ring);
int us_ring_producer_acquire(us_ring_s *ring, ldf timeout); int us_ring_producer_acquire(us_ring_s *ring, ldf timeout);
void us_ring_producer_release(us_ring_s *ring, uint index); void us_ring_producer_release(us_ring_s *ring, uint ri);
int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout); int us_ring_consumer_acquire(us_ring_s *ring, ldf timeout);
void us_ring_consumer_release(us_ring_s *ring, uint index); void us_ring_consumer_release(us_ring_s *ring, uint ri);

View File

@ -77,7 +77,7 @@ int us_unjpeg(const us_frame_s *src, us_frame_s *dest, bool decode) {
if (decode) { if (decode) {
JSAMPARRAY scanlines; JSAMPARRAY scanlines;
scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr) &jpeg, JPOOL_IMAGE, dest->stride, 1); scanlines = (*jpeg.mem->alloc_sarray)((j_common_ptr)&jpeg, JPOOL_IMAGE, dest->stride, 1);
us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2); us_frame_realloc_data(dest, ((dest->width * dest->height) << 1) * 2);
while (jpeg.output_scanline < jpeg.output_height) { while (jpeg.output_scanline < jpeg.output_height) {

View File

@ -81,8 +81,8 @@ us_encoder_s *us_encoder_init(void) {
void us_encoder_destroy(us_encoder_s *enc) { void us_encoder_destroy(us_encoder_s *enc) {
us_encoder_runtime_s *const run = enc->run; us_encoder_runtime_s *const run = enc->run;
if (run->m2ms != NULL) { if (run->m2ms != NULL) {
for (uint index = 0; index < run->n_m2ms; ++index) { for (uint i = 0; i < run->n_m2ms; ++i) {
US_DELETE(run->m2ms[index], us_m2m_encoder_destroy); US_DELETE(run->m2ms[i], us_m2m_encoder_destroy);
} }
free(run->m2ms); free(run->m2ms);
} }
@ -135,10 +135,13 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
} }
} else if (type == US_ENCODER_TYPE_M2M_VIDEO || type == US_ENCODER_TYPE_M2M_IMAGE) { } else if (type == US_ENCODER_TYPE_M2M_VIDEO || type == US_ENCODER_TYPE_M2M_IMAGE) {
US_LOG_DEBUG("Preparing M2M-%s encoder ...", (type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE")); US_LOG_DEBUG("Preparing M2M-%s encoder ...",
(type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"));
if (run->m2ms == NULL) { if (run->m2ms == NULL) {
US_CALLOC(run->m2ms, n_workers); US_CALLOC(run->m2ms, n_workers);
} }
for (; run->n_m2ms < n_workers; ++run->n_m2ms) { for (; run->n_m2ms < n_workers; ++run->n_m2ms) {
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости // Начинаем с нуля и доинициализируем на следующих заходах при необходимости
char name[32]; char name[32];
@ -163,8 +166,11 @@ void us_encoder_open(us_encoder_s *enc, us_capture_s *cap) {
US_MUTEX_UNLOCK(run->mutex); US_MUTEX_UNLOCK(run->mutex);
enc->run->pool = us_workers_pool_init( enc->run->pool = us_workers_pool_init(
"JPEG", "jw", n_workers, "JPEG",
_worker_job_init, (void*)enc, "jw",
n_workers,
_worker_job_init,
(void*)enc,
_worker_job_destroy, _worker_job_destroy,
_worker_run_job); _worker_run_job);
} }
@ -201,20 +207,20 @@ static bool _worker_run_job(us_worker_s *wr) {
us_encoder_runtime_s *const run = job->enc->run; us_encoder_runtime_s *const run = job->enc->run;
const us_frame_s *const src = &job->hw->raw; const us_frame_s *const src = &job->hw->raw;
us_frame_s *const dest = job->dest; us_frame_s *const dest = job->dest;
const uint i = job->hw->buf.index;
if (run->type == US_ENCODER_TYPE_CPU) { if (run->type == US_ENCODER_TYPE_CPU) {
US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u", US_LOG_VERBOSE("Compressing JPEG using CPU: worker=%s, buffer=%u", wr->name, i);
wr->name, job->hw->buf.index);
us_cpu_encoder_compress(src, dest, run->quality); us_cpu_encoder_compress(src, dest, run->quality);
} else if (run->type == US_ENCODER_TYPE_HW) { } else if (run->type == US_ENCODER_TYPE_HW) {
US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u", US_LOG_VERBOSE("Compressing JPEG using HW (just copying): worker=%s, buffer=%u", wr->name, i);
wr->name, job->hw->buf.index);
us_hw_encoder_compress(src, dest); us_hw_encoder_compress(src, dest);
} else if (run->type == US_ENCODER_TYPE_M2M_VIDEO || run->type == US_ENCODER_TYPE_M2M_IMAGE) { } else if (run->type == US_ENCODER_TYPE_M2M_VIDEO || run->type == US_ENCODER_TYPE_M2M_IMAGE) {
US_LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u", US_LOG_VERBOSE("Compressing JPEG using M2M-%s: worker=%s, buffer=%u",
(run->type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"), wr->name, job->hw->buf.index); (run->type == US_ENCODER_TYPE_M2M_VIDEO ? "VIDEO" : "IMAGE"),
wr->name, i);
if (us_m2m_encoder_compress(run->m2ms[wr->number], src, dest, false) < 0) { if (us_m2m_encoder_compress(run->m2ms[wr->number], src, dest, false) < 0) {
goto error; goto error;
} }
@ -226,11 +232,10 @@ static bool _worker_run_job(us_worker_s *wr) {
US_LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u", US_LOG_VERBOSE("Compressed new JPEG: size=%zu, time=%0.3Lf, worker=%s, buffer=%u",
job->dest->used, job->dest->used,
job->dest->encode_end_ts - job->dest->encode_begin_ts, job->dest->encode_end_ts - job->dest->encode_begin_ts,
wr->name, wr->name, i);
job->hw->buf.index);
return true; return true;
error: error:
US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, job->hw->buf.index); US_LOG_ERROR("Compression failed: worker=%s, buffer=%u", wr->name, i);
return false; return false;
} }

View File

@ -27,6 +27,19 @@
#include "encoder.h" #include "encoder.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../../../libs/types.h"
#include "../../../libs/tools.h"
#include "../../../libs/frame.h"
typedef struct { typedef struct {
struct jpeg_destination_mgr mgr; // Default manager struct jpeg_destination_mgr mgr; // Default manager

View File

@ -22,16 +22,7 @@
#pragma once #pragma once
#include <stdio.h> #include "../../../libs/types.h"
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <jpeglib.h>
#include <linux/videodev2.h>
#include "../../../libs/tools.h"
#include "../../../libs/frame.h" #include "../../../libs/frame.h"

View File

@ -27,9 +27,19 @@
#include "encoder.h" #include "encoder.h"
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "../../../libs/types.h"
#include "../../../libs/frame.h"
#include "huffman.h"
void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest); void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest);
static bool _is_huffman(const uint8_t *data); static bool _is_huffman(const u8 *data);
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) { void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest) {
@ -41,8 +51,8 @@ void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG); us_frame_encoding_begin(src, dest, V4L2_PIX_FMT_JPEG);
if (!_is_huffman(src->data)) { if (!_is_huffman(src->data)) {
const uint8_t *src_ptr = src->data; const u8 *src_ptr = src->data;
const uint8_t *const src_end = src->data + src->used; const u8 *const src_end = src->data + src->used;
while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) { while ((((src_ptr[0] << 8) | src_ptr[1]) != 0xFFC0) && (src_ptr < src_end)) {
src_ptr += 1; src_ptr += 1;
@ -52,7 +62,7 @@ void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
return; return;
} }
const size_t paste = src_ptr - src->data; const uz paste = src_ptr - src->data;
us_frame_set_data(dest, src->data, paste); us_frame_set_data(dest, src->data, paste);
us_frame_append_data(dest, US_HUFFMAN_TABLE, sizeof(US_HUFFMAN_TABLE)); us_frame_append_data(dest, US_HUFFMAN_TABLE, sizeof(US_HUFFMAN_TABLE));
@ -65,14 +75,14 @@ void _copy_plus_huffman(const us_frame_s *src, us_frame_s *dest) {
us_frame_encoding_end(dest); us_frame_encoding_end(dest);
} }
static bool _is_huffman(const uint8_t *data) { static bool _is_huffman(const u8 *data) {
unsigned count = 0; uint count = 0;
while ((((uint16_t)data[0] << 8) | data[1]) != 0xFFDA) { while ((((u16)data[0] << 8) | data[1]) != 0xFFDA) {
if (count++ > 2048) { if (count++ > 2048) {
return false; return false;
} }
if ((((uint16_t)data[0] << 8) | data[1]) == 0xFFC4) { if ((((u16)data[0] << 8) | data[1]) == 0xFFC4) {
return true; return true;
} }
data += 1; data += 1;

View File

@ -22,16 +22,7 @@
#pragma once #pragma once
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <linux/videodev2.h>
#include "../../../libs/frame.h" #include "../../../libs/frame.h"
#include "huffman.h"
void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest); void us_hw_encoder_compress(const us_frame_s *src, us_frame_s *dest);

View File

@ -27,10 +27,10 @@
#pragma once #pragma once
#include <stdint.h> #include "../../../libs/types.h"
static const uint8_t US_HUFFMAN_TABLE[] = { static const u8 US_HUFFMAN_TABLE[] = {
0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x01, 0x00, 0x03,

View File

@ -32,7 +32,7 @@ us_gpio_s us_g_gpio = {
.role = x_role, \ .role = x_role, \
.consumer = NULL, \ .consumer = NULL, \
.line = NULL, \ .line = NULL, \
.state = false \ .on = false \
} }
.prog_running = MAKE_OUTPUT("prog-running"), .prog_running = MAKE_OUTPUT("prog-running"),
.stream_online = MAKE_OUTPUT("stream-online"), .stream_online = MAKE_OUTPUT("stream-online"),
@ -46,8 +46,8 @@ us_gpio_s us_g_gpio = {
}; };
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip); static void _gpio_output_init(us_gpio_output_s *out, struct gpiod_chip *chip);
static void _gpio_output_destroy(us_gpio_output_s *output); static void _gpio_output_destroy(us_gpio_output_s *out);
void us_gpio_init(void) { void us_gpio_init(void) {
@ -92,23 +92,23 @@ void us_gpio_destroy(void) {
} }
} }
int us_gpio_inner_set(us_gpio_output_s *output, bool state) { int us_gpio_inner_set(us_gpio_output_s *out, bool on) {
int retval = 0; int retval = 0;
# ifndef HAVE_GPIOD2 # ifndef HAVE_GPIOD2
assert(us_g_gpio.chip != NULL); assert(us_g_gpio.chip != NULL);
# endif # endif
assert(output->line != NULL); assert(out->line != NULL);
assert(output->state != state); // Must be checked in macro for the performance assert(out->on != on); // Must be checked in macro for the performance
US_MUTEX_LOCK(us_g_gpio.mutex); US_MUTEX_LOCK(us_g_gpio.mutex);
# ifdef HAVE_GPIOD2 # ifdef HAVE_GPIOD2
if (gpiod_line_request_set_value(output->line, output->pin, state) < 0) { if (gpiod_line_request_set_value(out->line, out->pin, on) < 0) {
# else # else
if (gpiod_line_set_value(output->line, (int)state) < 0) { if (gpiod_line_set_value(out->line, (int)on) < 0) {
# endif # endif
US_LOG_PERROR("GPIO: Can't write value %d to line %s", state, output->consumer); \ US_LOG_PERROR("GPIO: Can't write value %d to line %s", on, out->consumer); \
_gpio_output_destroy(output); _gpio_output_destroy(out);
retval = -1; retval = -1;
} }
@ -116,12 +116,12 @@ int us_gpio_inner_set(us_gpio_output_s *output, bool state) {
return retval; return retval;
} }
static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip) { static void _gpio_output_init(us_gpio_output_s *out, struct gpiod_chip *chip) {
assert(output->line == NULL); assert(out->line == NULL);
US_ASPRINTF(output->consumer, "%s::%s", us_g_gpio.consumer_prefix, output->role); US_ASPRINTF(out->consumer, "%s::%s", us_g_gpio.consumer_prefix, out->role);
if (output->pin >= 0) { if (out->pin >= 0) {
# ifdef HAVE_GPIOD2 # ifdef HAVE_GPIOD2
struct gpiod_line_settings *line_settings; struct gpiod_line_settings *line_settings;
assert(line_settings = gpiod_line_settings_new()); assert(line_settings = gpiod_line_settings_new());
@ -130,51 +130,51 @@ static void _gpio_output_init(us_gpio_output_s *output, struct gpiod_chip *chip)
struct gpiod_line_config *line_config; struct gpiod_line_config *line_config;
assert(line_config = gpiod_line_config_new()); assert(line_config = gpiod_line_config_new());
const unsigned offset = output->pin; const unsigned offset = out->pin;
assert(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings)); assert(!gpiod_line_config_add_line_settings(line_config, &offset, 1, line_settings));
struct gpiod_request_config *request_config; struct gpiod_request_config *req_config;
assert(request_config = gpiod_request_config_new()); assert(req_config = gpiod_request_config_new());
gpiod_request_config_set_consumer(request_config, output->consumer); gpiod_request_config_set_consumer(req_config, out->consumer);
if ((output->line = gpiod_chip_request_lines(chip, request_config, line_config)) == NULL) { if ((out->line = gpiod_chip_request_lines(chip, req_config, line_config)) == NULL) {
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer); US_LOG_PERROR("GPIO: Can't request pin=%d as %s", out->pin, out->consumer);
} }
gpiod_request_config_free(request_config); gpiod_request_config_free(req_config);
gpiod_line_config_free(line_config); gpiod_line_config_free(line_config);
gpiod_line_settings_free(line_settings); gpiod_line_settings_free(line_settings);
if (output->line == NULL) { if (out->line == NULL) {
_gpio_output_destroy(output); _gpio_output_destroy(out);
} }
# else # else
if ((output->line = gpiod_chip_get_line(chip, output->pin)) != NULL) { if ((out->line = gpiod_chip_get_line(chip, out->pin)) != NULL) {
if (gpiod_line_request_output(output->line, output->consumer, 0) < 0) { if (gpiod_line_request_output(out->line, out->consumer, 0) < 0) {
US_LOG_PERROR("GPIO: Can't request pin=%d as %s", output->pin, output->consumer); US_LOG_PERROR("GPIO: Can't request pin=%d as %s", out->pin, out->consumer);
_gpio_output_destroy(output); _gpio_output_destroy(out);
} }
} else { } else {
US_LOG_PERROR("GPIO: Can't get pin=%d as %s", output->pin, output->consumer); US_LOG_PERROR("GPIO: Can't get pin=%d as %s", out->pin, out->consumer);
} }
# endif # endif
} }
} }
static void _gpio_output_destroy(us_gpio_output_s *output) { static void _gpio_output_destroy(us_gpio_output_s *out) {
if (output->line != NULL) { if (out->line != NULL) {
# ifdef HAVE_GPIOD2 # ifdef HAVE_GPIOD2
gpiod_line_request_release(output->line); gpiod_line_request_release(out->line);
# else # else
gpiod_line_release(output->line); gpiod_line_release(out->line);
# endif # endif
output->line = NULL; out->line = NULL;
} }
if (output->consumer != NULL) { if (out->consumer != NULL) {
free(output->consumer); free(out->consumer);
output->consumer = NULL; out->consumer = NULL;
} }
output->state = false; out->on = false;
} }

View File

@ -44,7 +44,7 @@ typedef struct {
# else # else
struct gpiod_line *line; struct gpiod_line *line;
# endif # endif
bool state; bool on;
} us_gpio_output_s; } us_gpio_output_s;
typedef struct { typedef struct {
@ -69,27 +69,27 @@ extern us_gpio_s us_g_gpio;
void us_gpio_init(void); void us_gpio_init(void);
void us_gpio_destroy(void); void us_gpio_destroy(void);
int us_gpio_inner_set(us_gpio_output_s *output, bool state); int us_gpio_inner_set(us_gpio_output_s *out, bool on);
#define SET_STATE(x_output, x_state) { \ #define SET_ON(x_out, x_on) { \
if (x_output.line && x_output.state != x_state) { \ if (x_out.line && x_out.on != x_on) { \
if (!us_gpio_inner_set(&x_output, x_state)) { \ if (!us_gpio_inner_set(&x_out, x_on)) { \
x_output.state = x_state; \ x_out.on = x_on; \
} \ } \
} \ } \
} }
INLINE void us_gpio_set_prog_running(bool state) { INLINE void us_gpio_set_prog_running(bool on) {
SET_STATE(us_g_gpio.prog_running, state); SET_ON(us_g_gpio.prog_running, on);
} }
INLINE void us_gpio_set_stream_online(bool state) { INLINE void us_gpio_set_stream_online(bool on) {
SET_STATE(us_g_gpio.stream_online, state); SET_ON(us_g_gpio.stream_online, on);
} }
INLINE void us_gpio_set_has_http_clients(bool state) { INLINE void us_gpio_set_has_http_clients(bool on) {
SET_STATE(us_g_gpio.has_http_clients, state); SET_ON(us_g_gpio.has_http_clients, on);
} }
#undef SET_STATE #undef SET_ON

View File

@ -67,6 +67,6 @@ const char *us_guess_mime_type(const char *path) {
} }
}); });
misc: misc:
return "application/misc"; return "application/misc";
} }

View File

@ -75,17 +75,17 @@
#endif #endif
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server); static int _http_preprocess_request(struct evhttp_request *req, us_server_s *server);
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server); static int _http_check_run_compat_action(struct evhttp_request *req, void *v_server);
static void _http_callback_root(struct evhttp_request *request, void *v_server); static void _http_callback_root(struct evhttp_request *req, void *v_server);
static void _http_callback_favicon(struct evhttp_request *request, void *v_server); static void _http_callback_favicon(struct evhttp_request *req, void *v_server);
static void _http_callback_static(struct evhttp_request *request, void *v_server); static void _http_callback_static(struct evhttp_request *req, void *v_server);
static void _http_callback_state(struct evhttp_request *request, void *v_server); static void _http_callback_state(struct evhttp_request *req, void *v_server);
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server); static void _http_callback_snapshot(struct evhttp_request *req, void *v_server);
static void _http_callback_stream(struct evhttp_request *request, void *v_server); static void _http_callback_stream(struct evhttp_request *req, void *v_server);
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx); static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_ctx);
static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx); static void _http_callback_stream_error(struct bufferevent *buf_event, short what, void *v_ctx);
@ -96,18 +96,18 @@ static void _http_send_snapshot(us_server_s *server);
static bool _expose_frame(us_server_s *server, const us_frame_s *frame); static bool _expose_frame(us_server_s *server, const us_frame_s *frame);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("HTTP: " x_msg, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("HTTP: " x_msg, ##__VA_ARGS__)
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_INFO(x_msg, ...) US_LOG_INFO("HTTP: " x_msg, ##__VA_ARGS__)
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("HTTP: " x_msg, ##__VA_ARGS__)
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("HTTP: " x_msg, ##__VA_ARGS__) #define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("HTTP: " x_msg, ##__VA_ARGS__)
#define _A_EVBUFFER_NEW(x_buf) assert((x_buf = evbuffer_new()) != NULL) #define _A_EVBUFFER_NEW(x_buf) assert((x_buf = evbuffer_new()) != NULL)
#define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size)) #define _A_EVBUFFER_ADD(x_buf, x_data, x_size) assert(!evbuffer_add(x_buf, x_data, x_size))
#define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) assert(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0) #define _A_EVBUFFER_ADD_PRINTF(x_buf, x_fmt, ...) assert(evbuffer_add_printf(x_buf, x_fmt, ##__VA_ARGS__) >= 0)
#define _A_ADD_HEADER(x_request, x_key, x_value) \ #define _A_ADD_HEADER(x_req, x_key, x_value) \
assert(!evhttp_add_header(evhttp_request_get_output_headers(x_request), x_key, x_value)) assert(!evhttp_add_header(evhttp_request_get_output_headers(x_req), x_key, x_value))
us_server_s *us_server_init(us_stream_s *stream) { us_server_s *us_server_init(us_stream_s *stream) {
@ -260,66 +260,66 @@ void us_server_loop_break(us_server_s *server) {
event_base_loopbreak(server->run->base); event_base_loopbreak(server->run->base);
} }
static int _http_preprocess_request(struct evhttp_request *request, us_server_s *server) { static int _http_preprocess_request(struct evhttp_request *req, us_server_s *server) {
const us_server_runtime_s *const run = server->run; const us_server_runtime_s *const run = server->run;
atomic_store(&server->stream->run->http->last_request_ts, us_get_now_monotonic()); atomic_store(&server->stream->run->http->last_req_ts, us_get_now_monotonic());
if (server->allow_origin[0] != '\0') { if (server->allow_origin[0] != '\0') {
const char *const cors_headers = us_evhttp_get_header(request, "Access-Control-Request-Headers"); const char *const cors_headers = us_evhttp_get_header(req, "Access-Control-Request-Headers");
const char *const cors_method = us_evhttp_get_header(request, "Access-Control-Request-Method"); const char *const cors_method = us_evhttp_get_header(req, "Access-Control-Request-Method");
_A_ADD_HEADER(request, "Access-Control-Allow-Origin", server->allow_origin); _A_ADD_HEADER(req, "Access-Control-Allow-Origin", server->allow_origin);
_A_ADD_HEADER(request, "Access-Control-Allow-Credentials", "true"); _A_ADD_HEADER(req, "Access-Control-Allow-Credentials", "true");
if (cors_headers != NULL) { if (cors_headers != NULL) {
_A_ADD_HEADER(request, "Access-Control-Allow-Headers", cors_headers); _A_ADD_HEADER(req, "Access-Control-Allow-Headers", cors_headers);
} }
if (cors_method != NULL) { if (cors_method != NULL) {
_A_ADD_HEADER(request, "Access-Control-Allow-Methods", cors_method); _A_ADD_HEADER(req, "Access-Control-Allow-Methods", cors_method);
} }
} }
if (evhttp_request_get_command(request) == EVHTTP_REQ_OPTIONS) { if (evhttp_request_get_command(req) == EVHTTP_REQ_OPTIONS) {
evhttp_send_reply(request, HTTP_OK, "OK", NULL); evhttp_send_reply(req, HTTP_OK, "OK", NULL);
return -1; return -1;
} }
if (run->auth_token != NULL) { if (run->auth_token != NULL) {
const char *const token = us_evhttp_get_header(request, "Authorization"); const char *const token = us_evhttp_get_header(req, "Authorization");
if (token == NULL || strcmp(token, run->auth_token) != 0) { if (token == NULL || strcmp(token, run->auth_token) != 0) {
_A_ADD_HEADER(request, "WWW-Authenticate", "Basic realm=\"Restricted area\""); _A_ADD_HEADER(req, "WWW-Authenticate", "Basic realm=\"Restricted area\"");
evhttp_send_reply(request, 401, "Unauthorized", NULL); evhttp_send_reply(req, 401, "Unauthorized", NULL);
return -1; return -1;
} }
} }
if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { if (evhttp_request_get_command(req) == EVHTTP_REQ_HEAD) {
evhttp_send_reply(request, HTTP_OK, "OK", NULL); evhttp_send_reply(req, HTTP_OK, "OK", NULL);
return -1; return -1;
} }
return 0; return 0;
} }
#define PREPROCESS_REQUEST { \ #define PREPROCESS_REQUEST { \
if (_http_preprocess_request(request, server) < 0) { \ if (_http_preprocess_request(req, server) < 0) { \
return; \ return; \
} \ } \
} }
static int _http_check_run_compat_action(struct evhttp_request *request, void *v_server) { static int _http_check_run_compat_action(struct evhttp_request *req, void *v_server) {
// MJPG-Streamer compatibility layer // MJPG-Streamer compatibility layer
int retval = -1; int retval = -1;
struct evkeyvalq params; struct evkeyvalq params;
evhttp_parse_query(evhttp_request_get_uri(request), &params); evhttp_parse_query(evhttp_request_get_uri(req), &params);
const char *const action = evhttp_find_header(&params, "action"); const char *const action = evhttp_find_header(&params, "action");
if (action && !strcmp(action, "snapshot")) { if (action && !strcmp(action, "snapshot")) {
_http_callback_snapshot(request, v_server); _http_callback_snapshot(req, v_server);
retval = 0; retval = 0;
} else if (action && !strcmp(action, "stream")) { } else if (action && !strcmp(action, "stream")) {
_http_callback_stream(request, v_server); _http_callback_stream(req, v_server);
retval = 0; retval = 0;
} }
@ -328,12 +328,12 @@ static int _http_check_run_compat_action(struct evhttp_request *request, void *v
} }
#define COMPAT_REQUEST { \ #define COMPAT_REQUEST { \
if (_http_check_run_compat_action(request, v_server) == 0) { \ if (_http_check_run_compat_action(req, v_server) == 0) { \
return; \ return; \
} \ } \
} }
static void _http_callback_root(struct evhttp_request *request, void *v_server) { static void _http_callback_root(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
@ -342,13 +342,13 @@ static void _http_callback_root(struct evhttp_request *request, void *v_server)
struct evbuffer *buf; struct evbuffer *buf;
_A_EVBUFFER_NEW(buf); _A_EVBUFFER_NEW(buf);
_A_EVBUFFER_ADD_PRINTF(buf, "%s", US_HTML_INDEX_PAGE); _A_EVBUFFER_ADD_PRINTF(buf, "%s", US_HTML_INDEX_PAGE);
_A_ADD_HEADER(request, "Content-Type", "text/html"); _A_ADD_HEADER(req, "Content-Type", "text/html");
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf); evbuffer_free(buf);
} }
static void _http_callback_favicon(struct evhttp_request *request, void *v_server) { static void _http_callback_favicon(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
@ -356,13 +356,13 @@ static void _http_callback_favicon(struct evhttp_request *request, void *v_serve
struct evbuffer *buf; struct evbuffer *buf;
_A_EVBUFFER_NEW(buf); _A_EVBUFFER_NEW(buf);
_A_EVBUFFER_ADD(buf, (const void*)US_FAVICON_ICO_DATA, US_FAVICON_ICO_DATA_SIZE); _A_EVBUFFER_ADD(buf, (const void*)US_FAVICON_ICO_DATA, US_FAVICON_ICO_DATA_SIZE);
_A_ADD_HEADER(request, "Content-Type", "image/x-icon"); _A_ADD_HEADER(req, "Content-Type", "image/x-icon");
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf); evbuffer_free(buf);
} }
static void _http_callback_static(struct evhttp_request *request, void *v_server) { static void _http_callback_static(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
@ -376,7 +376,7 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
{ {
const char *uri_path; const char *uri_path;
if ((uri = evhttp_uri_parse(evhttp_request_get_uri(request))) == NULL) { if ((uri = evhttp_uri_parse(evhttp_request_get_uri(req))) == NULL) {
goto bad_request; goto bad_request;
} }
if ((uri_path = (char*)evhttp_uri_get_path(uri)) == NULL) { if ((uri_path = (char*)evhttp_uri_get_path(uri)) == NULL) {
@ -413,17 +413,17 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
// and will close it when finished transferring data // and will close it when finished transferring data
fd = -1; fd = -1;
_A_ADD_HEADER(request, "Content-Type", us_guess_mime_type(static_path)); _A_ADD_HEADER(req, "Content-Type", us_guess_mime_type(static_path));
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
goto cleanup; goto cleanup;
} }
bad_request: bad_request:
evhttp_send_error(request, HTTP_BADREQUEST, NULL); evhttp_send_error(req, HTTP_BADREQUEST, NULL);
goto cleanup; goto cleanup;
not_found: not_found:
evhttp_send_error(request, HTTP_NOTFOUND, NULL); evhttp_send_error(req, HTTP_NOTFOUND, NULL);
goto cleanup; goto cleanup;
cleanup: cleanup:
@ -436,7 +436,7 @@ cleanup:
#undef COMPAT_REQUEST #undef COMPAT_REQUEST
static void _http_callback_state(struct evhttp_request *request, void *v_server) { static void _http_callback_state(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
us_server_runtime_s *const run = server->run; us_server_runtime_s *const run = server->run;
us_server_exposed_s *const ex = run->exposed; us_server_exposed_s *const ex = run->exposed;
@ -451,60 +451,61 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
struct evbuffer *buf; struct evbuffer *buf;
_A_EVBUFFER_NEW(buf); _A_EVBUFFER_NEW(buf);
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"{\"ok\": true, \"result\": {" "{\"ok\": true, \"result\": {"
" \"instance_id\": \"%s\"," " \"instance_id\": \"%s\","
" \"encoder\": {\"type\": \"%s\", \"quality\": %u},", " \"encoder\": {\"type\": \"%s\", \"quality\": %u},",
server->instance_id, server->instance_id,
us_encoder_type_to_string(enc_type), us_encoder_type_to_string(enc_type),
enc_quality enc_quality);
);
# ifdef WITH_V4P # ifdef WITH_V4P
if (stream->drm != NULL) { if (stream->drm != NULL) {
us_fpsi_meta_s meta; us_fpsi_meta_s meta;
const uint fps = us_fpsi_get(stream->run->http->drm_fpsi, &meta); const uint fps = us_fpsi_get(stream->run->http->drm_fpsi, &meta);
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
" \"drm\": {\"live\": %s, \"fps\": %u},", " \"drm\": {\"live\": %s, \"fps\": %u},",
us_bool_to_string(meta.online), us_bool_to_string(meta.online),
fps fps);
);
} }
# endif # endif
if (stream->h264_sink != NULL) { if (stream->h264_sink != NULL) {
us_fpsi_meta_s meta; us_fpsi_meta_s meta;
const uint fps = us_fpsi_get(stream->run->http->h264_fpsi, &meta); const uint fps = us_fpsi_get(stream->run->http->h264_fpsi, &meta);
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
" \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s, \"fps\": %u},", " \"h264\": {\"bitrate\": %u, \"gop\": %u, \"online\": %s, \"fps\": %u},",
stream->h264_bitrate, stream->h264_bitrate,
stream->h264_gop, stream->h264_gop,
us_bool_to_string(meta.online), us_bool_to_string(meta.online),
fps fps);
);
} }
if (stream->jpeg_sink != NULL || stream->h264_sink != NULL) { if (stream->jpeg_sink != NULL || stream->h264_sink != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf, " \"sinks\": {"); _A_EVBUFFER_ADD_PRINTF(buf, " \"sinks\": {");
if (stream->jpeg_sink != NULL) { if (stream->jpeg_sink != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"\"jpeg\": {\"has_clients\": %s}", "\"jpeg\": {\"has_clients\": %s}",
us_bool_to_string(atomic_load(&stream->jpeg_sink->has_clients)) us_bool_to_string(atomic_load(&stream->jpeg_sink->has_clients)));
);
} }
if (stream->h264_sink != NULL) { if (stream->h264_sink != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"%s\"h264\": {\"has_clients\": %s}", "%s\"h264\": {\"has_clients\": %s}",
(stream->jpeg_sink ? ", " : ""), (stream->jpeg_sink ? ", " : ""),
us_bool_to_string(atomic_load(&stream->h264_sink->has_clients)) us_bool_to_string(atomic_load(&stream->h264_sink->has_clients)));
);
} }
_A_EVBUFFER_ADD_PRINTF(buf, "},"); _A_EVBUFFER_ADD_PRINTF(buf, "},");
} }
us_fpsi_meta_s captured_meta; us_fpsi_meta_s captured_meta;
const uint captured_fps = us_fpsi_get(stream->run->http->captured_fpsi, &captured_meta); const uint captured_fps = us_fpsi_get(stream->run->http->captured_fpsi, &captured_meta);
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
" \"source\": {\"resolution\": {\"width\": %u, \"height\": %u}," " \"source\": {\"resolution\": {\"width\": %u, \"height\": %u},"
" \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u}," " \"online\": %s, \"desired_fps\": %u, \"captured_fps\": %u},"
" \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {", " \"stream\": {\"queued_fps\": %u, \"clients\": %u, \"clients_stat\": {",
@ -514,11 +515,11 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
stream->desired_fps, stream->desired_fps,
captured_fps, captured_fps,
us_fpsi_get(ex->queued_fpsi, NULL), us_fpsi_get(ex->queued_fpsi, NULL),
run->stream_clients_count run->stream_clients_count);
);
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s," "\"%" PRIx64 "\": {\"fps\": %u, \"extra_headers\": %s, \"advance_headers\": %s,"
" \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s", " \"dual_final_frames\": %s, \"zero_data\": %s, \"key\": \"%s\"}%s",
client->id, client->id,
@ -528,18 +529,17 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
us_bool_to_string(client->dual_final_frames), us_bool_to_string(client->dual_final_frames),
us_bool_to_string(client->zero_data), us_bool_to_string(client->zero_data),
(client->key != NULL ? client->key : "0"), (client->key != NULL ? client->key : "0"),
(client->next ? ", " : "") (client->next ? ", " : ""));
);
}); });
_A_EVBUFFER_ADD_PRINTF(buf, "}}}}"); _A_EVBUFFER_ADD_PRINTF(buf, "}}}}");
_A_ADD_HEADER(request, "Content-Type", "application/json"); _A_ADD_HEADER(req, "Content-Type", "application/json");
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf); evbuffer_free(buf);
} }
static void _http_callback_snapshot(struct evhttp_request *request, void *v_server) { static void _http_callback_snapshot(struct evhttp_request *req, void *v_server) {
us_server_s *const server = v_server; us_server_s *const server = v_server;
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
@ -547,14 +547,14 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
us_snapshot_client_s *client; us_snapshot_client_s *client;
US_CALLOC(client, 1); US_CALLOC(client, 1);
client->server = server; client->server = server;
client->request = request; client->req = req;
client->request_ts = us_get_now_monotonic(); client->req_ts = us_get_now_monotonic();
atomic_fetch_add(&server->stream->run->http->snapshot_requested, 1); atomic_fetch_add(&server->stream->run->http->snapshot_requested, 1);
US_LIST_APPEND(server->run->snapshot_clients, client); US_LIST_APPEND(server->run->snapshot_clients, client);
} }
static void _http_callback_stream(struct evhttp_request *request, void *v_server) { static void _http_callback_stream(struct evhttp_request *req, void *v_server) {
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2814 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2814
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2789 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L2789
// https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L362 // https://github.com/libevent/libevent/blob/29cc8386a2f7911eaa9336692a2c5544d8b4734f/http.c#L362
@ -566,17 +566,17 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
PREPROCESS_REQUEST; PREPROCESS_REQUEST;
struct evhttp_connection *const conn = evhttp_request_get_connection(request); struct evhttp_connection *const conn = evhttp_request_get_connection(req);
if (conn != NULL) { if (conn != NULL) {
us_stream_client_s *client; us_stream_client_s *client;
US_CALLOC(client, 1); US_CALLOC(client, 1);
client->server = server; client->server = server;
client->request = request; client->req = req;
client->need_initial = true; client->need_initial = true;
client->need_first_frame = true; client->need_first_frame = true;
struct evkeyvalq params; struct evkeyvalq params;
evhttp_parse_query(evhttp_request_get_uri(request), &params); evhttp_parse_query(evhttp_request_get_uri(req), &params);
# define PARSE_PARAM(x_type, x_name) client->x_name = us_evkeyvalq_get_##x_type(&params, #x_name) # define PARSE_PARAM(x_type, x_name) client->x_name = us_evkeyvalq_get_##x_type(&params, #x_name)
PARSE_PARAM(string, key); PARSE_PARAM(string, key);
PARSE_PARAM(true, extra_headers); PARSE_PARAM(true, extra_headers);
@ -586,7 +586,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
# undef PARSE_PARAM # undef PARSE_PARAM
evhttp_clear_headers(&params); evhttp_clear_headers(&params);
client->hostport = us_evhttp_get_hostport(request); client->hostport = us_evhttp_get_hostport(req);
client->id = us_get_now_id(); client->id = us_get_now_id();
{ {
@ -621,7 +621,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client); bufferevent_setcb(buf_event, NULL, NULL, _http_callback_stream_error, (void*)client);
bufferevent_enable(buf_event, EV_READ); bufferevent_enable(buf_event, EV_READ);
} else { } else {
evhttp_request_free(request); evhttp_request_free(req);
} }
} }
@ -659,21 +659,24 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
# define BOUNDARY "boundarydonotcross" # define BOUNDARY "boundarydonotcross"
# define ADD_ADVANCE_HEADERS \ # define ADD_ADVANCE_HEADERS \
_A_EVBUFFER_ADD_PRINTF(buf, \ _A_EVBUFFER_ADD_PRINTF( \
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, us_get_now_real()) buf, \
"Content-Type: image/jpeg" RN "X-Timestamp: %.06Lf" RN RN, \
us_get_now_real())
if (client->need_initial) { if (client->need_initial) {
_A_EVBUFFER_ADD_PRINTF(buf, "HTTP/1.0 200 OK" RN); _A_EVBUFFER_ADD_PRINTF(buf, "HTTP/1.0 200 OK" RN);
if (client->server->allow_origin[0] != '\0') { if (client->server->allow_origin[0] != '\0') {
const char *const cors_headers = us_evhttp_get_header(client->request, "Access-Control-Request-Headers"); const char *const cors_headers = us_evhttp_get_header(client->req, "Access-Control-Request-Headers");
const char *const cors_method = us_evhttp_get_header(client->request, "Access-Control-Request-Method"); const char *const cors_method = us_evhttp_get_header(client->req, "Access-Control-Request-Method");
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"Access-Control-Allow-Origin: %s" RN "Access-Control-Allow-Origin: %s" RN
"Access-Control-Allow-Credentials: true" RN, "Access-Control-Allow-Credentials: true" RN,
client->server->allow_origin client->server->allow_origin);
);
if (cors_headers != NULL) { if (cors_headers != NULL) {
_A_EVBUFFER_ADD_PRINTF(buf, "Access-Control-Allow-Headers: %s" RN, cors_headers); _A_EVBUFFER_ADD_PRINTF(buf, "Access-Control-Allow-Headers: %s" RN, cors_headers);
} }
@ -682,7 +685,8 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
} }
} }
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN "Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0" RN
"Pragma: no-cache" RN "Pragma: no-cache" RN
"Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN "Expires: Mon, 3 Jan 2000 12:34:56 GMT" RN
@ -693,8 +697,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
(server->instance_id[0] == '\0' ? "" : "_"), (server->instance_id[0] == '\0' ? "" : "_"),
server->instance_id, server->instance_id,
(client->key != NULL ? client->key : "0"), (client->key != NULL ? client->key : "0"),
client->id client->id);
);
if (client->advance_headers) { if (client->advance_headers) {
ADD_ADVANCE_HEADERS; ADD_ADVANCE_HEADERS;
@ -705,18 +708,20 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
} }
if (!client->advance_headers) { if (!client->advance_headers) {
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"Content-Type: image/jpeg" RN "Content-Type: image/jpeg" RN
"Content-Length: %zu" RN "Content-Length: %zu" RN
"X-Timestamp: %.06Lf" RN "X-Timestamp: %.06Lf" RN
"%s", "%s",
(!client->zero_data ? ex->frame->used : 0), (!client->zero_data ? ex->frame->used : 0),
us_get_now_real(), us_get_now_real(),
(client->extra_headers ? "" : RN) (client->extra_headers ? "" : RN));
);
const ldf now_ts = us_get_now_monotonic(); const ldf now_ts = us_get_now_monotonic();
if (client->extra_headers) { if (client->extra_headers) {
_A_EVBUFFER_ADD_PRINTF(buf, _A_EVBUFFER_ADD_PRINTF(
buf,
"X-UStreamer-Online: %s" RN "X-UStreamer-Online: %s" RN
"X-UStreamer-Dropped: %u" RN "X-UStreamer-Dropped: %u" RN
"X-UStreamer-Width: %u" RN "X-UStreamer-Width: %u" RN
@ -745,8 +750,7 @@ static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_c
ex->expose_cmp_ts, ex->expose_cmp_ts,
ex->expose_end_ts, ex->expose_end_ts,
now_ts, now_ts,
now_ts - ex->frame->grab_begin_ts now_ts - ex->frame->grab_begin_ts);
);
} }
} }
@ -791,7 +795,7 @@ static void _http_callback_stream_error(struct bufferevent *buf_event, short wha
run->stream_clients_count, client->hostport, client->id, reason); run->stream_clients_count, client->hostport, client->id, reason);
free(reason); free(reason);
struct evhttp_connection *conn = evhttp_request_get_connection(client->request); struct evhttp_connection *conn = evhttp_request_get_connection(client->req);
US_DELETE(conn, evhttp_connection_free); US_DELETE(conn, evhttp_connection_free);
us_fpsi_destroy(client->fpsi); us_fpsi_destroy(client->fpsi);
@ -808,7 +812,7 @@ static void _http_send_stream(us_server_s *server, bool stream_updated, bool fra
bool has_clients = true; bool has_clients = true;
US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement US_LIST_ITERATE(run->stream_clients, client, { // cppcheck-suppress constStatement
struct evhttp_connection *const conn = evhttp_request_get_connection(client->request); struct evhttp_connection *const conn = evhttp_request_get_connection(client->req);
if (conn != NULL) { if (conn != NULL) {
// Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов, // Фикс для бага WebKit. При включенной опции дропа одинаковых фреймов,
// WebKit отрисовывает последний фрейм в серии с некоторой задержкой, // WebKit отрисовывает последний фрейм в серии с некоторой задержкой,
@ -852,22 +856,22 @@ static void _http_send_snapshot(us_server_s *server) {
# define ADD_TIME_HEADER(x_key, x_value) { \ # define ADD_TIME_HEADER(x_key, x_value) { \
US_SNPRINTF(header_buf, 255, "%.06Lf", x_value); \ US_SNPRINTF(header_buf, 255, "%.06Lf", x_value); \
_A_ADD_HEADER(request, x_key, header_buf); \ _A_ADD_HEADER(req, x_key, header_buf); \
} }
# define ADD_UNSIGNED_HEADER(x_key, x_value) { \ # define ADD_UNSIGNED_HEADER(x_key, x_value) { \
US_SNPRINTF(header_buf, 255, "%u", x_value); \ US_SNPRINTF(header_buf, 255, "%u", x_value); \
_A_ADD_HEADER(request, x_key, header_buf); \ _A_ADD_HEADER(req, x_key, header_buf); \
} }
us_fpsi_meta_s captured_meta; us_fpsi_meta_s captured_meta;
us_fpsi_get(server->stream->run->http->captured_fpsi, &captured_meta); us_fpsi_get(server->stream->run->http->captured_fpsi, &captured_meta);
US_LIST_ITERATE(server->run->snapshot_clients, client, { // cppcheck-suppress constStatement US_LIST_ITERATE(server->run->snapshot_clients, client, { // cppcheck-suppress constStatement
struct evhttp_request *request = client->request; struct evhttp_request *req = client->req;
const bool has_fresh_snapshot = (atomic_load(&server->stream->run->http->snapshot_requested) == 0); const bool has_fresh_snapshot = (atomic_load(&server->stream->run->http->snapshot_requested) == 0);
const bool timed_out = (client->request_ts + US_MAX((uint)1, server->stream->error_delay * 3) < us_get_now_monotonic()); const bool timed_out = (client->req_ts + US_MAX((uint)1, server->stream->error_delay * 3) < us_get_now_monotonic());
if (has_fresh_snapshot || timed_out) { if (has_fresh_snapshot || timed_out) {
us_frame_s *frame = ex->frame; us_frame_s *frame = ex->frame;
@ -883,15 +887,15 @@ static void _http_send_snapshot(us_server_s *server) {
_A_EVBUFFER_NEW(buf); _A_EVBUFFER_NEW(buf);
_A_EVBUFFER_ADD(buf, (const void*)frame->data, frame->used); _A_EVBUFFER_ADD(buf, (const void*)frame->data, frame->used);
_A_ADD_HEADER(request, "Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0"); _A_ADD_HEADER(req, "Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, pre-check=0, post-check=0, max-age=0");
_A_ADD_HEADER(request, "Pragma", "no-cache"); _A_ADD_HEADER(req, "Pragma", "no-cache");
_A_ADD_HEADER(request, "Expires", "Mon, 3 Jan 2000 12:34:56 GMT"); _A_ADD_HEADER(req, "Expires", "Mon, 3 Jan 2000 12:34:56 GMT");
char header_buf[256]; char header_buf[256];
ADD_TIME_HEADER("X-Timestamp", us_get_now_real()); ADD_TIME_HEADER("X-Timestamp", us_get_now_real());
_A_ADD_HEADER(request, "X-UStreamer-Online", us_bool_to_string(frame->online)); _A_ADD_HEADER(req, "X-UStreamer-Online", us_bool_to_string(frame->online));
ADD_UNSIGNED_HEADER("X-UStreamer-Width", frame->width); ADD_UNSIGNED_HEADER("X-UStreamer-Width", frame->width);
ADD_UNSIGNED_HEADER("X-UStreamer-Height", frame->height); ADD_UNSIGNED_HEADER("X-UStreamer-Height", frame->height);
ADD_TIME_HEADER("X-UStreamer-Grab-Begin-Timestamp", frame->grab_begin_ts); ADD_TIME_HEADER("X-UStreamer-Grab-Begin-Timestamp", frame->grab_begin_ts);
@ -900,9 +904,9 @@ static void _http_send_snapshot(us_server_s *server) {
ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", frame->encode_end_ts); ADD_TIME_HEADER("X-UStreamer-Encode-End-Timestamp", frame->encode_end_ts);
ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", us_get_now_monotonic()); ADD_TIME_HEADER("X-UStreamer-Send-Timestamp", us_get_now_monotonic());
_A_ADD_HEADER(request, "Content-Type", "image/jpeg"); _A_ADD_HEADER(req, "Content-Type", "image/jpeg");
evhttp_send_reply(request, HTTP_OK, "OK", buf); evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf); evbuffer_free(buf);
US_LIST_REMOVE(server->run->snapshot_clients, client); US_LIST_REMOVE(server->run->snapshot_clients, client);

View File

@ -38,7 +38,7 @@
typedef struct { typedef struct {
struct us_server_sx *server; struct us_server_sx *server;
struct evhttp_request *request; struct evhttp_request *req;
char *key; char *key;
bool extra_headers; bool extra_headers;
@ -59,8 +59,8 @@ typedef struct {
typedef struct { typedef struct {
struct us_server_sx *server; struct us_server_sx *server;
struct evhttp_request *request; struct evhttp_request *req;
ldf request_ts; ldf req_ts;
US_LIST_DECLARE; US_LIST_DECLARE;
} us_snapshot_client_s; } us_snapshot_client_s;

View File

@ -36,12 +36,12 @@
#include "path.h" #include "path.h"
char *us_find_static_file_path(const char *root_path, const char *request_path) { char *us_find_static_file_path(const char *root_path, const char *req_path) {
char *path = NULL; char *path = NULL;
char *const simplified_path = us_simplify_request_path(request_path); char *const simplified_path = us_simplify_request_path(req_path);
if (simplified_path[0] == '\0') { if (simplified_path[0] == '\0') {
US_LOG_VERBOSE("HTTP: Invalid request path %s to static", request_path); US_LOG_VERBOSE("HTTP: Invalid request path %s to static", req_path);
goto error; goto error;
} }

View File

@ -23,4 +23,4 @@
#pragma once #pragma once
char *us_find_static_file_path(const char *root_path, const char *request_path); char *us_find_static_file_path(const char *root_path, const char *req_path);

View File

@ -82,27 +82,27 @@ evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool
return fd; return fd;
} }
const char *us_evhttp_get_header(struct evhttp_request *request, const char *key) { const char *us_evhttp_get_header(struct evhttp_request *req, const char *key) {
return evhttp_find_header(evhttp_request_get_input_headers(request), key); return evhttp_find_header(evhttp_request_get_input_headers(req), key);
} }
char *us_evhttp_get_hostport(struct evhttp_request *request) { char *us_evhttp_get_hostport(struct evhttp_request *req) {
char *addr = NULL; char *addr = NULL;
unsigned short port = 0; unsigned short port = 0;
struct evhttp_connection *conn = evhttp_request_get_connection(request); struct evhttp_connection *conn = evhttp_request_get_connection(req);
if (conn != NULL) { if (conn != NULL) {
char *peer; char *peer;
evhttp_connection_get_peer(conn, &peer, &port); evhttp_connection_get_peer(conn, &peer, &port);
addr = us_strdup(peer); addr = us_strdup(peer);
} }
const char *xff = us_evhttp_get_header(request, "X-Forwarded-For"); const char *xff = us_evhttp_get_header(req, "X-Forwarded-For");
if (xff != NULL) { if (xff != NULL) {
US_DELETE(addr, free); US_DELETE(addr, free);
assert((addr = strndup(xff, 1024)) != NULL); assert((addr = strndup(xff, 1024)) != NULL);
for (uint index = 0; addr[index]; ++index) { for (uint i = 0; addr[i]; ++i) {
if (addr[index] == ',') { if (addr[i] == ',') {
addr[index] = '\0'; addr[i] = '\0';
break; break;
} }
} }

View File

@ -32,8 +32,8 @@
evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode); evutil_socket_t us_evhttp_bind_unix(struct evhttp *http, const char *path, bool rm, mode_t mode);
const char *us_evhttp_get_header(struct evhttp_request *request, const char *key); const char *us_evhttp_get_header(struct evhttp_request *req, const char *key);
char *us_evhttp_get_hostport(struct evhttp_request *request); char *us_evhttp_get_hostport(struct evhttp_request *req);
bool us_evkeyvalq_get_true(struct evkeyvalq *params, const char *key); bool us_evkeyvalq_get_true(struct evkeyvalq *params, const char *key);
char *us_evkeyvalq_get_string(struct evkeyvalq *params, const char *key); char *us_evkeyvalq_get_string(struct evkeyvalq *params, const char *key);

View File

@ -42,25 +42,39 @@
static us_m2m_encoder_s *_m2m_encoder_init( static us_m2m_encoder_s *_m2m_encoder_init(
const char *name, const char *path, uint output_format, const char *name,
uint bitrate, uint gop, uint quality, bool allow_dma, bool boost); const char *path,
uint out_format,
uint bitrate,
uint gop,
uint quality,
bool allow_dma,
bool boost);
static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame); static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame);
static int _m2m_encoder_init_buffers( static int _m2m_encoder_init_buffers(
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type, us_m2m_encoder_s *enc,
us_m2m_buffer_s **bufs_ptr, uint *n_bufs_ptr, bool dma); const char *name,
enum v4l2_buf_type type,
us_m2m_buffer_s **bufs_ptr,
uint *n_bufs_ptr,
bool dma);
static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc); static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc);
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key); static int _m2m_encoder_compress_raw(
us_m2m_encoder_s *enc,
const us_frame_s *src,
us_frame_s *dest,
bool force_key);
#define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__) #define _LOG_ERROR(x_msg, ...) US_LOG_ERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
#define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__) #define _LOG_PERROR(x_msg, ...) US_LOG_PERROR("%s: " x_msg, enc->name, ##__VA_ARGS__)
#define _LOG_INFO(x_msg, ...) US_LOG_INFO("%s: " x_msg, enc->name, ##__VA_ARGS__) #define _LOG_INFO(x_msg, ...) US_LOG_INFO("%s: " x_msg, enc->name, ##__VA_ARGS__)
#define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("%s: " x_msg, enc->name, ##__VA_ARGS__) #define _LOG_VERBOSE(x_msg, ...) US_LOG_VERBOSE("%s: " x_msg, enc->name, ##__VA_ARGS__)
#define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__) #define _LOG_DEBUG(x_msg, ...) US_LOG_DEBUG("%s: " x_msg, enc->name, ##__VA_ARGS__)
us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, uint bitrate, uint gop, bool boost) { us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, uint bitrate, uint gop, bool boost) {
@ -95,8 +109,8 @@ void us_m2m_encoder_destroy(us_m2m_encoder_s *enc) {
int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) { int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) {
us_m2m_encoder_runtime_s *const run = enc->run; us_m2m_encoder_runtime_s *const run = enc->run;
uint dest_format = enc->output_format; uint dest_format = enc->out_format;
switch (enc->output_format) { switch (enc->out_format) {
case V4L2_PIX_FMT_JPEG: case V4L2_PIX_FMT_JPEG:
force_key = false; force_key = false;
// fall through // fall through
@ -138,9 +152,15 @@ int us_m2m_encoder_compress(us_m2m_encoder_s *enc, const us_frame_s *src, us_fra
} }
static us_m2m_encoder_s *_m2m_encoder_init( static us_m2m_encoder_s *_m2m_encoder_init(
const char *name, const char *path, uint output_format, const char *name,
uint bitrate, uint gop, uint quality, bool allow_dma, bool boost) { const char *path,
uint out_format,
uint bitrate,
uint gop,
uint quality,
bool allow_dma,
bool boost
) {
US_LOG_INFO("%s: Initializing encoder ...", name); US_LOG_INFO("%s: Initializing encoder ...", name);
us_m2m_encoder_runtime_s *run; us_m2m_encoder_runtime_s *run;
@ -152,11 +172,11 @@ static us_m2m_encoder_s *_m2m_encoder_init(
US_CALLOC(enc, 1); US_CALLOC(enc, 1);
enc->name = us_strdup(name); enc->name = us_strdup(name);
if (path == NULL) { if (path == NULL) {
enc->path = us_strdup(output_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11"); enc->path = us_strdup(out_format == V4L2_PIX_FMT_JPEG ? "/dev/video31" : "/dev/video11");
} else { } else {
enc->path = us_strdup(path); enc->path = us_strdup(path);
} }
enc->output_format = output_format; enc->out_format = out_format;
enc->bitrate = bitrate; enc->bitrate = bitrate;
enc->gop = gop; enc->gop = gop;
enc->quality = quality; enc->quality = quality;
@ -180,7 +200,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
if ( if (
run->p_width == frame->width run->p_width == frame->width
&& run->p_height == frame->height && run->p_height == frame->height
&& run->p_input_format == frame->format && run->p_in_format == frame->format
&& run->p_stride == frame->stride && run->p_stride == frame->stride
&& run->p_dma == dma && run->p_dma == dma
) { ) {
@ -189,18 +209,18 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
_LOG_INFO("Configuring encoder: DMA=%d ...", dma); _LOG_INFO("Configuring encoder: DMA=%d ...", dma);
_LOG_DEBUG("Encoder changes: width=%u->%u, height=%u->%u, input_format=%u->%u, stride=%u->%u, dma=%u->%u", _LOG_DEBUG("Encoder changes: width=%u->%u, height=%u->%u, in_format=%u->%u, stride=%u->%u, dma=%u->%u",
run->p_width, frame->width, run->p_width, frame->width,
run->p_height, frame->height, run->p_height, frame->height,
run->p_input_format, frame->format, run->p_in_format, frame->format,
run->p_stride, frame->stride, run->p_stride, frame->stride,
run->p_dma, dma); run->p_dma, dma);
_m2m_encoder_cleanup(enc); _m2m_encoder_cleanup(enc);
run->p_width = frame->width; run->p_width = frame->width;
run->p_height = frame->height; run->p_height = frame->height;
run->p_input_format = frame->format; run->p_in_format = frame->format;
run->p_stride = frame->stride; run->p_stride = frame->stride;
run->p_dma = dma; run->p_dma = dma;
@ -218,7 +238,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
_LOG_DEBUG("Configuring option " #x_cid " ..."); \ _LOG_DEBUG("Configuring option " #x_cid " ..."); \
_E_XIOCTL(VIDIOC_S_CTRL, &m_ctl, "Can't set option " #x_cid); \ _E_XIOCTL(VIDIOC_S_CTRL, &m_ctl, "Can't set option " #x_cid); \
} }
if (enc->output_format == V4L2_PIX_FMT_H264) { if (enc->out_format == V4L2_PIX_FMT_H264) {
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate); SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate);
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, enc->gop); SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, enc->gop);
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE); SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE);
@ -234,9 +254,9 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
SET_OPTION(V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 1); SET_OPTION(V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 1);
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 16); SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 16);
SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 32); SET_OPTION(V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 32);
} else if (enc->output_format == V4L2_PIX_FMT_MJPEG) { } else if (enc->out_format == V4L2_PIX_FMT_MJPEG) {
SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate); SET_OPTION(V4L2_CID_MPEG_VIDEO_BITRATE, enc->bitrate);
} else if (enc->output_format == V4L2_PIX_FMT_JPEG) { } else if (enc->out_format == V4L2_PIX_FMT_JPEG) {
SET_OPTION(V4L2_CID_JPEG_COMPRESSION_QUALITY, enc->quality); SET_OPTION(V4L2_CID_JPEG_COMPRESSION_QUALITY, enc->quality);
} }
# undef SET_OPTION # undef SET_OPTION
@ -246,7 +266,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
fmt.fmt.pix_mp.width = run->p_width; fmt.fmt.pix_mp.width = run->p_width;
fmt.fmt.pix_mp.height = run->p_height; fmt.fmt.pix_mp.height = run->p_height;
fmt.fmt.pix_mp.pixelformat = run->p_input_format; fmt.fmt.pix_mp.pixelformat = run->p_in_format;
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // FIXME: Wrong colors fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_JPEG; // FIXME: Wrong colors
fmt.fmt.pix_mp.num_planes = 1; fmt.fmt.pix_mp.num_planes = 1;
@ -260,22 +280,22 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.width = run->p_width; fmt.fmt.pix_mp.width = run->p_width;
fmt.fmt.pix_mp.height = run->p_height; fmt.fmt.pix_mp.height = run->p_height;
fmt.fmt.pix_mp.pixelformat = enc->output_format; fmt.fmt.pix_mp.pixelformat = enc->out_format;
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT; fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT;
fmt.fmt.pix_mp.num_planes = 1; fmt.fmt.pix_mp.num_planes = 1;
// fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0; // fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
if (enc->output_format == V4L2_PIX_FMT_H264) { if (enc->out_format == V4L2_PIX_FMT_H264) {
// https://github.com/pikvm/ustreamer/issues/169 // https://github.com/pikvm/ustreamer/issues/169
// https://github.com/raspberrypi/linux/pull/5232 // https://github.com/raspberrypi/linux/pull/5232
fmt.fmt.pix_mp.plane_fmt[0].sizeimage = (1024 + 512) << 10; // 1.5Mb fmt.fmt.pix_mp.plane_fmt[0].sizeimage = (1024 + 512) << 10; // 1.5Mb
} }
_LOG_DEBUG("Configuring OUTPUT format ..."); _LOG_DEBUG("Configuring OUTPUT format ...");
_E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format"); _E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
if (fmt.fmt.pix_mp.pixelformat != enc->output_format) { if (fmt.fmt.pix_mp.pixelformat != enc->out_format) {
char fourcc_str[8]; char fourcc_str[8];
_LOG_ERROR("The OUTPUT format can't be configured as %s", _LOG_ERROR("The OUTPUT format can't be configured as %s",
us_fourcc_to_string(enc->output_format, fourcc_str, 8)); us_fourcc_to_string(enc->out_format, fourcc_str, 8));
_LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt"); _LOG_ERROR("In case of Raspberry Pi, try to append 'start_x=1' to /boot/config.txt");
goto error; goto error;
} }
@ -283,7 +303,7 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
if ( if (
(run->p_width * run->p_height <= 1280 * 720) (run->p_width * run->p_height <= 1280 * 720)
|| ((enc->output_format == V4L2_PIX_FMT_H264) && enc->boost) || ((enc->out_format == V4L2_PIX_FMT_H264) && enc->boost)
) { ) {
// H264 требует каких-то лимитов. Больше 30 не поддерживается, а при 0 // H264 требует каких-то лимитов. Больше 30 не поддерживается, а при 0
// через какое-то время начинает производить некорректные фреймы. // через какое-то время начинает производить некорректные фреймы.
@ -306,14 +326,22 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame)
} }
if (_m2m_encoder_init_buffers( if (_m2m_encoder_init_buffers(
enc, (dma ? "INPUT-DMA" : "INPUT"), V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, enc,
&run->input_bufs, &run->n_input_bufs, dma (dma ? "INPUT-DMA" : "INPUT"),
V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
&run->in_bufs,
&run->n_in_bufs,
dma
) < 0) { ) < 0) {
goto error; goto error;
} }
if (_m2m_encoder_init_buffers( if (_m2m_encoder_init_buffers(
enc, "OUTPUT", V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, enc,
&run->output_bufs, &run->n_output_bufs, false "OUTPUT",
V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
&run->out_bufs,
&run->n_out_bufs,
false
) < 0) { ) < 0) {
goto error; goto error;
} }
@ -338,9 +366,13 @@ error:
} }
static int _m2m_encoder_init_buffers( static int _m2m_encoder_init_buffers(
us_m2m_encoder_s *enc, const char *name, enum v4l2_buf_type type, us_m2m_encoder_s *enc,
us_m2m_buffer_s **bufs_ptr, uint *n_bufs_ptr, bool dma) { const char *name,
enum v4l2_buf_type type,
us_m2m_buffer_s **bufs_ptr,
uint *n_bufs_ptr,
bool dma
) {
us_m2m_encoder_runtime_s *const run = enc->run; us_m2m_encoder_runtime_s *const run = enc->run;
_LOG_DEBUG("Initializing %s buffers ...", name); _LOG_DEBUG("Initializing %s buffers ...", name);
@ -420,11 +452,11 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
# define DELETE_BUFFERS(x_name, x_target) { \ # define DELETE_BUFFERS(x_name, x_target) { \
if (run->x_target##_bufs != NULL) { \ if (run->x_target##_bufs != NULL) { \
say = true; \ say = true; \
for (uint m_index = 0; m_index < run->n_##x_target##_bufs; ++m_index) { \ for (uint m_i = 0; m_i < run->n_##x_target##_bufs; ++m_i) { \
us_m2m_buffer_s *m_buf = &run->x_target##_bufs[m_index]; \ us_m2m_buffer_s *m_buf = &run->x_target##_bufs[m_i]; \
if (m_buf->allocated > 0 && m_buf->data != NULL) { \ if (m_buf->allocated > 0 && m_buf->data != NULL) { \
if (munmap(m_buf->data, m_buf->allocated) < 0) { \ if (munmap(m_buf->data, m_buf->allocated) < 0) { \
_LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_index); \ _LOG_PERROR("Can't unmap %s buffer=%u", #x_name, m_i); \
} \ } \
} \ } \
} \ } \
@ -432,8 +464,8 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
} \ } \
run->n_##x_target##_bufs = 0; \ run->n_##x_target##_bufs = 0; \
} }
DELETE_BUFFERS("OUTPUT", output); DELETE_BUFFERS("OUTPUT", out);
DELETE_BUFFERS("INPUT", input); DELETE_BUFFERS("INPUT", in);
# undef DELETE_BUFFERS # undef DELETE_BUFFERS
if (run->fd >= 0) { if (run->fd >= 0) {
@ -452,7 +484,12 @@ static void _m2m_encoder_cleanup(us_m2m_encoder_s *enc) {
} }
} }
static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *src, us_frame_s *dest, bool force_key) { static int _m2m_encoder_compress_raw(
us_m2m_encoder_s *enc,
const us_frame_s *src,
us_frame_s *dest,
bool force_key
) {
us_m2m_encoder_runtime_s *const run = enc->run; us_m2m_encoder_runtime_s *const run = enc->run;
assert(run->ready); assert(run->ready);
@ -465,28 +502,28 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
_E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe"); _E_XIOCTL(VIDIOC_S_CTRL, &ctl, "Can't force keyframe");
} }
struct v4l2_buffer input_buf = {0}; struct v4l2_buffer in_buf = {0};
struct v4l2_plane input_plane = {0}; struct v4l2_plane in_plane = {0};
input_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; in_buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
input_buf.length = 1; in_buf.length = 1;
input_buf.m.planes = &input_plane; in_buf.m.planes = &in_plane;
if (run->p_dma) { if (run->p_dma) {
input_buf.index = 0; in_buf.index = 0;
input_buf.memory = V4L2_MEMORY_DMABUF; in_buf.memory = V4L2_MEMORY_DMABUF;
input_buf.field = V4L2_FIELD_NONE; in_buf.field = V4L2_FIELD_NONE;
input_plane.m.fd = src->dma_fd; in_plane.m.fd = src->dma_fd;
_LOG_DEBUG("Using INPUT-DMA buffer=%u", input_buf.index); _LOG_DEBUG("Using INPUT-DMA buffer=%u", in_buf.index);
} else { } else {
input_buf.memory = V4L2_MEMORY_MMAP; in_buf.memory = V4L2_MEMORY_MMAP;
_LOG_DEBUG("Grabbing INPUT buffer ..."); _LOG_DEBUG("Grabbing INPUT buffer ...");
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't grab INPUT buffer"); _E_XIOCTL(VIDIOC_DQBUF, &in_buf, "Can't grab INPUT buffer");
if (input_buf.index >= run->n_input_bufs) { if (in_buf.index >= run->n_in_bufs) {
_LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u", _LOG_ERROR("V4L2 error: grabbed invalid INPUT: buffer=%u, n_bufs=%u",
input_buf.index, run->n_input_bufs); in_buf.index, run->n_in_bufs);
goto error; goto error;
} }
_LOG_DEBUG("Grabbed INPUT buffer=%u", input_buf.index); _LOG_DEBUG("Grabbed INPUT buffer=%u", in_buf.index);
} }
const u64 now_ts = us_get_now_monotonic_u64(); const u64 now_ts = us_get_now_monotonic_u64();
@ -495,21 +532,21 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
.tv_usec = now_ts % 1000000, .tv_usec = now_ts % 1000000,
}; };
input_buf.timestamp.tv_sec = ts.tv_sec; in_buf.timestamp.tv_sec = ts.tv_sec;
input_buf.timestamp.tv_usec = ts.tv_usec; in_buf.timestamp.tv_usec = ts.tv_usec;
input_plane.bytesused = src->used; in_plane.bytesused = src->used;
input_plane.length = src->used; in_plane.length = src->used;
if (!run->p_dma) { if (!run->p_dma) {
memcpy(run->input_bufs[input_buf.index].data, src->data, src->used); memcpy(run->in_bufs[in_buf.index].data, src->data, src->used);
} }
const char *input_name = (run->p_dma ? "INPUT-DMA" : "INPUT"); const char *in_name = (run->p_dma ? "INPUT-DMA" : "INPUT");
_LOG_DEBUG("Sending%s %s buffer ...", (!run->p_dma ? " (releasing)" : ""), input_name); _LOG_DEBUG("Sending%s %s buffer ...", (!run->p_dma ? " (releasing)" : ""), in_name);
_E_XIOCTL(VIDIOC_QBUF, &input_buf, "Can't send %s buffer", input_name); _E_XIOCTL(VIDIOC_QBUF, &in_buf, "Can't send %s buffer", in_name);
// Для не-DMA отправка буфера по факту являтся освобождением этого буфера // Для не-DMA отправка буфера по факту являтся освобождением этого буфера
bool input_released = !run->p_dma; bool in_released = !run->p_dma;
// https://github.com/pikvm/ustreamer/issues/253 // https://github.com/pikvm/ustreamer/issues/253
// За секунду точно должно закодироваться. // За секунду точно должно закодироваться.
@ -529,37 +566,37 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr
} }
if (enc_poll.revents & POLLIN) { if (enc_poll.revents & POLLIN) {
if (!input_released) { if (!in_released) {
_LOG_DEBUG("Releasing %s buffer=%u ...", input_name, input_buf.index); _LOG_DEBUG("Releasing %s buffer=%u ...", in_name, in_buf.index);
_E_XIOCTL(VIDIOC_DQBUF, &input_buf, "Can't release %s buffer=%u", _E_XIOCTL(VIDIOC_DQBUF, &in_buf, "Can't release %s buffer=%u",
input_name, input_buf.index); in_name, in_buf.index);
input_released = true; in_released = true;
} }
struct v4l2_buffer output_buf = {0}; struct v4l2_buffer out_buf = {0};
struct v4l2_plane output_plane = {0}; struct v4l2_plane out_plane = {0};
output_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; out_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
output_buf.memory = V4L2_MEMORY_MMAP; out_buf.memory = V4L2_MEMORY_MMAP;
output_buf.length = 1; out_buf.length = 1;
output_buf.m.planes = &output_plane; out_buf.m.planes = &out_plane;
_LOG_DEBUG("Fetching OUTPUT buffer ..."); _LOG_DEBUG("Fetching OUTPUT buffer ...");
_E_XIOCTL(VIDIOC_DQBUF, &output_buf, "Can't fetch OUTPUT buffer"); _E_XIOCTL(VIDIOC_DQBUF, &out_buf, "Can't fetch OUTPUT buffer");
bool done = false; bool done = false;
if (ts.tv_sec != output_buf.timestamp.tv_sec || ts.tv_usec != output_buf.timestamp.tv_usec) { if (ts.tv_sec != out_buf.timestamp.tv_sec || ts.tv_usec != out_buf.timestamp.tv_usec) {
// Енкодер первый раз может выдать буфер с мусором и нулевым таймстампом, // Енкодер первый раз может выдать буфер с мусором и нулевым таймстампом,
// так что нужно убедиться, что мы читаем выходной буфер, соответствующий // так что нужно убедиться, что мы читаем выходной буфер, соответствующий
// входному (с тем же таймстампом). // входному (с тем же таймстампом).
_LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch"); _LOG_DEBUG("Need to retry OUTPUT buffer due timestamp mismatch");
} else { } else {
us_frame_set_data(dest, run->output_bufs[output_buf.index].data, output_plane.bytesused); us_frame_set_data(dest, run->out_bufs[out_buf.index].data, out_plane.bytesused);
dest->key = output_buf.flags & V4L2_BUF_FLAG_KEYFRAME; dest->key = out_buf.flags & V4L2_BUF_FLAG_KEYFRAME;
dest->gop = enc->gop; dest->gop = enc->gop;
done = true; done = true;
} }
_LOG_DEBUG("Releasing OUTPUT buffer=%u ...", output_buf.index); _LOG_DEBUG("Releasing OUTPUT buffer=%u ...", out_buf.index);
_E_XIOCTL(VIDIOC_QBUF, &output_buf, "Can't release OUTPUT buffer=%u", output_buf.index); _E_XIOCTL(VIDIOC_QBUF, &out_buf, "Can't release OUTPUT buffer=%u", out_buf.index);
if (done) { if (done) {
break; break;

View File

@ -34,14 +34,14 @@ typedef struct {
typedef struct { typedef struct {
int fd; int fd;
uint fps_limit; uint fps_limit;
us_m2m_buffer_s *input_bufs; us_m2m_buffer_s *in_bufs;
uint n_input_bufs; uint n_in_bufs;
us_m2m_buffer_s *output_bufs; us_m2m_buffer_s *out_bufs;
uint n_output_bufs; uint n_out_bufs;
uint p_width; uint p_width;
uint p_height; uint p_height;
uint p_input_format; uint p_in_format;
uint p_stride; uint p_stride;
bool p_dma; bool p_dma;
@ -53,7 +53,7 @@ typedef struct {
typedef struct { typedef struct {
char *name; char *name;
char *path; char *path;
uint output_format; uint out_format;
uint bitrate; uint bitrate;
uint gop; uint gop;
uint quality; uint quality;

View File

@ -83,13 +83,13 @@ int main(int argc, char *argv[]) {
US_LOGGING_INIT; US_LOGGING_INIT;
US_THREAD_RENAME("main"); US_THREAD_RENAME("main");
us_options_s *options = us_options_init(argc, argv); us_options_s *opts = us_options_init(argc, argv);
us_capture_s *cap = us_capture_init(); us_capture_s *cap = us_capture_init();
us_encoder_s *enc = us_encoder_init(); us_encoder_s *enc = us_encoder_init();
_g_stream = us_stream_init(cap, enc); _g_stream = us_stream_init(cap, enc);
_g_server = us_server_init(_g_stream); _g_server = us_server_init(_g_stream);
if ((exit_code = options_parse(options, cap, enc, _g_stream, _g_server)) == 0) { if ((exit_code = us_options_parse(opts, cap, enc, _g_stream, _g_server)) == 0) {
us_stream_update_blank(_g_stream, cap); us_stream_update_blank(_g_stream, cap);
# ifdef WITH_GPIO # ifdef WITH_GPIO
us_gpio_init(); us_gpio_init();
@ -120,7 +120,7 @@ int main(int argc, char *argv[]) {
us_stream_destroy(_g_stream); us_stream_destroy(_g_stream);
us_encoder_destroy(enc); us_encoder_destroy(enc);
us_capture_destroy(cap); us_capture_destroy(cap);
us_options_destroy(options); us_options_destroy(opts);
if (exit_code == 0) { if (exit_code == 0) {
US_LOG_INFO("Bye-bye"); US_LOG_INFO("Bye-bye");

View File

@ -22,6 +22,35 @@
#include "options.h" #include "options.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include "../libs/types.h"
#include "../libs/const.h"
#include "../libs/logging.h"
#include "../libs/process.h"
#include "../libs/frame.h"
#include "../libs/memsink.h"
#include "../libs/options.h"
#include "../libs/capture.h"
#ifdef WITH_V4P
# include "../libs/drm/drm.h"
#endif
#include "encoder.h"
#include "stream.h"
#include "http/server.h"
#ifdef WITH_GPIO
# include "gpio/gpio.h"
#endif
enum _US_OPT_VALUES { enum _US_OPT_VALUES {
_O_DEVICE = 'd', _O_DEVICE = 'd',
@ -252,53 +281,65 @@ static const struct option _LONG_OPTS[] = {
}; };
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited); static int _parse_resolution(const char *str, uint *width, uint *height, bool limited);
static int _check_instance_id(const char *str); static int _check_instance_id(const char *str);
static void _features(void); static void _features(void);
static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server);
static void _help(
FILE *fp,
const us_capture_s *cap,
const us_encoder_s *enc,
const us_stream_s *stream,
const us_server_s *server);
us_options_s *us_options_init(unsigned argc, char *argv[]) { us_options_s *us_options_init(uint argc, char *argv[]) {
us_options_s *options; us_options_s *opts;
US_CALLOC(options, 1); US_CALLOC(opts, 1);
options->argc = argc; opts->argc = argc;
options->argv = argv; opts->argv = argv;
US_CALLOC(options->argv_copy, argc); US_CALLOC(opts->argv_copy, argc);
for (unsigned index = 0; index < argc; ++index) { for (uint i = 0; i < argc; ++i) {
options->argv_copy[index] = us_strdup(argv[index]); opts->argv_copy[i] = us_strdup(argv[i]);
} }
return options; return opts;
} }
void us_options_destroy(us_options_s *options) { void us_options_destroy(us_options_s *opts) {
US_DELETE(options->jpeg_sink, us_memsink_destroy); US_DELETE(opts->jpeg_sink, us_memsink_destroy);
US_DELETE(options->raw_sink, us_memsink_destroy); US_DELETE(opts->raw_sink, us_memsink_destroy);
US_DELETE(options->h264_sink, us_memsink_destroy); US_DELETE(opts->h264_sink, us_memsink_destroy);
# ifdef WITH_V4P # ifdef WITH_V4P
US_DELETE(options->drm, us_drm_destroy); US_DELETE(opts->drm, us_drm_destroy);
# endif # endif
for (unsigned index = 0; index < options->argc; ++index) { for (uint i = 0; i < opts->argc; ++i) {
free(options->argv_copy[index]); free(opts->argv_copy[i]);
} }
free(options->argv_copy); free(opts->argv_copy);
free(options); free(opts);
} }
int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, us_stream_s *stream, us_server_s *server) { int us_options_parse(
us_options_s *opts,
us_capture_s *cap,
us_encoder_s *enc,
us_stream_s *stream,
us_server_s *server
) {
# define OPT_SET(x_dest, x_value) { \ # define OPT_SET(x_dest, x_value) { \
x_dest = x_value; \ x_dest = x_value; \
break; \ break; \
} }
# define OPT_NUMBER(x_name, x_dest, x_min, x_max, x_base) { \ # define OPT_NUMBER(x_name, x_dest, x_min, x_max, x_base) { \
errno = 0; char *m_end = NULL; const long long m_tmp = strtoll(optarg, &m_end, x_base); \ errno = 0; char *m_end = NULL; const sll m_tmp = strtoll(optarg, &m_end, x_base); \
if (errno || *m_end || m_tmp < x_min || m_tmp > x_max) { \ if (errno || *m_end || m_tmp < x_min || m_tmp > x_max) { \
printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", x_name, optarg, (long long)x_min, (long long)x_max); \ printf("Invalid value for '%s=%s': min=%lld, max=%lld\n", x_name, optarg, (sll)x_min, (sll)x_max); \
return -1; \ return -1; \
} \ } \
x_dest = m_tmp; \ x_dest = m_tmp; \
@ -362,8 +403,8 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
const char *x_prefix##_name = NULL; \ const char *x_prefix##_name = NULL; \
mode_t x_prefix##_mode = 0660; \ mode_t x_prefix##_mode = 0660; \
bool x_prefix##_rm = false; \ bool x_prefix##_rm = false; \
unsigned x_prefix##_client_ttl = 10; \ uint x_prefix##_client_ttl = 10; \
unsigned x_prefix##_timeout = 1; uint x_prefix##_timeout = 1;
ADD_SINK(jpeg_sink); ADD_SINK(jpeg_sink);
ADD_SINK(raw_sink); ADD_SINK(raw_sink);
ADD_SINK(h264_sink); ADD_SINK(h264_sink);
@ -376,7 +417,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
char short_opts[128]; char short_opts[128];
us_build_short_options(_LONG_OPTS, short_opts, 128); us_build_short_options(_LONG_OPTS, short_opts, 128);
for (int ch; (ch = getopt_long(options->argc, options->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) { for (int ch; (ch = getopt_long(opts->argc, opts->argv_copy, short_opts, _LONG_OPTS, NULL)) >= 0;) {
switch (ch) { switch (ch) {
case _O_DEVICE: OPT_SET(cap->path, optarg); case _O_DEVICE: OPT_SET(cap->path, optarg);
case _O_INPUT: OPT_NUMBER("--input", cap->input, 0, 128, 0); case _O_INPUT: OPT_NUMBER("--input", cap->input, 0, 128, 0);
@ -475,8 +516,8 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
# ifdef WITH_V4P # ifdef WITH_V4P
case _O_V4P: case _O_V4P:
options->drm = us_drm_init(); opts->drm = us_drm_init();
stream->drm = options->drm; stream->drm = opts->drm;
break; break;
# endif # endif
@ -522,7 +563,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
# define ADD_SINK(x_label, x_prefix) { \ # define ADD_SINK(x_label, x_prefix) { \
if (x_prefix##_name && x_prefix##_name[0] != '\0') { \ if (x_prefix##_name && x_prefix##_name[0] != '\0') { \
options->x_prefix = us_memsink_init_opened( \ opts->x_prefix = us_memsink_init_opened( \
x_label, \ x_label, \
x_prefix##_name, \ x_prefix##_name, \
true, \ true, \
@ -532,7 +573,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
x_prefix##_timeout \ x_prefix##_timeout \
); \ ); \
} \ } \
stream->x_prefix = options->x_prefix; \ stream->x_prefix = opts->x_prefix; \
} }
ADD_SINK("JPEG", jpeg_sink); ADD_SINK("JPEG", jpeg_sink);
ADD_SINK("RAW", raw_sink); ADD_SINK("RAW", raw_sink);
@ -541,7 +582,7 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
# ifdef WITH_SETPROCTITLE # ifdef WITH_SETPROCTITLE
if (process_name_prefix != NULL) { if (process_name_prefix != NULL) {
us_process_set_name_prefix(options->argc, options->argv, process_name_prefix); us_process_set_name_prefix(opts->argc, opts->argv, process_name_prefix);
} }
# endif # endif
@ -555,9 +596,9 @@ int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, u
return 0; return 0;
} }
static int _parse_resolution(const char *str, unsigned *width, unsigned *height, bool limited) { static int _parse_resolution(const char *str, uint *width, uint *height, bool limited) {
unsigned tmp_width; uint tmp_width;
unsigned tmp_height; uint tmp_height;
if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) { if (sscanf(str, "%ux%u", &tmp_width, &tmp_height) != 2) {
return -1; return -1;
} }
@ -636,7 +677,13 @@ static void _features(void) {
# endif # endif
} }
static void _help(FILE *fp, const us_capture_s *cap, const us_encoder_s *enc, const us_stream_s *stream, const us_server_s *server) { static void _help(
FILE *fp,
const us_capture_s *cap,
const us_encoder_s *enc,
const us_stream_s *stream,
const us_server_s *server
) {
# define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__) # define SAY(x_msg, ...) fprintf(fp, x_msg "\n", ##__VA_ARGS__)
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer"); SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
SAY("═══════════════════════════════════════════════════"); SAY("═══════════════════════════════════════════════════");

View File

@ -22,23 +22,8 @@
#pragma once #pragma once
#include <stdio.h> #include "../libs/types.h"
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <limits.h>
#include <getopt.h>
#include <errno.h>
#include <assert.h>
#include "../libs/const.h"
#include "../libs/logging.h"
#include "../libs/process.h"
#include "../libs/frame.h"
#include "../libs/memsink.h" #include "../libs/memsink.h"
#include "../libs/options.h"
#include "../libs/capture.h" #include "../libs/capture.h"
#ifdef WITH_V4P #ifdef WITH_V4P
# include "../libs/drm/drm.h" # include "../libs/drm/drm.h"
@ -47,13 +32,10 @@
#include "encoder.h" #include "encoder.h"
#include "stream.h" #include "stream.h"
#include "http/server.h" #include "http/server.h"
#ifdef WITH_GPIO
# include "gpio/gpio.h"
#endif
typedef struct { typedef struct {
unsigned argc; uint argc;
char **argv; char **argv;
char **argv_copy; char **argv_copy;
us_memsink_s *jpeg_sink; us_memsink_s *jpeg_sink;
@ -65,7 +47,12 @@ typedef struct {
} us_options_s; } us_options_s;
us_options_s *us_options_init(unsigned argc, char *argv[]); us_options_s *us_options_init(uint argc, char *argv[]);
void us_options_destroy(us_options_s *options); void us_options_destroy(us_options_s *options);
int options_parse(us_options_s *options, us_capture_s *cap, us_encoder_s *enc, us_stream_s *stream, us_server_s *server); int us_options_parse(
us_options_s *options,
us_capture_s *cap,
us_encoder_s *enc,
us_stream_s *stream,
us_server_s *server);

View File

@ -62,14 +62,14 @@
typedef struct { typedef struct {
pthread_t tid; pthread_t tid;
us_capture_s *cap; us_capture_s *cap;
us_queue_s *queue; us_queue_s *q;
pthread_mutex_t *mutex; pthread_mutex_t *mutex;
atomic_bool *stop; atomic_bool *stop;
} _releaser_context_s; } _releaser_context_s;
typedef struct { typedef struct {
pthread_t tid; pthread_t tid;
us_queue_s *queue; us_queue_s *q;
us_stream_s *stream; us_stream_s *stream;
atomic_bool *stop; atomic_bool *stop;
} _worker_context_s; } _worker_context_s;
@ -83,7 +83,7 @@ static void *_h264_thread(void *v_ctx);
static void *_drm_thread(void *v_ctx); static void *_drm_thread(void *v_ctx);
#endif #endif
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *queue); static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *q);
static bool _stream_has_jpeg_clients_cached(us_stream_s *stream); static bool _stream_has_jpeg_clients_cached(us_stream_s *stream);
static bool _stream_has_any_clients_cached(us_stream_s *stream); static bool _stream_has_any_clients_cached(us_stream_s *stream);
@ -108,7 +108,7 @@ us_stream_s *us_stream_init(us_capture_s *cap, us_encoder_s *enc) {
US_RING_INIT_WITH_ITEMS(http->jpeg_ring, 4, us_frame_init); US_RING_INIT_WITH_ITEMS(http->jpeg_ring, 4, us_frame_init);
atomic_init(&http->has_clients, false); atomic_init(&http->has_clients, false);
atomic_init(&http->snapshot_requested, 0); atomic_init(&http->snapshot_requested, 0);
atomic_init(&http->last_request_ts, 0); atomic_init(&http->last_req_ts, 0);
http->captured_fpsi = us_fpsi_init("STREAM-CAPTURED", true); http->captured_fpsi = us_fpsi_init("STREAM-CAPTURED", true);
us_stream_runtime_s *run; us_stream_runtime_s *run;
@ -154,7 +154,7 @@ void us_stream_loop(us_stream_s *stream) {
us_stream_runtime_s *const run = stream->run; us_stream_runtime_s *const run = stream->run;
us_capture_s *const cap = stream->cap; us_capture_s *const cap = stream->cap;
atomic_store(&run->http->last_request_ts, us_get_now_monotonic()); atomic_store(&run->http->last_req_ts, us_get_now_monotonic());
if (stream->h264_sink != NULL) { if (stream->h264_sink != NULL) {
run->h264_enc = us_m2m_h264_encoder_init( run->h264_enc = us_m2m_h264_encoder_init(
@ -177,10 +177,10 @@ void us_stream_loop(us_stream_s *stream) {
const uint n_releasers = cap->run->n_bufs; const uint n_releasers = cap->run->n_bufs;
_releaser_context_s *releasers; _releaser_context_s *releasers;
US_CALLOC(releasers, n_releasers); US_CALLOC(releasers, n_releasers);
for (uint index = 0; index < n_releasers; ++index) { for (uint i = 0; i < n_releasers; ++i) {
_releaser_context_s *ctx = &releasers[index]; _releaser_context_s *ctx = &releasers[i];
ctx->cap = cap; ctx->cap = cap;
ctx->queue = us_queue_init(1); ctx->q = us_queue_init(1);
ctx->mutex = &release_mutex; ctx->mutex = &release_mutex;
ctx->stop = &threads_stop; ctx->stop = &threads_stop;
US_THREAD_CREATE(ctx->tid, _releaser_thread, ctx); US_THREAD_CREATE(ctx->tid, _releaser_thread, ctx);
@ -190,7 +190,7 @@ void us_stream_loop(us_stream_s *stream) {
_worker_context_s *x_ctx = NULL; \ _worker_context_s *x_ctx = NULL; \
if (x_cond) { \ if (x_cond) { \
US_CALLOC(x_ctx, 1); \ US_CALLOC(x_ctx, 1); \
x_ctx->queue = us_queue_init(x_capacity); \ x_ctx->q = us_queue_init(x_capacity); \
x_ctx->stream = stream; \ x_ctx->stream = stream; \
x_ctx->stop = &threads_stop; \ x_ctx->stop = &threads_stop; \
US_THREAD_CREATE(x_ctx->tid, (x_thread), x_ctx); \ US_THREAD_CREATE(x_ctx->tid, (x_thread), x_ctx); \
@ -222,7 +222,7 @@ void us_stream_loop(us_stream_s *stream) {
# define QUEUE_HW(x_ctx) if (x_ctx != NULL) { \ # define QUEUE_HW(x_ctx) if (x_ctx != NULL) { \
us_capture_hwbuf_incref(hw); \ us_capture_hwbuf_incref(hw); \
us_queue_put(x_ctx->queue, hw, 0); \ us_queue_put(x_ctx->q, hw, 0); \
} }
QUEUE_HW(jpeg_ctx); QUEUE_HW(jpeg_ctx);
QUEUE_HW(raw_ctx); QUEUE_HW(raw_ctx);
@ -231,7 +231,7 @@ void us_stream_loop(us_stream_s *stream) {
QUEUE_HW(drm_ctx); QUEUE_HW(drm_ctx);
# endif # endif
# undef QUEUE_HW # undef QUEUE_HW
us_queue_put(releasers[hw->buf.index].queue, hw, 0); // Plan to release us_queue_put(releasers[hw->buf.index].q, hw, 0); // Plan to release
// Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков // Мы не обновляем здесь состояние синков, потому что это происходит внутри обслуживающих их потоков
_stream_check_suicide(stream); _stream_check_suicide(stream);
@ -249,7 +249,7 @@ void us_stream_loop(us_stream_s *stream) {
# define DELETE_WORKER(x_ctx) if (x_ctx != NULL) { \ # define DELETE_WORKER(x_ctx) if (x_ctx != NULL) { \
US_THREAD_JOIN(x_ctx->tid); \ US_THREAD_JOIN(x_ctx->tid); \
us_queue_destroy(x_ctx->queue); \ us_queue_destroy(x_ctx->q); \
free(x_ctx); \ free(x_ctx); \
} }
# ifdef WITH_V4P # ifdef WITH_V4P
@ -260,9 +260,9 @@ void us_stream_loop(us_stream_s *stream) {
DELETE_WORKER(jpeg_ctx); DELETE_WORKER(jpeg_ctx);
# undef DELETE_WORKER # undef DELETE_WORKER
for (uint index = 0; index < n_releasers; ++index) { for (uint i = 0; i < n_releasers; ++i) {
US_THREAD_JOIN(releasers[index].tid); US_THREAD_JOIN(releasers[i].tid);
us_queue_destroy(releasers[index].queue); us_queue_destroy(releasers[i].q);
} }
free(releasers); free(releasers);
US_MUTEX_DESTROY(release_mutex); US_MUTEX_DESTROY(release_mutex);
@ -292,7 +292,7 @@ static void *_releaser_thread(void *v_ctx) {
while (!atomic_load(ctx->stop)) { while (!atomic_load(ctx->stop)) {
us_capture_hwbuf_s *hw; us_capture_hwbuf_s *hw;
if (us_queue_get(ctx->queue, (void**)&hw, 0.1) < 0) { if (us_queue_get(ctx->q, (void**)&hw, 0.1) < 0) {
continue; continue;
} }
@ -348,7 +348,7 @@ static void *_jpeg_thread(void *v_ctx) {
} }
} }
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue); us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
if (hw == NULL) { if (hw == NULL) {
continue; continue;
} }
@ -399,7 +399,7 @@ static void *_raw_thread(void *v_ctx) {
_worker_context_s *ctx = v_ctx; _worker_context_s *ctx = v_ctx;
while (!atomic_load(ctx->stop)) { while (!atomic_load(ctx->stop)) {
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue); us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
if (hw == NULL) { if (hw == NULL) {
continue; continue;
} }
@ -423,7 +423,7 @@ static void *_h264_thread(void *v_ctx) {
uint step = 1; uint step = 1;
while (!atomic_load(ctx->stop)) { while (!atomic_load(ctx->stop)) {
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue); us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
if (hw == NULL) { if (hw == NULL) {
continue; continue;
} }
@ -472,7 +472,7 @@ static void *_drm_thread(void *v_ctx) {
# define SLOWDOWN { \ # define SLOWDOWN { \
const ldf m_next_ts = us_get_now_monotonic() + 1; \ const ldf m_next_ts = us_get_now_monotonic() + 1; \
while (!atomic_load(ctx->stop) && us_get_now_monotonic() < m_next_ts) { \ while (!atomic_load(ctx->stop) && us_get_now_monotonic() < m_next_ts) { \
us_capture_hwbuf_s *m_pass_hw = _get_latest_hw(ctx->queue); \ us_capture_hwbuf_s *m_pass_hw = _get_latest_hw(ctx->q); \
if (m_pass_hw != NULL) { \ if (m_pass_hw != NULL) { \
us_capture_hwbuf_decref(m_pass_hw); \ us_capture_hwbuf_decref(m_pass_hw); \
} \ } \
@ -485,7 +485,7 @@ static void *_drm_thread(void *v_ctx) {
CHECK(us_drm_wait_for_vsync(stream->drm)); CHECK(us_drm_wait_for_vsync(stream->drm));
US_DELETE(prev_hw, us_capture_hwbuf_decref); US_DELETE(prev_hw, us_capture_hwbuf_decref);
us_capture_hwbuf_s *hw = _get_latest_hw(ctx->queue); us_capture_hwbuf_s *hw = _get_latest_hw(ctx->q);
if (hw == NULL) { if (hw == NULL) {
continue; continue;
} }
@ -521,14 +521,14 @@ static void *_drm_thread(void *v_ctx) {
} }
#endif #endif
static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *queue) { static us_capture_hwbuf_s *_get_latest_hw(us_queue_s *q) {
us_capture_hwbuf_s *hw; us_capture_hwbuf_s *hw;
if (us_queue_get(queue, (void**)&hw, 0.1) < 0) { if (us_queue_get(q, (void**)&hw, 0.1) < 0) {
return NULL; return NULL;
} }
while (!us_queue_is_empty(queue)) { // Берем только самый свежий кадр while (!us_queue_is_empty(q)) { // Берем только самый свежий кадр
us_capture_hwbuf_decref(hw); us_capture_hwbuf_decref(hw);
assert(!us_queue_get(queue, (void**)&hw, 0)); assert(!us_queue_get(q, (void**)&hw, 0));
} }
return hw; return hw;
} }
@ -647,11 +647,11 @@ static int _stream_init_loop(us_stream_s *stream) {
US_LOG_INFO("Device error, exiting ..."); US_LOG_INFO("Device error, exiting ...");
us_process_suicide(); us_process_suicide();
} }
for (uint count = 0; count < stream->error_delay * 10; ++count) { for (uint i = 0; i < stream->error_delay * 10; ++i) {
if (atomic_load(&run->stop)) { if (atomic_load(&run->stop)) {
break; break;
} }
if (count % 10 == 0) { if (i % 10 == 0) {
// Каждую секунду повторяем blank // Каждую секунду повторяем blank
uint width = stream->cap->run->width; uint width = stream->cap->run->width;
uint height = stream->cap->run->height; uint height = stream->cap->run->height;
@ -716,12 +716,14 @@ close:
static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame) { static void _stream_expose_jpeg(us_stream_s *stream, const us_frame_s *frame) {
us_stream_runtime_s *const run = stream->run; us_stream_runtime_s *const run = stream->run;
int ri; int ri;
while ((ri = us_ring_producer_acquire(run->http->jpeg_ring, 0)) < 0) { while ((ri = us_ring_producer_acquire(run->http->jpeg_ring, 0)) < 0) {
if (atomic_load(&run->stop)) { if (atomic_load(&run->stop)) {
return; return;
} }
} }
us_frame_s *const dest = run->http->jpeg_ring->items[ri]; us_frame_s *const dest = run->http->jpeg_ring->items[ri];
us_frame_copy(frame, dest); us_frame_copy(frame, dest);
us_ring_producer_release(run->http->jpeg_ring, ri); us_ring_producer_release(run->http->jpeg_ring, ri);
@ -741,6 +743,7 @@ static void _stream_encode_expose_h264(us_stream_s *stream, const us_frame_s *fr
if (stream->h264_sink == NULL) { if (stream->h264_sink == NULL) {
return; return;
} }
us_stream_runtime_s *run = stream->run; us_stream_runtime_s *run = stream->run;
us_fpsi_meta_s meta = {.online = false}; us_fpsi_meta_s meta = {.online = false};
@ -767,16 +770,16 @@ static void _stream_check_suicide(us_stream_s *stream) {
if (stream->exit_on_no_clients == 0) { if (stream->exit_on_no_clients == 0) {
return; return;
} }
us_stream_runtime_s *const run = stream->run;
const atomic_ullong *last_req_ts = &stream->run->http->last_req_ts;
const ldf now_ts = us_get_now_monotonic(); const ldf now_ts = us_get_now_monotonic();
const ull http_last_request_ts = atomic_load(&run->http->last_request_ts); // Seconds
if (_stream_has_any_clients_cached(stream)) { if (_stream_has_any_clients_cached(stream)) {
atomic_store(&run->http->last_request_ts, now_ts); atomic_store(last_req_ts, now_ts);
} else if (http_last_request_ts + stream->exit_on_no_clients < now_ts) { } else if (atomic_load(last_req_ts) + stream->exit_on_no_clients < now_ts) {
US_LOG_INFO("No requests or HTTP/sink clients found in last %u seconds, exiting ...", US_LOG_INFO("No requests or HTTP/sink clients found in last %u seconds, exiting ...",
stream->exit_on_no_clients); stream->exit_on_no_clients);
atomic_store(last_req_ts, now_ts); // Prevent a signal spam
us_process_suicide(); us_process_suicide();
atomic_store(&run->http->last_request_ts, now_ts);
} }
} }

View File

@ -57,7 +57,7 @@ typedef struct {
us_ring_s *jpeg_ring; us_ring_s *jpeg_ring;
atomic_bool has_clients; atomic_bool has_clients;
atomic_uint snapshot_requested; atomic_uint snapshot_requested;
atomic_ullong last_request_ts; // Seconds atomic_ullong last_req_ts; // Seconds
us_fpsi_s *captured_fpsi; us_fpsi_s *captured_fpsi;
} us_stream_http_s; } us_stream_http_s;

View File

@ -37,11 +37,14 @@ static void *_worker_thread(void *v_worker);
us_workers_pool_s *us_workers_pool_init( us_workers_pool_s *us_workers_pool_init(
const char *name, const char *wr_prefix, uint n_workers, const char *name,
us_workers_pool_job_init_f job_init, void *job_init_arg, const char *wr_prefix,
uint n_workers,
us_workers_pool_job_init_f job_init,
void *job_init_arg,
us_workers_pool_job_destroy_f job_destroy, us_workers_pool_job_destroy_f job_destroy,
us_workers_pool_run_job_f run_job) { us_workers_pool_run_job_f run_job
) {
US_LOG_INFO("Creating pool %s with %u workers ...", name, n_workers); US_LOG_INFO("Creating pool %s with %u workers ...", name, n_workers);
us_workers_pool_s *pool; us_workers_pool_s *pool;
@ -57,12 +60,12 @@ us_workers_pool_s *us_workers_pool_init(
US_MUTEX_INIT(pool->free_workers_mutex); US_MUTEX_INIT(pool->free_workers_mutex);
US_COND_INIT(pool->free_workers_cond); US_COND_INIT(pool->free_workers_cond);
for (uint index = 0; index < pool->n_workers; ++index) { for (uint i = 0; i < pool->n_workers; ++i) {
us_worker_s *wr; us_worker_s *wr;
US_CALLOC(wr, 1); US_CALLOC(wr, 1);
wr->number = index; wr->number = i;
US_ASPRINTF(wr->name, "%s-%u", wr_prefix, index); US_ASPRINTF(wr->name, "%s-%u", wr_prefix, i);
US_MUTEX_INIT(wr->has_job_mutex); US_MUTEX_INIT(wr->has_job_mutex);
atomic_init(&wr->has_job, false); atomic_init(&wr->has_job, false);
@ -112,7 +115,10 @@ us_worker_s *us_workers_pool_wait(us_workers_pool_s *pool) {
us_worker_s *found = NULL; us_worker_s *found = NULL;
US_LIST_ITERATE(pool->workers, wr, { // cppcheck-suppress constStatement US_LIST_ITERATE(pool->workers, wr, { // cppcheck-suppress constStatement
if (!atomic_load(&wr->has_job) && (found == NULL || found->job_start_ts <= wr->job_start_ts)) { if (
!atomic_load(&wr->has_job)
&& (found == NULL || found->job_start_ts <= wr->job_start_ts)
) {
found = wr; found = wr;
} }
}); });

View File

@ -75,8 +75,11 @@ typedef struct us_workers_pool_sx {
us_workers_pool_s *us_workers_pool_init( us_workers_pool_s *us_workers_pool_init(
const char *name, const char *wr_prefix, uint n_workers, const char *name,
us_workers_pool_job_init_f job_init, void *job_init_arg, const char *wr_prefix,
uint n_workers,
us_workers_pool_job_init_f job_init,
void *job_init_arg,
us_workers_pool_job_destroy_f job_destroy, us_workers_pool_job_destroy_f job_destroy,
us_workers_pool_run_job_f run_job); us_workers_pool_run_job_f run_job);

View File

@ -41,9 +41,9 @@ def main() -> None:
data_text = "{\n\t" + ",\n\t".join( data_text = "{\n\t" + ",\n\t".join(
", ".join( ", ".join(
f"0x{ch:02X}" f"0x{ch:02X}"
for ch in data[index:index + 20] for ch in data[i:i + 20]
) )
for index in range(0, len(data), 20) for i in range(0, len(data), 20)
) + ",\n}" ) + ",\n}"
text = f"{common.C_PREPEND}\n" text = f"{common.C_PREPEND}\n"