Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH] avformat/mp3dec: Parse iTunes gapless info
@ 2022-01-28  5:21 Christopher Snowhill
  2022-01-28 12:25 ` Anton Khirnov
  0 siblings, 1 reply; 5+ messages in thread
From: Christopher Snowhill @ 2022-01-28  5:21 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: Christopher Snowhill

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>
---
Curses, Apple Mail is broken, too. Using git imap-send instead.

 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

diff --git a/libavformat/mp3dec.c b/libavformat/mp3dec.c
index f617348b2e..d03174db03 100644
--- a/libavformat/mp3dec.c
+++ b/libavformat/mp3dec.c
@@ -313,6 +313,75 @@ static void mp3_parse_vbri_tag(AVFormatContext *s, AVStream *st, int64_t base)
     }
 }
 
+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) {
+        mp3->start_pad = start_pad;
+        mp3->end_pad = end_pad - (528 + 1);
+        sti->start_skip_samples = mp3->start_pad + 528 + 1;
+        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
  */
@@ -321,8 +390,10 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base)
     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);
 
@@ -345,16 +416,29 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base)
     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;
 }
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

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 5+ messages in thread
* [FFmpeg-devel] [PATCH] avformat/mp3dec: Parse iTunes gapless info
@ 2022-01-28  5:16 Christopher Snowhill
  0 siblings, 0 replies; 5+ messages in thread
From: Christopher Snowhill @ 2022-01-28  5:16 UTC (permalink / raw)
  To: ffmpeg-devel

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>
---
Curses, Apple Mail is broken, too. Using git imap-send instead. If
this doesn't work, I haven't a damn clue how to send email anywhere. I
don't have any mail clients that won't munge my mail. Git send-email
is broken on macOS 12.2 because it uses System Perl, and System Perl
refuses to find CPAN installed IO::Socket::SSL.

 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

diff --git a/libavformat/mp3dec.c b/libavformat/mp3dec.c
index f617348b2e..d03174db03 100644
--- a/libavformat/mp3dec.c
+++ b/libavformat/mp3dec.c
@@ -313,6 +313,75 @@ static void mp3_parse_vbri_tag(AVFormatContext
*s, AVStream *st, int64_t base)
     }
 }

+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) {
+        mp3->start_pad = start_pad;
+        mp3->end_pad = end_pad - (528 + 1);
+        sti->start_skip_samples = mp3->start_pad + 528 + 1;
+        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
  */
@@ -321,8 +390,10 @@ static int mp3_parse_vbr_tags(AVFormatContext *s,
AVStream *st, int64_t base)
     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);

@@ -345,16 +416,29 @@ static int mp3_parse_vbr_tags(AVFormatContext
*s, AVStream *st, int64_t base)
     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;
 }
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
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 5+ messages in thread
* [FFmpeg-devel] [PATCH] avformat/mp3dec: Parse iTunes gapless info
@ 2022-01-28  3:53 Christopher Snowhill
  0 siblings, 0 replies; 5+ messages in thread
From: Christopher Snowhill @ 2022-01-28  3:53 UTC (permalink / raw)
  To: ffmpeg-devel

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>
---
Let's see if I can get Apple Mail to do what Canary Mail cannot: Send an .eml without removing all the "extraneous" whitespace.

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

diff --git a/libavformat/mp3dec.c b/libavformat/mp3dec.c
index f617348b2e..d03174db03 100644
--- a/libavformat/mp3dec.c
+++ b/libavformat/mp3dec.c
@@ -313,6 +313,75 @@ static void mp3_parse_vbri_tag(AVFormatContext *s, AVStream *st, int64_t base)
    }
}

+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) {
+        mp3->start_pad = start_pad;
+        mp3->end_pad = end_pad - (528 + 1);
+        sti->start_skip_samples = mp3->start_pad + 528 + 1;
+        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
 */
@@ -321,8 +390,10 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base)
    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);

@@ -345,16 +416,29 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base)
    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;
}
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

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 5+ messages in thread
* [FFmpeg-devel] [PATCH] avformat/mp3dec: Parse iTunes gapless info
@ 2022-01-28  2:29 Christopher Snowhill
  0 siblings, 0 replies; 5+ messages in thread
From: Christopher Snowhill @ 2022-01-28  2:29 UTC (permalink / raw)
  To: ffmpeg-devel

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

diff --git a/libavformat/mp3dec.c b/libavformat/mp3dec.c
index f617348b2e..d03174db03 100644
--- a/libavformat/mp3dec.c
+++ b/libavformat/mp3dec.c
@@ -313,6 +313,75 @@ static void mp3_parse_vbri_tag(AVFormatContext *s, AVStream *st, int64_t base)
}
}

+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) {
+ mp3->start_pad = start_pad;
+ mp3->end_pad = end_pad - (528 + 1);
+ sti->start_skip_samples = mp3->start_pad + 528 + 1;
+ 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
*/
@@ -321,8 +390,10 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base)
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);

@@ -345,16 +416,29 @@ static int mp3_parse_vbr_tags(AVFormatContext *s, AVStream *st, int64_t base)
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;
}
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
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2022-01-28 12:25 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-01-28  5:21 [FFmpeg-devel] [PATCH] avformat/mp3dec: Parse iTunes gapless info Christopher Snowhill
2022-01-28 12:25 ` Anton Khirnov
  -- strict thread matches above, loose matches on Subject: below --
2022-01-28  5:16 Christopher Snowhill
2022-01-28  3:53 Christopher Snowhill
2022-01-28  2:29 Christopher Snowhill

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git