Skip to content

Commit

Permalink
improved persistent logic
Browse files Browse the repository at this point in the history
  • Loading branch information
mdevaev committed Mar 3, 2024
1 parent 06eda04 commit b556dfb
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 61 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ Without arguments, ```ustreamer``` will try to open ```/dev/video0``` with 640x4

:exclamation: Please note that since µStreamer v2.0 cross-domain requests were disabled by default for [security reasons](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). To enable the old behavior, use the option `--allow-origin=\*`.

The recommended way of running µStreamer with [Auvidea B101](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
The recommended way of running µStreamer with [TC358743-based capture device](https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=120702&start=400#p1339178) on Raspberry Pi:
```
$ ./ustreamer \
--format=uyvy \ # Device input format
--encoder=m2m-image \ # Hardware encoding on V4L2 M2M driver
--workers=3 \ # Workers number
--persistent \ # Don't re-initialize device on timeout (for example when HDMI cable was disconnected)
--persistent \ # Suppress repetitive signal source errors (for example when HDMI cable was disconnected)
--dv-timings \ # Use DV-timings
--drop-same-frames=30 # Save the traffic
```
Expand Down
6 changes: 3 additions & 3 deletions man/ustreamer.1
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Without arguments, \fBustreamer\fR will try to open \fB/dev/video0\fR with 640x4

Please note that since µStreamer v2\.0 cross\-domain requests were disabled by default for security reasons\. To enable the old behavior, use the option \fB\-\-allow\-origin=\e*\fR\.

For example, the recommended way of running µStreamer with Auvidea B101 on a Raspberry Pi is:
For example, the recommended way of running µStreamer with TC358743-based capture device on a Raspberry Pi is:

\fBustreamer \e\fR
.RS
Expand All @@ -27,7 +27,7 @@ For example, the recommended way of running µStreamer with Auvidea B101 on a Ra
.nf
\fB\-\-workers=3 \e\fR # Maximum workers for V4L2 encoder
.nf
\fB\-\-persistent \e\fR # Don\'t re\-initialize device on timeout (for example when HDMI cable was disconnected)
\fB\-\-persistent \e\fR # Suppress repetitive signal source errors (for example when HDMI cable was disconnected)
.nf
\fB\-\-dv\-timings \e\fR # Use DV\-timings
.nf
Expand Down Expand Up @@ -69,7 +69,7 @@ Desired FPS. Default: maximum possible.
Drop frames smaller then this limit. Useful if the device produces small\-sized garbage frames. Default: 128 bytes.
.TP
.BR \-n ", " \-\-persistent
Don't re\-initialize device on timeout. Default: disabled.
Suppress repetitive signal source errors. Default: disabled.
.TP
.BR \-t ", " \-\-dv\-timings
Enable DV-timings querying and events processing to automatic resolution change. Default: disabled.
Expand Down
86 changes: 58 additions & 28 deletions src/libs/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ static int _device_consume_event(us_device_s *dev);
static void _v4l2_buffer_copy(const struct v4l2_buffer *src, struct v4l2_buffer *dest);
static bool _device_is_buffer_valid(us_device_s *dev, const struct v4l2_buffer *buf, const u8 *data);
static int _device_open_check_cap(us_device_s *dev);
static int _device_open_dv_timings(us_device_s *dev);
static int _device_open_dv_timings(us_device_s *dev, bool apply);
static int _device_open_format(us_device_s *dev, bool first);
static void _device_open_hw_fps(us_device_s *dev);
static void _device_open_jpeg_quality(us_device_s *dev);
Expand Down Expand Up @@ -174,20 +174,40 @@ int us_device_parse_io_method(const char *str) {
int us_device_open(us_device_s *dev) {
us_device_runtime_s *const run = dev->run;

if (access(dev->path, R_OK | W_OK) < 0) {
if (run->open_error_reported != -errno) {
run->open_error_reported = -errno; // Don't confuse it with __LINE__
US_LOG_PERROR("No access to capture device");
}
goto tmp_error;
}

_D_LOG_DEBUG("Opening capture device ...");
if ((run->fd = open(dev->path, O_RDWR|O_NONBLOCK)) < 0) {
if ((run->fd = open(dev->path, O_RDWR | O_NONBLOCK)) < 0) {
_D_LOG_PERROR("Can't capture open device");
goto error;
}
_D_LOG_DEBUG("Capture device fd=%d opened", run->fd);

if (dev->dv_timings && dev->persistent) {
_D_LOG_DEBUG("Probing DV-timings or QuerySTD ...");
if (_device_open_dv_timings(dev, false) < 0) {
const int line = __LINE__;
if (run->open_error_reported != line) {
run->open_error_reported = line;
_D_LOG_ERROR("No signal from source");
}
goto tmp_error;
}
}

if (_device_open_check_cap(dev) < 0) {
goto error;
}
if (_device_apply_resolution(dev, dev->width, dev->height, dev->run->hz)) {
goto error;
}
if (dev->dv_timings && _device_open_dv_timings(dev) < 0) {
if (dev->dv_timings && _device_open_dv_timings(dev, true) < 0) {
goto error;
}
if (_device_open_format(dev, true) < 0) {
Expand Down Expand Up @@ -216,10 +236,17 @@ int us_device_open(us_device_s *dev) {
goto error;
}
run->streamon = true;

run->open_error_reported = 0;
_D_LOG_INFO("Capturing started");
return 0;

tmp_error:
us_device_close(dev);
return -2;

error:
run->open_error_reported = 0;
us_device_close(dev);
return -1;
}
Expand Down Expand Up @@ -268,7 +295,6 @@ void us_device_close(us_device_s *dev) {
}

US_CLOSE_FD(run->fd);
run->persistent_timeout_reported = false;

if (say) {
_D_LOG_INFO("Capturing stopped");
Expand All @@ -279,17 +305,13 @@ int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw) {
// Это сложная функция, которая делает сразу много всего, чтобы получить новый фрейм.
// - Вызывается _device_wait_buffer() с select() внутри, чтобы подождать новый фрейм
// или эвент V4L2. Обработка эвентов более приоритетна, чем кадров.
// - При таймауте select() в _device_wait_buffer() возвращаем -2 для персистентных
// устройств типа TC358743, для остальных же возвращаем ошибку -1.
// - Если есть новые фреймы, то пропустить их все, пока не закончатся и вернуть
// самый-самый свежий, содержащий при этом валидные данные.
// - Если таковых не нашлось, вернуть -3.
// - Если таковых не нашлось, вернуть -2.
// - Ошибка -1 возвращается при любых сбоях.

switch (_device_wait_buffer(dev)) {
case 0: break; // New frame
case -2: return -2; // Persistent timeout
default: return -1; // Error
if (_device_wait_buffer(dev) < 0) {
return -1;
}

us_device_runtime_s *const run = dev->run;
Expand Down Expand Up @@ -372,7 +394,7 @@ int us_device_grab_buffer(us_device_s *dev, us_hw_buffer_s **hw) {
if (buf_got) {
break; // Process any latest valid frame
} else if (broken) {
return -3; // If we have only broken frames on this capture session
return -2; // If we have only broken frames on this capture session
}
}
_D_LOG_PERROR("Can't grab HW buffer");
Expand Down Expand Up @@ -452,18 +474,9 @@ int _device_wait_buffer(us_device_s *dev) {
}
return -1;
} else if (selected == 0) {
if (!dev->persistent) {
// Если устройство не персистентное, то таймаут является ошибкой
_D_LOG_ERROR("Device select() timeout");
return -1;
}
if (!run->persistent_timeout_reported) {
_D_LOG_ERROR("Persistent device timeout (unplugged)");
run->persistent_timeout_reported = true;
}
return -2; // Таймаут, нет новых фреймов
_D_LOG_ERROR("Device select() timeout");
return -1;
} else {
run->persistent_timeout_reported = false;
if (has_error && _device_consume_event(dev) < 0) {
return -1; // Restart required
}
Expand Down Expand Up @@ -586,13 +599,20 @@ static int _device_open_check_cap(us_device_s *dev) {
return 0;
}

static int _device_open_dv_timings(us_device_s *dev) {
static int _device_open_dv_timings(us_device_s *dev, bool apply) {
// Just probe only if @apply is false

const us_device_runtime_s *const run = dev->run;

int dv_errno = 0;

struct v4l2_dv_timings dv = {0};
_D_LOG_DEBUG("Querying DV-timings ...");
_D_LOG_DEBUG("Querying DV-timings (apply=%u) ...", apply);
if (us_xioctl(run->fd, VIDIOC_QUERY_DV_TIMINGS, &dv) < 0) {
dv_errno = errno; // ENOLINK if no signal
goto querystd;
} else if (!apply) {
goto probe_only;
}

float hz = 0;
Expand All @@ -602,11 +622,11 @@ static int _device_open_dv_timings(us_device_s *dev) {
const uint vtot = V4L2_DV_BT_FRAME_HEIGHT(&dv.bt) / (dv.bt.interlaced ? 2 : 1);
const uint fps = ((htot * vtot) > 0 ? ((100 * (u64)dv.bt.pixelclock)) / (htot * vtot) : 0);
hz = (fps / 100) + (fps % 100) / 100.0;
_D_LOG_INFO("Got new DV-timings: %ux%u%s%.02f, pixclk=%llu, vsync=%u, hsync=%u",
_D_LOG_INFO("Detected DV-timings: %ux%u%s%.02f, pixclk=%llu, vsync=%u, hsync=%u",
dv.bt.width, dv.bt.height, (dv.bt.interlaced ? "i" : "p"), hz,
(ull)dv.bt.pixelclock, dv.bt.vsync, dv.bt.hsync); // See #11 about %llu
} else {
_D_LOG_INFO("Got new DV-timings: %ux%u, pixclk=%llu, vsync=%u, hsync=%u",
_D_LOG_INFO("Detected DV-timings: %ux%u, pixclk=%llu, vsync=%u, hsync=%u",
dv.bt.width, dv.bt.height,
(ull)dv.bt.pixelclock, dv.bt.vsync, dv.bt.hsync);
}
Expand All @@ -624,8 +644,16 @@ static int _device_open_dv_timings(us_device_s *dev) {
querystd:
_D_LOG_DEBUG("Failed to query DV-timings, trying QuerySTD ...");
if (us_xioctl(run->fd, VIDIOC_QUERYSTD, &dev->standard) < 0) {
_D_LOG_ERROR("Failed to query DV-timings and QuerySTD");
if (apply) {
char *std_error = us_errno_to_string(errno); // Read the errno first
char *dv_error = us_errno_to_string(dv_errno);
_D_LOG_ERROR("Failed to query DV-timings (%s) and QuerySTD (%s)", dv_error, std_error);
free(dv_error);
free(std_error);
}
return -1;
} else if (!apply) {
goto probe_only;
}
if (us_xioctl(run->fd, VIDIOC_S_STD, &dev->standard) < 0) {
_D_LOG_PERROR("Can't set apply standard: %s", _standard_to_string(dev->standard));
Expand All @@ -640,6 +668,8 @@ static int _device_open_dv_timings(us_device_s *dev) {
_D_LOG_PERROR("Can't subscribe to V4L2_EVENT_SOURCE_CHANGE");
return -1;
}

probe_only:
return 0;
}

Expand Down
2 changes: 1 addition & 1 deletion src/libs/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ typedef struct {
enum v4l2_buf_type capture_type;
bool capture_mplane;
bool streamon;
bool persistent_timeout_reported;
int open_error_reported;
} us_device_runtime_s;

typedef enum {
Expand Down
46 changes: 25 additions & 21 deletions src/ustreamer/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,7 @@ void us_stream_loop(us_stream_s *stream) {
us_hw_buffer_s *hw;
const int buf_index = us_device_grab_buffer(dev, &hw);
switch (buf_index) {
case -3: continue; // Broken frame
case -2: continue; // Persistent timeout
case -2: continue; // Broken frame
case -1: goto close; // Error
}
assert(buf_index >= 0);
Expand Down Expand Up @@ -248,6 +247,10 @@ void us_stream_loop(us_stream_s *stream) {

us_encoder_close(stream->enc);
us_device_close(dev);

if (!atomic_load(&run->stop)) {
US_SEP_INFO('=');
}
}

US_DELETE(run->h264, us_h264_stream_destroy);
Expand Down Expand Up @@ -421,7 +424,7 @@ static bool _stream_has_any_clients(us_stream_s *stream) {
static int _stream_init_loop(us_stream_s *stream) {
us_stream_runtime_s *const run = stream->run;

int access_errno = 0;
bool waiting_reported = false;
while (!atomic_load(&stream->run->stop)) {
_stream_check_suicide(stream);

Expand All @@ -445,32 +448,33 @@ static int _stream_init_loop(us_stream_s *stream) {

_SINK_PUT(raw_sink, run->blank->raw);

if (access(stream->dev->path, R_OK|W_OK) < 0) {
if (access_errno != errno) {
US_SEP_INFO('=');
US_LOG_PERROR("Can't access device");
US_LOG_INFO("Waiting for the device access ...");
access_errno = errno;
}
goto sleep_and_retry;
}

US_SEP_INFO('=');
access_errno = 0;

stream->dev->dma_export = (
stream->enc->type == US_ENCODER_TYPE_M2M_VIDEO
|| stream->enc->type == US_ENCODER_TYPE_M2M_IMAGE
|| run->h264 != NULL
);
if (us_device_open(stream->dev) == 0) {
us_encoder_open(stream->enc, stream->dev);
return 0;
switch (us_device_open(stream->dev)) {
case -2:
if (!waiting_reported) {
waiting_reported = true;
US_LOG_INFO("Waiting for the capture device ...");
}
goto sleep_and_retry;
case -1:
waiting_reported = false;
goto sleep_and_retry;
default: break;
}
US_LOG_INFO("Sleeping %u seconds before new stream init ...", stream->error_delay);
us_encoder_open(stream->enc, stream->dev);
return 0;

sleep_and_retry:
sleep(stream->error_delay);
for (uint count = 0; count < stream->error_delay * 10; ++count) {
if (atomic_load(&run->stop)) {
break;
}
usleep(100 * 1000);
}
}
return -1;
}
Expand Down
10 changes: 4 additions & 6 deletions src/v4p/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ static void _main_loop(void) {
}

if (us_device_open(dev) < 0) {
if (us_drm_wait_for_vsync(drm) == 0) {
us_drm_expose(drm, US_DRM_EXPOSE_NO_SIGNAL, NULL, 0);
}
goto close;
}

Expand All @@ -219,12 +222,7 @@ static void _main_loop(void) {
us_hw_buffer_s *hw;
const int buf_index = us_device_grab_buffer(dev, &hw);
switch (buf_index) {
case -3: continue; // Broken frame
case -2: // Persistent timeout
if (us_drm_expose(drm, US_DRM_EXPOSE_NO_SIGNAL, NULL, 0) < 0) {
_slowdown();
continue;
}
case -2: continue; // Broken frame
case -1: goto close; // Any error
}
assert(buf_index >= 0);
Expand Down

0 comments on commit b556dfb

Please sign in to comment.