WavPack: AudioProperties improvements

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.
This commit is contained in:
Tsuda Kageyu 2015-05-21 18:41:22 +09:00
parent 447a4739c5
commit 22f250eaa4
5 changed files with 158 additions and 69 deletions

View 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()

View File

@ -33,53 +33,50 @@
#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;
unsigned int samples = d->data.toUInt(12, false);
if(samples == ~0u) {
if(d->file && d->style != Fast) {
samples = seekFinalIndex();
if(data.size() < 32) {
debug("WavPack::Properties::read() -- data is too short.");
break;
}
else {
samples = 0;
if(!data.startsWith("wvpk")) {
debug("WavPack::Properties::read() -- Block header not found.");
break;
}
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;
}

View 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;

BIN
tests/data/four_channels.wv Normal file

Binary file not shown.

View 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()