]> granicus.if.org Git - taglib/commitdiff
WAV: AudioProperties improvements
authorTsuda Kageyu <tsuda.kageyu@gmail.com>
Thu, 21 May 2015 08:34:35 +0000 (17:34 +0900)
committerTsuda Kageyu <tsuda.kageyu@gmail.com>
Thu, 18 Jun 2015 08:43:09 +0000 (17:43 +0900)
Add lengthInSeconds(), lengthInMilliseconds() properties. (#503)
Add bitsPerSample() property besides sampleWidth(). (#360)
Add format() property. (#360)
Add some tests for audio properties.
Add some supplementary comments.

taglib/riff/wav/wavfile.cpp
taglib/riff/wav/wavproperties.cpp
taglib/riff/wav/wavproperties.h
tests/data/alaw.wav [new file with mode: 0644]
tests/data/float64.wav [new file with mode: 0644]
tests/test_wav.cpp

index aa367b5d953e50c12d335d392be1bf26028c4251..4d2b41967282cce1f38483a410cc5123a81c095b 100644 (file)
@@ -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" && readProperties) {
-      if(streamLength == 0) {
-        streamLength = chunkDataSize(i);
+      else if(name == "data") {
+        if(streamLength == 0)
+          streamLength = chunkDataSize(i) + chunkPadding(i);
+        else
+          debug("RIFF::WAV::File::read() - Duplicate 'data' chunk found.");
       }
-      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;
     }
   }
index 439a19547bb2621d85472e903f120d92e77cef0d..e61973ab1bb801d5e5fd8d6eee33695f55740fe1 100644 (file)
@@ -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())
+{
+  debug("RIFF::WAV::Properties::Properties() -- This constructor is no longer used.");
+}
+
+RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, 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)
+TagLib::RIFF::WAV::Properties::Properties(const ByteVector &data, uint streamLength, uint totalSamples, ReadStyle style) :
+  AudioProperties(style),
+  d(new PropertiesPrivate())
 {
-  d = new PropertiesPrivate(streamLength);
-  read(data);
+  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<int>(length + 0.5);
+    d->bitrate = static_cast<int>(streamLength * 8.0 / length + 0.5);
+  }
+  else {
+    const uint byteRate = data.toUInt(8, false);
+    if(byteRate > 0) {
+      d->length  = static_cast<int>(streamLength * 1000.0 / byteRate + 0.5);
+      d->bitrate = static_cast<int>(byteRate * 8.0 / 1000.0 + 0.5);
+    }
+  }
 }
index 2023f5392caaf746c99cd841036f4cbae751886c..12367eb280650886c54b78a11c573cd449440779 100644 (file)
@@ -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 (file)
index 0000000..cf548ef
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 (file)
index 0000000..d34f692
Binary files /dev/null and b/tests/data/float64.wav differ
index 1b71f6eab2933116f3915c39fb8935d508d44038..bcb91753098ec923a8ca498dda95315014bd2749 100644 (file)
@@ -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()