]> granicus.if.org Git - taglib/commitdiff
WavPack: AudioProperties improvements
authorTsuda Kageyu <tsuda.kageyu@gmail.com>
Thu, 21 May 2015 09:41:22 +0000 (18:41 +0900)
committerTsuda Kageyu <tsuda.kageyu@gmail.com>
Thu, 18 Jun 2015 08:59:06 +0000 (17:59 +0900)
Add lengthInSeconds(), lengthInMilliseconds() properties. (#503)
Add isLossless() property.
Support multi channel. (#92)
Remove some data members which are not needed to carry.
Add some tests for audio properties.
Add some supplementary comments.

taglib/wavpack/wavpackfile.cpp
taglib/wavpack/wavpackproperties.cpp
taglib/wavpack/wavpackproperties.h
tests/data/four_channels.wv [new file with mode: 0644]
tests/test_wavpack.cpp

index 13b8c570d614529a0d9ce0977bb137e9777ced1c..8d7463d363814d20087ee30151e166ce494060f7 100644 (file)
@@ -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()
index ec12282daf525325e2e982f64d05ffcb2ac17dfd..b25bd966e538fa896433c22e91ae29cc8e5bdf7b 100644 (file)
 #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;
+    if(data.size() < 32) {
+      debug("WavPack::Properties::read() -- data is too short.");
+      break;
+    }
 
-  unsigned int samples = d->data.toUInt(12, false);
-  if(samples == ~0u) {
-    if(d->file && d->style != Fast) {
-      samples = seekFinalIndex();
+    if(!data.startsWith("wvpk")) {
+      debug("WavPack::Properties::read() -- Block header not found.");
+      break;
     }
-    else {
-      samples = 0;
+
+    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<int>(length + 0.5);
+    d->bitrate = static_cast<int>(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;
 }
index 94bccd7938fd5d936f699b0bc8dd1c58e96ac353..2955b1d80159aad2d2402a1e8382b619aa6b1f96 100644 (file)
@@ -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 (file)
index 0000000..de682f2
Binary files /dev/null and b/tests/data/four_channels.wv differ
index e9f891ccab8c9b0a2de81d19d2b0df2e43ded87d..357d4aafef7167979b26ffe4f7fadd1d5d451f73 100644 (file)
@@ -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()