From: Ronald B. <rb...@us...> - 2003-05-02 21:24:28
|
CVS Root: /cvsroot/gstreamer Module: gst-plugins Changes by: rbultje Date: Fri May 02 2003 14:17:08 PDT Log message: Implement element synchronization (#108301) Modified files: sys/v4l : gstv4lmjpegsrc.c gstv4lmjpegsrc.h gstv4lsrc.c gstv4lsrc.h v4lmjpegsrc_calls.c v4lsrc_calls.c sys/v4l2 : gstv4l2src.c gstv4l2src.h v4l2src_calls.c Links: http://cvs.sf.net/cgi-bin/viewcvs.cgi/gstreamer/gst-plugins/sys/v4l/gstv4lmjpegsrc.c.diff?r1=1.25&r2=1.26 http://cvs.sf.net/cgi-bin/viewcvs.cgi/gstreamer/gst-plugins/sys/v4l/gstv4lmjpegsrc.h.diff?r1=1.5&r2=1.6 http://cvs.sf.net/cgi-bin/viewcvs.cgi/gstreamer/gst-plugins/sys/v4l/gstv4lsrc.c.diff?r1=1.33&r2=1.34 http://cvs.sf.net/cgi-bin/viewcvs.cgi/gstreamer/gst-plugins/sys/v4l/gstv4lsrc.h.diff?r1=1.11&r2=1.12 http://cvs.sf.net/cgi-bin/viewcvs.cgi/gstreamer/gst-plugins/sys/v4l/v4lmjpegsrc_calls.c.diff?r1=1.13&r2=1.14 http://cvs.sf.net/cgi-bin/viewcvs.cgi/gstreamer/gst-plugins/sys/v4l/v4lsrc_calls.c.diff?r1=1.23&r2=1.24 http://cvs.sf.net/cgi-bin/viewcvs.cgi/gstreamer/gst-plugins/sys/v4l2/gstv4l2src.c.diff?r1=1.10&r2=1.11 http://cvs.sf.net/cgi-bin/viewcvs.cgi/gstreamer/gst-plugins/sys/v4l2/gstv4l2src.h.diff?r1=1.3&r2=1.4 http://cvs.sf.net/cgi-bin/viewcvs.cgi/gstreamer/gst-plugins/sys/v4l2/v4l2src_calls.c.diff?r1=1.6&r2=1.7 ====Begin Diffs==== Index: gstv4lmjpegsrc.c =================================================================== RCS file: /cvsroot/gstreamer/gst-plugins/sys/v4l/gstv4lmjpegsrc.c,v retrieving revision 1.25 retrieving revision 1.26 diff -u -d -r1.25 -r1.26 --- gstv4lmjpegsrc.c 9 Mar 2003 15:07:52 -0000 1.25 +++ gstv4lmjpegsrc.c 2 May 2003 21:16:54 -0000 1.26 @@ -37,7 +37,10 @@ /* V4lMjpegSrc signals and args */ enum { - /* FILL ME */ + SIGNAL_FRAME_CAPTURE, + SIGNAL_FRAME_DROP, + SIGNAL_FRAME_INSERT, + SIGNAL_FRAME_LOST, LAST_SIGNAL }; @@ -54,7 +57,8 @@ ARG_HEIGHT, ARG_QUALITY, ARG_NUMBUFS, - ARG_BUFSIZE + ARG_BUFSIZE, + ARG_USE_FIXED_FPS }; @@ -68,7 +72,7 @@ gint64 src_value, GstFormat *dest_format, gint64 *dest_value); -static GstPadLinkReturn gst_v4lmjpegsrc_srcconnect (GstPad *pad, +static GstPadLinkReturn gst_v4lmjpegsrc_srcconnect (GstPad *pad, GstCaps *caps); static GstBuffer* gst_v4lmjpegsrc_get (GstPad *pad); @@ -82,6 +86,10 @@ GValue *value, GParamSpec *pspec); +/* set_clock function for A/V sync */ +static void gst_v4lmjpegsrc_set_clock (GstElement *element, + GstClock *clock); + /* state handling */ static GstElementStateReturn gst_v4lmjpegsrc_change_state (GstElement *element); @@ -99,7 +107,7 @@ static GstPadTemplate *src_template; static GstElementClass *parent_class = NULL; -/*static guint gst_v4lmjpegsrc_signals[LAST_SIGNAL] = { 0 }; */ +static guint gst_v4lmjpegsrc_signals[LAST_SIGNAL] = { 0 }; GType @@ -139,46 +147,76 @@ g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_X_OFFSET, g_param_spec_int("x_offset","x_offset","x_offset", - G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); + G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_Y_OFFSET, g_param_spec_int("y_offset","y_offset","y_offset", - G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); + G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_F_WIDTH, g_param_spec_int("frame_width","frame_width","frame_width", - G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); + G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_F_HEIGHT, g_param_spec_int("frame_height","frame_height","frame_height", - G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); + G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_H_DECIMATION, g_param_spec_int("h_decimation","h_decimation","h_decimation", - G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); + G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_V_DECIMATION, g_param_spec_int("v_decimation","v_decimation","v_decimation", - G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); + G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_WIDTH, g_param_spec_int("width","width","width", - G_MININT,G_MAXINT,0,G_PARAM_READABLE)); + G_MININT,G_MAXINT,0,G_PARAM_READABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_HEIGHT, g_param_spec_int("height","height","height", - G_MININT,G_MAXINT,0,G_PARAM_READABLE)); + G_MININT,G_MAXINT,0,G_PARAM_READABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_QUALITY, g_param_spec_int("quality","quality","quality", - G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); + G_MININT,G_MAXINT,0,G_PARAM_WRITABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_NUMBUFS, g_param_spec_int("num_buffers","num_buffers","num_buffers", - G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_BUFSIZE, g_param_spec_int("buffer_size","buffer_size","buffer_size", - G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_USE_FIXED_FPS, + g_param_spec_boolean("use_fixed_fps", "Use Fixed FPS", + "Drop/Insert frames to reach a certain FPS (TRUE) " + "or adapt FPS to suit the number of frabbed frames", + TRUE, G_PARAM_READWRITE)); + + /* signals */ + gst_v4lmjpegsrc_signals[SIGNAL_FRAME_CAPTURE] = + g_signal_new("frame_capture", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4lMjpegSrcClass, frame_capture), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + gst_v4lmjpegsrc_signals[SIGNAL_FRAME_DROP] = + g_signal_new("frame_drop", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4lMjpegSrcClass, frame_drop), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + gst_v4lmjpegsrc_signals[SIGNAL_FRAME_INSERT] = + g_signal_new("frame_insert", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4lMjpegSrcClass, frame_insert), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + gst_v4lmjpegsrc_signals[SIGNAL_FRAME_LOST] = + g_signal_new("frame_lost", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4lMjpegSrcClass, frame_lost), + NULL, NULL, g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); gobject_class->set_property = gst_v4lmjpegsrc_set_property; gobject_class->get_property = gst_v4lmjpegsrc_get_property; gstelement_class->change_state = gst_v4lmjpegsrc_change_state; + + gstelement_class->set_clock = gst_v4lmjpegsrc_set_clock; } @@ -215,33 +253,61 @@ v4lmjpegsrc->numbufs = 64; v4lmjpegsrc->bufsize = 256; + + /* no clock */ + v4lmjpegsrc->clock = NULL; + + /* fps */ + v4lmjpegsrc->use_fixed_fps = TRUE; } -static gboolean -gst_v4lmjpegsrc_srcconvert (GstPad *pad, - GstFormat src_format, - gint64 src_value, - GstFormat *dest_format, - gint64 *dest_value) +static gdouble +gst_v4lmjpegsrc_get_fps (GstV4lMjpegSrc *v4lmjpegsrc) { - GstV4lMjpegSrc *v4lmjpegsrc; gint norm; gdouble fps; + + if (!v4lmjpegsrc->use_fixed_fps && + v4lmjpegsrc->clock != NULL && + v4lmjpegsrc->handled > 0) { + /* try to get time from clock master and calculate fps */ + GstClockTime time = gst_clock_get_time(v4lmjpegsrc->clock) - v4lmjpegsrc->substract_time; + return v4lmjpegsrc->handled * GST_SECOND / time; + } - v4lmjpegsrc = GST_V4LMJPEGSRC (gst_pad_get_parent (pad)); + /* if that failed ... */ if (!GST_V4L_IS_OPEN(GST_V4LELEMENT(v4lmjpegsrc))) - return FALSE; + return 0.; if (!gst_v4l_get_chan_norm(GST_V4LELEMENT(v4lmjpegsrc), NULL, &norm)) - return FALSE; + return 0.; if (norm == VIDEO_MODE_NTSC) fps = 30000/1001; else fps = 25.; + return fps; +} + + +static gboolean +gst_v4lmjpegsrc_srcconvert (GstPad *pad, + GstFormat src_format, + gint64 src_value, + GstFormat *dest_format, + gint64 *dest_value) +{ + GstV4lMjpegSrc *v4lmjpegsrc; + gdouble fps; + + v4lmjpegsrc = GST_V4LMJPEGSRC (gst_pad_get_parent (pad)); + + if ((fps = gst_v4lmjpegsrc_get_fps(v4lmjpegsrc)) == 0) + return FALSE; + switch (src_format) { case GST_FORMAT_TIME: switch (*dest_format) { @@ -351,11 +417,16 @@ GstV4lMjpegSrc *v4lmjpegsrc; GstBuffer *buf; gint num; + gdouble fps = 0; g_return_val_if_fail (pad != NULL, NULL); v4lmjpegsrc = GST_V4LMJPEGSRC (gst_pad_get_parent (pad)); + if (v4lmjpegsrc->use_fixed_fps && + (fps = gst_v4lmjpegsrc_get_fps(v4lmjpegsrc)) == 0) + return NULL; + buf = gst_buffer_new_from_pool(v4lmjpegsrc->bufferpool, 0, 0); if (!buf) { @@ -364,15 +435,86 @@ return NULL; } - /* grab a frame from the device */ - if (!gst_v4lmjpegsrc_grab_frame(v4lmjpegsrc, &num, &(GST_BUFFER_SIZE(buf)))) - return NULL; + if (v4lmjpegsrc->need_writes > 0) { + /* use last frame */ + num = v4lmjpegsrc->last_frame; + v4lmjpegsrc->need_writes--; + } else if (v4lmjpegsrc->clock && v4lmjpegsrc->use_fixed_fps) { + GstClockTime time; + gboolean have_frame = FALSE; + + do { + /* by default, we use the frame once */ + v4lmjpegsrc->need_writes = 1; + + /* grab a frame from the device */ + if (!gst_v4lmjpegsrc_grab_frame(v4lmjpegsrc, &num, &v4lmjpegsrc->last_size)) + return NULL; + + v4lmjpegsrc->last_frame = num; + time = GST_TIMEVAL_TO_TIME(v4lmjpegsrc->bsync.timestamp) - + v4lmjpegsrc->substract_time; + + /* first check whether we lost any frames according to the device */ + if (v4lmjpegsrc->last_seq != 0) { + if (v4lmjpegsrc->bsync.seq - v4lmjpegsrc->last_seq > 1) { + v4lmjpegsrc->need_writes = v4lmjpegsrc->bsync.seq - v4lmjpegsrc->last_seq; + g_signal_emit(G_OBJECT(v4lmjpegsrc), + gst_v4lmjpegsrc_signals[SIGNAL_FRAME_LOST], 0, + v4lmjpegsrc->bsync.seq - v4lmjpegsrc->last_seq - 1); + } + } + v4lmjpegsrc->last_seq = v4lmjpegsrc->bsync.seq; + + /* decide how often we're going to write the frame - set + * v4lmjpegsrc->need_writes to (that-1) and have_frame to TRUE + * if we're going to write it - else, just continue. + * + * time is generally the system or audio clock. Let's + * say that we've written one second of audio, then we want + * to have written one second of video too, within the same + * timeframe. This means that if time - begin_time = X sec, + * we want to have written X*fps frames. If we've written + * more - drop, if we've written less - dup... */ + if (v4lmjpegsrc->handled * fps * GST_SECOND - time > 1.5 * fps * GST_SECOND) { + /* yo dude, we've got too many frames here! Drop! DROP! */ + v4lmjpegsrc->need_writes--; /* -= (v4lmjpegsrc->handled - (time / fps)); */ + g_signal_emit(G_OBJECT(v4lmjpegsrc), + gst_v4lmjpegsrc_signals[SIGNAL_FRAME_DROP], 0); + } else if (v4lmjpegsrc->handled * fps * GST_SECOND - time < - 1.5 * fps * GST_SECOND) { + /* this means we're lagging far behind */ + v4lmjpegsrc->need_writes++; /* += ((time / fps) - v4lmjpegsrc->handled); */ + g_signal_emit(G_OBJECT(v4lmjpegsrc), + gst_v4lmjpegsrc_signals[SIGNAL_FRAME_INSERT], 0); + } + + if (v4lmjpegsrc->need_writes > 0) { + have_frame = TRUE; + v4lmjpegsrc->use_num_times[num] = v4lmjpegsrc->need_writes; + v4lmjpegsrc->need_writes--; + } else { + gst_v4lmjpegsrc_requeue_frame(v4lmjpegsrc, num); + } + } while (!have_frame); + } else { + /* grab a frame from the device */ + if (!gst_v4lmjpegsrc_grab_frame(v4lmjpegsrc, &num, &v4lmjpegsrc->last_size)) + return NULL; + + v4lmjpegsrc->use_num_times[num] = 1; + } + GST_BUFFER_DATA(buf) = gst_v4lmjpegsrc_get_buffer(v4lmjpegsrc, num); - if (!v4lmjpegsrc->first_timestamp) - v4lmjpegsrc->first_timestamp = v4lmjpegsrc->bsync.timestamp.tv_sec * GST_SECOND + - v4lmjpegsrc->bsync.timestamp.tv_usec * GST_SECOND/1000000; - GST_BUFFER_TIMESTAMP(buf) = v4lmjpegsrc->bsync.timestamp.tv_sec * GST_SECOND + - v4lmjpegsrc->bsync.timestamp.tv_usec * GST_SECOND/1000000 - v4lmjpegsrc->first_timestamp; + GST_BUFFER_SIZE(buf) = v4lmjpegsrc->last_size; + if (v4lmjpegsrc->use_fixed_fps) + GST_BUFFER_TIMESTAMP(buf) = v4lmjpegsrc->handled * GST_SECOND / fps; + else /* calculate time based on our own clock */ + GST_BUFFER_TIMESTAMP(buf) = GST_TIMEVAL_TO_TIME(v4lmjpegsrc->bsync.timestamp) - + v4lmjpegsrc->substract_time; + + v4lmjpegsrc->handled++; + g_signal_emit(G_OBJECT(v4lmjpegsrc), + gst_v4lmjpegsrc_signals[SIGNAL_FRAME_CAPTURE], 0); return buf; } @@ -417,6 +559,11 @@ case ARG_BUFSIZE: v4lmjpegsrc->bufsize = g_value_get_int(value); break; + case ARG_USE_FIXED_FPS: + if (!GST_V4L_IS_ACTIVE(GST_V4LELEMENT(v4lmjpegsrc))) { + v4lmjpegsrc->use_fixed_fps = g_value_get_boolean(value); + } + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -469,6 +616,9 @@ case ARG_BUFSIZE: g_value_set_int(value, v4lmjpegsrc->breq.size); break; + case ARG_USE_FIXED_FPS: + g_value_set_boolean(value, v4lmjpegsrc->use_fixed_fps); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -481,6 +631,7 @@ { GstV4lMjpegSrc *v4lmjpegsrc; GstElementStateReturn parent_value; + GTimeVal time; g_return_val_if_fail(GST_IS_V4LMJPEGSRC(element), GST_STATE_FAILURE); @@ -490,14 +641,24 @@ case GST_STATE_READY_TO_PAUSED: /* actual buffer set-up used to be done here - but I moved * it to capsnego itself */ - v4lmjpegsrc->first_timestamp = 0; + v4lmjpegsrc->handled = 0; + v4lmjpegsrc->need_writes = 0; + v4lmjpegsrc->last_frame = 0; + v4lmjpegsrc->substract_time = 0; break; case GST_STATE_PAUSED_TO_PLAYING: /* queue all buffer, start streaming capture */ if (!gst_v4lmjpegsrc_capture_start(v4lmjpegsrc)) return GST_STATE_FAILURE; + g_get_current_time(&time); + v4lmjpegsrc->substract_time = GST_TIMEVAL_TO_TIME(time) - + v4lmjpegsrc->substract_time; + v4lmjpegsrc->last_seq = 0; break; case GST_STATE_PLAYING_TO_PAUSED: + g_get_current_time(&time); + v4lmjpegsrc->substract_time = GST_TIMEVAL_TO_TIME(time) - + v4lmjpegsrc->substract_time; /* de-queue all queued buffers */ if (!gst_v4lmjpegsrc_capture_stop(v4lmjpegsrc)) return GST_STATE_FAILURE; @@ -547,6 +708,14 @@ } +static void +gst_v4lmjpegsrc_set_clock (GstElement *element, + GstClock *clock) +{ + GST_V4LMJPEGSRC(element)->clock = clock; +} + + static GstBuffer* gst_v4lmjpegsrc_buffer_new (GstBufferPool *pool, guint64 location, @@ -583,7 +752,10 @@ for (n=0;n<v4lmjpegsrc->breq.count;n++) if (GST_BUFFER_DATA(buf) == gst_v4lmjpegsrc_get_buffer(v4lmjpegsrc, n)) { - gst_v4lmjpegsrc_requeue_frame(v4lmjpegsrc, n); + v4lmjpegsrc->use_num_times[n]--; + if (v4lmjpegsrc->use_num_times[n] <= 0) { + gst_v4lmjpegsrc_requeue_frame(v4lmjpegsrc, n); + } break; } Index: gstv4lmjpegsrc.h =================================================================== RCS file: /cvsroot/gstreamer/gst-plugins/sys/v4l/gstv4lmjpegsrc.h,v retrieving revision 1.5 retrieving revision 1.6 diff -u -d -r1.5 -r1.6 --- gstv4lmjpegsrc.h 9 Sep 2002 07:12:29 -0000 1.5 +++ gstv4lmjpegsrc.h 2 May 2003 21:16:54 -0000 1.6 @@ -55,8 +55,24 @@ struct mjpeg_sync bsync; struct mjpeg_requestbuffers breq; - /* first timestamp */ - guint64 first_timestamp; + /* A/V sync... frame counter and internal cache */ + gulong handled; + gint last_frame; + gint last_size; + gint need_writes; + gulong last_seq; + + /* clock */ + GstClock *clock; + + /* time to substract from clock time to get back to timestamp */ + GstClockTime substract_time; + + /* how often are we going to use each frame? */ + gint *use_num_times; + + /* how are we going to push buffers? */ + gboolean use_fixed_fps; /* caching values */ gint x_offset; @@ -76,6 +92,12 @@ struct _GstV4lMjpegSrcClass { GstV4lElementClass parent_class; + + void (*frame_capture) (GObject *object); + void (*frame_drop) (GObject *object); + void (*frame_insert) (GObject *object); + void (*frame_lost) (GObject *object, + gint num_lost); }; GType gst_v4lmjpegsrc_get_type(void); Index: gstv4lsrc.c =================================================================== RCS file: /cvsroot/gstreamer/gst-plugins/sys/v4l/gstv4lsrc.c,v retrieving revision 1.33 retrieving revision 1.34 diff -u -d -r1.33 -r1.34 --- gstv4lsrc.c 9 Mar 2003 15:07:52 -0000 1.33 +++ gstv4lsrc.c 2 May 2003 21:16:54 -0000 1.34 @@ -39,6 +39,9 @@ /* V4lSrc signals and args */ enum { /* FILL ME */ + SIGNAL_FRAME_CAPTURE, + SIGNAL_FRAME_DROP, + SIGNAL_FRAME_INSERT, LAST_SIGNAL }; @@ -50,7 +53,8 @@ ARG_PALETTE, ARG_PALETTE_NAME, ARG_NUMBUFS, - ARG_BUFSIZE + ARG_BUFSIZE, + ARG_USE_FIXED_FPS }; @@ -64,7 +68,7 @@ gint64 src_value, GstFormat *dest_format, gint64 *dest_value); -static GstPadLinkReturn gst_v4lsrc_srcconnect (GstPad *pad, +static GstPadLinkReturn gst_v4lsrc_srcconnect (GstPad *pad, GstCaps *caps); static GstBuffer* gst_v4lsrc_get (GstPad *pad); @@ -90,12 +94,16 @@ GstBuffer *buf, gpointer user_data); +/* set_clock function for a/V sync */ +static void gst_v4lsrc_set_clock (GstElement *element, + GstClock *clock); + static GstCaps *capslist = NULL; static GstPadTemplate *src_template; static GstElementClass *parent_class = NULL;\ -/*static guint gst_v4lsrc_signals[LAST_SIGNAL] = { 0 }; */ +static guint gst_v4lsrc_signals[LAST_SIGNAL] = { 0 }; GType @@ -134,28 +142,53 @@ parent_class = g_type_class_ref(GST_TYPE_V4LELEMENT); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_WIDTH, - g_param_spec_int("width","width","width", - G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + g_param_spec_int("width", "Width", "Video width", + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_HEIGHT, - g_param_spec_int("height","height","height", - G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + g_param_spec_int("height", "Height", "Video height", + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_PALETTE, - g_param_spec_int("palette","palette","palette", - G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + g_param_spec_int("palette", "Palette", "Video palette", + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_PALETTE_NAME, - g_param_spec_string("palette_name","palette_name","palette_name", - NULL, G_PARAM_READABLE)); + g_param_spec_string("palette_name", "Palette name", + "Name of the current video palette", + NULL, G_PARAM_READABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_NUMBUFS, - g_param_spec_int("num_buffers","num_buffers","num_buffers", - G_MININT,G_MAXINT,0,G_PARAM_READABLE)); + g_param_spec_int("num_buffers","Num Buffers","Number of buffers", + G_MININT,G_MAXINT,0,G_PARAM_READABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_BUFSIZE, - g_param_spec_int("buffer_size","buffer_size","buffer_size", - G_MININT,G_MAXINT,0,G_PARAM_READABLE)); + g_param_spec_int("buffer_size","Buffer Size","Size of buffers", + G_MININT,G_MAXINT,0,G_PARAM_READABLE)); + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_USE_FIXED_FPS, + g_param_spec_boolean("use_fixed_fps", "Use Fixed FPS", + "Drop/Insert frames to reach a certain FPS (TRUE) " + "or adapt FPS to suit the number of frabbed frames", + TRUE, G_PARAM_READWRITE)); + + /* signals */ + gst_v4lsrc_signals[SIGNAL_FRAME_CAPTURE] = + g_signal_new("frame_capture", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4lSrcClass, frame_capture), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + gst_v4lsrc_signals[SIGNAL_FRAME_DROP] = + g_signal_new("frame_drop", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4lSrcClass, frame_drop), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + gst_v4lsrc_signals[SIGNAL_FRAME_INSERT] = + g_signal_new("frame_insert", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4lSrcClass, frame_insert), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); gobject_class->set_property = gst_v4lsrc_set_property; gobject_class->get_property = gst_v4lsrc_get_property; gstelement_class->change_state = gst_v4lsrc_change_state; + + gstelement_class->set_clock = gst_v4lsrc_set_clock; } @@ -181,33 +214,61 @@ v4lsrc->width = 160; v4lsrc->height = 120; v4lsrc->buffer_size = 0; + + /* no clock */ + v4lsrc->clock = NULL; + + /* fps */ + v4lsrc->use_fixed_fps = TRUE; } -static gboolean -gst_v4lsrc_srcconvert (GstPad *pad, - GstFormat src_format, - gint64 src_value, - GstFormat *dest_format, - gint64 *dest_value) +static gdouble +gst_v4lsrc_get_fps (GstV4lSrc *v4lsrc) { - GstV4lSrc *v4lsrc; gint norm; gdouble fps; - v4lsrc = GST_V4LSRC (gst_pad_get_parent (pad)); + if (!v4lsrc->use_fixed_fps && + v4lsrc->clock != NULL && + v4lsrc->handled > 0) { + /* try to get time from clock master and calculate fps */ + GstClockTime time = gst_clock_get_time(v4lsrc->clock) - v4lsrc->substract_time; + return v4lsrc->handled * GST_SECOND / time; + } + + /* if that failed ... */ if (!GST_V4L_IS_OPEN(GST_V4LELEMENT(v4lsrc))) - return FALSE; + return 0.; if (!gst_v4l_get_chan_norm(GST_V4LELEMENT(v4lsrc), NULL, &norm)) - return FALSE; + return 0.; if (norm == VIDEO_MODE_NTSC) fps = 30000/1001; else fps = 25.; + return fps; +} + + +static gboolean +gst_v4lsrc_srcconvert (GstPad *pad, + GstFormat src_format, + gint64 src_value, + GstFormat *dest_format, + gint64 *dest_value) +{ + GstV4lSrc *v4lsrc; + gdouble fps; + + v4lsrc = GST_V4LSRC (gst_pad_get_parent (pad)); + + if ((fps = gst_v4lsrc_get_fps(v4lsrc)) == 0) + return FALSE; + switch (src_format) { case GST_FORMAT_TIME: switch (*dest_format) { @@ -448,11 +509,16 @@ GstV4lSrc *v4lsrc; GstBuffer *buf; gint num; + gdouble fps = 0.; g_return_val_if_fail (pad != NULL, NULL); v4lsrc = GST_V4LSRC (gst_pad_get_parent (pad)); + if (v4lsrc->use_fixed_fps && + (fps = gst_v4lsrc_get_fps(v4lsrc)) == 0) + return NULL; + buf = gst_buffer_new_from_pool(v4lsrc->bufferpool, 0, 0); if (!buf) { @@ -461,19 +527,73 @@ return NULL; } - /* grab a frame from the device */ - if (!gst_v4lsrc_grab_frame(v4lsrc, &num)) - return NULL; + if (v4lsrc->need_writes > 0) { + /* use last frame */ + num = v4lsrc->last_frame; + v4lsrc->need_writes--; + } else if (v4lsrc->clock && v4lsrc->use_fixed_fps) { + GstClockTime time; + gboolean have_frame = FALSE; + + do { + /* by default, we use the frame once */ + v4lsrc->need_writes = 1; + + /* grab a frame from the device */ + if (!gst_v4lsrc_grab_frame(v4lsrc, &num)) + return NULL; + + v4lsrc->last_frame = num; + time = v4lsrc->timestamp_soft_sync[num] - v4lsrc->substract_time; + + /* decide how often we're going to write the frame - set + * v4lsrc->need_writes to (that-1) and have_frame to TRUE + * if we're going to write it - else, just continue. + * + * time is generally the system or audio clock. Let's + * say that we've written one second of audio, then we want + * to have written one second of video too, within the same + * timeframe. This means that if time - begin_time = X sec, + * we want to have written X*fps frames. If we've written + * more - drop, if we've written less - dup... */ + if (v4lsrc->handled * fps * GST_SECOND - time > 1.5 * fps * GST_SECOND) { + /* yo dude, we've got too many frames here! Drop! DROP! */ + v4lsrc->need_writes--; /* -= (v4lsrc->handled - (time / fps)); */ + g_signal_emit(G_OBJECT(v4lsrc), + gst_v4lsrc_signals[SIGNAL_FRAME_DROP], 0); + } else if (v4lsrc->handled * fps * GST_SECOND - time < - 1.5 * fps * GST_SECOND) { + /* this means we're lagging far behind */ + v4lsrc->need_writes++; /* += ((time / fps) - v4lsrc->handled); */ + g_signal_emit(G_OBJECT(v4lsrc), + gst_v4lsrc_signals[SIGNAL_FRAME_INSERT], 0); + } + + if (v4lsrc->need_writes > 0) { + have_frame = TRUE; + v4lsrc->use_num_times[num] = v4lsrc->need_writes; + v4lsrc->need_writes--; + } else { + gst_v4lsrc_requeue_frame(v4lsrc, num); + } + } while (!have_frame); + } else { + /* grab a frame from the device */ + if (!gst_v4lsrc_grab_frame(v4lsrc, &num)) + return NULL; + + v4lsrc->use_num_times[num] = 1; + } + GST_BUFFER_DATA(buf) = gst_v4lsrc_get_buffer(v4lsrc, num); GST_BUFFER_SIZE(buf) = v4lsrc->buffer_size; + if (v4lsrc->use_fixed_fps) + GST_BUFFER_TIMESTAMP(buf) = v4lsrc->handled * GST_SECOND / fps; + else /* calculate time based on our own clock */ + GST_BUFFER_TIMESTAMP(buf) = v4lsrc->timestamp_soft_sync[num] - v4lsrc->substract_time; - if (!v4lsrc->first_timestamp) - v4lsrc->first_timestamp = - GST_TIMEVAL_TO_TIME(v4lsrc->timestamp_soft_sync[num]); - - GST_BUFFER_TIMESTAMP(buf) = - GST_TIMEVAL_TO_TIME(v4lsrc->timestamp_soft_sync[num]) - - v4lsrc->first_timestamp; + v4lsrc->handled++; + g_signal_emit(G_OBJECT(v4lsrc), + gst_v4lsrc_signals[SIGNAL_FRAME_CAPTURE], 0); return buf; } @@ -503,6 +623,12 @@ v4lsrc->palette = g_value_get_int(value); break; + case ARG_USE_FIXED_FPS: + if (!GST_V4L_IS_ACTIVE(GST_V4LELEMENT(v4lsrc))) { + v4lsrc->use_fixed_fps = g_value_get_boolean(value); + } + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -549,6 +675,10 @@ g_value_set_int(value, v4lsrc->mbuf.size/(v4lsrc->mbuf.frames*1024)); break; + case ARG_USE_FIXED_FPS: + g_value_set_boolean(value, v4lsrc->use_fixed_fps); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -560,6 +690,7 @@ gst_v4lsrc_change_state (GstElement *element) { GstV4lSrc *v4lsrc; + GTimeVal time; gint transition = GST_STATE_TRANSITION (element); g_return_val_if_fail(GST_IS_V4LSRC(element), GST_STATE_FAILURE); @@ -570,7 +701,10 @@ case GST_STATE_NULL_TO_READY: break; case GST_STATE_READY_TO_PAUSED: - v4lsrc->first_timestamp = 0; + v4lsrc->handled = 0; + v4lsrc->need_writes = 0; + v4lsrc->last_frame = 0; + v4lsrc->substract_time = 0; /* buffer setup used to be done here, but I moved it to * capsnego */ break; @@ -578,11 +712,15 @@ /* queue all buffer, start streaming capture */ if (!gst_v4lsrc_capture_start(v4lsrc)) return GST_STATE_FAILURE; + g_get_current_time(&time); + v4lsrc->substract_time = GST_TIMEVAL_TO_TIME(time) - v4lsrc->substract_time; break; case GST_STATE_PLAYING_TO_PAUSED: /* de-queue all queued buffers */ if (!gst_v4lsrc_capture_stop(v4lsrc)) return GST_STATE_FAILURE; + g_get_current_time(&time); + v4lsrc->substract_time = GST_TIMEVAL_TO_TIME(time) - v4lsrc->substract_time; break; case GST_STATE_PAUSED_TO_READY: /* stop capturing, unmap all buffers */ @@ -594,21 +732,7 @@ } if (GST_ELEMENT_CLASS (parent_class)->change_state) - GST_ELEMENT_CLASS (parent_class)->change_state (element); - - /* FIXME, this gives a not supported error on my machine? - switch (transition) { - case GST_STATE_NULL_TO_READY: - if ((GST_V4LELEMENT(v4lsrc)->norm >= VIDEO_MODE_PAL || - GST_V4LELEMENT(v4lsrc)->norm < VIDEO_MODE_AUTO) || - GST_V4LELEMENT(v4lsrc)->channel < 0) - if (!gst_v4l_set_chan_norm(GST_V4LELEMENT(v4lsrc), - 0, GST_V4LELEMENT(v4lsrc)->norm)) - return GST_STATE_FAILURE; - break; - } - */ - + return GST_ELEMENT_CLASS (parent_class)->change_state (element); return GST_STATE_SUCCESS; } @@ -651,7 +775,10 @@ for (n=0;n<v4lsrc->mbuf.frames;n++) if (GST_BUFFER_DATA(buf) == gst_v4lsrc_get_buffer(v4lsrc, n)) { - gst_v4lsrc_requeue_frame(v4lsrc, n); + v4lsrc->use_num_times[n]--; + if (v4lsrc->use_num_times[n] <= 0) { + gst_v4lsrc_requeue_frame(v4lsrc, n); + } break; } @@ -661,6 +788,14 @@ /* free struct */ gst_buffer_default_free(buf); +} + + +static void +gst_v4lsrc_set_clock (GstElement *element, + GstClock *clock) +{ + GST_V4LSRC(element)->clock = clock; } Index: gstv4lsrc.h =================================================================== RCS file: /cvsroot/gstreamer/gst-plugins/sys/v4l/gstv4lsrc.h,v retrieving revision 1.11 retrieving revision 1.12 diff -u -d -r1.11 -r1.12 --- gstv4lsrc.h 1 Mar 2003 14:35:17 -0000 1.11 +++ gstv4lsrc.h 2 May 2003 21:16:55 -0000 1.12 @@ -58,7 +58,7 @@ /* a seperate GThread for the sync() thread (improves correctness of timestamps) */ gint8 *isready_soft_sync; /* 1 = ok, 0 = waiting, -1 = error */ - struct timeval *timestamp_soft_sync; + GstClockTime *timestamp_soft_sync; GThread * thread_soft_sync; GMutex * mutex_soft_sync; GCond ** cond_soft_sync; @@ -69,8 +69,22 @@ /* True if we want the soft sync thread to stop */ gboolean quit; - /* first timestamp */ - guint64 first_timestamp; + /* A/V sync... frame counter and internal cache */ + gulong handled; + gint last_frame; + gint need_writes; + + /* clock */ + GstClock *clock; + + /* time to substract from clock time to get back to timestamp */ + GstClockTime substract_time; + + /* how often are we going to use each frame? */ + gint *use_num_times; + + /* how are we going to push buffers? */ + gboolean use_fixed_fps; /* caching values */ gint width; @@ -80,6 +94,10 @@ struct _GstV4lSrcClass { GstV4lElementClass parent_class; + + void (*frame_capture) (GObject *object); + void (*frame_drop) (GObject *object); + void (*frame_insert) (GObject *object); }; GType gst_v4lsrc_get_type(void); Index: v4lmjpegsrc_calls.c =================================================================== RCS file: /cvsroot/gstreamer/gst-plugins/sys/v4l/v4lmjpegsrc_calls.c,v retrieving revision 1.13 retrieving revision 1.14 diff -u -d -r1.13 -r1.14 --- v4lmjpegsrc_calls.c 9 Mar 2003 15:07:53 -0000 1.13 +++ v4lmjpegsrc_calls.c 2 May 2003 21:16:55 -0000 1.14 @@ -426,6 +426,15 @@ gst_info("Got %ld buffers of size %ld KB\n", v4lmjpegsrc->breq.count, v4lmjpegsrc->breq.size/1024); + v4lmjpegsrc->use_num_times = (gint *) malloc(sizeof(gint) * v4lmjpegsrc->breq.count); + if (!v4lmjpegsrc->use_num_times) + { + gst_element_error(GST_ELEMENT(v4lmjpegsrc), + "Error creating sync-use-time tracker: %s", + g_strerror(errno)); + return FALSE; + } + /* Map the buffers */ GST_V4LELEMENT(v4lmjpegsrc)->buffer = mmap(0, v4lmjpegsrc->breq.count * v4lmjpegsrc->breq.size, @@ -573,6 +582,8 @@ /* unmap the buffer */ munmap(GST_V4LELEMENT(v4lmjpegsrc)->buffer, v4lmjpegsrc->breq.size * v4lmjpegsrc->breq.count); GST_V4LELEMENT(v4lmjpegsrc)->buffer = NULL; + + free(v4lmjpegsrc->use_num_times); return TRUE; } Index: v4lsrc_calls.c =================================================================== RCS file: /cvsroot/gstreamer/gst-plugins/sys/v4l/v4lsrc_calls.c,v retrieving revision 1.23 retrieving revision 1.24 diff -u -d -r1.23 -r1.24 --- v4lsrc_calls.c 9 Mar 2003 15:07:53 -0000 1.23 +++ v4lsrc_calls.c 2 May 2003 21:16:55 -0000 1.24 @@ -130,7 +130,13 @@ g_mutex_lock(v4lsrc->mutex_soft_sync); - gettimeofday(&(v4lsrc->timestamp_soft_sync[num]), NULL); + if (v4lsrc->clock) { + v4lsrc->timestamp_soft_sync[num] = gst_clock_get_time(v4lsrc->clock); + } else { + GTimeVal time; + g_get_current_time(&time); + v4lsrc->timestamp_soft_sync[num] = GST_TIMEVAL_TO_TIME(time); + } v4lsrc->isready_soft_sync[num] = FRAME_SYNCED; g_cond_broadcast(v4lsrc->cond_soft_sync[num]); @@ -227,9 +233,10 @@ gst_v4lsrc_sync_next_frame (GstV4lSrc *v4lsrc, gint *num) { - *num = v4lsrc->sync_frame; + DEBUG("syncing on next frame (%d)", *num); + /* "software sync()" on the frame */ g_mutex_lock(v4lsrc->mutex_soft_sync); while (v4lsrc->isready_soft_sync[*num] == FRAME_DONE) @@ -243,7 +250,6 @@ return FALSE; v4lsrc->isready_soft_sync[*num] = FRAME_DONE; - v4lsrc->sync_frame = (v4lsrc->sync_frame + 1)%v4lsrc->mbuf.frames; return TRUE; @@ -312,7 +318,7 @@ v4lsrc->mbuf.frames, palette_name[v4lsrc->mmap.format], v4lsrc->mbuf.size/(v4lsrc->mbuf.frames*1024)); - /* keep trakc of queued buffers */ + /* keep track of queued buffers */ v4lsrc->frame_queued = (gint8 *) malloc(sizeof(gint8) * v4lsrc->mbuf.frames); if (!v4lsrc->frame_queued) { @@ -332,8 +338,8 @@ g_strerror(errno)); return FALSE; } - v4lsrc->timestamp_soft_sync = (struct timeval *) - malloc(sizeof(struct timeval) * v4lsrc->mbuf.frames); + v4lsrc->timestamp_soft_sync = (GstClockTime *) + malloc(sizeof(GstClockTime) * v4lsrc->mbuf.frames); if (!v4lsrc->timestamp_soft_sync) { gst_element_error(GST_ELEMENT(v4lsrc), @@ -351,6 +357,14 @@ } for (n=0;n<v4lsrc->mbuf.frames;n++) v4lsrc->cond_soft_sync[n] = g_cond_new(); + v4lsrc->use_num_times = (gint *) malloc(sizeof(gint) * v4lsrc->mbuf.frames); + if (!v4lsrc->use_num_times) + { + gst_element_error(GST_ELEMENT(v4lsrc), + "Error creating sync-use-time tracker: %s", + g_strerror(errno)); + return FALSE; + } v4lsrc->mutex_queued_frames = g_mutex_new(); v4lsrc->cond_queued_frames = g_cond_new(); @@ -526,6 +540,7 @@ free(v4lsrc->cond_soft_sync); free(v4lsrc->isready_soft_sync); free(v4lsrc->timestamp_soft_sync); + free(v4lsrc->use_num_times); /* unmap the buffer */ munmap(GST_V4LELEMENT(v4lsrc)->buffer, v4lsrc->mbuf.size); Index: gstv4l2src.c =================================================================== RCS file: /cvsroot/gstreamer/gst-plugins/sys/v4l2/gstv4l2src.c,v retrieving revision 1.10 retrieving revision 1.11 diff -u -d -r1.10 -r1.11 --- gstv4l2src.c 2 Mar 2003 21:58:52 -0000 1.10 +++ gstv4l2src.c 2 May 2003 21:16:56 -0000 1.11 @@ -34,7 +34,10 @@ /* V4l2Src signals and args */ enum { - /* FILL ME */ + SIGNAL_FRAME_CAPTURE, + SIGNAL_FRAME_DROP, + SIGNAL_FRAME_INSERT, + SIGNAL_FRAME_LOST, LAST_SIGNAL }; @@ -46,7 +49,8 @@ ARG_PALETTE, ARG_PALETTE_NAMES, ARG_NUMBUFS, - ARG_BUFSIZE + ARG_BUFSIZE, + ARG_USE_FIXED_FPS }; @@ -66,7 +70,7 @@ gint64 src_value, GstFormat *dest_format, gint64 *dest_value); -static GstPadLinkReturn gst_v4l2src_srcconnect (GstPad *pad, +static GstPadLinkReturn gst_v4l2src_srcconnect (GstPad *pad, GstCaps *caps); static GstCaps * gst_v4l2src_getcaps (GstPad *pad, GstCaps *caps); @@ -85,6 +89,11 @@ /* state handling */ static GstElementStateReturn gst_v4l2src_change_state (GstElement *element); +/* set_clock function for A/V sync */ +static void gst_v4l2src_set_clock (GstElement *element, + GstClock *clock); + + /* bufferpool functions */ static GstBuffer * gst_v4l2src_buffer_new (GstBufferPool *pool, guint64 offset, @@ -98,7 +107,7 @@ static GstPadTemplate *src_template; static GstElementClass *parent_class = NULL; -/*static guint gst_v4l2src_signals[LAST_SIGNAL] = { 0 }; */ +static guint gst_v4l2src_signals[LAST_SIGNAL] = { 0 }; GType @@ -141,24 +150,53 @@ g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_WIDTH, g_param_spec_int("width","width","width", - G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_HEIGHT, g_param_spec_int("height","height","height", - G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_PALETTE, g_param_spec_int("palette","palette","palette", - G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_PALETTE_NAMES, g_param_spec_pointer("palette_name","palette_name","palette_name", - G_PARAM_READABLE)); + G_PARAM_READABLE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_NUMBUFS, g_param_spec_int("num_buffers","num_buffers","num_buffers", - G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); + G_MININT,G_MAXINT,0,G_PARAM_READWRITE)); g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_BUFSIZE, g_param_spec_int("buffer_size","buffer_size","buffer_size", - G_MININT,G_MAXINT,0,G_PARAM_READABLE)); + G_MININT,G_MAXINT,0,G_PARAM_READABLE)); + + g_object_class_install_property(G_OBJECT_CLASS(klass), ARG_USE_FIXED_FPS, + g_param_spec_boolean("use_fixed_fps", "Use Fixed FPS", + "Drop/Insert frames to reach a certain FPS (TRUE) " + "or adapt FPS to suit the number of frabbed frames", + TRUE, G_PARAM_READWRITE)); + + /* signals */ + gst_v4l2src_signals[SIGNAL_FRAME_CAPTURE] = + g_signal_new("frame_capture", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4l2SrcClass, frame_capture), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + gst_v4l2src_signals[SIGNAL_FRAME_DROP] = + g_signal_new("frame_drop", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4l2SrcClass, frame_drop), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + gst_v4l2src_signals[SIGNAL_FRAME_INSERT] = + g_signal_new("frame_insert", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4l2SrcClass, frame_insert), + NULL, NULL, g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + gst_v4l2src_signals[SIGNAL_FRAME_LOST] = + g_signal_new("frame_lost", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(GstV4l2SrcClass, frame_lost), + NULL, NULL, g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); + gobject_class->set_property = gst_v4l2src_set_property; gobject_class->get_property = gst_v4l2src_get_property; @@ -167,6 +205,8 @@ v4l2_class->open = gst_v4l2src_open; v4l2_class->close = gst_v4l2src_close; + + gstelement_class->set_clock = gst_v4l2src_set_clock; } @@ -194,6 +234,12 @@ v4l2src->formats = NULL; v4l2src->format_list = NULL; + + /* no clock */ + v4l2src->clock = NULL; + + /* fps */ + v4l2src->use_fixed_fps = TRUE; } @@ -213,20 +259,24 @@ } -static gboolean -gst_v4l2src_srcconvert (GstPad *pad, - GstFormat src_format, - gint64 src_value, - GstFormat *dest_format, - gint64 *dest_value) +static gdouble +gst_v4l2src_get_fps (GstV4l2Src *v4l2src) { - GstV4l2Src *v4l2src; gint norm; struct v4l2_standard *std; gdouble fps; - v4l2src = GST_V4L2SRC (gst_pad_get_parent (pad)); + if (!v4l2src->use_fixed_fps && + v4l2src->clock != NULL && + v4l2src->handled > 0) { + /* try to get time from clock master and calculate fps */ + GstClockTime time = gst_clock_get_time(v4l2src->clock) - + v4l2src->substract_time; + return v4l2src->handled * GST_SECOND / time; + } + /* if that failed ... */ + if (!GST_V4L2_IS_OPEN(GST_V4L2ELEMENT(v4l2src))) return FALSE; @@ -235,6 +285,25 @@ std = ((struct v4l2_standard *) g_list_nth_data(GST_V4L2ELEMENT(v4l2src)->norms, norm)); fps = std->frameperiod.numerator / std->frameperiod.denominator; + + return fps; +} + + +static gboolean +gst_v4l2src_srcconvert (GstPad *pad, + GstFormat src_format, + gint64 src_value, + GstFormat *dest_format, + gint64 *dest_value) +{ + GstV4l2Src *v4l2src; + gdouble fps; + + v4l2src = GST_V4L2SRC (gst_pad_get_parent (pad)); + + if ((fps = gst_v4l2src_get_fps(v4l2src)) == 0) + return FALSE; switch (src_format) { case GST_FORMAT_TIME: @@ -679,11 +748,16 @@ GstV4l2Src *v4l2src; GstBuffer *buf; gint num; + gdouble fps = 0; g_return_val_if_fail (pad != NULL, NULL); v4l2src = GST_V4L2SRC(gst_pad_get_parent (pad)); + if (v4l2src->use_fixed_fps && + (fps = gst_v4l2src_get_fps(v4l2src)) == 0) + return NULL; + buf = gst_buffer_new_from_pool(v4l2src->bufferpool, 0, 0); if (!buf) { gst_element_error(GST_ELEMENT(v4l2src), @@ -691,16 +765,91 @@ return NULL; } - /* grab a frame from the device */ - if (!gst_v4l2src_grab_frame(v4l2src, &num)) - return NULL; + if (v4l2src->need_writes > 0) { + /* use last frame */ + num = v4l2src->last_frame; + v4l2src->need_writes--; + } else if (v4l2src->clock && v4l2src->use_fixed_fps) { + GstClockTime time; + gboolean have_frame = FALSE; + + do { + /* by default, we use the frame once */ + v4l2src->need_writes = 1; + + /* grab a frame from the device */ + if (!gst_v4l2src_grab_frame(v4l2src, &num)) + return NULL; + + v4l2src->last_frame = num; + time = GST_TIMEVAL_TO_TIME(v4l2src->bufsettings.timestamp) - + v4l2src->substract_time; + + /* first check whether we lost any frames according to the device */ + if (v4l2src->last_seq != 0) { + if (v4l2src->bufsettings.sequence - v4l2src->last_seq > 1) { + v4l2src->need_writes = v4l2src->bufsettings.sequence - + v4l2src->last_seq; + g_signal_emit(G_OBJECT(v4l2src), + gst_v4l2src_signals[SIGNAL_FRAME_LOST], + 0, + v4l2src->bufsettings.sequence - + v4l2src->last_seq - 1); + } + } + v4l2src->last_seq = v4l2src->bufsettings.sequence; + + /* decide how often we're going to write the frame - set + * v4lmjpegsrc->need_writes to (that-1) and have_frame to TRUE + * if we're going to write it - else, just continue. + * + * time is generally the system or audio clock. Let's + * say that we've written one second of audio, then we want + * to have written one second of video too, within the same + * timeframe. This means that if time - begin_time = X sec, + * we want to have written X*fps frames. If we've written + * more - drop, if we've written less - dup... */ + if (v4l2src->handled * fps * GST_SECOND - time > + 1.5 * fps * GST_SECOND) { + /* yo dude, we've got too many frames here! Drop! DROP! */ + v4l2src->need_writes--; /* -= (v4l2src->handled - (time / fps)); */ + g_signal_emit(G_OBJECT(v4l2src), + gst_v4l2src_signals[SIGNAL_FRAME_DROP], 0); + } else if (v4l2src->handled * fps * GST_SECOND - time < + -1.5 * fps * GST_SECOND) { + /* this means we're lagging far behind */ + v4l2src->need_writes++; /* += ((time / fps) - v4l2src->handled); */ + g_signal_emit(G_OBJECT(v4l2src), + gst_v4l2src_signals[SIGNAL_FRAME_INSERT], 0); + } + + if (v4l2src->need_writes > 0) { + have_frame = TRUE; + v4l2src->use_num_times[num] = v4l2src->need_writes; + v4l2src->need_writes--; + } else { + gst_v4l2src_requeue_frame(v4l2src, num); + } + } while (!have_frame); + } else { + /* grab a frame from the device */ + if (!gst_v4l2src_grab_frame(v4l2src, &num)) + return NULL; + + v4l2src->use_num_times[num] = 1; + } + GST_BUFFER_DATA(buf) = GST_V4L2ELEMENT(v4l2src)->buffer[num]; GST_BUFFER_SIZE(buf) = v4l2src->bufsettings.bytesused; - if (!v4l2src->first_timestamp) - v4l2src->first_timestamp = v4l2src->bufsettings.timestamp.tv_sec * GST_SECOND + - v4l2src->bufsettings.timestamp.tv_usec * (GST_SECOND/1000000); - GST_BUFFER_TIMESTAMP(buf) = v4l2src->bufsettings.length - v4l2src->first_timestamp; + if (v4l2src->use_fixed_fps) + GST_BUFFER_TIMESTAMP(buf) = v4l2src->handled * GST_SECOND / fps; + else /* calculate time based on our own clock */ + GST_BUFFER_TIMESTAMP(buf) = GST_TIMEVAL_TO_TIME(v4l2src->bufsettings.timestamp) - + v4l2src->substract_time; + v4l2src->handled++; + g_signal_emit(G_OBJECT(v4l2src), + gst_v4l2src_signals[SIGNAL_FRAME_CAPTURE], 0); return buf; } @@ -741,6 +890,12 @@ } break; + case ARG_USE_FIXED_FPS: + if (!GST_V4L2_IS_ACTIVE(GST_V4L2ELEMENT(v4l2src))) { + v4l2src->use_fixed_fps = g_value_get_boolean(value); + } + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -784,6 +939,10 @@ g_value_set_int(value, v4l2src->format.fmt.pix.sizeimage); break; + case ARG_USE_FIXED_FPS: + g_value_set_boolean(value, v4l2src->use_fixed_fps); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -797,6 +956,7 @@ GstV4l2Src *v4l2src; gint transition = GST_STATE_TRANSITION (element); GstElementStateReturn parent_return; + GTimeVal time; g_return_val_if_fail(GST_IS_V4L2SRC(element), GST_STATE_FAILURE); v4l2src = GST_V4L2SRC(element); @@ -813,15 +973,25 @@ return GST_STATE_FAILURE; break; case GST_STATE_READY_TO_PAUSED: - v4l2src->first_timestamp = 0; + v4l2src->handled = 0; + v4l2src->need_writes = 0; + v4l2src->last_frame = 0; + v4l2src->substract_time = 0; /* buffer setup moved to capsnego */ break; case GST_STATE_PAUSED_TO_PLAYING: /* queue all buffer, start streaming capture */ if (!gst_v4l2src_capture_start(v4l2src)) return GST_STATE_FAILURE; + g_get_current_time(&time); + v4l2src->substract_time = GST_TIMEVAL_TO_TIME(time) - + v4l2src->substract_time; + v4l2src->last_seq = 0; break; case GST_STATE_PLAYING_TO_PAUSED: + g_get_current_time(&time); + v4l2src->substract_time = GST_TIMEVAL_TO_TIME(time) - + v4l2src->substract_time; /* de-queue all queued buffers */ if (!gst_v4l2src_capture_stop(v4l2src)) return GST_STATE_FAILURE; @@ -839,6 +1009,14 @@ } +static void +gst_v4l2src_set_clock (GstElement *element, + GstClock *clock) +{ + GST_V4L2SRC(element)->clock = clock; +} + + static GstBuffer* gst_v4l2src_buffer_new (GstBufferPool *pool, guint64 offset, @@ -877,7 +1055,10 @@ for (n=0;n<v4l2src->breq.count;n++) if (GST_BUFFER_DATA(buf) == GST_V4L2ELEMENT(v4l2src)->buffer[n]) { - gst_v4l2src_requeue_frame(v4l2src, n); + v4l2src->use_num_times[n]--; + if (v4l2src->use_num_times[n] <= 0) { + gst_v4l2src_requeue_frame(v4l2src, n); + } break; } Index: gstv4l2src.h =================================================================== RCS file: /cvsroot/gstreamer/gst-plugins/sys/v4l2/gstv4l2src.h,v retrieving revision 1.3 retrieving revision 1.4 diff -u -d -r1.3 -r1.4 --- gstv4l2src.h 2 Mar 2003 21:58:52 -0000 1.3 +++ gstv4l2src.h 2 May 2003 21:16:56 -0000 1.4 @@ -51,7 +51,24 @@ struct v4l2_buffer bufsettings; struct v4l2_requestbuffers breq; struct v4l2_format format; - guint64 first_timestamp; + + /* A/V sync... frame counter and internal cache */ + gulong handled; + gint last_frame; + gint need_writes; + gulong last_seq; + + /* clock */ + GstClock *clock; + + /* time to substract from clock time to get back to timestamp */ + GstClockTime substract_time; + + /* how often are we going to use each frame? */ + gint *use_num_times; + + /* how are we going to push buffers? */ + gboolean use_fixed_fps; /* bufferpool for the buffers we're gonna use */ GstBufferPool *bufferpool; @@ -64,6 +81,12 @@ struct _GstV4l2SrcClass { GstV4l2ElementClass parent_class; + + void (*frame_capture) (GObject *object); + void (*frame_drop) (GObject *object); + void (*frame_insert) (GObject *object); + void (*frame_lost) (GObject *object, + gint num_lost); }; Index: v4l2src_calls.c =================================================================== RCS file: /cvsroot/gstreamer/gst-plugins/sys/v4l2/v4l2src_calls.c,v retrieving revision 1.6 retrieving revision 1.7 diff -u -d -r1.6 -r1.7 --- v4l2src_calls.c 2 Mar 2003 21:58:52 -0000 1.6 +++ v4l2src_calls.c 2 May 2003 21:16:56 -0000 1.7 @@ -255,6 +255,14 @@ gst_info("Got %d buffers (%s) of size %d KB\n", v4l2src->breq.count, desc, v4l2src->format.fmt.pix.sizeimage/1024); + v4l2src->use_num_times = (gint *) malloc(sizeof(gint) * v4l2src->breq.count); + if (!v4l2src->use_num_times) { + gst_element_error(GST_ELEMENT(v4l2src), + "Error creating sync-use-time tracker: %s", + g_strerror(errno)); + return FALSE; + } + /* Map the buffers */ GST_V4L2ELEMENT(v4l2src)->buffer = (guint8 **) g_malloc(sizeof(guint8*) * v4l2src->breq.count); for (n=0;n<v4l2src->breq.count;n++) { @@ -400,6 +408,8 @@ } g_free(GST_V4L2ELEMENT(v4l2src)->buffer); GST_V4L2ELEMENT(v4l2src)->buffer = NULL; + + free(v4l2src->use_num_times); return TRUE; } |