2022-01-28 03:31:15 +00:00
|
|
|
From 70c12a33220903962c3d4acdcfc37f6984eadb7d Mon Sep 17 00:00:00 2001
|
|
|
|
From: Christopher Snowhill <kode54@gmail.com>
|
|
|
|
Date: Thu, 27 Jan 2022 18:16:08 -0800
|
|
|
|
Subject: [PATCH] avformat/mp3dec: Parse iTunes gapless info
|
|
|
|
X-Unsent: 1
|
|
|
|
To: ffmpeg-devel@ffmpeg.org
|
|
|
|
|
|
|
|
Parse the ID3v2 iTunSMPB comment tag containing gapless decoding info,
|
|
|
|
and also add the expected test results for the implementation.
|
|
|
|
|
|
|
|
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
|
|
|
|
---
|
|
|
|
libavformat/mp3dec.c | 86 ++++++++++++++++++++++++++++++-
|
|
|
|
tests/fate/gapless.mak | 3 ++
|
|
|
|
tests/ref/fate/gapless-mp3-itunes | 5 ++
|
|
|
|
3 files changed, 93 insertions(+), 1 deletion(-)
|
|
|
|
create mode 100644 tests/ref/fate/gapless-mp3-itunes
|
|
|
|
|
2022-01-24 12:26:20 +00:00
|
|
|
diff --git a/libavformat/mp3dec.c b/libavformat/mp3dec.c
|
2022-01-28 03:31:15 +00:00
|
|
|
index f617348b2e..d03174db03 100644
|
2022-01-24 12:26:20 +00:00
|
|
|
--- a/libavformat/mp3dec.c
|
|
|
|
+++ b/libavformat/mp3dec.c
|
2022-01-28 03:31:15 +00:00
|
|
|
@@ -313,6 +313,75 @@ static void mp3_parse_vbri_tag(AVFormatContext *s, AVStream *st, int64_t base)
|
2022-01-24 12:26:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
+static void mp3_parse_itunes_tag(AVFormatContext *s, AVStream *st, MPADecodeHeader *c, int64_t base, int vbrtag_size, unsigned int *size, uint64_t *duration, uint32_t spf)
|
|
|
|
+{
|
|
|
|
+ uint32_t v;
|
|
|
|
+ AVDictionaryEntry *de;
|
|
|
|
+ FFStream *const sti = ffstream(st);
|
|
|
|
+ MP3DecContext *mp3 = s->priv_data;
|
|
|
|
+ size_t length;
|
|
|
|
+ uint32_t zero, start_pad, end_pad;
|
|
|
|
+ uint64_t last_eight_frames_offset;
|
|
|
|
+ int64_t temp_duration, file_size;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ if (!s->metadata || !(de = av_dict_get(s->metadata, "iTunSMPB", NULL, 0)))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ length = strlen(de->value);
|
|
|
|
+
|
|
|
|
+ /* Minimum length is one digit per field plus the whitespace, maximum length should depend on field type
|
|
|
|
+ * There are four fields we need in the first six, the rest are currently zero padding */
|
|
|
|
+ if (length < (12 + 11) || length > (10 * 8 + 2 * 16 + 11))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ file_size = avio_size(s->pb);
|
|
|
|
+ if (file_size < 0)
|
|
|
|
+ file_size = 0;
|
|
|
|
+
|
|
|
|
+ if (sscanf(de->value, "%"PRIx32" %"PRIx32" %"PRIx32" %"PRIx64" %"PRIx32" %"PRIx64, &zero, &start_pad, &end_pad, &temp_duration, &zero, &last_eight_frames_offset) < 6 ||
|
|
|
|
+ temp_duration < 0 ||
|
|
|
|
+ start_pad > (576 * 2 * 32) ||
|
|
|
|
+ end_pad > (576 * 2 * 64) ||
|
|
|
|
+ (file_size && (last_eight_frames_offset >= (file_size - base - vbrtag_size)))) {
|
|
|
|
+ *duration = 0;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ *duration = temp_duration;
|
|
|
|
+
|
|
|
|
+ if (end_pad >= 528 + 1) {
|
2022-01-28 03:31:15 +00:00
|
|
|
+ mp3->start_pad = start_pad;
|
2022-01-24 12:26:20 +00:00
|
|
|
+ mp3->end_pad = end_pad - (528 + 1);
|
2022-01-28 03:31:15 +00:00
|
|
|
+ sti->start_skip_samples = mp3->start_pad + 528 + 1;
|
2022-01-24 12:26:20 +00:00
|
|
|
+ if (mp3->frames) {
|
|
|
|
+ sti->first_discard_sample = -mp3->end_pad + 528 + 1 + mp3->frames * (int64_t)spf;
|
|
|
|
+ sti->last_discard_sample = mp3->frames * (int64_t)spf;
|
|
|
|
+ }
|
|
|
|
+ if (!st->start_time)
|
|
|
|
+ st->start_time = av_rescale_q(sti->start_skip_samples,
|
|
|
|
+ (AVRational){1, c->sample_rate},
|
|
|
|
+ st->time_base);
|
|
|
|
+ av_log(s, AV_LOG_DEBUG, "pad %d %d\n", mp3->start_pad, mp3-> end_pad);
|
|
|
|
+ }
|
|
|
|
+ if (!s->pb->seekable)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ *size = (unsigned int) last_eight_frames_offset;
|
|
|
|
+ if (avio_seek(s->pb, base + vbrtag_size + last_eight_frames_offset, SEEK_SET) < 0)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < 8; i++) {
|
|
|
|
+ v = avio_rb32(s->pb);
|
|
|
|
+ if (ff_mpa_check_header(v) < 0)
|
|
|
|
+ return;
|
|
|
|
+ if (avpriv_mpegaudio_decode_header(c, v) != 0)
|
|
|
|
+ break;
|
|
|
|
+ *size += c->frame_size;
|
|
|
|
+ avio_skip(s->pb, c->frame_size - 4);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
/**
|
|
|
|
* Try to find Xing/Info/VBRI tags and compute duration from info therein
|
|
|
|
*/
|
2022-01-28 03:31:15 +00:00
|
|
|
@@ -321,8 +390,10 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base)
|
2022-01-24 12:26:20 +00:00
|
|
|
uint32_t v, spf;
|
|
|
|
MPADecodeHeader c;
|
|
|
|
int vbrtag_size = 0;
|
|
|
|
+ unsigned int size = 0;
|
|
|
|
MP3DecContext *mp3 = s->priv_data;
|
|
|
|
int ret;
|
|
|
|
+ uint64_t duration = 0;
|
|
|
|
|
|
|
|
ffio_init_checksum(s->pb, ff_crcA001_update, 0);
|
|
|
|
|
2022-01-28 03:31:15 +00:00
|
|
|
@@ -345,16 +416,29 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base)
|
2022-01-24 12:26:20 +00:00
|
|
|
mp3_parse_vbri_tag(s, st, base);
|
|
|
|
|
|
|
|
if (!mp3->frames && !mp3->header_filesize)
|
|
|
|
+ vbrtag_size = 0;
|
|
|
|
+
|
|
|
|
+ mp3_parse_itunes_tag(s, st, &c, base, vbrtag_size, &size, &duration, spf);
|
|
|
|
+
|
|
|
|
+ if (!mp3->frames && !size && !duration)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* Skip the vbr tag frame */
|
|
|
|
avio_seek(s->pb, base + vbrtag_size, SEEK_SET);
|
|
|
|
|
|
|
|
- if (mp3->frames)
|
|
|
|
+ if (duration)
|
|
|
|
+ st->duration = av_rescale_q(duration, (AVRational){1, c.sample_rate}, st->time_base);
|
|
|
|
+ else if (mp3->frames)
|
|
|
|
st->duration = av_rescale_q(mp3->frames, (AVRational){spf, c.sample_rate},
|
|
|
|
st->time_base);
|
|
|
|
if (mp3->header_filesize && mp3->frames && !mp3->is_cbr)
|
|
|
|
st->codecpar->bit_rate = av_rescale(mp3->header_filesize, 8 * c.sample_rate, mp3->frames * (int64_t)spf);
|
|
|
|
+ if (size) {
|
|
|
|
+ if (duration)
|
|
|
|
+ st->codecpar->bit_rate = av_rescale(size, 8 * c.sample_rate, duration);
|
|
|
|
+ else if (mp3->frames)
|
|
|
|
+ st->codecpar->bit_rate = av_rescale(size, 8 * c.sample_rate, mp3->frames * (int64_t)spf);
|
|
|
|
+ }
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2022-01-28 03:31:15 +00:00
|
|
|
diff --git a/tests/fate/gapless.mak b/tests/fate/gapless.mak
|
|
|
|
index 68a396e187..b8c93b984c 100644
|
|
|
|
--- a/tests/fate/gapless.mak
|
|
|
|
+++ b/tests/fate/gapless.mak
|
|
|
|
@@ -1,6 +1,9 @@
|
|
|
|
FATE_GAPLESS-$(CONFIG_MP3_DEMUXER) += fate-gapless-mp3
|
|
|
|
fate-gapless-mp3: CMD = gapless $(TARGET_SAMPLES)/gapless/gapless.mp3 "-c:a mp3"
|
|
|
|
|
|
|
|
+FATE_GAPLESS-$(CONFIG_MP3_DEMUXER) += fate-gapless-mp3-itunes
|
|
|
|
+fate-gapless-mp3-itunes: CMD = gapless $(TARGET_SAMPLES)/gapless/gapless-itunes.mp3 "-c:a mp3"
|
|
|
|
+
|
|
|
|
FATE_GAPLESSINFO_PROBE-$(CONFIG_MP3_DEMUXER) += fate-gapless-mp3-side-data
|
|
|
|
fate-gapless-mp3-side-data: CMD = ffprobe_demux $(TARGET_SAMPLES)/gapless/gapless.mp3
|
|
|
|
|
|
|
|
diff --git a/tests/ref/fate/gapless-mp3-itunes b/tests/ref/fate/gapless-mp3-itunes
|
|
|
|
new file mode 100644
|
|
|
|
index 0000000000..f3295e2652
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/tests/ref/fate/gapless-mp3-itunes
|
|
|
|
@@ -0,0 +1,5 @@
|
|
|
|
+1e285368a8a176bc729712b06f724f21 *tests/data/fate/gapless-mp3-itunes.out-1
|
|
|
|
+cde648358dcd59911c4990ed59bf7d40
|
|
|
|
+4a39c54de9687ea8f66ea8204133c5c0 *tests/data/fate/gapless-mp3-itunes.out-2
|
|
|
|
+cde648358dcd59911c4990ed59bf7d40
|
|
|
|
+c8cd71407ac5bbfadf5ccf7b5a64430c *tests/data/fate/gapless-mp3-itunes.out-3
|
|
|
|
--
|
|
|
|
2.34.1
|
|
|
|
|