#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()
}
int WavPack::Properties::length() const
+{
+ return lengthInSeconds();
+}
+
+int WavPack::Properties::lengthInSeconds() const
+{
+ return d->length / 1000;
+}
+
+int WavPack::Properties::lengthInMilliseconds() const
{
return d->length;
}
return d->bitsPerSample;
}
+bool WavPack::Properties::isLossless() const
+{
+ return d->lossless;
+}
+
TagLib::uint WavPack::Properties::sampleFrames() const
{
return d->sampleFrames;
// 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)
#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;
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;
}
*/
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;
/*!
*/
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;
/*!
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;
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()