diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 13b8c570..8d7463d3 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -272,10 +272,8 @@ void WavPack::File::read(bool readProperties, Properties::ReadStyle /* propertie // Look for WavPack audio properties - if(readProperties) { - seek(0); + if(readProperties) d->properties = new Properties(this, length() - d->APESize); - } } long WavPack::File::findAPE() diff --git a/taglib/wavpack/wavpackproperties.cpp b/taglib/wavpack/wavpackproperties.cpp index ec12282d..b25bd966 100644 --- a/taglib/wavpack/wavpackproperties.cpp +++ b/taglib/wavpack/wavpackproperties.cpp @@ -33,53 +33,50 @@ #include "wavpackproperties.h" #include "wavpackfile.h" +// Implementation of this class is based on the information at: +// http://www.wavpack.com/file_format.txt + using namespace TagLib; class WavPack::Properties::PropertiesPrivate { public: - PropertiesPrivate(const ByteVector &d, long length, ReadStyle s) : - data(d), - streamLength(length), - style(s), + PropertiesPrivate() : length(0), bitrate(0), sampleRate(0), channels(0), version(0), bitsPerSample(0), - sampleFrames(0), - file(0) {} + lossless(false), + sampleFrames(0) {} - ByteVector data; - long streamLength; - ReadStyle style; int length; int bitrate; int sampleRate; int channels; int version; int bitsPerSample; + bool lossless; uint sampleFrames; - File *file; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -WavPack::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) +WavPack::Properties::Properties(const ByteVector & /*data*/, long /*streamLength*/, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(data, streamLength, style); - read(); + debug("WavPack::Properties::Properties() -- This constructor is no longer used."); } -WavPack::Properties::Properties(File *file, long streamLength, ReadStyle style) : AudioProperties(style) +WavPack::Properties::Properties(File *file, long streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - ByteVector data = file->readBlock(32); - d = new PropertiesPrivate(data, streamLength, style); - d->file = file; - read(); + read(file, streamLength); } WavPack::Properties::~Properties() @@ -88,6 +85,16 @@ WavPack::Properties::~Properties() } int WavPack::Properties::length() const +{ + return lengthInSeconds(); +} + +int WavPack::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int WavPack::Properties::lengthInMilliseconds() const { return d->length; } @@ -117,6 +124,11 @@ int WavPack::Properties::bitsPerSample() const return d->bitsPerSample; } +bool WavPack::Properties::isLossless() const +{ + return d->lossless; +} + TagLib::uint WavPack::Properties::sampleFrames() const { return d->sampleFrames; @@ -126,12 +138,16 @@ TagLib::uint WavPack::Properties::sampleFrames() const // private members //////////////////////////////////////////////////////////////////////////////// -static const unsigned int sample_rates[] = { - 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, - 32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 }; +namespace +{ + const unsigned int sample_rates[] = { + 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000, 192000, 0 }; +} #define BYTES_STORED 3 #define MONO_FLAG 4 +#define LOSSLESS_FLAG 8 #define SHIFT_LSB 13 #define SHIFT_MASK (0x1fL << SHIFT_LSB) @@ -144,44 +160,64 @@ static const unsigned int sample_rates[] = { #define FINAL_BLOCK 0x1000 -void WavPack::Properties::read() +void WavPack::Properties::read(File *file, long streamLength) { - if(!d->data.startsWith("wvpk")) - return; + long offset = 0; - d->version = d->data.toShort(8, false); - if(d->version < MIN_STREAM_VERS || d->version > MAX_STREAM_VERS) - return; + while(true) { + file->seek(offset); + const ByteVector data = file->readBlock(32); - const unsigned int flags = d->data.toUInt(24, false); - d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - - ((flags & SHIFT_MASK) >> SHIFT_LSB); - d->sampleRate = sample_rates[(flags & SRATE_MASK) >> SRATE_LSB]; - d->channels = (flags & MONO_FLAG) ? 1 : 2; - - unsigned int samples = d->data.toUInt(12, false); - if(samples == ~0u) { - if(d->file && d->style != Fast) { - samples = seekFinalIndex(); + if(data.size() < 32) { + debug("WavPack::Properties::read() -- data is too short."); + break; } - else { - samples = 0; + + if(!data.startsWith("wvpk")) { + debug("WavPack::Properties::read() -- Block header not found."); + break; } + + const uint flags = data.toUInt(24, false); + + if(offset == 0) { + d->version = data.toShort(8, false); + if(d->version < MIN_STREAM_VERS || d->version > MAX_STREAM_VERS) + break; + + d->bitsPerSample = ((flags & BYTES_STORED) + 1) * 8 - ((flags & SHIFT_MASK) >> SHIFT_LSB); + d->sampleRate = sample_rates[(flags & SRATE_MASK) >> SRATE_LSB]; + d->lossless = !(flags & LOSSLESS_FLAG); + d->sampleFrames = data.toUInt(12, false); + } + + d->channels += (flags & MONO_FLAG) ? 1 : 2; + + if(flags & FINAL_BLOCK) + break; + + const uint blockSize = data.toUInt(4, false); + offset += blockSize + 8; } - d->length = d->sampleRate > 0 ? (samples + (d->sampleRate / 2)) / d->sampleRate : 0; - d->sampleFrames = samples; - d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; + if(d->sampleFrames == ~0u) + d->sampleFrames = seekFinalIndex(file, streamLength); + + if(d->sampleFrames > 0 && d->sampleRate > 0) { + const double length = d->sampleFrames * 1000.0 / d->sampleRate; + d->length = static_cast(length + 0.5); + d->bitrate = static_cast(streamLength * 8.0 / length + 0.5); + } } -unsigned int WavPack::Properties::seekFinalIndex() +uint WavPack::Properties::seekFinalIndex(File *file, long streamLength) { - const long offset = d->file->rfind("wvpk", d->streamLength); + const long offset = file->rfind("wvpk", streamLength); if(offset == -1) return 0; - d->file->seek(offset); - const ByteVector data = d->file->readBlock(32); + file->seek(offset); + const ByteVector data = file->readBlock(32); if(data.size() < 32) return 0; @@ -189,12 +225,12 @@ unsigned int WavPack::Properties::seekFinalIndex() if(version < MIN_STREAM_VERS || version > MAX_STREAM_VERS) return 0; - const unsigned int flags = data.toUInt(24, false); + const uint flags = data.toUInt(24, false); if(!(flags & FINAL_BLOCK)) return 0; - const unsigned int blockIndex = data.toUInt(16, false); - const unsigned int blockSamples = data.toUInt(20, false); + const uint blockIndex = data.toUInt(16, false); + const uint blockSamples = data.toUInt(20, false); return blockIndex + blockSamples; } diff --git a/taglib/wavpack/wavpackproperties.h b/taglib/wavpack/wavpackproperties.h index 94bccd79..2955b1d8 100644 --- a/taglib/wavpack/wavpackproperties.h +++ b/taglib/wavpack/wavpackproperties.h @@ -71,9 +71,36 @@ namespace TagLib { */ virtual ~Properties(); - // Reimplementations. - + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \note This method is just an alias of lengthInSeconds(). + * + * \deprecated + */ virtual int length() const; + + /*! + * Returns the length of the file in seconds. The length is rounded down to + * the nearest whole second. + * + * \see lengthInMilliseconds() + */ + // BIC: make virtual + int lengthInSeconds() const; + + /*! + * Returns the length of the file in milliseconds. + * + * \see lengthInSeconds() + */ + // BIC: make virtual + int lengthInMilliseconds() const; + + /*! + * Returns the average bit rate of the file in kb/s. + */ virtual int bitrate() const; /*! @@ -81,12 +108,24 @@ namespace TagLib { */ virtual int sampleRate() const; + /*! + * Returns the number of audio channels. + */ virtual int channels() const; /*! - * Returns number of bits per sample. + * Returns the number of bits per audio sample. */ int bitsPerSample() const; + + /*! + * Returns whether or not the file is lossless encoded. + */ + bool isLossless() const; + + /*! + * Returns the total number of audio samples in file. + */ uint sampleFrames() const; /*! @@ -98,8 +137,8 @@ namespace TagLib { Properties(const Properties &); Properties &operator=(const Properties &); - void read(); - unsigned int seekFinalIndex(); + void read(File *file, long streamLength); + uint seekFinalIndex(File *file, long streamLength); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/data/four_channels.wv b/tests/data/four_channels.wv new file mode 100644 index 00000000..de682f24 Binary files /dev/null and b/tests/data/four_channels.wv differ diff --git a/tests/test_wavpack.cpp b/tests/test_wavpack.cpp index e9f891cc..357d4aaf 100644 --- a/tests/test_wavpack.cpp +++ b/tests/test_wavpack.cpp @@ -12,27 +12,43 @@ using namespace TagLib; class TestWavPack : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestWavPack); - CPPUNIT_TEST(testBasic); - CPPUNIT_TEST(testLengthScan); + CPPUNIT_TEST(testNoLengthProperties); + CPPUNIT_TEST(testMultiChannelProperties); + CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST_SUITE_END(); public: - void testBasic() + void testNoLengthProperties() { WavPack::File f(TEST_FILE_PATH_C("no_length.wv")); - WavPack::Properties *props = f.audioProperties(); - CPPUNIT_ASSERT_EQUAL(44100, props->sampleRate()); - CPPUNIT_ASSERT_EQUAL(2, props->channels()); - CPPUNIT_ASSERT_EQUAL(1, props->bitrate()); - CPPUNIT_ASSERT_EQUAL(0x407, props->version()); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(true, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(163392U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); } - void testLengthScan() + void testMultiChannelProperties() { - WavPack::File f(TEST_FILE_PATH_C("no_length.wv")); - WavPack::Properties *props = f.audioProperties(); - CPPUNIT_ASSERT_EQUAL(4, props->length()); + WavPack::File f(TEST_FILE_PATH_C("four_channels.wv")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3833, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(112, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isLossless()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(169031U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1031, f.audioProperties()->version()); } void testFuzzedFile()