mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-19 08:16:31 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
690d291818 | ||
|
|
8cfb3fc301 | ||
|
|
69ab3fa831 | ||
|
|
79d6c76084 | ||
|
|
f15d2fc194 | ||
|
|
99aa4828c5 | ||
|
|
93969f487c | ||
|
|
61407c6596 | ||
|
|
6c95a56f3a | ||
|
|
283f53a961 | ||
|
|
ef8fb8216e |
@@ -1,7 +1,7 @@
|
||||
[bumpversion]
|
||||
commit = True
|
||||
tag = True
|
||||
current_version = 4.4
|
||||
current_version = 4.6
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)
|
||||
serialize =
|
||||
{major}.{minor}
|
||||
|
||||
@@ -4,3 +4,6 @@
|
||||
# Allow source code
|
||||
!Makefile
|
||||
!src/**
|
||||
!python/**
|
||||
!janus/**
|
||||
!man/**
|
||||
|
||||
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_file = lf
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.{py,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -2,16 +2,16 @@
|
||||
/linters/.mypy_cache/
|
||||
/pkg/arch/pkg/
|
||||
/pkg/arch/src/
|
||||
/pkg/arch/v*.tar.gz
|
||||
/pkg/arch/ustreamer-*.pkg.tar.xz
|
||||
/pkg/arch/ustreamer-*.pkg.tar.zst
|
||||
/src/build/
|
||||
/src/*.bin
|
||||
/python/build/
|
||||
/janus/build/
|
||||
/ustreamer
|
||||
/ustreamer-dump
|
||||
/config.mk
|
||||
/vgcore.*
|
||||
/*.sock
|
||||
/*.so
|
||||
vgcore.*
|
||||
*.sock
|
||||
*.so
|
||||
*.bin
|
||||
*.pkg.tar.xz
|
||||
*.pkg.tar.zst
|
||||
*.tar.gz
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
#define PRE 3 // Annex B prefix length
|
||||
|
||||
|
||||
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, rtp_callback_f callback);
|
||||
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback);
|
||||
static void _rtp_write_header(rtp_s *rtp, uint32_t pts, bool marked);
|
||||
|
||||
static ssize_t _find_annexb(const uint8_t *data, size_t size);
|
||||
@@ -99,6 +99,9 @@ char *rtp_make_sdp(rtp_s *rtp) {
|
||||
}
|
||||
|
||||
void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
|
||||
// There is a complicated logic here but everything works as it should:
|
||||
// - https://github.com/pikvm/ustreamer/issues/115#issuecomment-893071775
|
||||
|
||||
assert(frame->format == V4L2_PIX_FMT_H264);
|
||||
|
||||
const uint32_t pts = get_now_monotonic_u64() * 9 / 100; // PTS units are in 90 kHz
|
||||
@@ -118,7 +121,7 @@ void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
|
||||
if (data[size - 1] == 0) { // Check for extra 00
|
||||
--size;
|
||||
}
|
||||
_rtp_process_nalu(rtp, data, size, pts, callback);
|
||||
_rtp_process_nalu(rtp, data, size, pts, false, callback);
|
||||
}
|
||||
|
||||
last_offset = offset;
|
||||
@@ -127,18 +130,16 @@ void rtp_wrap_h264(rtp_s *rtp, const frame_s *frame, rtp_callback_f callback) {
|
||||
if (last_offset >= 0) {
|
||||
const uint8_t *data = frame->data + last_offset + PRE;
|
||||
size_t size = frame->used - last_offset - PRE;
|
||||
_rtp_process_nalu(rtp, data, size, pts, callback);
|
||||
_rtp_process_nalu(rtp, data, size, pts, true, callback);
|
||||
}
|
||||
}
|
||||
|
||||
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, rtp_callback_f callback) {
|
||||
void _rtp_process_nalu(rtp_s *rtp, const uint8_t *data, size_t size, uint32_t pts, bool marked, rtp_callback_f callback) {
|
||||
const unsigned ref_idc = (data[0] >> 5) & 3;
|
||||
const unsigned type = data[0] & 0x1F;
|
||||
|
||||
bool marked = false; // M=1 if this is an access unit
|
||||
frame_s *ps = NULL;
|
||||
switch (type) {
|
||||
case 1 ... 5: marked = true; break;
|
||||
case 7: ps = rtp->sps; break;
|
||||
case 8: ps = rtp->pps; break;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ disable =
|
||||
too-many-ancestors,
|
||||
no-else-return,
|
||||
len-as-condition,
|
||||
unspecified-encoding,
|
||||
|
||||
[REPORTS]
|
||||
msg-template = {symbol} -- {path}:{line}({obj}): {msg}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer-dump.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER-DUMP 1 "version 4.4" "January 2021"
|
||||
.TH USTREAMER-DUMP 1 "version 4.6" "January 2021"
|
||||
|
||||
.SH NAME
|
||||
ustreamer-dump \- Dump uStreamer's memory sink to file
|
||||
@@ -38,6 +38,12 @@ Filename to dump output to. Use '-' for stdout. Default: just consume the sink.
|
||||
.TP
|
||||
.BR \-j ", " \-\-output-json
|
||||
Format output as JSON. Required option --output. Default: disabled.
|
||||
.TP
|
||||
.BR \-c ", " \-\-count\ \fIN
|
||||
Limit the number of frames. Default: 0 (infinite).
|
||||
.TP
|
||||
.BR \-i ", "\-\-interval\ \fIsec
|
||||
Delay between reading frames (float). Default: 0.
|
||||
|
||||
.SS "Logging options"
|
||||
.TP
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Manpage for ustreamer.
|
||||
.\" Open an issue or pull request to https://github.com/pikvm/ustreamer to correct errors or typos
|
||||
.TH USTREAMER 1 "version 4.4" "November 2020"
|
||||
.TH USTREAMER 1 "version 4.6" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPG video from any V4L2 device to the network
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
pkgname=ustreamer
|
||||
pkgver=4.4
|
||||
pkgver=4.6
|
||||
pkgrel=1
|
||||
pkgdesc="Lightweight and fast MJPG-HTTP streamer"
|
||||
url="https://github.com/pikvm/ustreamer"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ustreamer
|
||||
PKG_VERSION:=4.4
|
||||
PKG_VERSION:=4.6
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Maxim Devaev <mdevaev@gmail.com>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from distutils.core import setup
|
||||
if __name__ == "__main__":
|
||||
setup(
|
||||
name="ustreamer",
|
||||
version="4.4",
|
||||
version="4.6",
|
||||
description="uStreamer tools",
|
||||
author="Maxim Devaev",
|
||||
author_email="mdevaev@gmail.com",
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <limits.h>
|
||||
#include <float.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
@@ -44,6 +46,8 @@ enum _OPT_VALUES {
|
||||
_O_SINK_TIMEOUT = 't',
|
||||
_O_OUTPUT = 'o',
|
||||
_O_OUTPUT_JSON = 'j',
|
||||
_O_COUNT = 'c',
|
||||
_O_INTERVAL = 'i',
|
||||
|
||||
_O_HELP = 'h',
|
||||
_O_VERSION = 'v',
|
||||
@@ -61,6 +65,8 @@ static const struct option _LONG_OPTS[] = {
|
||||
{"sink-timeout", required_argument, NULL, _O_SINK_TIMEOUT},
|
||||
{"output", required_argument, NULL, _O_OUTPUT},
|
||||
{"output-json", no_argument, NULL, _O_OUTPUT_JSON},
|
||||
{"count", required_argument, NULL, _O_COUNT},
|
||||
{"interval", required_argument, NULL, _O_INTERVAL},
|
||||
|
||||
{"log-level", required_argument, NULL, _O_LOG_LEVEL},
|
||||
{"perf", no_argument, NULL, _O_PERF},
|
||||
@@ -89,7 +95,10 @@ typedef struct {
|
||||
static void _signal_handler(int signum);
|
||||
static void _install_signal_handlers(void);
|
||||
|
||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx);
|
||||
static int _dump_sink(
|
||||
const char *sink_name, unsigned sink_timeout,
|
||||
long long count, long double interval,
|
||||
_output_context_s *ctx);
|
||||
|
||||
static void _help(FILE *fp);
|
||||
|
||||
@@ -102,6 +111,8 @@ int main(int argc, char *argv[]) {
|
||||
unsigned sink_timeout = 1;
|
||||
char *output_path = NULL;
|
||||
bool output_json = false;
|
||||
long long count = 0;
|
||||
long double interval = 0;
|
||||
|
||||
# define OPT_SET(_dest, _value) { \
|
||||
_dest = _value; \
|
||||
@@ -118,6 +129,16 @@ int main(int argc, char *argv[]) {
|
||||
break; \
|
||||
}
|
||||
|
||||
# define OPT_LDOUBLE(_name, _dest, _min, _max) { \
|
||||
errno = 0; char *_end = NULL; long double _tmp = strtold(optarg, &_end); \
|
||||
if (errno || *_end || _tmp < _min || _tmp > _max) { \
|
||||
printf("Invalid value for '%s=%s': min=%Lf, max=%Lf\n", _name, optarg, (long double)_min, (long double)_max); \
|
||||
return 1; \
|
||||
} \
|
||||
_dest = _tmp; \
|
||||
break; \
|
||||
}
|
||||
|
||||
char short_opts[128];
|
||||
build_short_options(_LONG_OPTS, short_opts, 128);
|
||||
|
||||
@@ -127,6 +148,8 @@ int main(int argc, char *argv[]) {
|
||||
case _O_SINK_TIMEOUT: OPT_NUMBER("--sink-timeout", sink_timeout, 1, 60, 0);
|
||||
case _O_OUTPUT: OPT_SET(output_path, optarg);
|
||||
case _O_OUTPUT_JSON: OPT_SET(output_json, true);
|
||||
case _O_COUNT: OPT_NUMBER("--count", count, 0, LLONG_MAX, 0);
|
||||
case _O_INTERVAL: OPT_LDOUBLE("--interval", interval, 0, 60);
|
||||
|
||||
case _O_LOG_LEVEL: OPT_NUMBER("--log-level", us_log_level, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, 0);
|
||||
case _O_PERF: OPT_SET(us_log_level, LOG_LEVEL_PERF);
|
||||
@@ -143,6 +166,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
# undef OPT_LDOUBLE
|
||||
# undef OPT_NUMBER
|
||||
# undef OPT_SET
|
||||
|
||||
@@ -163,7 +187,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
_install_signal_handlers();
|
||||
int retval = abs(_dump_sink(sink_name, sink_timeout, &ctx));
|
||||
int retval = abs(_dump_sink(sink_name, sink_timeout, count, interval, &ctx));
|
||||
if (ctx.v_output && ctx.destroy) {
|
||||
ctx.destroy(ctx.v_output);
|
||||
}
|
||||
@@ -201,7 +225,17 @@ static void _install_signal_handlers(void) {
|
||||
assert(!sigaction(SIGPIPE, &sig_act, NULL));
|
||||
}
|
||||
|
||||
static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_context_s *ctx) {
|
||||
static int _dump_sink(
|
||||
const char *sink_name, unsigned sink_timeout,
|
||||
long long count, long double interval,
|
||||
_output_context_s *ctx) {
|
||||
|
||||
if (count == 0) {
|
||||
count = -1;
|
||||
}
|
||||
|
||||
useconds_t interval_us = interval * 1000000;
|
||||
|
||||
frame_s *frame = frame_init();
|
||||
memsink_s *sink = NULL;
|
||||
|
||||
@@ -243,6 +277,17 @@ static int _dump_sink(const char *sink_name, unsigned sink_timeout, _output_cont
|
||||
if (ctx->v_output) {
|
||||
ctx->write(ctx->v_output, frame);
|
||||
}
|
||||
|
||||
if (count >= 0) {
|
||||
--count;
|
||||
if (count <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (interval_us > 0) {
|
||||
usleep(interval_us);
|
||||
}
|
||||
} else if (error == -2) {
|
||||
usleep(1000);
|
||||
} else {
|
||||
@@ -282,6 +327,8 @@ static void _help(FILE *fp) {
|
||||
SAY(" -t|--sink-timeout <sec> ─ Timeout for the upcoming frame. Default: 1.\n");
|
||||
SAY(" -o|--output <filename> ─── Filename to dump output to. Use '-' for stdout. Default: just consume the sink.\n");
|
||||
SAY(" -j|--output-json ──────── Format output as JSON. Required option --output. Default: disabled.\n");
|
||||
SAY(" -c|--count <N> ───────── Limit the number of frames. Default: 0 (infinite).\n");
|
||||
SAY(" -i|--interval <sec> ───── Delay between reading frames (float). Default: 0.\n");
|
||||
SAY("Logging options:");
|
||||
SAY("════════════════");
|
||||
SAY(" --log-level <N> ──── Verbosity level of messages from 0 (info) to 3 (debug).");
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_MAJOR 4
|
||||
#define VERSION_MINOR 4
|
||||
#define VERSION_MINOR 6
|
||||
|
||||
#define MAKE_VERSION2(_major, _minor) #_major "." #_minor
|
||||
#define MAKE_VERSION1(_major, _minor) MAKE_VERSION2(_major, _minor)
|
||||
|
||||
@@ -87,7 +87,11 @@ void memsink_destroy(memsink_s *sink) {
|
||||
}
|
||||
|
||||
bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||
// Возвращает true, если есть клиенты ИЛИ изменились метаданные
|
||||
// Return true (the need to write to memsink) on any of these conditions:
|
||||
// - EWOULDBLOCK - we have an active client;
|
||||
// - Incorrect magic or version - need to first write;
|
||||
// - We have some active clients by last_client_ts;
|
||||
// - Frame meta differs (like size, format, but not timestamp).
|
||||
|
||||
assert(sink->server);
|
||||
|
||||
@@ -100,15 +104,18 @@ bool memsink_server_check(memsink_s *sink, const frame_s *frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
atomic_store(&sink->has_clients, (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic()));
|
||||
if (sink->mem->magic != MEMSINK_MAGIC || sink->mem->version != MEMSINK_VERSION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool retval = (atomic_load(&sink->has_clients) || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));
|
||||
bool has_clients = (sink->mem->last_client_ts + sink->client_ttl > get_now_monotonic());
|
||||
atomic_store(&sink->has_clients, has_clients);
|
||||
|
||||
if (flock(sink->fd, LOCK_UN) < 0) {
|
||||
LOG_PERROR("%s-sink: Can't unlock memory", sink->name);
|
||||
return false;
|
||||
}
|
||||
return retval;
|
||||
return (has_clients || !FRAME_COMPARE_META_USED_NOTS(sink->mem, frame));;
|
||||
}
|
||||
|
||||
int memsink_server_put(memsink_s *sink, const frame_s *frame) {
|
||||
|
||||
Reference in New Issue
Block a user