diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index aa367b5d..e7b8dc8a 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -70,20 +70,20 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::WAV::File::File(FileName file, bool readProperties, - Properties::ReadStyle propertiesStyle) : RIFF::File(file, LittleEndian) +RIFF::WAV::File::File(FileName file, bool readProperties, Properties::ReadStyle) : + RIFF::File(file, LittleEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } -RIFF::WAV::File::File(IOStream *stream, bool readProperties, - Properties::ReadStyle propertiesStyle) : RIFF::File(stream, LittleEndian) +RIFF::WAV::File::File(IOStream *stream, bool readProperties, Properties::ReadStyle) : + RIFF::File(stream, LittleEndian), + d(new FilePrivate()) { - d = new FilePrivate; if(isOpen()) - read(readProperties, propertiesStyle); + read(readProperties); } RIFF::WAV::File::~File() @@ -189,11 +189,9 @@ bool RIFF::WAV::File::hasInfoTag() const // private members //////////////////////////////////////////////////////////////////////////////// -void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +void RIFF::WAV::File::read(bool readProperties) { - ByteVector formatData; - uint streamLength = 0; - for(uint i = 0; i < chunkCount(); i++) { + for(uint i = 0; i < chunkCount(); ++i) { const ByteVector name = chunkName(i); if(name == "ID3 " || name == "id3 ") { if(!d->tag[ID3v2Index]) { @@ -207,7 +205,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,32 +215,16 @@ void RIFF::WAV::File::read(bool readProperties, Properties::ReadStyle properties } } } - else if(name == "fmt " && readProperties) { - if(formatData.isEmpty()) { - formatData = chunkData(i); - } - else { - debug("RIFF::WAV::File::read() - Duplicate 'fmt ' chunk found."); - } - } - else if(name == "data" && readProperties) { - if(streamLength == 0) { - streamLength = chunkDataSize(i); - } - else { - debug("RIFF::WAV::File::read() - Duplicate 'data' chunk found."); - } - } } if(!d->tag[ID3v2Index]) - d->tag.set(ID3v2Index, new ID3v2::Tag); + d->tag.set(ID3v2Index, new ID3v2::Tag()); if(!d->tag[InfoIndex]) - d->tag.set(InfoIndex, new RIFF::Info::Tag); + d->tag.set(InfoIndex, new RIFF::Info::Tag()); - if(!formatData.isEmpty()) - d->properties = new Properties(formatData, streamLength, propertiesStyle); + if(readProperties) + d->properties = new Properties(this, Properties::Average); } void RIFF::WAV::File::strip(TagTypes tags) @@ -264,7 +246,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/wavfile.h b/taglib/riff/wav/wavfile.h index d8186a69..99bfda17 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -170,7 +170,7 @@ namespace TagLib { File(const File &); File &operator=(const File &); - void read(bool readProperties, Properties::ReadStyle propertiesStyle); + void read(bool readProperties); void strip(TagTypes tags); @@ -179,6 +179,8 @@ namespace TagLib { */ uint findInfoTagChunk(); + friend class Properties; + class FilePrivate; FilePrivate *d; }; diff --git a/taglib/riff/wav/wavproperties.cpp b/taglib/riff/wav/wavproperties.cpp index 439a1954..39f0dddc 100644 --- a/taglib/riff/wav/wavproperties.cpp +++ b/taglib/riff/wav/wavproperties.cpp @@ -23,55 +23,66 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#include +#include "wavfile.h" #include "wavproperties.h" -#include -#include -#include -#include - using namespace TagLib; +namespace +{ + // Quoted from RFC 2361. + enum WaveFormat + { + FORMAT_UNKNOWN = 0x0000, + FORMAT_PCM = 0x0001 + }; +} + 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(File *file, ReadStyle style) : + AudioProperties(style), + d(new PropertiesPrivate()) +{ + read(file); } RIFF::WAV::Properties::~Properties() @@ -80,6 +91,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 +120,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 +135,78 @@ 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(File *file) { + ByteVector data; + uint streamLength = 0; + uint totalSamples = 0; + + for(uint i = 0; i < file->chunkCount(); ++i) { + const ByteVector name = file->chunkName(i); + if(name == "fmt ") { + if(data.isEmpty()) + data = file->chunkData(i); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'fmt ' chunk found."); + } + else if(name == "data") { + if(streamLength == 0) + streamLength = file->chunkDataSize(i) + file->chunkPadding(i); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'data' chunk found."); + } + else if(name == "fact") { + if(totalSamples == 0) + totalSamples = file->chunkData(i).toUInt(0, false); + else + debug("RIFF::WAV::Properties::read() - Duplicate 'fact' chunk found."); + } + } + if(data.size() < 16) { - debug("RIFF::WAV::Properties::read() - \"fmt \" chunk is too short for WAV."); + debug("RIFF::WAV::Properties::read() - 'fmt ' chunk not found or too short."); 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); + if(streamLength == 0) { + debug("RIFF::WAV::Properties::read() - 'data' chunk not found."); + return; + } - const uint byteRate = data.toUInt(8, false); - d->bitrate = byteRate * 8 / 1000; + d->format = data.toShort(0, false); + if(d->format != FORMAT_PCM && totalSamples == 0) { + debug("RIFF::WAV::Properties::read() - Non-PCM format, but 'fact' chunk not found."); + return; + } - 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)); + d->channels = data.toShort(2, false); + d->sampleRate = data.toUInt(4, false); + d->bitsPerSample = data.toShort(14, false); + + if(totalSamples > 0) + d->sampleFrames = totalSamples; + else if(d->channels > 0 && d->bitsPerSample > 0) + d->sampleFrames = streamLength / (d->channels * ((d->bitsPerSample + 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..1fd6a136 100644 --- a/taglib/riff/wav/wavproperties.h +++ b/taglib/riff/wav/wavproperties.h @@ -52,35 +52,106 @@ 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 + * WAV::File \a file. + */ + Properties(File *file, 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 the WAVE Form Registration + * Numbers in RFC 2361. + */ + int format() const; + private: Properties(const Properties &); Properties &operator=(const Properties &); - void read(const ByteVector &data); + void read(File *file); 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()