diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index ff965bb6..519a0467 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -310,8 +310,7 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty // Look for MPC metadata if(readProperties) { - d->properties = new Properties(readBlock(MPC::HeaderSize), - length() - d->ID3v2Size - d->APESize); + d->properties = new Properties(this, length() - d->ID3v2Size - d->APESize); } } diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index c906ae67..61ac6d67 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -32,6 +32,8 @@ #include "mpcproperties.h" +#include "tlist.h" + namespace TagLib { class Tag; @@ -180,7 +182,6 @@ namespace TagLib { */ void remove(int tags = AllTags); - private: File(const File &); File &operator=(const File &); diff --git a/taglib/mpc/mpcproperties.cpp b/taglib/mpc/mpcproperties.cpp index 3ce44cc2..7046db99 100644 --- a/taglib/mpc/mpcproperties.cpp +++ b/taglib/mpc/mpcproperties.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "mpcproperties.h" #include "mpcfile.h" @@ -35,8 +36,7 @@ using namespace TagLib; class MPC::Properties::PropertiesPrivate { public: - PropertiesPrivate(const ByteVector &d, long length, ReadStyle s) : - data(d), + PropertiesPrivate(long length, ReadStyle s) : streamLength(length), style(s), version(0), @@ -45,9 +45,12 @@ public: sampleRate(0), channels(0), totalFrames(0), - sampleFrames(0) {} + sampleFrames(0), + trackGain(0), + trackPeak(0), + albumGain(0), + albumPeak(0) {} - ByteVector data; long streamLength; ReadStyle style; int version; @@ -57,6 +60,11 @@ public: int channels; uint totalFrames; uint sampleFrames; + uint trackGain; + uint trackPeak; + uint albumGain; + uint albumPeak; + String flags; }; //////////////////////////////////////////////////////////////////////////////// @@ -65,8 +73,22 @@ public: MPC::Properties::Properties(const ByteVector &data, long streamLength, ReadStyle style) : AudioProperties(style) { - d = new PropertiesPrivate(data, streamLength, style); - read(); + d = new PropertiesPrivate(streamLength, style); + readSV7(data); +} + +MPC::Properties::Properties(File *file, long streamLength, ReadStyle style) : AudioProperties(style) +{ + d = new PropertiesPrivate(streamLength, style); + ByteVector magic = file->readBlock(4); + if(magic == "MPCK") { + // Musepack version 8 + readSV8(file); + } + else { + // Musepack version 7 or older, fixed size header + readSV7(magic + file->readBlock(MPC::HeaderSize - 4)); + } } MPC::Properties::~Properties() @@ -99,37 +121,169 @@ int MPC::Properties::mpcVersion() const return d->version; } -uint MPC::Properties::totalFrames() const +TagLib::uint MPC::Properties::totalFrames() const { return d->totalFrames; } -uint MPC::Properties::sampleFrames() const +TagLib::uint MPC::Properties::sampleFrames() const { return d->sampleFrames; } +int MPC::Properties::trackGain() const +{ + return d->trackGain; +} + +int MPC::Properties::trackPeak() const +{ + return d->trackPeak; +} + +int MPC::Properties::albumGain() const +{ + return d->albumGain; +} + +int MPC::Properties::albumPeak() const +{ + return d->albumPeak; +} + //////////////////////////////////////////////////////////////////////////////// // private members //////////////////////////////////////////////////////////////////////////////// +unsigned long readSize(File *file, TagLib::uint &sizelength) +{ + unsigned char tmp; + unsigned long size = 0; + + do { + ByteVector b = file->readBlock(1); + tmp = b[0]; + size = (size << 7) | (tmp & 0x7F); + sizelength++; + } while((tmp & 0x80)); + return size; +} + +unsigned long readSize(const ByteVector &data, TagLib::uint &sizelength) +{ + unsigned char tmp; + unsigned long size = 0; + unsigned long pos = 0; + + do { + tmp = data[pos++]; + size = (size << 7) | (tmp & 0x7F); + sizelength++; + } while((tmp & 0x80) && (pos < data.size())); + return size; +} + static const unsigned short sftable [4] = { 44100, 48000, 37800, 32000 }; -void MPC::Properties::read() +void MPC::Properties::readSV8(File *file) { - if(!d->data.startsWith("MP+")) - return; + bool readSH = false, readRG = false; - d->version = d->data[3] & 15; + while(!readSH && !readRG) { + ByteVector packetType = file->readBlock(2); + uint packetSizeLength = 0; + unsigned long packetSize = readSize(file, packetSizeLength); + unsigned long dataSize = packetSize - 2 - packetSizeLength; - if(d->version >= 7) { - d->totalFrames = d->data.mid(4, 4).toUInt(false); + if(packetType == "SH") { + // Stream Header + // http://trac.musepack.net/wiki/SV8Specification#StreamHeaderPacket + ByteVector data = file->readBlock(dataSize); + readSH = true; - std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(d->data.mid(8, 4).toUInt(false))); + TagLib::uint pos = 4; + d->version = data[pos]; + pos += 1; + d->sampleFrames = readSize(data.mid(pos), pos); + ulong begSilence = readSize(data.mid(pos), pos); + + std::bitset<16> flags(TAGLIB_CONSTRUCT_BITSET(data.mid(pos, 2).toUShort(true))); + pos += 2; + + d->sampleRate = sftable[flags[15] * 4 + flags[14] * 2 + flags[13]]; + d->channels = flags[7] * 8 + flags[6] * 4 + flags[5] * 2 + flags[4] + 1; + + if((d->sampleFrames - begSilence) != 0) + d->bitrate = d->streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence); + d->bitrate = d->bitrate / 1000; + + d->length = (d->sampleFrames - begSilence) / d->sampleRate; + } + + else if (packetType == "RG") { + // Replay Gain + // http://trac.musepack.net/wiki/SV8Specification#ReplaygainPacket + ByteVector data = file->readBlock(dataSize); + readRG = true; + + int replayGainVersion = data[0]; + if(replayGainVersion == 1) { + d->trackGain = data.mid(1, 2).toUInt(true); + d->trackPeak = data.mid(3, 2).toUInt(true); + d->albumGain = data.mid(5, 2).toUInt(true); + d->albumPeak = data.mid(7, 2).toUInt(true); + } + } + + else if(packetType == "SE") { + break; + } + + else { + file->seek(dataSize, File::Current); + } + } +} + +void MPC::Properties::readSV7(const ByteVector &data) +{ + if(data.startsWith("MP+")) { + d->version = data[3] & 15; + if(d->version < 7) + return; + + d->totalFrames = data.mid(4, 4).toUInt(false); + + std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.mid(8, 4).toUInt(false))); d->sampleRate = sftable[flags[17] * 2 + flags[16]]; d->channels = 2; - uint gapless = d->data.mid(5, 4).toUInt(false); + uint gapless = data.mid(5, 4).toUInt(false); + + d->trackGain = data.mid(14,2).toShort(false); + d->trackPeak = data.mid(12,2).toUInt(false); + d->albumGain = data.mid(18,2).toShort(false); + d->albumPeak = data.mid(16,2).toUInt(false); + + // convert gain info + if(d->trackGain != 0) { + int tmp = (int)((64.82 - (short)d->trackGain / 100.) * 256. + .5); + if(tmp >= (1 << 16) || tmp < 0) tmp = 0; + d->trackGain = tmp; + } + + if(d->albumGain != 0) { + int tmp = (int)((64.82 - d->albumGain / 100.) * 256. + .5); + if(tmp >= (1 << 16) || tmp < 0) tmp = 0; + d->albumGain = tmp; + } + + if (d->trackPeak != 0) + d->trackPeak = (int)(log10(d->trackPeak) * 20 * 256 + .5); + + if (d->albumPeak != 0) + d->albumPeak = (int)(log10(d->albumPeak) * 20 * 256 + .5); + bool trueGapless = (gapless >> 31) & 0x0001; if(trueGapless) { uint lastFrameSamples = (gapless >> 20) & 0x07FF; @@ -139,7 +293,7 @@ void MPC::Properties::read() d->sampleFrames = d->totalFrames * 1152 - 576; } else { - uint headerData = d->data.mid(0, 4).toUInt(false); + uint headerData = data.mid(0, 4).toUInt(false); d->bitrate = (headerData >> 23) & 0x01ff; d->version = (headerData >> 11) & 0x03ff; @@ -147,9 +301,9 @@ void MPC::Properties::read() d->channels = 2; if(d->version >= 5) - d->totalFrames = d->data.mid(4, 4).toUInt(false); + d->totalFrames = data.mid(4, 4).toUInt(false); else - d->totalFrames = d->data.mid(6, 2).toUInt(false); + d->totalFrames = data.mid(6, 2).toUInt(false); d->sampleFrames = d->totalFrames * 1152 - 576; } @@ -159,3 +313,4 @@ void MPC::Properties::read() if(!d->bitrate) d->bitrate = d->length > 0 ? ((d->streamLength * 8L) / d->length) / 1000 : 0; } + diff --git a/taglib/mpc/mpcproperties.h b/taglib/mpc/mpcproperties.h index 8225306f..adf40d83 100644 --- a/taglib/mpc/mpcproperties.h +++ b/taglib/mpc/mpcproperties.h @@ -50,9 +50,17 @@ namespace TagLib { /*! * Create an instance of MPC::Properties with the data read from the * ByteVector \a data. + * + * This constructor is deprecated. It only works for MPC version up to 7. */ Properties(const ByteVector &data, long streamLength, ReadStyle style = Average); + /*! + * Create an instance of MPC::Properties with the data read directly + * from a MPC::File. + */ + Properties(File *file, long streamLength, ReadStyle style = Average); + /*! * Destroys this MPC::Properties instance. */ @@ -66,17 +74,44 @@ namespace TagLib { virtual int channels() const; /*! - * Returns the version of the bitstream (SV4-SV7) + * Returns the version of the bitstream (SV4-SV8) */ int mpcVersion() const; uint totalFrames() const; uint sampleFrames() const; + /*! + * Returns the track gain as an integer value, + * to convert to dB: trackGain in dB = 64.82 - (trackGain / 256) + */ + int trackGain() const; + + /*! + * Returns the track peak as an integer value, + * to convert to dB: trackPeak in dB = trackPeak / 256 + * to convert to floating [-1..1]: trackPeak = 10^(trackPeak / 256 / 20)/32768 + */ + int trackPeak() const; + + /*! + * Returns the album gain as an integer value, + * to convert to dB: albumGain in dB = 64.82 - (albumGain / 256) + */ + int albumGain() const; + + /*! + * Returns the album peak as an integer value, + * to convert to dB: albumPeak in dB = albumPeak / 256 + * to convert to floating [-1..1]: albumPeak = 10^(albumPeak / 256 / 20)/32768 + */ + int albumPeak() const; + private: Properties(const Properties &); Properties &operator=(const Properties &); - void read(); + void readSV7(const ByteVector &data); + void readSV8(File *file); class PropertiesPrivate; PropertiesPrivate *d; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 67b29581..d916d10e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,6 +9,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2 ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg/id3v2/frames ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpeg + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mpc ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mp4 ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/riff ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/riff/aiff @@ -59,6 +60,7 @@ SET(test_runner_SRCS test_s3m.cpp test_it.cpp test_xm.cpp + test_mpc.cpp ) ADD_EXECUTABLE(test_runner ${test_runner_SRCS}) diff --git a/tests/data/sv4_header.mpc b/tests/data/sv4_header.mpc new file mode 100644 index 00000000..214f7ac4 Binary files /dev/null and b/tests/data/sv4_header.mpc differ diff --git a/tests/data/sv5_header.mpc b/tests/data/sv5_header.mpc new file mode 100644 index 00000000..6d17e65f Binary files /dev/null and b/tests/data/sv5_header.mpc differ diff --git a/tests/data/sv8_header.mpc b/tests/data/sv8_header.mpc new file mode 100644 index 00000000..3405545a Binary files /dev/null and b/tests/data/sv8_header.mpc differ diff --git a/tests/test_mpc.cpp b/tests/test_mpc.cpp new file mode 100644 index 00000000..12da6ed3 --- /dev/null +++ b/tests/test_mpc.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestMPC : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestMPC); + CPPUNIT_TEST(testPropertiesSV8); + CPPUNIT_TEST(testPropertiesSV7); + CPPUNIT_TEST(testPropertiesSV5); + CPPUNIT_TEST(testPropertiesSV4); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testPropertiesSV8() + { + MPC::File f(TEST_FILE_PATH_C("sv8_header.mpc")); + CPPUNIT_ASSERT_EQUAL(8, f.audioProperties()->mpcVersion()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + } + + void testPropertiesSV7() + { + MPC::File f(TEST_FILE_PATH_C("click.mpc")); + CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->mpcVersion()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + } + + void testPropertiesSV5() + { + MPC::File f(TEST_FILE_PATH_C("sv5_header.mpc")); + CPPUNIT_ASSERT_EQUAL(5, f.audioProperties()->mpcVersion()); + CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + } + + void testPropertiesSV4() + { + MPC::File f(TEST_FILE_PATH_C("sv4_header.mpc")); + CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->mpcVersion()); + CPPUNIT_ASSERT_EQUAL(26, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestMPC);