diff --git a/README.md b/README.md index dbf4b39ea..af8ac4d48 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/man/ustreamer.1 b/man/ustreamer.1 index d6b7fb8cc..20d65d7a8 100644 --- a/man/ustreamer.1 +++ b/man/ustreamer.1 @@ -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 @@ -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 @@ -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. diff --git a/src/libs/device.c b/src/libs/device.c index 0ae9b6ce7..d8d2c58d8 100644 --- a/src/libs/device.c +++ b/src/libs/device.c @@ -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); @@ -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) { @@ -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; } @@ -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"); @@ -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; @@ -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"); @@ -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 } @@ -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; @@ -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); } @@ -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)); @@ -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; } diff --git a/src/libs/device.h b/src/libs/device.h index 6aef7de85..db78693de 100644 --- a/src/libs/device.h +++ b/src/libs/device.h @@ -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 { diff --git a/src/ustreamer/stream.c b/src/ustreamer/stream.c index b1687955e..81fa26679 100644 --- a/src/ustreamer/stream.c +++ b/src/ustreamer/stream.c @@ -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); @@ -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); @@ -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); @@ -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; } diff --git a/src/v4p/main.c b/src/v4p/main.c index 590245fde..cb11604f1 100644 --- a/src/v4p/main.c +++ b/src/v4p/main.c @@ -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; } @@ -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);