summaryrefslogtreecommitdiffstats
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/flac/gstflacdec.c199
-rw-r--r--ext/flac/gstflacdec.h4
2 files changed, 198 insertions, 5 deletions
diff --git a/ext/flac/gstflacdec.c b/ext/flac/gstflacdec.c
index 251990f5..1bfcf487 100644
--- a/ext/flac/gstflacdec.c
+++ b/ext/flac/gstflacdec.c
@@ -254,6 +254,175 @@ gst_flac_dec_update_metadata (GstFlacDec * flacdec,
return TRUE;
}
+/* CRC-8, poly = x^8 + x^2 + x^1 + x^0, init = 0 */
+static const guint8 crc8_table[256] = {
+ 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
+ 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
+ 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
+ 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
+ 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
+ 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
+ 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
+ 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
+ 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
+ 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
+ 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
+ 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
+ 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
+ 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
+ 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
+ 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
+ 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
+ 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
+ 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
+ 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
+ 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
+ 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
+ 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
+ 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
+ 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
+ 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
+ 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
+ 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
+ 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
+ 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
+ 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
+ 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
+};
+
+static guint8
+gst_flac_calculate_crc8 (guint8 * data, guint length)
+{
+ guint8 crc = 0;
+
+ while (length--) {
+ crc = crc8_table[crc ^ *data];
+ ++data;
+ }
+
+ return crc;
+}
+
+static gboolean
+gst_flac_dec_scan_got_frame (GstFlacDec * flacdec, guint8 * data, guint size,
+ gint64 * last_sample_num)
+{
+ guint headerlen;
+ guint sr_from_end = 0; /* can be 0, 8 or 16 */
+ guint bs_from_end = 0; /* can be 0, 8 or 16 */
+ guint32 val = 0;
+ guint8 bs, sr, ca, ss, pb;
+
+ if (size < 10)
+ return FALSE;
+
+ /* sync */
+ if (data[0] != 0xFF || data[1] != 0xF8)
+ return FALSE;
+
+ bs = (data[2] & 0xF0) >> 8; /* blocksize marker */
+ sr = (data[2] & 0x0F); /* samplerate marker */
+ ca = (data[3] & 0xF0) >> 8; /* channel assignment */
+ ss = (data[3] & 0x0F) >> 1; /* sample size marker */
+ pb = (data[3] & 0x01); /* padding bit */
+
+ GST_LOG ("got sync, bs=%x,sr=%x,ca=%x,ss=%x,pb=%x", bs, sr, ca, ss, pb);
+
+ if (sr == 0x0F || sr == 0x01 || sr == 0x02 || sr == 0x03 ||
+ ca >= 0x0B || ss == 0x03 || ss == 0x07) {
+ return FALSE;
+ }
+
+ /* read block size from end of header? */
+ if (bs == 6)
+ bs_from_end = 8;
+ else if (bs == 7)
+ bs_from_end = 16;
+
+ /* read sample rate from end of header? */
+ if (sr == 0x0C)
+ sr_from_end = 8;
+ else if (sr == 0x0D || sr == 0x0E)
+ sr_from_end = 16;
+
+ val = (guint32) g_utf8_get_char_validated ((gchar *) data + 4, -1);
+
+ if (val == (guint32) - 1 || val == (guint32) - 2) {
+ GST_LOG_OBJECT (flacdec, "failed to read sample/frame");
+ return FALSE;
+ }
+
+ headerlen = 4 + g_unichar_to_utf8 ((gunichar) val, NULL) +
+ (bs_from_end / 8) + (sr_from_end / 8);
+
+ if (gst_flac_calculate_crc8 (data, headerlen) != data[headerlen])
+ return FALSE;
+
+ if (flacdec->min_blocksize == flacdec->max_blocksize) {
+ *last_sample_num = (val + 1) * flacdec->min_blocksize;
+ } else {
+ *last_sample_num = val; /* FIXME: + length of last block in samples */
+ }
+
+ GST_DEBUG_OBJECT (flacdec, "last sample %" G_GINT64_FORMAT " = %"
+ GST_TIME_FORMAT, *last_sample_num,
+ GST_TIME_ARGS (*last_sample_num * GST_SECOND / flacdec->sample_rate));
+
+ return TRUE;
+}
+
+#define SCANBLOCK_SIZE (64*1024)
+
+static void
+gst_flac_dec_scan_for_last_block (GstFlacDec * flacdec, gint64 * samples)
+{
+ GstFormat format = GST_FORMAT_BYTES;
+ gint64 file_size, offset;
+
+ GST_INFO_OBJECT (flacdec, "total number of samples unknown, scanning file");
+
+ if (!gst_pad_query_peer_duration (flacdec->sinkpad, &format, &file_size)) {
+ GST_WARNING_OBJECT (flacdec, "failed to query upstream size!");
+ return;
+ }
+
+ GST_DEBUG_OBJECT (flacdec, "upstream size: %" G_GINT64_FORMAT, file_size);
+
+ offset = file_size - 1;
+ while (offset >= MAX (SCANBLOCK_SIZE / 2, file_size / 2)) {
+ GstFlowReturn flow;
+ GstBuffer *buf = NULL;
+ guint8 *data;
+ guint size;
+
+ /* divide by 2 = not very sophisticated way to deal with overlapping */
+ offset -= SCANBLOCK_SIZE / 2;
+ GST_LOG_OBJECT (flacdec, "looking for frame at %" G_GINT64_FORMAT
+ "-%" G_GINT64_FORMAT, offset, offset + SCANBLOCK_SIZE);
+
+ flow = gst_pad_pull_range (flacdec->sinkpad, offset, SCANBLOCK_SIZE, &buf);
+ if (flow != GST_FLOW_OK) {
+ GST_DEBUG_OBJECT (flacdec, "flow = %s", gst_flow_get_name (flow));
+ return;
+ }
+
+ size = GST_BUFFER_SIZE (buf);
+ data = GST_BUFFER_DATA (buf);
+
+ while (size > 16) {
+ if (gst_flac_dec_scan_got_frame (flacdec, data, size, samples)) {
+ GST_DEBUG_OBJECT (flacdec, "frame sync at offset %" G_GINT64_FORMAT,
+ offset + GST_BUFFER_SIZE (buf) - size);
+ gst_buffer_unref (buf);
+ return;
+ }
+ ++data;
+ --size;
+ }
+
+ gst_buffer_unref (buf);
+ }
+}
static void
gst_flac_dec_metadata_callback (const FLAC__SeekableStreamDecoder * decoder,
@@ -264,12 +433,32 @@ gst_flac_dec_metadata_callback (const FLAC__SeekableStreamDecoder * decoder,
flacdec = GST_FLAC_DEC (client_data);
switch (metadata->type) {
- case FLAC__METADATA_TYPE_STREAMINFO:
- gst_segment_set_duration (&flacdec->segment, GST_FORMAT_DEFAULT,
- metadata->data.stream_info.total_samples);
- if (flacdec->segment.stop == -1)
- flacdec->segment.stop = metadata->data.stream_info.total_samples;
+ case FLAC__METADATA_TYPE_STREAMINFO:{
+ gint64 samples;
+
+ samples = metadata->data.stream_info.total_samples;
+
+ flacdec->min_blocksize = metadata->data.stream_info.min_blocksize;
+ flacdec->max_blocksize = metadata->data.stream_info.max_blocksize;
+ flacdec->sample_rate = metadata->data.stream_info.sample_rate;
+
+ GST_DEBUG_OBJECT (flacdec, "blocksize: min=%u, max=%u",
+ flacdec->min_blocksize, flacdec->max_blocksize);
+
+ if (samples == 0) {
+ gst_flac_dec_scan_for_last_block (flacdec, &samples);
+ }
+
+ GST_DEBUG_OBJECT (flacdec, "total samples = %" G_GINT64_FORMAT, samples);
+
+ if (samples > 0) {
+ gst_segment_set_duration (&flacdec->segment, GST_FORMAT_DEFAULT,
+ samples);
+ if (flacdec->segment.stop == -1)
+ flacdec->segment.stop = samples;
+ }
break;
+ }
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
gst_flac_dec_update_metadata (flacdec, metadata);
break;
diff --git a/ext/flac/gstflacdec.h b/ext/flac/gstflacdec.h
index 8987e562..75d4326f 100644
--- a/ext/flac/gstflacdec.h
+++ b/ext/flac/gstflacdec.h
@@ -64,6 +64,10 @@ struct _GstFlacDec {
gint depth;
gint width;
gint sample_rate;
+
+ /* from the stream info, needed for scanning */
+ guint16 min_blocksize;
+ guint16 max_blocksize;
};
struct _GstFlacDecClass {