mirror of
https://github.com/pikvm/ustreamer.git
synced 2025-12-23 18:50:00 +00:00
supported /dev/video21
This commit is contained in:
parent
2b969dd20d
commit
aa03e1610f
@ -3,14 +3,14 @@
|
||||
.TH USTREAMER 1 "version 4.13" "November 2020"
|
||||
|
||||
.SH NAME
|
||||
ustreamer \- stream MJPG video from any V4L2 device to the network
|
||||
ustreamer \- stream MJPEG video from any V4L2 device to the network
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B ustreamer
|
||||
.RI [OPTIONS]
|
||||
|
||||
.SH DESCRIPTION
|
||||
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the Pi-KVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
|
||||
µStreamer (\fBustreamer\fP) is a lightweight and very quick server to stream MJPEG video from any V4L2 device to the network. All new browsers have native support of this video format, as well as most video players such as mplayer, VLC etc. µStreamer is a part of the Pi-KVM project designed to stream VGA and HDMI screencast hardware data with the highest resolution and FPS possible.
|
||||
|
||||
.SH USAGE
|
||||
Without arguments, \fBustreamer\fR will try to open \fB/dev/video0\fR with 640x480 resolution and start streaming on \fBhttp://127\.0\.0\.1:8080\fR\. You can override this behavior using parameters \fB\-\-device\fR, \fB\-\-host\fR and \fB\-\-port\fR\. For example, to stream to the world, run: \fBustreamer --device=/dev/video1 --host=0.0.0.0 --port=80\fR
|
||||
@ -23,7 +23,7 @@ For example, the recommended way of running µStreamer with Auvidea B101 on a Ra
|
||||
.RS
|
||||
\fB\-\-format=uyvy \e\fR # Device input format
|
||||
.nf
|
||||
\fB\-\-encoder=v4l2 \e\fR # Hardware encoding with V4L2 M2M intraface
|
||||
\fB\-\-encoder=m2m-jpeg \e\fR # Hardware encoding with V4L2 M2M intraface
|
||||
.nf
|
||||
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
|
||||
.nf
|
||||
@ -84,18 +84,20 @@ Default: 1 (the number of CPU cores (but not more than 4)).
|
||||
.TP
|
||||
.BR \-q\ \fIN ", " \-\-quality\ \fIN
|
||||
Set quality of JPEG encoding from 1 to 100 (best). Default: 80.
|
||||
Note: If HW encoding is used (JPEG source format selected), this parameter attempts to configure the camera or capture device hardware's internal encoder. It does not re\-encode MJPG to MJPG to change the quality level for sources that already output MJPG.
|
||||
Note: If HW encoding is used (JPEG source format selected), this parameter attempts to configure the camera or capture device hardware's internal encoder. It does not re\-encode MJPEG to MJPEG to change the quality level for sources that already output MJPEG.
|
||||
.TP
|
||||
.BR \-c\ \fItype ", " \-\-encoder\ \fItype
|
||||
Use specified encoder. It may affect the number of workers.
|
||||
|
||||
CPU ─ Software MJPG encoding (default).
|
||||
CPU ─ Software MJPEG encoding (default).
|
||||
|
||||
HW ─ Use pre-encoded MJPG frames directly from camera hardware.
|
||||
HW ─ Use pre-encoded MJPEG frames directly from camera hardware.
|
||||
|
||||
V4L2 ─ GPU-accelerated MJPG encoding.
|
||||
M2M-MJPEG ─ GPU-accelerated MJPEG encoding.
|
||||
|
||||
NOOP ─ Don't compress MJPG stream (do nothing).
|
||||
M2M-JPEG ─ GPU-accelerated JPEG encoding.
|
||||
|
||||
NOOP ─ Don't compress MJPEG stream (do nothing).
|
||||
.TP
|
||||
.BR \-g\ \fIWxH,... ", " \-\-glitched\-resolutions\ \fIWxH,...
|
||||
It doesn't do anything. Still here for compatibility.
|
||||
@ -114,6 +116,9 @@ Timeout for device querying. Default: 1.
|
||||
.TP
|
||||
.BR \-\-device\-error\-delay\ \fIsec
|
||||
Delay before trying to connect to the device again after an error (timeout for example). Default: 1.
|
||||
.TP
|
||||
.BR \-\-m2m\-device\ \fI/dev/path
|
||||
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
|
||||
|
||||
.SS "Image control options"
|
||||
.TP
|
||||
@ -221,9 +226,6 @@ Timeout for lock. Default: 1.
|
||||
|
||||
.SS "H264 sink options"
|
||||
.TP
|
||||
.BR \-\-h264\-device\ \fI/dev/path
|
||||
Path to V4L2 mem-to-mem encoder device. Default: /dev/video11.
|
||||
.TP
|
||||
.BR \-\-h264\-sink\ \fIname
|
||||
Use the specified shared memory object to sink H264 frames. Default: disabled.
|
||||
.TP
|
||||
@ -244,6 +246,10 @@ H264 bitrate in Kbps. Default: 5000.
|
||||
.TP
|
||||
.BR \-\-h264\-gop\ \fIN
|
||||
Intarval between keyframes. Default: 30.
|
||||
.TP
|
||||
.BR \-\-h264\-m2m\-device\ \fI/dev/path
|
||||
Path to V4L2 mem-to-mem encoder device. Default: auto-select.
|
||||
|
||||
|
||||
.SS "Process options"
|
||||
.TP
|
||||
|
||||
@ -27,10 +27,11 @@ static const struct {
|
||||
const char *name;
|
||||
const encoder_type_e type;
|
||||
} _ENCODER_TYPES[] = {
|
||||
{"CPU", ENCODER_TYPE_CPU},
|
||||
{"HW", ENCODER_TYPE_HW},
|
||||
{"M2M", ENCODER_TYPE_M2M},
|
||||
{"NOOP", ENCODER_TYPE_NOOP},
|
||||
{"CPU", ENCODER_TYPE_CPU},
|
||||
{"HW", ENCODER_TYPE_HW},
|
||||
{"M2M-MJPEG", ENCODER_TYPE_M2M_MJPEG},
|
||||
{"M2M-JPEG", ENCODER_TYPE_M2M_JPEG},
|
||||
{"NOOP", ENCODER_TYPE_NOOP},
|
||||
};
|
||||
|
||||
|
||||
@ -53,7 +54,6 @@ encoder_s *encoder_init(void) {
|
||||
A_CALLOC(enc, 1);
|
||||
enc->type = run->type;
|
||||
enc->n_workers = get_cores_available();
|
||||
enc->m2m_path = "/dev/video11";
|
||||
enc->run = run;
|
||||
return enc;
|
||||
}
|
||||
@ -74,7 +74,7 @@ void encoder_destroy(encoder_s *enc) {
|
||||
|
||||
encoder_type_e encoder_parse_type(const char *str) {
|
||||
if (!strcasecmp(str, "OMX")) {
|
||||
return ENCODER_TYPE_M2M; // Just for compatibility
|
||||
return ENCODER_TYPE_M2M_JPEG; // Just for compatibility
|
||||
}
|
||||
for (unsigned index = 0; index < ARRAY_LEN(_ENCODER_TYPES); ++index) {
|
||||
if (!strcasecmp(str, _ENCODER_TYPES[index].name)) {
|
||||
@ -114,30 +114,42 @@ workers_pool_s *encoder_workers_pool_init(encoder_s *enc, device_s *dev) {
|
||||
quality = DR(jpeg_quality);
|
||||
n_workers = 1;
|
||||
|
||||
} else if (type == ENCODER_TYPE_M2M) {
|
||||
} else if (type == ENCODER_TYPE_M2M_MJPEG || type == ENCODER_TYPE_M2M_JPEG) {
|
||||
LOG_DEBUG("Preparing M2M encoder ...");
|
||||
if (ER(m2ms) == NULL) {
|
||||
A_CALLOC(ER(m2ms), n_workers);
|
||||
}
|
||||
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
if (ER(n_m2ms) < n_workers) {
|
||||
double b_min = ENCODER_M2M_BITRATE_MIN;
|
||||
double b_max = ENCODER_M2M_BITRATE_MAX;
|
||||
double step = ENCODER_M2M_BITRATE_STEP;
|
||||
double bitrate = log10(quality) * (b_max - b_min) / 2 + b_min;
|
||||
bitrate = step * round(bitrate / step);
|
||||
LOG_VERBOSE("Using JPEG M2M bitrate: %u%% -> %u Kbps", quality, (unsigned)bitrate);
|
||||
if (type == ENCODER_TYPE_M2M_MJPEG) {
|
||||
double b_min = ENCODER_M2M_BITRATE_MIN;
|
||||
double b_max = ENCODER_M2M_BITRATE_MAX;
|
||||
double step = ENCODER_M2M_BITRATE_STEP;
|
||||
double bitrate = log10(quality) * (b_max - b_min) / 2 + b_min;
|
||||
bitrate = step * round(bitrate / step);
|
||||
|
||||
// Начинаем с нуля и доинициализируем на следующих заходах при необходимости
|
||||
for (; ER(n_m2ms) < n_workers; ++ER(n_m2ms)) {
|
||||
assert(bitrate > 0);
|
||||
char name[32];
|
||||
snprintf(name, 32, "JPEG-%u", ER(n_m2ms));
|
||||
m2m_option_s options[] = {
|
||||
{"BITRATE", true, V4L2_CID_MPEG_VIDEO_BITRATE, bitrate * 1000},
|
||||
{NULL, false, 0, 0},
|
||||
};
|
||||
ER(m2ms[ER(n_m2ms)]) = m2m_encoder_init(name, enc->m2m_path, V4L2_PIX_FMT_MJPEG, 0, options);
|
||||
for (; ER(n_m2ms) < n_workers; ++ER(n_m2ms)) {
|
||||
assert(bitrate > 0);
|
||||
char name[32];
|
||||
snprintf(name, 32, "JPEG-%u", ER(n_m2ms));
|
||||
m2m_option_s options[] = {
|
||||
{"BITRATE", true, V4L2_CID_MPEG_VIDEO_BITRATE, bitrate * 1000},
|
||||
{NULL, false, 0, 0},
|
||||
};
|
||||
ER(m2ms[ER(n_m2ms)]) = m2m_encoder_init(name, enc->m2m_path, V4L2_PIX_FMT_MJPEG, 0, options);
|
||||
}
|
||||
|
||||
} else {
|
||||
for (; ER(n_m2ms) < n_workers; ++ER(n_m2ms)) {
|
||||
char name[32];
|
||||
snprintf(name, 32, "JPEG-%u", ER(n_m2ms));
|
||||
m2m_option_s options[] = {
|
||||
{"QUALITY", true, V4L2_CID_JPEG_COMPRESSION_QUALITY, quality},
|
||||
{NULL, false, 0, 0},
|
||||
};
|
||||
ER(m2ms[ER(n_m2ms)]) = m2m_encoder_init(name, enc->m2m_path, V4L2_PIX_FMT_JPEG, 0, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +237,7 @@ static bool _worker_run_job(worker_s *wr) {
|
||||
LOG_VERBOSE("Compressing buffer using HW (just copying)");
|
||||
hw_encoder_compress(src, dest);
|
||||
|
||||
} else if (ER(type) == ENCODER_TYPE_M2M) {
|
||||
} else if (ER(type) == ENCODER_TYPE_M2M_MJPEG || ER(type) == ENCODER_TYPE_M2M_JPEG) {
|
||||
LOG_VERBOSE("Compressing buffer using M2M");
|
||||
if (m2m_encoder_ensure_ready(ER(m2ms[wr->number]), src) < 0) {
|
||||
goto error;
|
||||
|
||||
@ -60,13 +60,14 @@
|
||||
#define ENCODER_M2M_BITRATE_STEP ((unsigned)CFG_ENCODER_M2M_BITRATE_STEP)
|
||||
|
||||
|
||||
#define ENCODER_TYPES_STR "CPU, HW, M2M, NOOP"
|
||||
#define ENCODER_TYPES_STR "CPU, HW, M2M-MJPEG, M2M-JPEG, NOOP"
|
||||
|
||||
typedef enum {
|
||||
ENCODER_TYPE_UNKNOWN, // Only for encoder_parse_type() and main()
|
||||
ENCODER_TYPE_CPU,
|
||||
ENCODER_TYPE_HW,
|
||||
ENCODER_TYPE_M2M,
|
||||
ENCODER_TYPE_M2M_MJPEG,
|
||||
ENCODER_TYPE_M2M_JPEG,
|
||||
ENCODER_TYPE_NOOP,
|
||||
} encoder_type_e;
|
||||
|
||||
|
||||
@ -43,12 +43,18 @@ static int _m2m_encoder_compress_raw(m2m_encoder_s *enc, const frame_s *src, fra
|
||||
|
||||
|
||||
m2m_encoder_s *m2m_encoder_init(const char *name, const char *path, unsigned format, unsigned fps, m2m_option_s *options) {
|
||||
assert(format == V4L2_PIX_FMT_H264 || format == V4L2_PIX_FMT_JPEG || format == V4L2_PIX_FMT_MJPEG);
|
||||
|
||||
LOG_INFO("%s: Initializing encoder ...", name);
|
||||
|
||||
m2m_encoder_s *enc;
|
||||
A_CALLOC(enc, 1);
|
||||
assert(enc->name = strdup(name));
|
||||
assert(enc->path = strdup(path));
|
||||
if (path == NULL) {
|
||||
assert(enc->path = strdup(format == V4L2_PIX_FMT_JPEG ? "/dev/video21" : "/dev/video11"));
|
||||
} else {
|
||||
assert(enc->path = strdup(path));
|
||||
}
|
||||
enc->output_format = format;
|
||||
enc->fps = fps;
|
||||
enc->last_online = -1;
|
||||
@ -153,8 +159,8 @@ static int _m2m_encoder_prepare(m2m_encoder_s *enc, const frame_s *frame) {
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT;
|
||||
fmt.fmt.pix_mp.num_planes = 1;
|
||||
fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
|
||||
fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10;
|
||||
//fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0;
|
||||
//fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10;
|
||||
E_LOG_DEBUG("Configuring OUTPUT format ...");
|
||||
E_XIOCTL(VIDIOC_S_FMT, &fmt, "Can't set OUTPUT format");
|
||||
if (fmt.fmt.pix_mp.pixelformat != enc->output_format) {
|
||||
@ -323,7 +329,7 @@ int m2m_encoder_compress(m2m_encoder_s *enc, const frame_s *src, frame_s *dest,
|
||||
|
||||
frame_encoding_begin(src, dest, (enc->output_format == V4L2_PIX_FMT_MJPEG ? V4L2_PIX_FMT_JPEG : enc->output_format));
|
||||
|
||||
force_key = (force_key || enc->last_online != src->online);
|
||||
force_key = (enc->output_format == V4L2_PIX_FMT_H264 && (force_key || enc->last_online != src->online));
|
||||
|
||||
if (_m2m_encoder_compress_raw(enc, src, dest, force_key) < 0) {
|
||||
_m2m_encoder_cleanup(enc);
|
||||
|
||||
@ -96,6 +96,7 @@ enum _OPT_VALUES {
|
||||
ADD_SINK(H264_SINK)
|
||||
_O_H264_BITRATE,
|
||||
_O_H264_GOP,
|
||||
_O_H264_M2M_DEVICE,
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@ -191,6 +192,7 @@ static const struct option _LONG_OPTS[] = {
|
||||
ADD_SINK("h264-", H264_SINK)
|
||||
{"h264-bitrate", required_argument, NULL, _O_H264_BITRATE},
|
||||
{"h264-gop", required_argument, NULL, _O_H264_GOP},
|
||||
{"h264-m2m-device", required_argument, NULL, _O_H264_M2M_DEVICE},
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@ -437,8 +439,9 @@ int options_parse(options_s *options, device_s *dev, encoder_s *enc, stream_s *s
|
||||
ADD_SINK("", sink, SINK)
|
||||
ADD_SINK("raw-", raw_sink, RAW_SINK)
|
||||
ADD_SINK("h264-", h264_sink, H264_SINK)
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 25000, 0);
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
case _O_H264_BITRATE: OPT_NUMBER("--h264-bitrate", stream->h264_bitrate, 25, 25000, 0);
|
||||
case _O_H264_GOP: OPT_NUMBER("--h264-gop", stream->h264_gop, 0, 60, 0);
|
||||
case _O_H264_M2M_DEVICE: OPT_SET(stream->h264_m2m_path, optarg);
|
||||
# undef ADD_SINK
|
||||
|
||||
# ifdef WITH_GPIO
|
||||
@ -569,7 +572,7 @@ static void _features(void) {
|
||||
|
||||
static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, server_s *server) {
|
||||
# define SAY(_msg, ...) fprintf(fp, _msg "\n", ##__VA_ARGS__)
|
||||
SAY("\nuStreamer - Lightweight and fast MJPG-HTTP streamer");
|
||||
SAY("\nuStreamer - Lightweight and fast MJPEG-HTTP streamer");
|
||||
SAY("═══════════════════════════════════════════════════");
|
||||
SAY("Version: %s; license: GPLv3", VERSION);
|
||||
SAY("Copyright (C) 2018-2021 Maxim Devaev <mdevaev@gmail.com>\n");
|
||||
@ -600,14 +603,15 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" Note: If HW encoding is used (JPEG source format selected),");
|
||||
SAY(" this parameter attempts to configure the camera");
|
||||
SAY(" or capture device hardware's internal encoder.");
|
||||
SAY(" It does not re-encode MJPG to MJPG to change the quality level");
|
||||
SAY(" for sources that already output MJPG.\n");
|
||||
SAY(" It does not re-encode MJPEG to MJPEG to change the quality level");
|
||||
SAY(" for sources that already output MJPEG.\n");
|
||||
SAY(" -c|--encoder <type> ───────────────── Use specified encoder. It may affect the number of workers.");
|
||||
SAY(" Available:");
|
||||
SAY(" * CPU ── Software MJPG encoding (default);");
|
||||
SAY(" * HW ─── Use pre-encoded MJPG frames directly from camera hardware;");
|
||||
SAY(" * M2M ── GPU-accelerated MJPG encoding using V4L2 M2M interface;");
|
||||
SAY(" * NOOP ─ Don't compress MJPG stream (do nothing).\n");
|
||||
SAY(" * CPU ──────── Software MJPEG encoding (default);");
|
||||
SAY(" * HW ───────── Use pre-encoded MJPEG frames directly from camera hardware;");
|
||||
SAY(" * M2M-MJPEG ── GPU-accelerated MJPEG encoding using V4L2 M2M interface;");
|
||||
SAY(" * M2M-JPEG ─── GPU-accelerated JPEG encoding using V4L2 M2M interface;");
|
||||
SAY(" * NOOP ─────── Don't compress MJPEG stream (do nothing).\n");
|
||||
SAY(" -g|--glitched-resolutions <WxH,...> ─ It doesn't do anything. Still here for compatibility.\n");
|
||||
SAY(" -k|--blank <path> ─────────────────── Path to JPEG file that will be shown when the device is disconnected");
|
||||
SAY(" during the streaming. Default: black screen 640x480 with 'NO SIGNAL'.\n");
|
||||
@ -621,7 +625,7 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
SAY(" --device-timeout <sec> ────────────── Timeout for device querying. Default: %u.\n", dev->timeout);
|
||||
SAY(" --device-error-delay <sec> ────────── Delay before trying to connect to the device again");
|
||||
SAY(" after an error (timeout for example). Default: %u.\n", stream->error_delay);
|
||||
SAY(" --m2m-device </dev/path> ──────────── Path to V4L2 M2M encoder device. Default: %s.\n", enc->m2m_path);
|
||||
SAY(" --m2m-device </dev/path> ──────────── Path to V4L2 M2M encoder device. Default: auto select.\n");
|
||||
SAY("Image control options:");
|
||||
SAY("══════════════════════");
|
||||
SAY(" --image-default ────────────────────── Reset all image settings below to default. Default: no change.\n");
|
||||
@ -673,8 +677,9 @@ static void _help(FILE *fp, device_s *dev, encoder_s *enc, stream_s *stream, ser
|
||||
ADD_SINK("JPEG", "")
|
||||
ADD_SINK("RAW", "raw-")
|
||||
ADD_SINK("H264", "h264-")
|
||||
SAY(" --h264-bitrate <kbps> ──────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
|
||||
SAY(" --h264-gop <N> ─────────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop);
|
||||
SAY(" --h264-bitrate <kbps> ───────── H264 bitrate in Kbps. Default: %u.\n", stream->h264_bitrate);
|
||||
SAY(" --h264-gop <N> ──────────────── Intarval between keyframes. Default: %u.\n", stream->h264_gop);
|
||||
SAY(" --h264-m2m-device </dev/path> ─ Path to V4L2 M2M encoder device. Default: auto select.\n");
|
||||
# undef ADD_SINK
|
||||
# ifdef WITH_GPIO
|
||||
SAY("GPIO options:");
|
||||
|
||||
@ -83,7 +83,7 @@ void stream_loop(stream_s *stream) {
|
||||
LOG_INFO("Using desired FPS: %u", stream->dev->desired_fps);
|
||||
|
||||
if (stream->h264_sink) {
|
||||
RUN(h264) = h264_stream_init(stream->h264_sink, stream->enc->m2m_path, stream->h264_bitrate, stream->h264_gop);
|
||||
RUN(h264) = h264_stream_init(stream->h264_sink, stream->h264_m2m_path, stream->h264_bitrate, stream->h264_gop);
|
||||
}
|
||||
|
||||
for (workers_pool_s *pool; (pool = _stream_init_loop(stream)) != NULL;) {
|
||||
@ -276,7 +276,11 @@ static workers_pool_s *_stream_init_one(stream_s *stream) {
|
||||
if (device_open(stream->dev) < 0) {
|
||||
goto error;
|
||||
}
|
||||
if (stream->enc->type == ENCODER_TYPE_M2M || (RUN(h264) && !is_jpeg(stream->dev->run->format))) {
|
||||
if (
|
||||
stream->enc->type == ENCODER_TYPE_M2M_MJPEG
|
||||
|| stream->enc->type == ENCODER_TYPE_M2M_JPEG
|
||||
|| (RUN(h264) && !is_jpeg(stream->dev->run->format))
|
||||
) {
|
||||
device_export_to_dma(stream->dev);
|
||||
}
|
||||
if (device_switch_capturing(stream->dev, true) < 0) {
|
||||
|
||||
@ -81,6 +81,7 @@ typedef struct {
|
||||
memsink_s *h264_sink;
|
||||
unsigned h264_bitrate;
|
||||
unsigned h264_gop;
|
||||
char *h264_m2m_path;
|
||||
|
||||
stream_runtime_s *run;
|
||||
} stream_s;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user