limit fps by m2m hardware to reduce latency

This commit is contained in:
Maxim Devaev
2024-03-05 21:00:50 +02:00
parent e92002c3d8
commit 0b8940d93d
3 changed files with 43 additions and 20 deletions

View File

@@ -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");
}

View File

@@ -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;

View File

@@ -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;