mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-02-28 04:36:33 +00:00
limit fps by m2m hardware to reduce latency
This commit is contained in:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user