mirror of
https://github.com/pikvm/ustreamer.git
synced 2026-03-15 03:53:43 +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(
|
static us_m2m_encoder_s *_m2m_encoder_init(
|
||||||
const char *name, const char *path, uint output_format,
|
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);
|
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) {
|
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
|
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) {
|
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 = step * round(bitrate / step);
|
||||||
bitrate *= 1000; // From Kbps
|
bitrate *= 1000; // From Kbps
|
||||||
assert(bitrate > 0);
|
assert(bitrate > 0);
|
||||||
// FIXME: То же самое про 30 or 0, но еще даже не проверено на низких разрешениях
|
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, bitrate, 0, 0, true);
|
||||||
return _m2m_encoder_init(name, path, V4L2_PIX_FMT_MJPEG, 30, bitrate, 0, 0, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, uint quality) {
|
us_m2m_encoder_s *us_m2m_jpeg_encoder_init(const char *name, const char *path, uint quality) {
|
||||||
// FIXME: DMA не работает
|
// 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) {
|
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(
|
static us_m2m_encoder_s *_m2m_encoder_init(
|
||||||
const char *name, const char *path, uint output_format,
|
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);
|
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->path = us_strdup(path);
|
||||||
}
|
}
|
||||||
enc->output_format = output_format;
|
enc->output_format = output_format;
|
||||||
enc->fps = fps;
|
|
||||||
enc->bitrate = bitrate;
|
enc->bitrate = bitrate;
|
||||||
enc->gop = gop;
|
enc->gop = gop;
|
||||||
enc->quality = quality;
|
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};
|
struct v4l2_streamparm setfps = {0};
|
||||||
setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
setfps.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
|
||||||
setfps.parm.output.timeperframe.numerator = 1;
|
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_LOG_DEBUG("Configuring INPUT FPS ...");
|
||||||
_E_XIOCTL(VIDIOC_S_PARM, &setfps, "Can't set INPUT FPS");
|
_E_XIOCTL(VIDIOC_S_PARM, &setfps, "Can't set INPUT FPS");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ typedef struct {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int fd;
|
int fd;
|
||||||
|
uint fps_limit;
|
||||||
us_m2m_buffer_s *input_bufs;
|
us_m2m_buffer_s *input_bufs;
|
||||||
uint n_input_bufs;
|
uint n_input_bufs;
|
||||||
us_m2m_buffer_s *output_bufs;
|
us_m2m_buffer_s *output_bufs;
|
||||||
@@ -52,7 +53,6 @@ typedef struct {
|
|||||||
char *name;
|
char *name;
|
||||||
char *path;
|
char *path;
|
||||||
uint output_format;
|
uint output_format;
|
||||||
uint fps;
|
|
||||||
uint bitrate;
|
uint bitrate;
|
||||||
uint gop;
|
uint gop;
|
||||||
uint quality;
|
uint quality;
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ static void *_jpeg_thread(void *v_ctx) {
|
|||||||
_worker_context_s *ctx = v_ctx;
|
_worker_context_s *ctx = v_ctx;
|
||||||
us_stream_s *stream = ctx->stream;
|
us_stream_s *stream = ctx->stream;
|
||||||
|
|
||||||
ldf grab_after = 0;
|
ldf grab_after_ts = 0;
|
||||||
uint fluency_passed = 0;
|
uint fluency_passed = 0;
|
||||||
|
|
||||||
while (!atomic_load(ctx->stop)) {
|
while (!atomic_load(ctx->stop)) {
|
||||||
@@ -351,18 +351,18 @@ static void *_jpeg_thread(void *v_ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ldf now_ts = us_get_now_monotonic();
|
const ldf now_ts = us_get_now_monotonic();
|
||||||
if (now_ts < grab_after) {
|
if (now_ts < grab_after_ts) {
|
||||||
fluency_passed += 1;
|
fluency_passed += 1;
|
||||||
US_LOG_VERBOSE("JPEG: Passed %u frames for fluency: now=%.03Lf, grab_after=%.03Lf",
|
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);
|
us_device_buffer_decref(hw);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
fluency_passed = 0;
|
fluency_passed = 0;
|
||||||
|
|
||||||
const ldf fluency_delay = us_workers_pool_get_fluency_delay(stream->enc->run->pool, ready_wr);
|
const ldf fluency_delay = us_workers_pool_get_fluency_delay(stream->enc->run->pool, ready_wr);
|
||||||
grab_after = now_ts + fluency_delay;
|
grab_after_ts = now_ts + fluency_delay;
|
||||||
US_LOG_VERBOSE("JPEG: Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after);
|
US_LOG_VERBOSE("JPEG: Fluency: delay=%.03Lf, grab_after=%.03Lf", fluency_delay, grab_after_ts);
|
||||||
|
|
||||||
ready_job->hw = hw;
|
ready_job->hw = hw;
|
||||||
us_workers_pool_assign(stream->enc->run->pool, ready_wr);
|
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) {
|
static void *_h264_thread(void *v_ctx) {
|
||||||
US_THREAD_SETTLE("str_h264");
|
US_THREAD_SETTLE("str_h264");
|
||||||
_worker_context_s *ctx = v_ctx;
|
_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();
|
ldf last_encode_ts = us_get_now_monotonic();
|
||||||
|
|
||||||
while (!atomic_load(ctx->stop)) {
|
while (!atomic_load(ctx->stop)) {
|
||||||
us_hw_buffer_s *hw = _get_latest_hw(ctx->queue);
|
us_hw_buffer_s *hw = _get_latest_hw(ctx->queue);
|
||||||
if (hw == NULL) {
|
if (hw == NULL) {
|
||||||
continue;
|
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_device_buffer_decref(hw);
|
||||||
US_LOG_VERBOSE("H264: Passed encoding because nobody is watching");
|
US_LOG_VERBOSE("H264: Passed encoding because nobody is watching");
|
||||||
continue;
|
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 ldf now_ts = us_get_now_monotonic();
|
||||||
const bool force_key = (last_encode_ts + 0.5 < now_ts);
|
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;
|
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);
|
us_device_buffer_decref(hw);
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
Reference in New Issue
Block a user