Skip to content

Commit

Permalink
limit fps by m2m hardware to reduce latency
Browse files Browse the repository at this point in the history
  • Loading branch information
mdevaev committed Mar 5, 2024
1 parent e92002c commit 68d598b
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 20 deletions.
31 changes: 19 additions & 12 deletions src/ustreamer/m2m.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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);

Expand All @@ -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;
Expand Down Expand Up @@ -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");
}
Expand Down
2 changes: 1 addition & 1 deletion src/ustreamer/m2m.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -52,7 +53,6 @@ typedef struct {
char *name;
char *path;
uint output_format;
uint fps;
uint bitrate;
uint gop;
uint quality;
Expand Down
30 changes: 23 additions & 7 deletions src/ustreamer/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand All @@ -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, минус небольшая
// погрешность - 20% от времени кадра, если вдруг время захвата будет нестабильно.
const ldf frame_interval = (ldf)1 / h264->enc->run->fps_limit;
grab_after_ts = hw->raw.grab_ts + frame_interval * 0.2;

us_device_buffer_decref(hw);
}
return NULL;
Expand Down

0 comments on commit 68d598b

Please sign in to comment.