diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/flac/gstflacdec.c | 199 | ||||
-rw-r--r-- | ext/flac/gstflacdec.h | 4 |
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 { |