ext/speex/gstspeexdec.c: Correctly take the granulepos from upstream if possible...
authorSebastian Dröge <slomo@circular-chaos.org>
Sun, 31 Aug 2008 14:39:57 +0000 (14:39 +0000)
committerSebastian Dröge <slomo@circular-chaos.org>
Sun, 31 Aug 2008 14:39:57 +0000 (14:39 +0000)
Original commit message from CVS:
* ext/speex/gstspeexdec.c: (speex_dec_chain_parse_data):
Correctly take the granulepos from upstream if possible and
correctly handle the granulepos in various calculations: the
granulepos is the sample number of the _last_ sample in a frame, not
the first.
* ext/speex/gstspeexenc.c: (gst_speex_enc_sinkevent),
(gst_speex_enc_encode), (gst_speex_enc_chain),
(gst_speex_enc_change_state):
* ext/speex/gstspeexenc.h:
Handle non-zero start timestamps in the encoder and detect/handle
stream discontinuities. Fixes bug #547075.

ChangeLog
ext/speex/gstspeexdec.c
ext/speex/gstspeexenc.c
ext/speex/gstspeexenc.h

index 0e71c1652c6974eee9945d4c6ce9feaea845cba4..1c974665417ccf8475deb3bb89e99a4ff0311587 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+2008-08-31  Sebastian Dröge  <sebastian.droege@collabora.co.uk>
+
+       * ext/speex/gstspeexdec.c: (speex_dec_chain_parse_data):
+       Correctly take the granulepos from upstream if possible and
+       correctly handle the granulepos in various calculations: the
+       granulepos is the sample number of the _last_ sample in a frame, not
+       the first.
+
+       * ext/speex/gstspeexenc.c: (gst_speex_enc_sinkevent),
+       (gst_speex_enc_encode), (gst_speex_enc_chain),
+       (gst_speex_enc_change_state):
+       * ext/speex/gstspeexenc.h:
+       Handle non-zero start timestamps in the encoder and detect/handle
+       stream discontinuities. Fixes bug #547075.
+
 2008-08-31  Sebastian Dröge  <sebastian.droege@collabora.co.uk>
 
        Patch by: Craig Keogh <cskeogh at adam dot com dot au>
index a42de61858dc50286f68858812f9472d2b354187..a38814519aa24465e9d126c04fd5876502be42d3 100644 (file)
@@ -664,6 +664,14 @@ speex_dec_chain_parse_data (GstSpeexDec * dec, GstBuffer * buf,
 
     GST_DEBUG_OBJECT (dec, "received buffer of size %u, fpp %d", size, fpp);
 
+    if (!GST_BUFFER_TIMESTAMP_IS_VALID (buf)
+        && GST_BUFFER_OFFSET_END_IS_VALID (buf)) {
+      dec->granulepos = GST_BUFFER_OFFSET_END (buf);
+      GST_DEBUG_OBJECT (dec,
+          "Taking granulepos from upstream: %" G_GUINT64_FORMAT,
+          dec->granulepos);
+    }
+
     /* copy timestamp */
   } else {
     /* concealment data, pass NULL as the bits parameters */
@@ -722,21 +730,21 @@ speex_dec_chain_parse_data (GstSpeexDec * dec, GstBuffer * buf,
     if (dec->granulepos == -1) {
       if (dec->segment.format != GST_FORMAT_TIME) {
         GST_WARNING_OBJECT (dec, "segment not initialized or not TIME format");
-        dec->granulepos = 0;
+        dec->granulepos = dec->frame_size;
       } else {
         dec->granulepos = gst_util_uint64_scale_int (dec->segment.last_stop,
-            dec->header->rate, GST_SECOND);
+            dec->header->rate, GST_SECOND) + dec->frame_size;
       }
       GST_DEBUG_OBJECT (dec, "granulepos=%" G_GINT64_FORMAT, dec->granulepos);
     }
 
     if (timestamp == -1) {
-      timestamp = gst_util_uint64_scale_int (dec->granulepos,
+      timestamp = gst_util_uint64_scale_int (dec->granulepos - dec->frame_size,
           GST_SECOND, dec->header->rate);
     }
 
-    GST_BUFFER_OFFSET (outbuf) = dec->granulepos;
-    GST_BUFFER_OFFSET_END (outbuf) = dec->granulepos + dec->frame_size;
+    GST_BUFFER_OFFSET (outbuf) = dec->granulepos - dec->frame_size;
+    GST_BUFFER_OFFSET_END (outbuf) = dec->granulepos;
     GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
     GST_BUFFER_DURATION (outbuf) = dec->frame_duration;
 
index e0beb53d305f8d22a0b1b97a76214816ef43e30e..b2653b3a05a57f1e347c638f61f1c3d82d42a322 100644 (file)
@@ -30,6 +30,7 @@
 
 #include <gst/gsttagsetter.h>
 #include <gst/tag/tag.h>
+#include <gst/audio/audio.h>
 #include "gstspeexenc.h"
 
 GST_DEBUG_CATEGORY_STATIC (speexenc_debug);
@@ -135,6 +136,8 @@ static void gst_speex_enc_set_property (GObject * object, guint prop_id,
 static GstStateChangeReturn gst_speex_enc_change_state (GstElement * element,
     GstStateChange transition);
 
+static GstFlowReturn gst_speex_enc_encode (GstSpeexEnc * enc, gboolean flush);
+
 static void
 gst_speex_enc_setup_interfaces (GType speexenc_type)
 {
@@ -822,7 +825,7 @@ gst_speex_enc_sinkevent (GstPad * pad, GstEvent * event)
 
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_EOS:
-      enc->eos = TRUE;
+      gst_speex_enc_encode (enc, TRUE);
       res = gst_pad_event_default (pad, event);
       break;
     case GST_EVENT_TAG:
@@ -847,6 +850,85 @@ gst_speex_enc_sinkevent (GstPad * pad, GstEvent * event)
   return res;
 }
 
+static GstFlowReturn
+gst_speex_enc_encode (GstSpeexEnc * enc, gboolean flush)
+{
+  gint frame_size = enc->frame_size;
+  gint bytes = frame_size * 2 * enc->channels;
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  if (flush && gst_adapter_available (enc->adapter) % bytes != 0) {
+    guint diff = gst_adapter_available (enc->adapter) % bytes;
+    GstBuffer *buf = gst_buffer_new_and_alloc (diff);
+
+    memset (GST_BUFFER_DATA (buf), 0, diff);
+    gst_adapter_push (enc->adapter, buf);
+  }
+
+  while (gst_adapter_available (enc->adapter) >= bytes) {
+    gint16 *data;
+    gint i;
+    gint outsize, written;
+    GstBuffer *outbuf;
+
+    data = (gint16 *) gst_adapter_peek (enc->adapter, bytes);
+
+    for (i = 0; i < frame_size * enc->channels; i++) {
+      enc->input[i] = (gfloat) data[i];
+    }
+    gst_adapter_flush (enc->adapter, bytes);
+
+    enc->samples_in += frame_size;
+
+    GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)", frame_size, bytes);
+
+    if (enc->channels == 2) {
+      speex_encode_stereo (enc->input, frame_size, &enc->bits);
+    }
+    speex_encode (enc->state, enc->input, &enc->bits);
+
+    enc->frameno++;
+    enc->frameno_out++;
+
+    if ((enc->frameno % enc->nframes) != 0)
+      continue;
+
+    speex_bits_insert_terminator (&enc->bits);
+    outsize = speex_bits_nbytes (&enc->bits);
+
+    ret = gst_pad_alloc_buffer_and_set_caps (enc->srcpad,
+        GST_BUFFER_OFFSET_NONE, outsize, GST_PAD_CAPS (enc->srcpad), &outbuf);
+
+    if ((GST_FLOW_OK != ret))
+      goto done;
+
+    written = speex_bits_write (&enc->bits,
+        (gchar *) GST_BUFFER_DATA (outbuf), outsize);
+    g_assert (written == outsize);
+    speex_bits_reset (&enc->bits);
+
+    GST_BUFFER_TIMESTAMP (outbuf) = enc->start_ts +
+        gst_util_uint64_scale_int (enc->frameno_out * frame_size -
+        enc->lookahead, GST_SECOND, enc->rate);
+    GST_BUFFER_DURATION (outbuf) = gst_util_uint64_scale_int (frame_size,
+        GST_SECOND, enc->rate);
+    /* set gp time and granulepos; see gst-plugins-base/ext/ogg/README */
+    GST_BUFFER_OFFSET_END (outbuf) = enc->granulepos_offset +
+        ((enc->frameno_out + 1) * frame_size - enc->lookahead);
+    GST_BUFFER_OFFSET (outbuf) =
+        gst_util_uint64_scale_int (GST_BUFFER_OFFSET_END (outbuf), GST_SECOND,
+        enc->rate);
+
+    ret = gst_speex_enc_push_buffer (enc, outbuf);
+
+    if ((GST_FLOW_OK != ret) && (GST_FLOW_NOT_LINKED != ret))
+      goto done;
+  }
+
+done:
+
+  return ret;
+}
 
 static GstFlowReturn
 gst_speex_enc_chain (GstPad * pad, GstBuffer * buf)
@@ -913,77 +995,79 @@ gst_speex_enc_chain (GstPad * pad, GstBuffer * buf)
     enc->header_sent = TRUE;
   }
 
-  {
-    gint frame_size = enc->frame_size;
-    gint bytes = frame_size * 2 * enc->channels;
-
-    GST_DEBUG_OBJECT (enc, "received buffer of %u bytes",
-        GST_BUFFER_SIZE (buf));
-
-    /* push buffer to adapter */
-    gst_adapter_push (enc->adapter, buf);
-    buf = NULL;
-
-    while (gst_adapter_available (enc->adapter) >= bytes) {
-      gint16 *data;
-      gint i;
-      gint outsize, written;
-      GstBuffer *outbuf;
-
-      data = (gint16 *) gst_adapter_peek (enc->adapter, bytes);
-
-      for (i = 0; i < frame_size * enc->channels; i++) {
-        enc->input[i] = (gfloat) data[i];
-      }
-      gst_adapter_flush (enc->adapter, bytes);
-
-      enc->samples_in += frame_size;
-
-      GST_DEBUG_OBJECT (enc, "encoding %d samples (%d bytes)", frame_size,
-          bytes);
+  /* Save the timestamp of the first buffer. This will be later
+   * used as offset for all following buffers */
+  if (enc->start_ts == GST_CLOCK_TIME_NONE) {
+    if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
+      enc->start_ts = GST_BUFFER_TIMESTAMP (buf);
+      enc->granulepos_offset = gst_util_uint64_scale
+          (GST_BUFFER_TIMESTAMP (buf), enc->rate, GST_SECOND);
+    } else {
+      enc->start_ts = 0;
+      enc->granulepos_offset = 0;
+    }
+  }
 
-      if (enc->channels == 2) {
-        speex_encode_stereo (enc->input, frame_size, &enc->bits);
-      }
-      speex_encode (enc->state, enc->input, &enc->bits);
+  /* Check if we have a continous stream, if not drop some samples or the buffer or
+   * insert some silence samples */
+  if (enc->next_ts != GST_CLOCK_TIME_NONE &&
+      GST_BUFFER_TIMESTAMP (buf) < enc->next_ts) {
+    guint64 diff = enc->next_ts - GST_BUFFER_TIMESTAMP (buf);
+    guint64 diff_bytes;
+
+    GST_WARNING_OBJECT (enc, "Buffer is older than previous "
+        "timestamp + duration (%" GST_TIME_FORMAT "< %" GST_TIME_FORMAT
+        "), cannot handle. Clipping buffer.",
+        GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
+        GST_TIME_ARGS (enc->next_ts));
+
+    diff_bytes = GST_CLOCK_TIME_TO_FRAMES (diff, enc->rate) * enc->channels * 2;
+    if (diff_bytes >= GST_BUFFER_SIZE (buf)) {
+      gst_buffer_unref (buf);
+      return GST_FLOW_OK;
+    }
+    buf = gst_buffer_make_metadata_writable (buf);
+    GST_BUFFER_DATA (buf) += diff_bytes;
+    GST_BUFFER_SIZE (buf) -= diff_bytes;
 
-      enc->frameno++;
+    GST_BUFFER_TIMESTAMP (buf) += diff;
+    if (GST_BUFFER_DURATION_IS_VALID (buf))
+      GST_BUFFER_DURATION (buf) -= diff;
+  }
 
-      if ((enc->frameno % enc->nframes) != 0)
-        continue;
+  if (enc->next_ts != GST_CLOCK_TIME_NONE
+      && GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
+    guint64 max_diff =
+        gst_util_uint64_scale (enc->frame_size, GST_SECOND, enc->rate);
 
-      speex_bits_insert_terminator (&enc->bits);
-      outsize = speex_bits_nbytes (&enc->bits);
+    if (GST_BUFFER_TIMESTAMP (buf) != enc->next_ts &&
+        GST_BUFFER_TIMESTAMP (buf) - enc->next_ts > max_diff) {
+      GST_WARNING_OBJECT (enc,
+          "Discontinuity detected: %" G_GUINT64_FORMAT " > %" G_GUINT64_FORMAT,
+          GST_BUFFER_TIMESTAMP (buf) - enc->next_ts, max_diff);
 
-      ret = gst_pad_alloc_buffer_and_set_caps (enc->srcpad,
-          GST_BUFFER_OFFSET_NONE, outsize, GST_PAD_CAPS (enc->srcpad), &outbuf);
+      gst_speex_enc_encode (enc, TRUE);
 
-      if ((GST_FLOW_OK != ret))
-        goto done;
+      enc->frameno_out = 0;
+      enc->start_ts = GST_BUFFER_TIMESTAMP (buf);
+      enc->granulepos_offset = gst_util_uint64_scale
+          (GST_BUFFER_TIMESTAMP (buf), enc->rate, GST_SECOND);
+    }
+  }
 
-      written = speex_bits_write (&enc->bits,
-          (gchar *) GST_BUFFER_DATA (outbuf), outsize);
-      g_assert (written == outsize);
-      speex_bits_reset (&enc->bits);
+  if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)
+      && GST_BUFFER_DURATION_IS_VALID (buf))
+    enc->next_ts = GST_BUFFER_TIMESTAMP (buf) + GST_BUFFER_DURATION (buf);
+  else
+    enc->next_ts = GST_CLOCK_TIME_NONE;
 
-      GST_BUFFER_TIMESTAMP (outbuf) =
-          gst_util_uint64_scale_int (enc->frameno * frame_size -
-          enc->lookahead, GST_SECOND, enc->rate);
-      GST_BUFFER_DURATION (outbuf) = gst_util_uint64_scale_int (frame_size,
-          GST_SECOND, enc->rate);
-      /* set gp time and granulepos; see gst-plugins-base/ext/ogg/README */
-      GST_BUFFER_OFFSET_END (outbuf) =
-          ((enc->frameno + 1) * frame_size - enc->lookahead);
-      GST_BUFFER_OFFSET (outbuf) =
-          gst_util_uint64_scale_int (GST_BUFFER_OFFSET_END (outbuf), GST_SECOND,
-          enc->rate);
+  GST_DEBUG_OBJECT (enc, "received buffer of %u bytes", GST_BUFFER_SIZE (buf));
 
-      ret = gst_speex_enc_push_buffer (enc, outbuf);
+  /* push buffer to adapter */
+  gst_adapter_push (enc->adapter, buf);
+  buf = NULL;
 
-      if ((GST_FLOW_OK != ret) && (GST_FLOW_NOT_LINKED != ret))
-        goto done;
-    }
-  }
+  ret = gst_speex_enc_encode (enc, FALSE);
 
 done:
 
@@ -1104,7 +1188,11 @@ gst_speex_enc_change_state (GstElement * element, GstStateChange transition)
     case GST_STATE_CHANGE_READY_TO_PAUSED:
       speex_bits_init (&enc->bits);
       enc->frameno = 0;
+      enc->frameno_out = 0;
       enc->samples_in = 0;
+      enc->start_ts = GST_CLOCK_TIME_NONE;
+      enc->next_ts = GST_CLOCK_TIME_NONE;
+      enc->granulepos_offset = 0;
       break;
     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
       /* fall through */
index a5de51105c5d2d69996e5434aa9c8dba10f5435b..a5bfb366077525bbad6a84e3bcefa19d6bdf881b 100644 (file)
@@ -92,7 +92,6 @@ struct _GstSpeexEnc {
 
   gboolean              setup;
   gboolean              header_sent;
-  gboolean              eos;
 
   guint64               samples_in;
   guint64               bytes_out;
@@ -103,11 +102,17 @@ struct _GstSpeexEnc {
 
   gint                  frame_size;
   guint64               frameno;
+  guint64               frameno_out;
 
   guint8                *comments;
   gint                  comment_len;
 
   gfloat                input[MAX_FRAME_SIZE];
+
+  /* Timestamp and granulepos tracking */
+  GstClockTime     start_ts;
+  GstClockTime     next_ts;
+  guint64          granulepos_offset;
 };
 
 struct _GstSpeexEncClass {