diff --git a/ChangeLog b/ChangeLog index 55337cd4a1..5faaf24e3d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2006-11-22 Michael Smith + + * ext/theora/gsttheoraenc.h: + * ext/theora/theoraenc.c: (gst_theora_enc_init), + (theora_enc_reset), (theora_enc_clear), (theora_enc_sink_setcaps), + (theora_buffer_from_packet), (theora_enc_is_discontinuous), + (theora_enc_chain), (theora_enc_change_state): + Mark discontinuities of > 3/4 of a frame, reinit encoder. + + * tests/check/pipelines/theoraenc.c: (check_buffer_granulepos), + (GST_START_TEST), (theoraenc_suite): + Enable discontinuity test, fix it. + 2006-11-21 Tim-Philipp Müller * ext/pango/gsttextoverlay.c: (gst_text_overlay_init), diff --git a/ext/theora/gsttheoraenc.h b/ext/theora/gsttheoraenc.h index 3fd890ea94..009a56d873 100644 --- a/ext/theora/gsttheoraenc.h +++ b/ext/theora/gsttheoraenc.h @@ -93,6 +93,9 @@ struct _GstTheoraEnc gint fps_n, fps_d; GstClockTime next_ts; + GstClockTime expected_ts; + gboolean next_discont; + guint packetno; guint64 bytes_out; guint64 granulepos_offset; diff --git a/ext/theora/theoraenc.c b/ext/theora/theoraenc.c index 7289d9ae93..16c8fafbb4 100644 --- a/ext/theora/theoraenc.c +++ b/ext/theora/theoraenc.c @@ -292,6 +292,7 @@ gst_theora_enc_init (GstTheoraEnc * enc, GstTheoraEncClass * g_class) GST_DEBUG_OBJECT (enc, "keyframe_frequency_force is %d, granule shift is %d", enc->info.keyframe_frequency_force, enc->granule_shift); + enc->expected_ts = GST_CLOCK_TIME_NONE; } static void @@ -307,6 +308,26 @@ theora_enc_finalize (GObject * object) G_OBJECT_CLASS (parent_class)->finalize (object); } +static void +theora_enc_reset (GstTheoraEnc * enc) +{ + theora_clear (&enc->state); + theora_encode_init (&enc->state, &enc->info); +} + +static void +theora_enc_clear (GstTheoraEnc * enc) +{ + enc->packetno = 0; + enc->bytes_out = 0; + enc->granulepos_offset = 0; + enc->timestamp_offset = 0; + + enc->next_ts = GST_CLOCK_TIME_NONE; + enc->next_discont = FALSE; + enc->expected_ts = GST_CLOCK_TIME_NONE; +} + static gboolean theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps) { @@ -374,7 +395,7 @@ theora_enc_sink_setcaps (GstPad * pad, GstCaps * caps) "keyframe_frequency_force is %d, granule shift is %d", enc->info.keyframe_frequency_force, enc->granule_shift); - theora_encode_init (&enc->state, &enc->info); + theora_enc_reset (enc); gst_object_unref (enc); @@ -418,6 +439,11 @@ theora_buffer_from_packet (GstTheoraEnc * enc, ogg_packet * packet, GST_BUFFER_TIMESTAMP (buf) = timestamp + enc->timestamp_offset; GST_BUFFER_DURATION (buf) = duration; + if (enc->next_discont) { + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); + enc->next_discont = FALSE; + } + /* the second most significant bit of the first data byte is cleared * for keyframes */ if ((packet->packet[0] & 0x40) == 0) { @@ -543,6 +569,34 @@ theora_enc_sink_event (GstPad * pad, GstEvent * event) return res; } +static gboolean +theora_enc_is_discontinuous (GstTheoraEnc * enc, GstBuffer * buffer) +{ + GstClockTime ts = GST_BUFFER_TIMESTAMP (buffer); + GstClockTimeDiff max_diff; + + /* Allow 3/4 a frame off */ + max_diff = (enc->info.fps_denominator * GST_SECOND * 3) / + (enc->info.fps_numerator * 4); + + if (ts != GST_CLOCK_TIME_NONE && enc->expected_ts != GST_CLOCK_TIME_NONE) { + if ((GstClockTimeDiff) (ts - enc->expected_ts) > max_diff) { + GST_DEBUG_OBJECT (enc, "Incoming TS %" GST_TIME_FORMAT + " exceeds expected value %" GST_TIME_FORMAT + " by too much, marking discontinuity", + GST_TIME_ARGS (ts), GST_TIME_ARGS (enc->expected_ts)); + return TRUE; + } + } + + if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DURATION (buffer))) + enc->expected_ts = ts + GST_BUFFER_DURATION (buffer); + else + enc->expected_ts = GST_CLOCK_TIME_NONE; + + return FALSE; +} + static GstFlowReturn theora_enc_chain (GstPad * pad, GstBuffer * buffer) { @@ -784,6 +838,16 @@ theora_enc_chain (GstPad * pad, GstBuffer * buffer) buffer = newbuf; } + if (theora_enc_is_discontinuous (enc, buffer)) { + theora_enc_reset (enc); + enc->granulepos_offset = + gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buffer), enc->fps_n, + GST_SECOND * enc->fps_d); + enc->timestamp_offset = GST_BUFFER_TIMESTAMP (buffer); + enc->next_ts = 0; + enc->next_discont = TRUE; + } + res = theora_encode_YUVin (&enc->state, &yuv); ret = GST_FLOW_OK; @@ -863,6 +927,8 @@ theora_enc_change_state (GstElement * element, GstStateChange transition) theora_clear (&enc->state); theora_comment_clear (&enc->comment); theora_info_clear (&enc->info); + + theora_enc_clear (enc); break; case GST_STATE_CHANGE_READY_TO_NULL: break; diff --git a/tests/check/pipelines/theoraenc.c b/tests/check/pipelines/theoraenc.c index 786ce4b96d..a6dbb1178e 100644 --- a/tests/check/pipelines/theoraenc.c +++ b/tests/check/pipelines/theoraenc.c @@ -62,6 +62,7 @@ static void check_buffer_granulepos (GstBuffer * buffer, gint64 granulepos) { GstClockTime clocktime; + int framecount; fail_unless (GST_BUFFER_OFFSET_END (buffer) == granulepos, "expected granulepos %" G_GUINT64_FORMAT @@ -70,8 +71,10 @@ check_buffer_granulepos (GstBuffer * buffer, gint64 granulepos) /* contrary to what we record as TIMESTAMP, we can use OFFSET to check * the granulepos correctly here */ - clocktime = gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND, - FRAMERATE); + framecount = GST_BUFFER_OFFSET_END (buffer); + framecount = granulepos >> GRANULEPOS_SHIFT; + framecount += granulepos & ((1 << GRANULEPOS_SHIFT) - 1); + clocktime = gst_util_uint64_scale (framecount, GST_SECOND, FRAMERATE); fail_unless (clocktime == GST_BUFFER_OFFSET (buffer), "expected OFFSET set to clocktime %" GST_TIME_FORMAT @@ -287,7 +290,6 @@ GST_START_TEST (test_continuity) GST_END_TEST; -#if 0 static gboolean drop_second_data_buffer (GstPad * droppad, GstBuffer * buffer, gpointer unused) { @@ -301,7 +303,6 @@ GST_START_TEST (test_discontinuity) gchar *pipe_str; GstBuffer *buffer; GError *error = NULL; - GstClockTime timestamp; guint drop_id; pipe_str = g_strdup_printf ("videotestsrc" @@ -336,7 +337,8 @@ GST_START_TEST (test_discontinuity) gst_object_unref (sink); } - drop_id = gst_pad_add_buffer_probe (droppad, drop_second_data_buffer, NULL); + drop_id = gst_pad_add_buffer_probe (droppad, + G_CALLBACK (drop_second_data_buffer), NULL); gst_buffer_straw_start_pipeline (bin, pad); /* header packets should have timestamp == NONE, granulepos 0 */ @@ -374,7 +376,9 @@ GST_START_TEST (test_discontinuity) /* check discontinuity with the next buffer */ buffer = gst_buffer_straw_get_buffer (bin, pad); check_buffer_duration (buffer, GST_SECOND / 10); - check_buffer_granulepos (buffer, 2); + /* After a discont, we'll always get a keyframe, so this one should be + * 2<