From 1a4ba790446ef0e043bec9cd9d78aa296235d47b Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Mon, 26 Sep 2016 16:25:14 +0100 Subject: [PATCH] vorbisenc: strip after-eos samples from the end of the eos buffer https://bugzilla.gnome.org/show_bug.cgi?id=768763 --- ext/vorbis/gstvorbisenc.c | 204 ++++++++++++++++++++++++++++++++++++++ ext/vorbis/gstvorbisenc.h | 5 + 2 files changed, 209 insertions(+) diff --git a/ext/vorbis/gstvorbisenc.c b/ext/vorbis/gstvorbisenc.c index 57f193f8eb..a5a838e09d 100644 --- a/ext/vorbis/gstvorbisenc.c +++ b/ext/vorbis/gstvorbisenc.c @@ -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 diff --git a/ext/vorbis/gstvorbisenc.h b/ext/vorbis/gstvorbisenc.h index 5eaa11b937..8ae22baa12 100644 --- a/ext/vorbis/gstvorbisenc.h +++ b/ext/vorbis/gstvorbisenc.h @@ -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 {