From patchwork Fri Jan 28 03:53:36 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christopher Snowhill X-Patchwork-Id: 33893 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6602:2c4e:0:0:0:0 with SMTP id x14csp84372iov; Thu, 27 Jan 2022 19:53:52 -0800 (PST) X-Google-Smtp-Source: ABdhPJygr/T+QQer33D9gahX1N/yxDve2PTHsYWsAkHDUmTWxo1jwozBaRU2tJWeLzePjBonETNc X-Received: by 2002:a05:6402:5186:: with SMTP id q6mr6405838edd.96.1643342032174; Thu, 27 Jan 2022 19:53:52 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1643342032; cv=none; d=google.com; s=arc-20160816; b=uHENxiQ5zSuYbAtYcKN5iYu7rxMno16Gtdzpj4TwbS5z/7GbMPNtzdAybt2IDA6wLn MscukJQ9JemyY8ziavIgiyGAsrmSwVSjq08jTI4OQfI+hwX6aUX3pBTLeW/jCLByASqi Bb9ODLx3gixrPzDn3td1UdNyo9lPVy3X+F/o/Qdnlb0itUCf2Tfq/qGE+nX3ZHKejo1I PklmlNZqC2KDfIsX3D5Kmoposm/D0lH6AFhy6xRJ3ZxTeMEgZYqYJKuyg7t/QnNCuXgJ UiaNwpBO4DTZc/MahNevwXEZds/QPOTNNwxb75kQEowSANfKs/8Mcbq7LoA1/dZk73+a hk0g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:to:date:message-id:mime-version:from :dkim-signature:delivered-to; bh=eps7OWjtuoXElxxNYquQDQAtHXO5N0PwlAQIauYyh8A=; b=cFGRvKc3sJj9Frm/qI5Ptt8GSj5eQgjaYHujl2FU2BadNNSrNx+YrEpu3Qr4Kn4Dj3 zoK1lgCZYQTEB7x1AAs/9mZByl1bmm9bLGi83tcbxyrnW47/l015syJg0FnJzdYLcN2f 1fMXelo71d9SR7Kjl83KSzvc8PgmF8+8ORDDgCaEjLezKTL/JDvf8KAJtYb8YV/IoBFB Xl7CkQpi/SP1EEmn5T+ljNW2zkMmJ2CCNuyKNQB5C+JN6gT0rLx4JgIaVW6gQo7Caq6X Tf4EQS7DdDBFI6Dw4EU5sYM8qbzGJe0+Mq/lojVw40amvpTFXvNoGyxIptrl2fW56fpA 71ig== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=GmpgQyRI; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id g13si2255653eds.489.2022.01.27.19.53.50; Thu, 27 Jan 2022 19:53:52 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=GmpgQyRI; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 5034D68B234; Fri, 28 Jan 2022 05:53:47 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pj1-f52.google.com (mail-pj1-f52.google.com [209.85.216.52]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 14AE268B055 for ; Fri, 28 Jan 2022 05:53:41 +0200 (EET) Received: by mail-pj1-f52.google.com with SMTP id s2-20020a17090ad48200b001b501977b23so9824277pju.2 for ; Thu, 27 Jan 2022 19:53:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:content-transfer-encoding:mime-version:subject:message-id:date :to; bh=0v7Q22LFWwvkSWKzd4zz67sLbHbgFFS8RHUUoh8dgo8=; b=GmpgQyRI/urgP16jnNYkszxgiLWSaKt7WzOe/2vg2wleWWmuIA19O1sxWb5V4Ev5j6 696FPoe3FkTFuFTs2JmfIHxIjI0glTA0rsIisMWMQjObVofpr2B1UgdLQiKQfCtIroYU SMoc8aWXe6SfGwIMkUv+a0X5NoSJ0Fbav6X3qMfKJAmz1uTlw7LE94OTCFvFQtshgiy6 EIeHAJUwXYAl2g/nI3dNbkHKAVEc4XwnWlQRj86BQDW9+YZ7U+I5qfBJyrX5+3dKPyjt CqKLIe8NHzIimuOEdn3dxP2vI+U1A6sz3X00pGZn1Bb1kpeXpXWrYySoFzmLcAffesI5 feHQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:content-transfer-encoding:mime-version :subject:message-id:date:to; bh=0v7Q22LFWwvkSWKzd4zz67sLbHbgFFS8RHUUoh8dgo8=; b=zWtF2epaiYyzvrLkWFXY7sHMrpCBpZMrSgFc6RAADVPG6+B2ZU8kA1AihrjEqYjQRd LX8IGHxEi8Oav0Egt4CrDZNuTtYy4vJTTWv2E1NzXcQhrOCerEP4tOC4R2Zqzek+jBiK 4SjyhAntwVCo6z92D3Een5eYOv6qxByfQWvCyIqkKjQjnFwm9HBljiZ8DE/l1sFVmoZn sLYLvp5U5pAfd6nHTa+jZShsPAi8piquc243kR6Bo9OPV2qcwHn+7rcg8DoLDTX7p1H/ 76hCDMNy2iety9XKqhklvj3AYn6xTPnNxhDcabFkIUEj6Dv0A0Imf+1dEqcUgi7BUqq4 IdZw== X-Gm-Message-State: AOAM533V7YH7Q4EjWMF/Bl3Ae2Ma6zj7NbDcTDnoms0eu63G+R60dNvc PEsugM3obv+R1lkbftFIaUT26qivUTIhhQ== X-Received: by 2002:a17:903:230f:: with SMTP id d15mr6896203plh.8.1643342019194; Thu, 27 Jan 2022 19:53:39 -0800 (PST) Received: from smtpclient.apple ([2600:6c51:4c3f:8ead:58c9:5b59:3706:8f0c]) by smtp.gmail.com with ESMTPSA id om6sm707243pjb.24.2022.01.27.19.53.38 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 27 Jan 2022 19:53:38 -0800 (PST) From: Christopher Snowhill Mime-Version: 1.0 (Mac OS X Mail 15.0 \(3693.60.0.1.1\)) Message-Id: <4A02C559-B67F-4F71-9728-7DE0523C7272@gmail.com> Date: Thu, 27 Jan 2022 19:53:36 -0800 To: ffmpeg-devel@ffmpeg.org X-Mailer: Apple Mail (2.3693.60.0.1.1) Subject: [FFmpeg-devel] [PATCH] avformat/mp3dec: Parse iTunes gapless info X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: nwzpWppfb3hj 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 --- 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