Signed-off-by: Laurent Pinchart <laurent.pinch...@ideasonboard.com> --- drivers/media/video/uvc/uvc_video.c | 196 ++++++++++++++++++++++++++++++++++- drivers/media/video/uvc/uvcvideo.h | 39 +++++++ 2 files changed, 234 insertions(+), 1 deletions(-)
Hi Yann, Here's a second version of the patch that prints the estimated SOF frequency. With this information we should have all the stats we need. Let me know if you think something is missing. I will then run this on the webcams I own and post the results. diff --git a/drivers/media/video/uvc/uvc_video.c b/drivers/media/video/uvc/uvc_video.c index fc766b9..feb585b 100644 --- a/drivers/media/video/uvc/uvc_video.c +++ b/drivers/media/video/uvc/uvc_video.c @@ -358,6 +358,191 @@ int uvc_commit_video(struct uvc_streaming *stream, } /* ------------------------------------------------------------------------ + * Timestamp statistics + */ + +static void uvc_video_stats_decode(struct uvc_streaming *stream, + const __u8 *data, int len) +{ + unsigned int header_size; + bool has_pts = false; + bool has_scr = false; + u16 scr_sof; + u32 scr_stc; + u32 pts; + + if (stream->stats.stream.nb_frames == 0 && + stream->stats.frame.nb_packets == 0) + ktime_get_ts(&stream->stats.stream.start_ts); + + switch (data[1] & (UVC_STREAM_PTS | UVC_STREAM_SCR)) { + case UVC_STREAM_PTS | UVC_STREAM_SCR: + header_size = 12; + has_pts = true; + has_scr = true; + break; + case UVC_STREAM_PTS: + header_size = 6; + has_pts = true; + break; + case UVC_STREAM_SCR: + header_size = 8; + has_scr = true; + break; + default: + header_size = 2; + break; + } + + /* Check for invalid headers. */ + if (len < header_size) { + stream->stats.frame.nb_invalid_headers++; + return; + } + + /* Extract the timestamps. */ + if (has_pts) + pts = get_unaligned_le32(&data[2]); + + if (has_scr) { + scr_stc = get_unaligned_le32(&data[header_size - 6]); + scr_sof = get_unaligned_le16(&data[header_size - 2]); + } + + /* Is PTS constant through the whole frame ? */ + if (has_pts && stream->stats.frame.nb_pts) { + if (stream->stats.frame.pts != pts) { + stream->stats.frame.nb_pts_diffs++; + stream->stats.frame.last_pts_diff = + stream->stats.frame.nb_packets; + } + } + + if (has_pts) { + stream->stats.frame.nb_pts++; + stream->stats.frame.pts = pts; + } + + /* Do all frames have a PTS in their first non-empty packet, or before + * their first empty packet ? + */ + if (stream->stats.frame.size == 0) { + if (len > header_size) + stream->stats.frame.has_initial_pts = has_pts; + if (len == header_size && has_pts) + stream->stats.frame.has_early_pts = true; + } + + /* Do the SCR.STC and SCR.SOF fields vary through the frame ? */ + if (has_scr && stream->stats.frame.nb_scr) { + if (stream->stats.frame.scr_stc != scr_stc) + stream->stats.frame.nb_scr_diffs++; + } + + if (has_scr) { + /* Expand the SOF counter to 32 bits and store its value. */ + if (stream->stats.stream.nb_frames > 0 || + stream->stats.frame.nb_scr > 0) + stream->stats.stream.scr_sof_count += + (scr_sof - stream->stats.stream.scr_sof) % 2048; + stream->stats.stream.scr_sof = scr_sof; + + stream->stats.frame.nb_scr++; + stream->stats.frame.scr_stc = scr_stc; + stream->stats.frame.scr_sof = scr_sof; + + if (scr_sof < stream->stats.stream.min_sof) + stream->stats.stream.min_sof = scr_sof; + if (scr_sof > stream->stats.stream.max_sof) + stream->stats.stream.max_sof = scr_sof; + } + + if (stream->stats.frame.size == 0 && len > header_size) + stream->stats.frame.first_data = stream->stats.frame.nb_packets; + + stream->stats.frame.size += len - header_size; + stream->stats.frame.nb_packets++; + if (len > header_size) + stream->stats.frame.nb_non_empty_packets++; + + if (data[1] & UVC_STREAM_ERR) + stream->stats.frame.nb_errors++; +} + +static void uvc_video_stats_update(struct uvc_streaming *stream) +{ + struct uvc_stats_frame *frame = &stream->stats.frame; + + uvc_trace(UVC_TRACE_TIMESTAMP, "frame %u stats: %u/%u/%u packets " + "%u/%u/%u pts (%searly %sinitial) %u/%u scr\n", + stream->sequence, frame->first_data, + frame->nb_non_empty_packets, frame->nb_packets, + frame->nb_pts_diffs, frame->last_pts_diff, frame->nb_pts, + frame->has_early_pts ? "" : "!", + frame->has_initial_pts ? "" : "!", + frame->nb_scr_diffs, frame->nb_scr); + + stream->stats.stream.nb_frames++; + + if (frame->has_early_pts) + stream->stats.stream.nb_pts_early++; + if (frame->has_initial_pts) + stream->stats.stream.nb_pts_initial++; + if (frame->last_pts_diff <= frame->first_data) + stream->stats.stream.nb_pts_constant++; + if (frame->nb_scr >= frame->nb_non_empty_packets) + stream->stats.stream.nb_scr_count_ok++; + if (frame->nb_scr_diffs + 1 == frame->nb_scr) + stream->stats.stream.nb_scr_diffs_ok++; + + memset(&stream->stats.frame, 0, sizeof(stream->stats.frame)); +} + +static void uvc_video_stats_dump(struct uvc_streaming *stream) +{ + unsigned int scr_sof_freq; + unsigned int duration; + struct timespec ts; + + ktime_get_ts(&ts); + + ts.tv_sec -= stream->stats.stream.start_ts.tv_sec; + ts.tv_nsec -= stream->stats.stream.start_ts.tv_nsec; + if (ts.tv_nsec < 0) { + ts.tv_sec--; + ts.tv_nsec += 1000000000; + } + + /* Compute the SCR.SOF frequency estimate. At the nominal 1kHz SOF + * frequency this will not overflow before more than 1h. + */ + duration = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + if (duration != 0) + scr_sof_freq = stream->stats.stream.scr_sof_count * 1000 + / duration; + else + scr_sof_freq = 0; + + uvc_trace(UVC_TRACE_TIMESTAMP, "stream stats: %u frames %u early pts " + "%u initial pts %u pts ok %u scr count ok %u scr diff ok, " + "%u <= sof <= %u, sof freq %u.%03u kHz\n", + stream->stats.stream.nb_frames, + stream->stats.stream.nb_pts_early, + stream->stats.stream.nb_pts_initial, + stream->stats.stream.nb_pts_constant, + stream->stats.stream.nb_scr_count_ok, + stream->stats.stream.nb_scr_diffs_ok, + stream->stats.stream.min_sof, stream->stats.stream.max_sof, + scr_sof_freq / 1000, scr_sof_freq % 1000); +} + +static void uvc_video_stats_init(struct uvc_streaming *stream) +{ + memset(&stream->stats, 0, sizeof(stream->stats)); + stream->stats.stream.min_sof = 2048; +} + +/* ------------------------------------------------------------------------ * Video codecs */ @@ -431,8 +616,13 @@ static int uvc_video_decode_start(struct uvc_streaming *stream, /* Increase the sequence number regardless of any buffer states, so * that discontinuous sequence numbers always indicate lost frames. */ - if (stream->last_fid != fid) + if (stream->last_fid != fid) { stream->sequence++; + if (stream->sequence) + uvc_video_stats_update(stream); + } + + uvc_video_stats_decode(stream, data, len); /* Store the payload FID bit and return immediately when the buffer is * NULL. @@ -861,6 +1051,8 @@ static void uvc_uninit_video(struct uvc_streaming *stream, int free_buffers) struct urb *urb; unsigned int i; + uvc_video_stats_dump(stream); + for (i = 0; i < UVC_URBS; ++i) { urb = stream->urb[i]; if (urb == NULL) @@ -994,6 +1186,8 @@ static int uvc_init_video(struct uvc_streaming *stream, gfp_t gfp_flags) stream->bulk.skip_payload = 0; stream->bulk.payload_size = 0; + uvc_video_stats_init(stream); + if (intf->num_altsetting > 1) { struct usb_host_endpoint *best_ep = NULL; unsigned int best_psize = 3 * 1024; diff --git a/drivers/media/video/uvc/uvcvideo.h b/drivers/media/video/uvc/uvcvideo.h index 20107fd..2dfa309 100644 --- a/drivers/media/video/uvc/uvcvideo.h +++ b/drivers/media/video/uvc/uvcvideo.h @@ -495,6 +495,44 @@ struct uvc_streaming { __u32 sequence; __u8 last_fid; + + struct { + struct { + struct timespec start_ts; /* Stream start timestamp */ + unsigned int nb_frames; /* Number of frames */ + unsigned int nb_pts_constant; /* Number of frames with constant PTS */ + unsigned int nb_pts_early; /* Number of frames with early PTS */ + unsigned int nb_pts_initial; /* Number of frames with initial PTS */ + unsigned int nb_scr_count_ok; /* Number of frames with at least one SCR per non empty packet */ + unsigned int nb_scr_diffs_ok; /* Number of frames with varying SCR.STC */ + unsigned int scr_sof_count; /* STC.SOF counter accumulated since stream start */ + unsigned int scr_sof; /* STC.SOF of the last packet */ + unsigned int min_sof; /* Minimum STC.SOF value */ + unsigned int max_sof; /* Maximum STC.SOF value */ + } stream; + + struct uvc_stats_frame { + unsigned int size; /* Number of bytes captured */ + unsigned int first_data; /* Index of the first non-empty packet */ + + unsigned int nb_packets; /* Number of packets */ + unsigned int nb_non_empty_packets; /* Number of non-empty packets */ + unsigned int nb_invalid_headers;/* Number of packets with an invalid header */ + unsigned int nb_errors; /* Number of packets with the error bit set */ + + unsigned int nb_pts; /* Number of packets with a PTS timestamp */ + unsigned int nb_pts_diffs; /* Number of PTS differences inside a frame */ + unsigned int last_pts_diff; /* Index of the last PTS difference */ + bool has_initial_pts; /* Whether the first non-empty packet has a PTS */ + bool has_early_pts; /* Whether a PTS is present before the first non-empty packet */ + u32 pts; /* PTS of the last packet */ + + unsigned int nb_scr; /* Number of packets with a SCR timestamp */ + unsigned int nb_scr_diffs; /* Number of SCR.STC differences inside a frame */ + u16 scr_sof; /* SCR.SOF of the last packet */ + u32 scr_stc; /* SCR.STC of the last packet */ + } frame; + } stats; }; enum uvc_device_state { @@ -566,6 +604,7 @@ struct uvc_driver { #define UVC_TRACE_SUSPEND (1 << 8) #define UVC_TRACE_STATUS (1 << 9) #define UVC_TRACE_VIDEO (1 << 10) +#define UVC_TRACE_TIMESTAMP (1 << 11) #define UVC_WARN_MINMAX 0 #define UVC_WARN_PROBE_DEF 1 -- Regards, Laurent Pinchart _______________________________________________ Linux-uvc-devel mailing list Linux-uvc-devel@lists.berlios.de https://lists.berlios.de/mailman/listinfo/linux-uvc-devel