vorbisenc: strip after-eos samples from the end of the eos buffer
authorVincent Penquerc'h <vincent.penquerch@collabora.co.uk>
Mon, 26 Sep 2016 15:25:14 +0000 (16:25 +0100)
committerVincent Penquerc'h <vincent.penquerch@collabora.co.uk>
Wed, 5 Oct 2016 13:49:52 +0000 (14:49 +0100)
https://bugzilla.gnome.org/show_bug.cgi?id=768763

ext/vorbis/gstvorbisenc.c
ext/vorbis/gstvorbisenc.h

index 57f193f..a5a838e 100644 (file)
@@ -222,6 +222,7 @@ gst_vorbis_enc_start (GstAudioEncoder * enc)
   GST_DEBUG_OBJECT (enc, "start");
   vorbisenc->tags = gst_tag_list_new_empty ();
   vorbisenc->header_sent = FALSE;
+  vorbisenc->last_size = 0;
 
   return TRUE;
 }
@@ -539,12 +540,200 @@ gst_vorbis_enc_flush (GstAudioEncoder * enc)
   vorbisenc->header_sent = FALSE;
 }
 
+/* copied and adapted from ext/ogg/gstoggstream.c */
+static gint64
+packet_duration_vorbis (GstVorbisEnc * enc, ogg_packet * packet)
+{
+  int mode;
+  int size;
+  int duration;
+
+  if (packet->bytes == 0 || packet->packet[0] & 1)
+    return 0;
+
+  mode = (packet->packet[0] >> 1) & ((1 << enc->vorbis_log2_num_modes) - 1);
+  size = enc->vorbis_mode_sizes[mode] ? enc->long_size : enc->short_size;
+
+  if (enc->last_size == 0) {
+    duration = 0;
+  } else {
+    duration = enc->last_size / 4 + size / 4;
+  }
+  enc->last_size = size;
+
+  GST_DEBUG_OBJECT (enc, "duration %d", (int) duration);
+
+  return duration;
+}
+
+/* copied and adapted from ext/ogg/gstoggstream.c */
+static void
+parse_vorbis_header_packet (GstVorbisEnc * enc, ogg_packet * packet)
+{
+  /*
+   * on the first (b_o_s) packet, determine the long and short sizes,
+   * and then calculate l/2, l/4 - s/4, 3 * l/4 - s/4, l/2 - s/2 and s/2
+   */
+
+  enc->long_size = 1 << (packet->packet[28] >> 4);
+  enc->short_size = 1 << (packet->packet[28] & 0xF);
+}
+
+/* copied and adapted from ext/ogg/gstoggstream.c */
+static void
+parse_vorbis_codebooks_packet (GstVorbisEnc * enc, ogg_packet * op)
+{
+  /*
+   * the code pages, a whole bunch of other fairly useless stuff, AND,
+   * RIGHT AT THE END (of a bunch of variable-length compressed rubbish that
+   * basically has only one actual set of values that everyone uses BUT YOU
+   * CAN'T BE SURE OF THAT, OH NO YOU CAN'T) is the only piece of data that's
+   * actually useful to us - the packet modes (because it's inconceivable to
+   * think people might want _just that_ and nothing else, you know, for
+   * seeking and stuff).
+   *
+   * Fortunately, because of the mandate that non-used bits must be zero
+   * at the end of the packet, we might be able to sneakily work backwards
+   * and find out the information we need (namely a mapping of modes to
+   * packet sizes)
+   */
+  unsigned char *current_pos = &op->packet[op->bytes - 1];
+  int offset;
+  int size;
+  int size_check;
+  int *mode_size_ptr;
+  int i;
+  int ii;
+
+  /*
+   * This is the format of the mode data at the end of the packet for all
+   * Vorbis Version 1 :
+   *
+   * [ 6:number_of_modes ]
+   * [ 1:size | 16:window_type(0) | 16:transform_type(0) | 8:mapping ]
+   * [ 1:size | 16:window_type(0) | 16:transform_type(0) | 8:mapping ]
+   * [ 1:size | 16:window_type(0) | 16:transform_type(0) | 8:mapping ]
+   * [ 1:framing(1) ]
+   *
+   * e.g.:
+   *
+   *              <-
+   * 0 0 0 0 0 1 0 0
+   * 0 0 1 0 0 0 0 0
+   * 0 0 1 0 0 0 0 0
+   * 0 0 1|0 0 0 0 0
+   * 0 0 0 0|0|0 0 0
+   * 0 0 0 0 0 0 0 0
+   * 0 0 0 0|0 0 0 0
+   * 0 0 0 0 0 0 0 0
+   * 0 0 0 0|0 0 0 0
+   * 0 0 0|1|0 0 0 0 |
+   * 0 0 0 0 0 0 0 0 V
+   * 0 0 0|0 0 0 0 0
+   * 0 0 0 0 0 0 0 0
+   * 0 0 1|0 0 0 0 0
+   * 0 0|1|0 0 0 0 0
+   *
+   *
+   * i.e. each entry is an important bit, 32 bits of 0, 8 bits of blah, a
+   * bit of 1.
+   * Let's find our last 1 bit first.
+   *
+   */
+
+  size = 0;
+
+  offset = 8;
+  while (!((1 << --offset) & *current_pos)) {
+    if (offset == 0) {
+      offset = 8;
+      current_pos -= 1;
+    }
+  }
+
+  while (1) {
+
+    /*
+     * from current_pos-5:(offset+1) to current_pos-1:(offset+1) should
+     * be zero
+     */
+    offset = (offset + 7) % 8;
+    if (offset == 7)
+      current_pos -= 1;
+
+    if (((current_pos[-5] & ~((1 << (offset + 1)) - 1)) != 0)
+        ||
+        current_pos[-4] != 0
+        ||
+        current_pos[-3] != 0
+        ||
+        current_pos[-2] != 0
+        || ((current_pos[-1] & ((1 << (offset + 1)) - 1)) != 0)
+        ) {
+      break;
+    }
+
+    size += 1;
+
+    current_pos -= 5;
+
+  }
+
+  /* Give ourselves a chance to recover if we went back too far by using
+   * the size check. */
+  for (ii = 0; ii < 2; ii++) {
+    if (offset > 4) {
+      size_check = (current_pos[0] >> (offset - 5)) & 0x3F;
+    } else {
+      /* mask part of byte from current_pos */
+      size_check = (current_pos[0] & ((1 << (offset + 1)) - 1));
+      /* shift to appropriate position */
+      size_check <<= (5 - offset);
+      /* or in part of byte from current_pos - 1 */
+      size_check |= (current_pos[-1] & ~((1 << (offset + 3)) - 1)) >>
+          (offset + 3);
+    }
+
+    size_check += 1;
+    if (size_check == size) {
+      break;
+    }
+    offset = (offset + 1) % 8;
+    if (offset == 0)
+      current_pos += 1;
+    current_pos += 5;
+    size -= 1;
+  }
+
+  /* Store mode size information in our info struct */
+  i = -1;
+  while ((1 << (++i)) < size);
+  enc->vorbis_log2_num_modes = i;
+
+  mode_size_ptr = enc->vorbis_mode_sizes;
+
+  for (i = 0; i < size; i++) {
+    offset = (offset + 1) % 8;
+    if (offset == 0)
+      current_pos += 1;
+    *mode_size_ptr++ = (current_pos[0] >> offset) & 0x1;
+    current_pos += 5;
+  }
+
+}
+
 static GstBuffer *
 gst_vorbis_enc_buffer_from_header_packet (GstVorbisEnc * vorbisenc,
     ogg_packet * packet)
 {
   GstBuffer *outbuf;
 
+  if (packet->bytes > 0 && packet->packet[0] == '\001') {
+    parse_vorbis_header_packet (vorbisenc, packet);
+  } else if (packet->bytes > 0 && packet->packet[0] == '\003') {
+    parse_vorbis_codebooks_packet (vorbisenc, packet);
+  }
+
   outbuf =
       gst_audio_encoder_allocate_output_buffer (GST_AUDIO_ENCODER (vorbisenc),
       packet->bytes);
@@ -759,6 +948,7 @@ static GstFlowReturn
 gst_vorbis_enc_output_buffers (GstVorbisEnc * vorbisenc)
 {
   GstFlowReturn ret;
+  gint64 duration;
 
   /* vorbis does some data preanalysis, then divides up blocks for
      more involved (potentially parallel) processing.  Get a single
@@ -780,6 +970,20 @@ gst_vorbis_enc_output_buffers (GstVorbisEnc * vorbisenc)
           gst_audio_encoder_allocate_output_buffer (GST_AUDIO_ENCODER
           (vorbisenc), op.bytes);
       gst_buffer_fill (buf, 0, op.packet, op.bytes);
+
+      /* we have to call this every packet, not just on e_o_s, since
+         each packet's duration depends on the previous one's */
+      duration = packet_duration_vorbis (vorbisenc, &op);
+      if (op.e_o_s) {
+        gint64 samples = op.granulepos - vorbisenc->samples_out;
+        if (samples < duration) {
+          gint64 trim_end = duration - samples;
+          GST_DEBUG_OBJECT (vorbisenc,
+              "Adding trim-end %" G_GUINT64_FORMAT, trim_end);
+          gst_buffer_add_audio_clipping_meta (buf, GST_FORMAT_DEFAULT, 0,
+              trim_end);
+        }
+      }
       /* tracking granulepos should tell us samples accounted for */
       ret =
           gst_audio_encoder_finish_frame (GST_AUDIO_ENCODER
index 5eaa11b..8ae22ba 100644 (file)
@@ -79,6 +79,11 @@ struct _GstVorbisEnc {
   gboolean         setup;
   gboolean         header_sent;
   gchar           *last_message;
+
+  int              long_size, short_size;
+  int              last_size;
+  int              vorbis_log2_num_modes;
+  int              vorbis_mode_sizes[256];
 };
 
 struct _GstVorbisEncClass {