diff --git a/src/ustreamer/m2m.c b/src/ustreamer/m2m.c index 36b5bb9..582ef47 100644 --- a/src/ustreamer/m2m.c +++ b/src/ustreamer/m2m.c @@ -43,7 +43,7 @@ static us_m2m_encoder_s *_m2m_encoder_init( const char *name, const char *path, uint output_format, - uint fps, uint bitrate, uint gop, uint quality, bool allow_dma); + uint bitrate, uint gop, uint quality, bool allow_dma); static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame); @@ -64,11 +64,8 @@ static int _m2m_encoder_compress_raw(us_m2m_encoder_s *enc, const us_frame_s *sr us_m2m_encoder_s *us_m2m_h264_encoder_init(const char *name, const char *path, uint bitrate, uint gop) { - // FIXME: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100 - // По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480 - // енкодер через несколько секунд перестает производить корректные фреймы. bitrate *= 1000; // From Kbps - return _m2m_encoder_init(name, path, V4L2_PIX_FMT_H264, 30, bitrate, gop, 0, true); + return _m2m_encoder_init(name, path, V4L2_PIX_FMT_H264, bitrate, gop, 0, true); } us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path, uint quality) { @@ -79,13 +76,12 @@ us_m2m_encoder_s *us_m2m_mjpeg_encoder_init(const char *name, const char *path, bitrate = step * round(bitrate / step); bitrate *= 1000; // From Kbps assert(bitrate > 0); - // FIXME: То же самое про 30 or 0, но еще даже не проверено на низких разрешениях - return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, 30, bitrate, 0, 0, true); + return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, bitrate, 0, 0, true); } us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, uint quality) { // FIXME: DMA не работает - return _m2m_encoder_init(name, path, V4L2_PIX_FMT_JPEG, 30, 0, 0, quality, false); + return _m2m_encoder_init(name, path, V4L2_PIX_FMT_JPEG, 0, 0, quality, false); } void us_m2m_encoder_destroy(us_m2m_encoder_s *enc) { @@ -127,7 +123,7 @@ 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( const char *name, const char *path, uint output_format, - uint fps, uint bitrate, uint gop, uint quality, bool allow_dma) { + uint bitrate, uint gop, uint quality, bool allow_dma) { US_LOG_INFO("%s: Initializing encoder ...", name); @@ -145,7 +141,6 @@ static us_m2m_encoder_s *_m2m_encoder_init( enc->path = us_strdup(path); } enc->output_format = output_format; - enc->fps = fps; enc->bitrate = bitrate; enc->gop = gop; enc->quality = quality; @@ -265,11 +260,23 @@ static void _m2m_encoder_ensure(us_m2m_encoder_s *enc, const us_frame_s *frame) } } - if (enc->fps > 0) { // TODO: Check this for MJPEG + if (run->p_width * run->p_height <= 1280 * 720) { + // H264 требует каких-то лимитов. Больше 30 не поддерживается, а при 0 + // через какое-то время начинает производить некорректные фреймы. + // Если же привысить fps, то резко увеличивается время кодирования. + run->fps_limit = 60; + } else { + run->fps_limit = 30; + } + // H264: 30 or 0? https://github.com/6by9/yavta/blob/master/yavta.c#L2100 + // По логике вещей правильно 0, но почему-то на низких разрешениях типа 640x480 + // енкодер через несколько секунд перестает производить корректные фреймы. + // JPEG: То же самое про 30 or 0, но еще даже не проверено на низких разрешениях. + { struct v4l2_streamparm setfps = {0}; setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; setfps.parm.output.timeperframe.numerator = 1; - setfps.parm.output.timeperframe.denominator = enc->fps; + setfps.parm.output.timeperframe.denominator = run->fps_limit; _E_LOG_DEBUG("Configuring INPUT FPS ..."); _E_XIOCTL(VIDIOC_S_PARM, &setfps, "Can't set INPUT FPS"); } diff --git a/src/ustreamer/m2m.h b/src/ustreamer/m2m.h index 9a74e18..d014fbb 100644 --- a/src/ustreamer/m2m.h +++ b/src/ustreamer/m2m.h @@ -33,6 +33,7 @@ typedef struct { typedef struct { int fd; + uint fps_limit; us_m2m_buffer_s *input_bufs; uint n_input_bufs; us_m2m_buffer_s *output_bufs; @@ -52,7 +53,6 @@ typedef struct { char *name; char *path; uint output_format; - uint fps; uint bitrate; uint gop; uint quality; diff --git a/src/ustreamer/stream.c b/src/ustreamer/stream.c index d00cf0b..d5c5b14 100644 --- a/src/ustreamer/stream.c +++ b/src/ustreamer/stream.c @@ -314,7 +314,7 @@ static void *_jpeg_thread(void *v_ctx) { _worker_context_s *ctx = v_ctx; us_stream_s *stream = ctx->stream; - ldf grab_after = 0; + ldf grab_after_ts = 0; uint fluency_passed = 0; while (!atomic_load(ctx->stop)) { @@ -351,18 +351,18 @@ static void *_jpeg_thread(void *v_ctx) { } const ldf now_ts = us_get_now_monotonic(); - if (now_ts < grab_after) { + if (now_ts < grab_after_ts) { fluency_passed += 1; US_LOG_VERBOSE("JPEG: Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf", - fluency_passed, now_ts, grab_after); + fluency_passed, now_ts, grab_after_ts); us_device_buffer_decref(hw); continue; } fluency_passed = 0; const ldf fluency_delay = us_workers_pool_get_fluency_delay(stream->enc->run->pool, ready_wr); - grab_after = now_ts + fluency_delay; - US_LOG_VERBOSE("JPEG: Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after); + grab_after_ts = now_ts + fluency_delay; + US_LOG_VERBOSE("JPEG: Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after_ts); ready_job->hw = hw; us_workers_pool_assign(stream->enc->run->pool, ready_wr); @@ -374,26 +374,42 @@ static void *_jpeg_thread(void *v_ctx) { static void *_h264_thread(void *v_ctx) { US_THREAD_SETTLE("str_h264"); _worker_context_s *ctx = v_ctx; + us_h264_stream_s *h264 = ctx->stream->run->h264; + ldf grab_after_ts = 0; ldf last_encode_ts = us_get_now_monotonic(); + while (!atomic_load(ctx->stop)) { us_hw_buffer_s *hw = _get_latest_hw(ctx->queue); if (hw == NULL) { continue; } - if (!us_memsink_server_check(ctx->stream->run->h264->sink, NULL)) { + if (!us_memsink_server_check(h264->sink, NULL)) { us_device_buffer_decref(hw); US_LOG_VERBOSE("H264: Passed encoding because nobody is watching"); continue; } + if (hw->raw.grab_ts < grab_after_ts) { + us_device_buffer_decref(hw); + US_LOG_VERBOSE("H264: Passed encoding for FPS limit: %u", h264->enc->run->fps_limit); + continue; + } + // Форсим кейфрейм, если от захвата давно не было фреймов const ldf now_ts = us_get_now_monotonic(); const bool force_key = (last_encode_ts + 0.5 < now_ts); + us_h264_stream_process(h264, &hw->raw, force_key); last_encode_ts = now_ts; - us_h264_stream_process(ctx->stream->run->h264, &hw->raw, force_key); + // M2M-енкодер увеличивает задержку на 100 милисекунд при 1080p, если скормить ему больше 30 FPS. + // Поэтому у нас есть два режима: 60 FPS для маленьких видео и 30 для 1920x1080(1200). + // Следующй фрейм захватывается не раньше, чем это требуется по FPS, минус небольшая + // погрешность (если захват неравномерный) - немного меньше 1/60, и примерно треть от 1/30. + const ldf frame_interval = (ldf)1 / h264->enc->run->fps_limit; + grab_after_ts = hw->raw.grab_ts + frame_interval - 0.01; + us_device_buffer_decref(hw); } return NULL;