diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index aa367b5d..4d2b4196 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -193,6 +193,7 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties { ByteVector formatData; uint streamLength = 0; + uint totalSamples = 0; for(uint i = 0; i < chunkCount(); i++) { const ByteVector name = chunkName(i); if(name == "ID3 " || name == "id3 ") { @@ -207,7 +208,7 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties } else if(name == "LIST") { const ByteVector data = chunkData(i); - if(data.mid(0, 4) == "INFO") { + if(data.startsWith("INFO")) { if(!d->tag[InfoIndex]) { d->tag.set(InfoIndex, new RIFF::Info::Tag(data)); d->hasInfo = true; @@ -217,20 +218,24 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties } } } - else if(name == "fmt " && readProperties) { - if(formatData.isEmpty()) { - formatData = chunkData(i); + else if(readProperties) { + if(name == "fmt ") { + if(formatData.isEmpty()) + formatData = chunkData(i); + else + debug("RIFF::WAV::File::read() - Duplicate 'fmt ' chunk found."); } - else { - debug("RIFF::WAV::File::read() - Duplicate 'fmt ' chunk found."); + else if(name == "data") { + if(streamLength == 0) + streamLength = chunkDataSize(i) + chunkPadding(i); + else + debug("RIFF::WAV::File::read() - Duplicate 'data' chunk found."); } - } - else if(name == "data" && readProperties) { - if(streamLength == 0) { - streamLength = chunkDataSize(i); - } - else { - debug("RIFF::WAV::File::read() - Duplicate 'data' chunk found."); + else if(name == "fact") { + if(totalSamples == 0) + totalSamples = chunkData(i).toUInt(0, false); + else + debug("RIFF::WAV::File::read() - Duplicate 'fact' chunk found."); } } } @@ -242,7 +247,7 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties d->tag.set(InfoIndex, new RIFF::Info::Tag); if(!formatData.isEmpty()) - d->properties = new Properties(formatData, streamLength, propertiesStyle); + d->properties = new Properties(formatData, streamLength, totalSamples, propertiesStyle); } void RIFF::WAV::File::strip(TagTypes tags) @@ -264,7 +269,7 @@ void RIFF::WAV::File::strip(TagTypes tags) TagLib::uint RIFF::WAV::File::findInfoTagChunk() { for(uint i = 0; i < chunkCount(); ++i) { - if(chunkName(i) == "LIST" && chunkData(i).mid(0, 4) == "INFO") { + if(chunkName(i) == "LIST" && chunkData(i).startsWith("INFO")) { return i; } } diff --git a/taglib/riff/wav/wavproperties.cpp b/taglib/riff/wav/wavproperties.cpp index 439a1954..e61973ab 100644 --- a/taglib/riff/wav/wavproperties.cpp +++ b/taglib/riff/wav/wavproperties.cpp @@ -35,43 +35,47 @@ using namespace TagLib; class RIFF::WAV::Properties::PropertiesPrivate { public: - PropertiesPrivate(uint streamLength = 0) : + PropertiesPrivate() : format(0), length(0), bitrate(0), sampleRate(0), channels(0), - sampleWidth(0), - sampleFrames(0), - streamLength(streamLength) - { + bitsPerSample(0), + sampleFrames(0) {} - } - - short format; + int format; int length; int bitrate; int sampleRate; int channels; - int sampleWidth; + int bitsPerSample; uint sampleFrames; - uint streamLength; }; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::WAV::Properties::Properties(const ByteVector &data, ReadStyle style) : AudioProperties(style) +RIFF::WAV::Properties::Properties(const ByteVector & /*data*/, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(); - read(data); + debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); } -RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, ReadStyle style) : AudioProperties(style) +RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) { - d = new PropertiesPrivate(streamLength); - read(data); + debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used."); +} + +TagLib::RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, uint totalSamples, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + read(data, streamLength, totalSamples); } RIFF::WAV::Properties::~Properties() @@ -80,6 +84,16 @@ RIFF::WAV::Properties::~Properties() } int RIFF::WAV::Properties::length() const +{ + return lengthInSeconds(); +} + +int RIFF::WAV::Properties::lengthInSeconds() const +{ + return d->length / 1000; +} + +int RIFF::WAV::Properties::lengthInMilliseconds() const { return d->length; } @@ -99,9 +113,14 @@ int RIFF::WAV::Properties::channels() const return d->channels; } +int RIFF::WAV::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + int RIFF::WAV::Properties::sampleWidth() const { - return d->sampleWidth; + return bitsPerSample(); } TagLib::uint RIFF::WAV::Properties::sampleFrames() const @@ -109,26 +128,42 @@ TagLib::uint RIFF::WAV::Properties::sampleFrames() const return d->sampleFrames; } +int RIFF::WAV::Properties::format() const +{ + return d->format; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::WAV::Properties::read(const ByteVector &data) +void RIFF::WAV::Properties::read(const ByteVector &data, uint streamLength, uint totalSamples) { if(data.size() < 16) { debug("RIFF::WAV::Properties::read() - \"fmt \" chunk is too short for WAV."); return; } - d->format = data.toShort(0, false); - d->channels = data.toShort(2, false); - d->sampleRate = data.toUInt(4, false); - d->sampleWidth = data.toShort(14, false); + d->format = data.toShort(0, false); + d->channels = data.toShort(2, false); + d->sampleRate = data.toUInt(4, false); + d->bitsPerSample = data.toShort(14, false); - const uint byteRate = data.toUInt(8, false); - d->bitrate = byteRate * 8 / 1000; + if(totalSamples > 0) + d->sampleFrames = totalSamples; + else if(d->channels > 0 && d->bitsPerSample > 0) + d->sampleFrames = streamLength / (d->channels * ((d->bitsPerSample + 7) / 8)); - d->length = byteRate > 0 ? d->streamLength / byteRate : 0; - if(d->channels > 0 && d->sampleWidth > 0) - d->sampleFrames = d->streamLength / (d->channels * ((d->sampleWidth + 7) / 8)); + 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); + } + else { + const uint byteRate = data.toUInt(8, false); + if(byteRate > 0) { + d->length = static_cast(streamLength * 1000.0 / byteRate + 0.5); + d->bitrate = static_cast(byteRate * 8.0 / 1000.0 + 0.5); + } + } } diff --git a/taglib/riff/wav/wavproperties.h b/taglib/riff/wav/wavproperties.h index 2023f539..12367eb2 100644 --- a/taglib/riff/wav/wavproperties.h +++ b/taglib/riff/wav/wavproperties.h @@ -52,35 +52,107 @@ namespace TagLib { /*! * Create an instance of WAV::Properties with the data read from the * ByteVector \a data. + * + * \deprecated */ Properties(const ByteVector &data, ReadStyle style); /*! * Create an instance of WAV::Properties with the data read from the * ByteVector \a data and the length calculated using \a streamLength. + * + * \deprecated */ Properties(const ByteVector &data, uint streamLength, ReadStyle style); + /*! + * Create an instance of WAV::Properties with the data read from the + * ByteVector \a data and the length calculated using \a streamLength + * and \a totalSamples. + * + * \note totalSamples can be zero if the file doesn't have a 'fact' chunk. + */ + Properties(const ByteVector &data, uint streamLength, uint totalSamples, ReadStyle style); + /*! * Destroys this WAV::Properties instance. */ 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; + + /*! + * Returns the sample rate in Hz. + */ virtual int sampleRate() const; + + /*! + * Returns the number of audio channels. + */ virtual int channels() const; + /*! + * Returns the number of bits per audio sample. + */ + int bitsPerSample() const; + + /*! + * Returns the number of bits per audio sample. + * + * \note This method is just an alias of bitsPerSample(). + * + * \deprecated + */ int sampleWidth() const; + + /*! + * Returns the number of sample frames. + */ uint sampleFrames() const; + /*! + * Returns the format ID of the file. + * 0 for unknown, 1 for PCM, 2 for ADPCM, 3 for 32/64-bit IEEE754, and so forth. + * + * \note For further information, refer to \a mmreg.h in Windows Media SDK. + */ + int format() const; + private: Properties(const Properties &); Properties &operator=(const Properties &); - void read(const ByteVector &data); + void read(const ByteVector &data, uint streamLength, uint totalSamples); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/data/alaw.wav b/tests/data/alaw.wav new file mode 100644 index 00000000..cf548eff Binary files /dev/null and b/tests/data/alaw.wav differ diff --git a/tests/data/float64.wav b/tests/data/float64.wav new file mode 100644 index 00000000..d34f692b Binary files /dev/null and b/tests/data/float64.wav differ diff --git a/tests/test_wav.cpp b/tests/test_wav.cpp index 1b71f6ea..bcb91753 100644 --- a/tests/test_wav.cpp +++ b/tests/test_wav.cpp @@ -14,7 +14,9 @@ using namespace TagLib; class TestWAV : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestWAV); - CPPUNIT_TEST(testLength); + CPPUNIT_TEST(testPCMProperties); + CPPUNIT_TEST(testALAWProperties); + CPPUNIT_TEST(testFloatProperties); CPPUNIT_TEST(testZeroSizeDataChunk); CPPUNIT_TEST(testID3v2Tag); CPPUNIT_TEST(testInfoTag); @@ -26,11 +28,52 @@ class TestWAV : public CppUnit::TestFixture public: - void testLength() + void testPCMProperties() { RIFF::WAV::File f(TEST_FILE_PATH_C("empty.wav")); - CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.audioProperties()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3675, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(32, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(1000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(3675U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->format()); + } + + void testALAWProperties() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("alaw.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3550, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(128, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(8000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(28400U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(6, f.audioProperties()->format()); + } + + void testFloatProperties() + { + RIFF::WAV::File f(TEST_FILE_PATH_C("float64.wav")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(97, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(5645, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->sampleWidth()); + CPPUNIT_ASSERT_EQUAL(4281U, f.audioProperties()->sampleFrames()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->format()); } void testZeroSizeDataChunk()