Skip to content

Commit

Permalink
Seek for next mp3 sync point when computing duration.
Browse files Browse the repository at this point in the history
`get_mp3_duration` assumed that the first mp3 sync point occurred
directly after the last ID3v2 tag, which is not necessarily the case.
This patch will seek from that point to the first mp3 sync.

Also, update the boost download locations. The boost tarballs are
now hosted at SourceForge.
  • Loading branch information
sp1ff committed Jan 26, 2025
1 parent 4cc8ed4 commit aab6fe0
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 23 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ jobs:
- os: ubuntu-22.04
boost: oldest
boost-ver: 1_75_0
boost-dl: https://boostorg.jfrog.io/artifactory/main/release/1.75.0/source/boost_1_75_0.tar.bz2
boost-dl: https://sourceforge.net/projects/boost/files/boost/1.75.0/boost_1_75_0.tar.bz2/download
- os: ubuntu-22.04
boost: pinned
boost-ver: 1_81_0
boost-dl: https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2
boost-dl: https://sourceforge.net/projects/boost/files/boost/1.81.0/boost_1_81_0.tar.bz2/download
- os: ubuntu-22.04
boost: latest
boost-ver: latest
Expand Down
5 changes: 5 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ scribbu News -- history of user-visible changes -*- outline -*-

** Bug Fixes

*** `scribbu m3u` will seek for a sync point

`scribbu m3u` reads any ID3v2 tags from the file, then assumed it was
at an mp3 sync point; if not, scribbu will now seek for the next sync
point.
*** Fix a build error in `winamp-genres.cc`
* Changes in scribbu 0.6

Expand Down
10 changes: 5 additions & 5 deletions macros/ltversion.m4
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@

# @configure_input@

# serial 4392 ltversion.m4
# serial 4442 ltversion.m4
# This file is part of GNU Libtool

m4_define([LT_PACKAGE_VERSION], [2.5.3-dirty])
m4_define([LT_PACKAGE_REVISION], [2.5.3])
m4_define([LT_PACKAGE_VERSION], [2.5.4.1-baa1-dirty])
m4_define([LT_PACKAGE_REVISION], [2.5.4.1])

AC_DEFUN([LTVERSION_VERSION],
[macro_version='2.5.3-dirty'
macro_revision='2.5.3'
[macro_version='2.5.4.1-baa1-dirty'
macro_revision='2.5.4.1'
_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?])
_LT_DECL(, macro_revision, 0)
])
37 changes: 37 additions & 0 deletions scribbu/mp3.cc
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,38 @@ scribbu::mp3_audio_frame::vbri() const
}
}

void
scribbu::find_sync(std::istream &is)
{
// OK, we're looking for a two-byte pattern of the form ff e* or ff f*.
// So, let's read two bytes. Cases:
// - they match -- done, reset pointer
// - byte two is ff, advance by one, read one more byte & repeat
// - skip forward two bytes & try again

const std::ios_base::iostate EXC_MASK = std::ios_base::badbit;

// Copy off the stream's exception mask, in case the caller is
// counting on it...
std::ios_base::iostate exc_mask = is.exceptions();
// and set it to a value convenient for our use.
is.exceptions(EXC_MASK);

std::istream::pos_type here = is.tellg();
std::uint8_t buf[2];
is.read((char*)buf, 2);
bool hit = is_sync(buf);
while (is && !hit) {
here += buf[1] == 0xff ? 1 : 2;
is.seekg(here, std::ios_base::beg);
is.read((char*)buf, 2);
hit = is_sync(buf);
}
if (hit) {
is.seekg(here, std::ios_base::beg);
}
}

double
scribbu::get_mp3_duration(std::istream &is)
{
Expand All @@ -566,6 +598,10 @@ scribbu::get_mp3_duration(std::istream &is)

std::uint8_t hdr[4];
is.read((char*)hdr, 4);
if (is && !is_sync(hdr)) {
find_sync(is);
is.read((char*)hdr, 4);
}
if (!is || !is_sync(hdr)) {
is.clear();
is.seekg(here, std::ios_base::beg);
Expand Down Expand Up @@ -602,5 +638,6 @@ scribbu::get_mp3_duration(std::istream &is)
}

is.exceptions(exc_mask);

return dur;
}
41 changes: 27 additions & 14 deletions scribbu/mp3.hh
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
*
* \section scribbu_mp3_overview Overview
*
* The MPEG specification went through three reviesions. MPEG 1 (ISO/IEC
* The MPEG specification went through three revisions. MPEG 1 (ISO/IEC
* 13818-3) and MPEG 2 (ISO/IEC 11172-3) are ISO standards. MPEG 2.5 is an
* unofficial extension of MPEG 2 to support lower sampling rates. MPEG 2/2.5 is
* also known by the abbreviation LSF, which stands for Lower Sampling
Expand All @@ -51,13 +51,13 @@
*
* Layer III can make use of a technique known as the "bit resevoir" wherein
* unused space at the end of a frame can be used to hold data for subsequent
* frames, meaning that decode a given frame may required previous frames. In
* frames, meaning that decoding a given frame may require previous frames. In
* the worst case, a decoder may need to read nine frames before being able to
* decode one (\ref scribbu_mp3_ref_03 "[3]").
*
* In all layers & all versions of the spec, each frame begins with a 32 bit
* header, followed by, perhaps, a 16-bit CRC in big-endian format. Next comes a
* block of information required by decoders to interpret the audio samples &
* header, followed by, perhaps, a 16-bit CRC in big-endian format. Next comes
* a block of information required by decoders to interpret the audio samples &
* finally the audio samples themselves.
*
* The audio data contains a fixed number of samples across all frames (the
Expand Down Expand Up @@ -161,14 +161,14 @@
values of bitrate. Very few decoders handle this.
F 2 11-10 sampling rate frequency index (values are in Hz)
+------+-------+-------+---------+
| bits | MPEG1 | MPEG2 | MPEG2.5 |
+------+-------+-------+---------+
| 00 | 44100 | 22050 | 11025 |
| 01 | 48000 | 24000 | 12000 |
| 10 | 32000 | 16000 | 8000 |
| 11 | reserv| reserv| reserv |
+------+-------+-------+---------+
+------+----------+----------+----------+
| bits | MPEG1 | MPEG2 | MPEG2.5 |
+------+----------+----------+----------+
| 00 | 44100 | 22050 | 11025 |
| 01 | 48000 | 24000 | 12000 |
| 10 | 32000 | 16000 | 8000 |
| 11 | reserved | reserved | reserved |
+------+----------+----------+----------+
G 1 9 padding bit
0 - frame is not padded
Expand Down Expand Up @@ -264,6 +264,7 @@
* Also: "The CRC is calculated by applying the CRC-16 algorithm (with the
* generator polynom 0x8005)" \ref scribbu_mp3_ref_01 "[1]".
*
* \section scribbu_mp3_frame_side_data Side Data
*
* scribbu does not explicitly model the side data at this time, but it
Expand All @@ -284,7 +285,7 @@
*
* \section scribbu_mp3_frame_header_vbri Variable Bitrate Information
*
* Of particular interest to this implementation is detrmining the track
* Of particular interest to this implementation is determining the track
* time-length, or duration. Files can be encoded at a constant bitrate (CBR)
* wherein each frame uses the same bitrate, or at a variable bitrate (VBR) in
* which the bitrate varies by frame. The former makes calculating the duration
Expand Down Expand Up @@ -328,7 +329,7 @@
* via bitrate & frame-size) and multiply it by the number of frames.
*
* MPD, for instance, after the initial guess above, checks for an Xing
* header, and if found, instead computes the duration by muliplying
* header, and if found, instead computes the duration by multiplying
* the number of frames by the first frame's duration (computed, through
* libmad, by the sample rate).
*
Expand Down Expand Up @@ -689,6 +690,18 @@ namespace scribbu {
std::uint32_t size_bytes_;
};

/**
* \brief Find the next mp3 sync in \a is
*
*
* \param is [in,out] an input stream; on return, it will either point to
* an mp3 sync point, or past the end of the stream
*
*
*/
void
find_sync(std::istream &is);

inline
bool
is_sync(std::uint8_t hdr[]) {
Expand Down
3 changes: 2 additions & 1 deletion test/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ CLEANFILES = id3v20B id3v21B report.last.csv report-tdf.last.tdf \
test-m3u-tmp/smoke_tests/id3v2.3.tag \
test-m3u-tmp/smoke_tests/LAME_aots_3951.mp3 \
test-m3u-tmp/smoke_tests/sat.mp3 \
test-m3u-tmp/smoke_tests/VBRI_16M.mp3
test-m3u-tmp/smoke_tests/VBRI_16M.mp3 \
test-m3u-tmp/smoke_tests/zoe.mp3
TESTS = $(check_PROGRAMS) test-split test-rename test-report test-report-tdf \
test-dump test-with-track-in test-fs-generator \
test-cleanup-encoded-by test-cleanup-from-audacity \
Expand Down
2 changes: 1 addition & 1 deletion test/data/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ duplicate_id3v2.mp3 with-track-in-golden.log unsynch.id3 searchresults.dat \
id3v22-tda.mp3 230-compressed.tag 230-picture.tag ozzy.tag rare_frames.mp3 \
golden-test-cleanup-encoded-by.out id3v2.4.ext.tag exit.tag \
golden-test-cleanup-from-audacity.out v1-only.mp3 wy.mp3 la-mer.mp3 \
LAME_aots_3951.mp3 VBRI_16M.mp3 sat.mp3 3931.mp3
LAME_aots_3951.mp3 VBRI_16M.mp3 sat.mp3 3931.mp3 zoe.mp3
Binary file added test/data/zoe.mp3
Binary file not shown.
113 changes: 113 additions & 0 deletions test/mp3.cc
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,116 @@ BOOST_AUTO_TEST_CASE( test_3931 )
BOOST_CHECK_CLOSE(x, 5.04, 1.0e-16);

}

BOOST_AUTO_TEST_CASE( test_zoe )
{
using namespace std;
using namespace scribbu;

const fs::path TEST_DATA(get_data_directory() / "zoe.mp3");

// 000000 49 44 33 this is an ID3 tag
// 000003 04 00 ID3 version 2.4.0
// 000005 00 no flags
// 000006 00 00 03 2a tag is 0x1aa = 426
// bytes in size
// 00000a 54 53 53 45 TSSE frame
// 00000e 00 00 frame is 0x0f =
// 000010 00 0f 15 bytes in size
// 000012 00 00 no flags
// 000014 03 UTF-8 encoded
// 000014 4c 61 76 66 35 39 2e 31 36 2e 31 "Lavf59.16.100"
// 000020 30 30 00
// 000023 50 43 4e 54 PCNT frame
// 000027 00 00 00 01 frame is one byte
// 00002b 00 00 no flags
// 00002d 01 count is one
// 00002e 54 50 TPE1 frame
// 000030 45 31
// 000032 00 00 00 0c frame is 12 bytes
// 000036 00 00 no flags
// 000038 03 UTF-8
// 000038 27 5a 6f c3 ab 20 4d "'Zoë Moon'"
// 000040 6f 6f 6e 27
// 000044 54 49 54 32 TIT2 frame
// 000048 00 00 00 08 8 bytes
// 00004c 00 00 no flags
// 00004e 03 UTF-8
// 00004f 46 "Falling"
// 000050 61 6c 6c 69 6e 67
// 000056 54 59 45 52 TYER frame
// 00005a 00 00 00 05 5 bytes
// 00005e 00 00 no flags
// 000060 03 UTF-8
// 000061 32 30 31 39 "2019"
// 000065 54 41 4c 42 TALB frame
// 000069 00 00 00 05 5 bytes
// 00006d 00 00 no flags
// 00006f 03 UTF-8
// 000070 45 64 65 6e "Eden"
// 000074 00 00 00 00 00 00 00 00 00 00 00 00 padding
// 000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >................<
// *

// frame sync is eleven on bits:
// 1111 1111 111* ****
// ff 1110 * or ff 1111 *
// ff e* or ff f*

// 000110 00 00 00 00 fd fe 00 00 00 00 4c 61 76 63 35 39 >..........Lavc59<

// 👆
// so we have a sync here
// 000120 2e 31 38 00 00 00 00 00 00 00 00 00 00 00 00 24 >.18............$<
// 000130 03 2c 00 00 00 00 00 57 b7 30 c3 34 8c b9 ff fb >.,.....W.0.4....<
// 000140 14 64 00 0f f0 00 00 69 00 00 00 08 00 00 0d 20 >.d.....i....... <
// 000150 00 00 01 00 00 01 a4 00 00 00 20 00 00 34 80 00 >.......... ..4..<
// 000160 00 04 4c 41 4d 45 33 2e 31 30 30 55 55 55 55 55 >..LAME3.100UUUUU<
// 000170 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 4c >UUUUUUUUUUUUUUUL<
// 000180 41 4d 45 33 2e 31 30 30 55 55 55 55 55 55 55 55 >AME3.100UUUUUUUU<
// 000190 55 55 55 55 55 55 55 55 55 55 55 55 55 55 ff fb >UUUUUUUUUUUUUU..<
// 0001a0 14 64 1e 0f f0 00 00 69 00 00 00 08 00 00 0d 20 >.d.....i....... <
// 0001b0 00 00 01 00
// tag ends here!
// 0001b4 00 01 a4 00 00 00 20 00 00 34 80 00 >.......... ..4..<
// 0001c0 00 04 55 55 55 55 55 55 55 55 55 55 55 55 55 55 >..UUUUUUUUUUUUUU<
// 0001d0 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 4c >UUUUUUUUUUUUUUUL<
// 0001e0 41 4d 45 33 2e 31 30 30 55 55 55 55 55 55 55 55 >AME3.100UUUUUUUU<
// 0001f0 55 55 55 55 55 55 55 55 55 55 55 55 55 55
// sync!
// 0001fe ff fb >UUUUUUUUUUUUUU..<
// 000200 14 64 3c 0f f0 00 00 69 00 00 00 08 00 00 0d 20 >.d<....i....... <
// 000210 00 00 01 00 00 01 a4 00 00 00 20 00 00 34 80 00 >.......... ..4..<
// 000220 00 04 55 55 55 55 55 55 55 55 55 55 55 55 55 55 >..UUUUUUUUUUUUUU<
// 000230 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 4c >UUUUUUUUUUUUUUUL<
// 000240 41 4d 45 33 2e 31 30 30 55 55 55 55 55 55 55 55 >AME3.100UUUUUUUU<
// 000250 55 55 55 55 55 55 55 55 55 55 55 55 55 55 ff fb >UUUUUUUUUUUUUU..<
// 000260 14 64 5a 0f f0 00 00 69 00 00 00 08 00 00 0d 20 >.dZ....i....... <
// 000270 00 00 01 00 00 01 a4 00 00 00 20 00 00 34 80 00 >.......... ..4..<
// 000280 00 04 55 55 55 55 55 55 55 55 55 55 55 55 55 55 >..UUUUUUUUUUUUUU<
// 000290 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 4c >UUUUUUUUUUUUUUUL<
// 0002a0 41 4d 45 33 2e 31 30 30 55 55 55 55 55 55 55 55 >AME3.100UUUUUUUU<
// 0002b0 55 55 55 55 55 55 55 55 55 55 55 55 55 55 ff fb >UUUUUUUUUUUUUU..<
// 0002c0 14 64 78 0f f0 00 00 69 00 00 00 08 00 00 0d 20 >.dx....i....... <
// 0002d0 00 00 01 00 00 01 a4 00 00 00 20 00 00 34 80 00 >.......... ..4..<
// 0002e0 00 04 55 55 55 55 55 55 55 55 55 55 55 55 55 55 >..UUUUUUUUUUUUUU<
// 0002f0 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 4c >UUUUUUUUUUUUUUUL<
// 000300 41 4d 45 33 2e 31 30 30 55 55 55 55 55 55 55 55 >AME3.100UUUUUUUU<
// 000310 55 55 55 55 55 55 55 55 55 55 55 55 55 55 ff fb >UUUUUUUUUUUUUU..<
// 000320 14 64 96 0f f0 00 00 69 00 00 00 08 00 00 0d 20 >.d.....i....... <
// 000330 00 00 01 00 00 01 a4 00 00 00 20 00 00 34 80 00 >.......... ..4..<
// 000340 00 04 55 55 55 55 55 55 55 55 55 55 55 55 55 55 >..UUUUUUUUUUUUUU<
// 000350 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 4c >UUUUUUUUUUUUUUUL<
// 000360 41 4d 45 33 2e 31 30 30 55 55 55 55 55 55 55 55 >AME3.100UUUUUUUU<
// 000370 55 55 55 55 55 55 55 55 55 55 55 55 55 55 ff fb >UUUUUUUUUUUUUU..<
// 000380 14 64 b4 0f f0 00 00 69 00 00 00 08 00 00 0d 20 >.d.....i....... <
// 000390 00 00 01 00 00 01 a4 00 00 00 20 00 00 34 80 00 >.......... ..4..<
// 0003a0 00 04 55 55 55 55 55 55 55 55 55 55 55 55 55 55 >..UUUUUUUUUUUUUU<
// 0003b0 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 a4 >UUUUUUUUUUUUUUU.<
// 0003c0 6d 32 22 52 04 a9 94 d4 90 f6 a8 43 a9 da 28 c0 >m2"R.......C..(.<
// 0003d0 41 84 59 72 e5 90 1b f6 cb dc 89 2b 0f 7c ff fb >A.Yr.......+.|..<

std::ifstream ifs(TEST_DATA, std::ifstream::binary);
double x = get_mp3_duration(ifs);
BOOST_CHECK_CLOSE(x, 268.44, 1.0e-16);
}

0 comments on commit aab6fe0

Please sign in to comment.