From 930168f99081e554e4d0ebaea2d51f891ac96473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Wed, 11 Jul 2012 14:13:41 +0200 Subject: [PATCH] Refactoring of the Musepack SV8 properties code --- taglib/mpc/mpcfile.cpp | 52 +--------- taglib/mpc/mpcfile.h | 2 - taglib/mpc/mpcproperties.cpp | 180 +++++++++++++++++++++-------------- taglib/mpc/mpcproperties.h | 12 ++- tests/CMakeLists.txt | 2 + tests/data/sv4_header.mpc | Bin 0 -> 128 bytes tests/data/sv5_header.mpc | Bin 0 -> 128 bytes tests/data/sv8_header.mpc | Bin 0 -> 114 bytes tests/test_mpc.cpp | 62 ++++++++++++ 9 files changed, 185 insertions(+), 125 deletions(-) create mode 100644 tests/data/sv4_header.mpc create mode 100644 tests/data/sv5_header.mpc create mode 100644 tests/data/sv8_header.mpc create mode 100644 tests/test_mpc.cpp diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index 7e33fdb4..519a0467 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -310,18 +310,7 @@ void MPC::File::read(bool readProperties, Properties::ReadStyle /* propertiesSty // Look for MPC metadata if(readProperties) { - // Checking MPC version - ByteVector magic = readBlock(4); - if (magic == MPC::V8MagicTitle) { - // Musepack version 8 or newer, we should find "SH" packet and "RG" packet - d->properties = new Properties(ByteVector("MPCK")+findHeaderPacket(), - length() - d->ID3v2Size - d->APESize); - } - else { - seek(tell()-4); - d->properties = new Properties(readBlock(MPC::HeaderSize), - length() - d->ID3v2Size - d->APESize); - } + d->properties = new Properties(this, length() - d->ID3v2Size - d->APESize); } } @@ -369,42 +358,3 @@ long MPC::File::findID3v2() return -1; } - -/* -The structure of MPC v8 packet: -key 16 bit -size n*8 bit, 0readBlock(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() @@ -109,12 +121,12 @@ 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; } @@ -143,107 +155,134 @@ int MPC::Properties::albumPeak() const // private members //////////////////////////////////////////////////////////////////////////////// - -ulong readSize(const ByteVector &b, uint &sizelength) +unsigned long readSize(File *file, TagLib::uint &sizelength) { unsigned char tmp; - ulong size = 0; - sizelength = 0; + unsigned long size = 0; do { - tmp = b[sizelength]; + 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("MPCK")) { - //no checksum checking so far, this code isn't working - //quint16 crc=qChecksum(b.mid(4).data(), b.length()-4); - //uint crc1=d->data.mid(0,2).toUInt(false); + bool readSH = false, readRG = false; + while(!readSH && !readRG) { + ByteVector packetType = file->readBlock(2); + uint packetSizeLength = 0; + unsigned long packetSize = readSize(file, packetSizeLength); + unsigned long dataSize = packetSize - 2 - packetSizeLength; - int pos=8; - d->version = d->data[pos]; - pos+=1; - uint sizelength=0; - d->sampleFrames = readSize(d->data.mid(pos), sizelength); - pos+=sizelength; - ulong begSilence = readSize(d->data.mid(pos), sizelength); - pos+=sizelength; + if(packetType == "SH") { + // Stream Header + // http://trac.musepack.net/wiki/SV8Specification#StreamHeaderPacket + ByteVector data = file->readBlock(dataSize); + readSH = true; - std::bitset<16> flags(TAGLIB_CONSTRUCT_BITSET(d->data.mid(pos,2).toUShort(true))); - pos+=2; + TagLib::uint pos = 4; + d->version = data[pos]; + pos += 1; + d->sampleFrames = readSize(data.mid(pos), pos); + ulong begSilence = readSize(data.mid(pos), pos); - 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; + std::bitset<16> flags(TAGLIB_CONSTRUCT_BITSET(data.mid(pos, 2).toUShort(true))); + pos += 2; - //bool msUsed = flags[3]; - //int audioBlockFrames = (flags[2]*4 + flags[1]*2 + flags[0]) * 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); + 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; + d->length = (d->sampleFrames - begSilence) / d->sampleRate; + } - // Replaygain info scanning - pos = d->data.find(ByteVector("RG"),pos); - if (pos>=0) { - int replayGainVersion = d->data[pos+3]; - if (replayGainVersion==1) { - d->trackGain = d->data.mid(pos+4,2).toUInt(true); - d->trackPeak = d->data.mid(pos+6,2).toUInt(true); - d->albumGain = d->data.mid(pos+8,2).toUInt(true); - d->albumPeak = d->data.mid(pos+10,2).toUInt(true); + 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); } } - return; + else if(packetType == "SE") { + break; + } + + else { + file->seek(dataSize, File::Current); + } } +} - if(!d->data.startsWith("MP+")) - return; +void MPC::Properties::readSV7(const ByteVector &data) +{ + if(data.startsWith("MP+")) { + d->version = data[3] & 15; + if(d->version < 7) + return; - d->version = d->data[3] & 15; + d->totalFrames = data.mid(4, 4).toUInt(false); - if(d->version >= 7) { - d->totalFrames = d->data.mid(4, 4).toUInt(false); - - std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(d->data.mid(8, 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 = d->data.mid(14,2).toShort(false); - d->trackPeak = d->data.mid(12,2).toUInt(false); - d->albumGain = d->data.mid(18,2).toShort(false); - d->albumPeak = d->data.mid(16,2).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) { + if(d->trackGain != 0) { int tmp = (int)((64.82 - (short)d->trackGain / 100.) * 256. + .5); - if (tmp >= (1 << 16) || tmp < 0) tmp = 0; + 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->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->trackPeak != 0) + d->trackPeak = (int)(log10(d->trackPeak) * 20 * 256 + .5); - if (d->albumPeak != 0) - d->albumPeak = (int) (log10(d->albumPeak) * 20 * 256 + .5); + if (d->albumPeak != 0) + d->albumPeak = (int)(log10(d->albumPeak) * 20 * 256 + .5); bool trueGapless = (gapless >> 31) & 0x0001; if(trueGapless) { @@ -254,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; @@ -262,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; } @@ -274,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 f67d434d..adf40d83 100644 --- a/taglib/mpc/mpcproperties.h +++ b/taglib/mpc/mpcproperties.h @@ -36,7 +36,6 @@ namespace TagLib { class File; static const uint HeaderSize = 8*7; - static const char * V8MagicTitle = "MPCK"; //! An implementation of audio property reading for MPC @@ -51,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. */ @@ -103,7 +110,8 @@ namespace TagLib { 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 0000000000000000000000000000000000000000..214f7ac4eb710e8e21de093ee7959dab8fe86bca GIT binary patch literal 128 zcmV-`0Du3%Cm;Z)0P+K!E+_X8&%*;!>;D5wsil6)pQDvY{}wzBH4T&wTPvrFTA(>cv{&D-_Dah=cBT5|n@ iE3UO<#~#&>*ohqgTB!C%gDl*OOm-ISqH9VS7Q>v%RY5rb literal 0 HcmV?d00001 diff --git a/tests/data/sv5_header.mpc b/tests/data/sv5_header.mpc new file mode 100644 index 0000000000000000000000000000000000000000..6d17e65f0f865303397cdb08632cca5a2aa0365a GIT binary patch literal 128 zcmV-`0Du3%FCYN&0{{RVcBcU78pg2y2S5XsQc5XpYAK~=`TMqG7PBld%R&|wvn-27 z)(~SG+b-oV^~l!Gc|2h@R~)T+MB#OHHdkBzU)gt`zIpg}+1_WT^O~=66~40@oh*DH|+sQguls;y*_RL literal 0 HcmV?d00001 diff --git a/tests/data/sv8_header.mpc b/tests/data/sv8_header.mpc new file mode 100644 index 0000000000000000000000000000000000000000..3405545a29460883251a5ccd6135ae440bca5815 GIT binary patch literal 114 zcmeYbaP|)N;Crv7*~rn-9LK;a8RX8x=vo_f28=z~0~zHQg8eyM7=XYrfJKH(ED#*R L#wc_k*p(Ro^T!wZ literal 0 HcmV?d00001 diff --git a/tests/test_mpc.cpp b/tests/test_mpc.cpp new file mode 100644 index 00000000..758263c2 --- /dev/null +++ b/tests/test_mpc.cpp @@ -0,0 +1,62 @@ +#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(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(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(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(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);