diff --git a/configure b/configure index 1eb8e923c1..1b75390004 100755 --- a/configure +++ b/configure @@ -1157,6 +1157,7 @@ HAVE_LIST=" attribute_may_alias attribute_packed cbrtf + clock_gettime closesocket cmov dcbzl @@ -3048,6 +3049,7 @@ fi # Solaris has nanosleep in -lrt, OpenSolaris no longer needs that check_func nanosleep || { check_func nanosleep -lrt && add_extralibs -lrt; } +check_func clock_gettime || { check_func clock_gettime -lrt && add_extralibs -lrt; } check_func fcntl check_func fork check_func getaddrinfo $network_extralibs diff --git a/doc/indevs.texi b/doc/indevs.texi index f41c1cbb51..e699e11f42 100644 --- a/doc/indevs.texi +++ b/doc/indevs.texi @@ -520,6 +520,13 @@ supported using @command{-list_formats all} for Video4Linux2 devices. Some usage examples of the video4linux2 devices with ffmpeg and ffplay: +The time base for the timestamps is 1 microsecond. Depending on the kernel +version and configuration, the timestamps may be derived from the real time +clock (origin at the Unix Epoch) or the monotonic clock (origin usually at +boot time, unaffected by NTP or manual changes to the clock). The +@option{-timestamps abs} or @option{-ts abs} option can be used to force +conversion into the real time clock. + Note that if FFmpeg is build with v4l-utils support ("--enable-libv4l2" option), it will always be used. @example diff --git a/libavdevice/Makefile b/libavdevice/Makefile index d7806ea435..7f0c1d3e08 100644 --- a/libavdevice/Makefile +++ b/libavdevice/Makefile @@ -28,7 +28,7 @@ OBJS-$(CONFIG_PULSE_INDEV) += pulse.o OBJS-$(CONFIG_SDL_OUTDEV) += sdl.o OBJS-$(CONFIG_SNDIO_INDEV) += sndio_common.o sndio_dec.o OBJS-$(CONFIG_SNDIO_OUTDEV) += sndio_common.o sndio_enc.o -OBJS-$(CONFIG_V4L2_INDEV) += v4l2.o +OBJS-$(CONFIG_V4L2_INDEV) += v4l2.o timefilter.o OBJS-$(CONFIG_V4L_INDEV) += v4l.o OBJS-$(CONFIG_VFWCAP_INDEV) += vfwcap.o OBJS-$(CONFIG_X11_GRAB_DEVICE_INDEV) += x11grab.o diff --git a/libavdevice/v4l2.c b/libavdevice/v4l2.c index 642df1d08a..cecc18dfda 100644 --- a/libavdevice/v4l2.c +++ b/libavdevice/v4l2.c @@ -51,6 +51,7 @@ #include "libavutil/log.h" #include "libavutil/opt.h" #include "avdevice.h" +#include "timefilter.h" #include "libavutil/parseutils.h" #include "libavutil/pixdesc.h" #include "libavutil/avstring.h" @@ -73,6 +74,28 @@ static const int desired_video_buffers = 256; #define V4L_RAWFORMATS 1 #define V4L_COMPFORMATS 2 +/** + * Return timestamps to the user exactly as returned by the kernel + */ +#define V4L_TS_DEFAULT 0 +/** + * Autodetect the kind of timestamps returned by the kernel and convert to + * absolute (wall clock) timestamps. + */ +#define V4L_TS_ABS 1 +/** + * Assume kernel timestamps are from the monotonic clock and convert to + * absolute timestamps. + */ +#define V4L_TS_MONO2ABS 2 + +/** + * Once the kind of timestamps returned by the kernel have been detected, + * the value of the timefilter (NULL or not) determines whether a conversion + * takes place. + */ +#define V4L_TS_CONVERT_READY V4L_TS_DEFAULT + struct video_data { AVClass *class; int fd; @@ -81,6 +104,9 @@ struct video_data { int frame_size; int interlaced; int top_field_first; + int ts_mode; + TimeFilter *timefilter; + int64_t last_time_m; int buffers; void **buf_start; @@ -455,6 +481,66 @@ static void mmap_release_buffer(AVPacket *pkt) pkt->size = 0; } +#if HAVE_CLOCK_GETTIME && defined(CLOCK_MONOTONIC) +static int64_t av_gettime_monotonic(void) +{ + struct timespec tv; + + clock_gettime(CLOCK_MONOTONIC, &tv); + return (int64_t)tv.tv_sec * 1000000 + tv.tv_nsec / 1000; +} +#endif + +static int init_convert_timestamp(AVFormatContext *ctx, int64_t ts) +{ + struct video_data *s = ctx->priv_data; + int64_t now; + + now = av_gettime(); + if (s->ts_mode == V4L_TS_ABS && + ts <= now + 1 * AV_TIME_BASE && ts >= now - 10 * AV_TIME_BASE) { + av_log(ctx, AV_LOG_INFO, "Detected absolute timestamps\n"); + s->ts_mode = V4L_TS_CONVERT_READY; + return 0; + } +#if HAVE_CLOCK_GETTIME && defined(CLOCK_MONOTONIC) + now = av_gettime_monotonic(); + if (s->ts_mode == V4L_TS_MONO2ABS || + (ts <= now + 1 * AV_TIME_BASE && ts >= now - 10 * AV_TIME_BASE)) { + int64_t period = av_rescale_q(1, ctx->streams[0]->codec->time_base, + AV_TIME_BASE_Q); + av_log(ctx, AV_LOG_INFO, "Detected monotonic timestamps, converting\n"); + /* microseconds instead of seconds, MHz instead of Hz */ + s->timefilter = ff_timefilter_new(1, period, 1.0E-6); + s->ts_mode = V4L_TS_CONVERT_READY; + return 0; + } +#endif + av_log(ctx, AV_LOG_ERROR, "Unknown timestamps\n"); + return AVERROR(EIO); +} + +static int convert_timestamp(AVFormatContext *ctx, int64_t *ts) +{ + struct video_data *s = ctx->priv_data; + + if (s->ts_mode) { + int r = init_convert_timestamp(ctx, *ts); + if (r < 0) + return r; + } +#if HAVE_CLOCK_GETTIME && defined(CLOCK_MONOTONIC) + if (s->timefilter) { + int64_t nowa = av_gettime(); + int64_t nowm = av_gettime_monotonic(); + ff_timefilter_update(s->timefilter, nowa, nowm - s->last_time_m); + s->last_time_m = nowm; + *ts = ff_timefilter_eval(s->timefilter, *ts - nowm); + } +#endif + return 0; +} + static int mmap_read_frame(AVFormatContext *ctx, AVPacket *pkt) { struct video_data *s = ctx->priv_data; @@ -490,6 +576,9 @@ static int mmap_read_frame(AVFormatContext *ctx, AVPacket *pkt) pkt->data= s->buf_start[buf.index]; pkt->size = buf.bytesused; pkt->pts = buf.timestamp.tv_sec * INT64_C(1000000) + buf.timestamp.tv_usec; + res = convert_timestamp(ctx, &pkt->pts); + if (res < 0) + return res; pkt->destruct = mmap_release_buffer; buf_descriptor = av_malloc(sizeof(struct buff_data)); if (buf_descriptor == NULL) { @@ -847,6 +936,11 @@ static const AVOption options[] = { { "all", "Show all available formats", OFFSET(list_format), AV_OPT_TYPE_CONST, {.dbl = V4L_ALLFORMATS }, 0, INT_MAX, DEC, "list_formats" }, { "raw", "Show only non-compressed formats", OFFSET(list_format), AV_OPT_TYPE_CONST, {.dbl = V4L_RAWFORMATS }, 0, INT_MAX, DEC, "list_formats" }, { "compressed", "Show only compressed formats", OFFSET(list_format), AV_OPT_TYPE_CONST, {.dbl = V4L_COMPFORMATS }, 0, INT_MAX, DEC, "list_formats" }, + { "timestamps", "Kind of timestamps for grabbed frames", OFFSET(ts_mode), AV_OPT_TYPE_INT, {.dbl = 0 }, 0, 2, DEC, "timestamps" }, + { "default", "Use timestamps from the kernel", OFFSET(ts_mode), AV_OPT_TYPE_CONST, {.dbl = V4L_TS_DEFAULT }, 0, 2, DEC, "timestamps" }, + { "abs", "Use absolute timestamps (wall clock)", OFFSET(ts_mode), AV_OPT_TYPE_CONST, {.dbl = V4L_TS_ABS }, 0, 2, DEC, "timestamps" }, + { "mono2abs", "Force conversion from monotonic to absolute timestamps", OFFSET(ts_mode), AV_OPT_TYPE_CONST, {.dbl = V4L_TS_MONO2ABS }, 0, 2, DEC, "timestamps" }, + { "ts", "Kind of timestamps for grabbed frames", OFFSET(ts_mode), AV_OPT_TYPE_INT, {.dbl = 0 }, 0, 2, DEC, "timestamps" }, { NULL }, };