mirror of
https://github.com/taglib/taglib.git
synced 2025-05-27 21:20:26 -04:00
MPEG: AudioProperties improvements
Add lengthInSeconds(), lengthInMilliseconds() properties. (#503) Support VBRI header in addition to Xing. (#136) Fix MPEG frame seeker functions. (maybe #190) Calculate MPEG frame length accurately. 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:
parent
447a4739c5
commit
9ec6d28239
@ -213,8 +213,8 @@ void MPEG::Header::parse(const ByteVector &data)
|
||||
}
|
||||
};
|
||||
|
||||
const int versionIndex = d->version == Version1 ? 0 : 1;
|
||||
const int layerIndex = d->layer > 0 ? d->layer - 1 : 0;
|
||||
const int versionIndex = (d->version == Version1) ? 0 : 1;
|
||||
const int layerIndex = (d->layer > 0) ? d->layer - 1 : 0;
|
||||
|
||||
// The bitrate index is encoded as the first 4 bits of the 3rd byte,
|
||||
// i.e. 1111xxxx
|
||||
@ -253,13 +253,6 @@ void MPEG::Header::parse(const ByteVector &data)
|
||||
d->isCopyrighted = flags[3];
|
||||
d->isPadded = flags[9];
|
||||
|
||||
// Calculate the frame length
|
||||
|
||||
if(d->layer == 1)
|
||||
d->frameLength = 24000 * 2 * d->bitrate / d->sampleRate + int(d->isPadded);
|
||||
else
|
||||
d->frameLength = 72000 * d->bitrate / d->sampleRate + int(d->isPadded);
|
||||
|
||||
// Samples per frame
|
||||
|
||||
static const int samplesPerFrame[3][2] = {
|
||||
@ -271,6 +264,15 @@ void MPEG::Header::parse(const ByteVector &data)
|
||||
|
||||
d->samplesPerFrame = samplesPerFrame[layerIndex][versionIndex];
|
||||
|
||||
// Calculate the frame length
|
||||
|
||||
static const int paddingSize[3] = { 4, 1, 1 };
|
||||
|
||||
d->frameLength = d->samplesPerFrame * d->bitrate * 125 / d->sampleRate;
|
||||
|
||||
if(d->isPadded)
|
||||
d->frameLength += paddingSize[layerIndex];
|
||||
|
||||
// Now that we're done parsing, set this to be a valid frame.
|
||||
|
||||
d->isValid = true;
|
||||
|
@ -140,7 +140,7 @@ namespace TagLib {
|
||||
bool isOriginal() const;
|
||||
|
||||
/*!
|
||||
* Returns the frame length.
|
||||
* Returns the frame length in bytes.
|
||||
*/
|
||||
int frameLength() const;
|
||||
|
||||
|
@ -29,16 +29,18 @@
|
||||
#include "mpegproperties.h"
|
||||
#include "mpegfile.h"
|
||||
#include "xingheader.h"
|
||||
#include "id3v2tag.h"
|
||||
#include "id3v2header.h"
|
||||
#include "apetag.h"
|
||||
#include "apefooter.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
class MPEG::Properties::PropertiesPrivate
|
||||
{
|
||||
public:
|
||||
PropertiesPrivate(File *f, ReadStyle s) :
|
||||
file(f),
|
||||
PropertiesPrivate() :
|
||||
xingHeader(0),
|
||||
style(s),
|
||||
length(0),
|
||||
bitrate(0),
|
||||
sampleRate(0),
|
||||
@ -55,9 +57,7 @@ public:
|
||||
delete xingHeader;
|
||||
}
|
||||
|
||||
File *file;
|
||||
XingHeader *xingHeader;
|
||||
ReadStyle style;
|
||||
int length;
|
||||
int bitrate;
|
||||
int sampleRate;
|
||||
@ -74,12 +74,11 @@ public:
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MPEG::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style)
|
||||
MPEG::Properties::Properties(File *file, ReadStyle style) :
|
||||
AudioProperties(style),
|
||||
d(new PropertiesPrivate())
|
||||
{
|
||||
d = new PropertiesPrivate(file, style);
|
||||
|
||||
if(file && file->isOpen())
|
||||
read();
|
||||
read(file);
|
||||
}
|
||||
|
||||
MPEG::Properties::~Properties()
|
||||
@ -88,6 +87,16 @@ MPEG::Properties::~Properties()
|
||||
}
|
||||
|
||||
int MPEG::Properties::length() const
|
||||
{
|
||||
return lengthInSeconds();
|
||||
}
|
||||
|
||||
int MPEG::Properties::lengthInSeconds() const
|
||||
{
|
||||
return d->length / 1000;
|
||||
}
|
||||
|
||||
int MPEG::Properties::lengthInMilliseconds() const
|
||||
{
|
||||
return d->length;
|
||||
}
|
||||
@ -146,109 +155,73 @@ bool MPEG::Properties::isOriginal() const
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MPEG::Properties::read()
|
||||
void MPEG::Properties::read(File *file)
|
||||
{
|
||||
// Since we've likely just looked for the ID3v1 tag, start at the end of the
|
||||
// file where we're least likely to have to have to move the disk head.
|
||||
|
||||
long last = d->file->lastFrameOffset();
|
||||
|
||||
if(last < 0) {
|
||||
debug("MPEG::Properties::read() -- Could not find a valid last MPEG frame in the stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
d->file->seek(last);
|
||||
Header lastHeader(d->file->readBlock(4));
|
||||
|
||||
long first = d->file->firstFrameOffset();
|
||||
// Only the first frame is required if we have a VBR header.
|
||||
|
||||
const long first = file->firstFrameOffset();
|
||||
if(first < 0) {
|
||||
debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!lastHeader.isValid()) {
|
||||
file->seek(first);
|
||||
const Header firstHeader(file->readBlock(4));
|
||||
|
||||
long pos = last;
|
||||
|
||||
while(pos > first) {
|
||||
|
||||
pos = d->file->previousFrameOffset(pos);
|
||||
|
||||
if(pos < 0)
|
||||
break;
|
||||
|
||||
d->file->seek(pos);
|
||||
Header header(d->file->readBlock(4));
|
||||
|
||||
if(header.isValid()) {
|
||||
lastHeader = header;
|
||||
last = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now jump back to the front of the file and read what we need from there.
|
||||
|
||||
d->file->seek(first);
|
||||
Header firstHeader(d->file->readBlock(4));
|
||||
|
||||
if(!firstHeader.isValid() || !lastHeader.isValid()) {
|
||||
debug("MPEG::Properties::read() -- Page headers were invalid.");
|
||||
if(!firstHeader.isValid()) {
|
||||
debug("MPEG::Properties::read() -- The first page header is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for a Xing header that will help us in gathering information about a
|
||||
// Check for a VBR header that will help us in gathering information about a
|
||||
// VBR stream.
|
||||
|
||||
int xingHeaderOffset = MPEG::XingHeader::xingHeaderOffset(firstHeader.version(),
|
||||
firstHeader.channelMode());
|
||||
|
||||
d->file->seek(first + xingHeaderOffset);
|
||||
d->xingHeader = new XingHeader(d->file->readBlock(16));
|
||||
|
||||
// Read the length and the bitrate from the Xing header.
|
||||
file->seek(first + 4);
|
||||
d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength() - 4));
|
||||
|
||||
if(d->xingHeader->isValid() &&
|
||||
firstHeader.sampleRate() > 0 &&
|
||||
d->xingHeader->totalFrames() > 0)
|
||||
{
|
||||
double timePerFrame =
|
||||
double(firstHeader.samplesPerFrame()) / firstHeader.sampleRate();
|
||||
firstHeader.samplesPerFrame() > 0 &&
|
||||
firstHeader.sampleRate() > 0) {
|
||||
|
||||
double length = timePerFrame * d->xingHeader->totalFrames();
|
||||
// Read the length and the bitrate from the VBR header.
|
||||
|
||||
d->length = int(length);
|
||||
d->bitrate = d->length > 0 ? (int)(d->xingHeader->totalSize() * 8 / length / 1000) : 0;
|
||||
const double timePerFrame = firstHeader.samplesPerFrame() * 1000.0 / firstHeader.sampleRate();
|
||||
const double length = timePerFrame * d->xingHeader->totalFrames();
|
||||
|
||||
d->length = static_cast<int>(length + 0.5);
|
||||
d->bitrate = static_cast<int>(d->xingHeader->totalSize() * 8.0 / length + 0.5);
|
||||
}
|
||||
else {
|
||||
// Since there was no valid Xing header found, we hope that we're in a constant
|
||||
// bitrate file.
|
||||
else if(firstHeader.bitrate() > 0) {
|
||||
|
||||
delete d->xingHeader;
|
||||
d->xingHeader = 0;
|
||||
// Since there was no valid VBR header found, we hope that we're in a constant
|
||||
// bitrate file.
|
||||
|
||||
// TODO: Make this more robust with audio property detection for VBR without a
|
||||
// Xing header.
|
||||
|
||||
if(firstHeader.frameLength() > 0 && firstHeader.bitrate() > 0) {
|
||||
int frames = (last - first) / firstHeader.frameLength() + 1;
|
||||
d->bitrate = firstHeader.bitrate();
|
||||
|
||||
d->length = int(float(firstHeader.frameLength() * frames) /
|
||||
float(firstHeader.bitrate() * 125) + 0.5);
|
||||
d->bitrate = firstHeader.bitrate();
|
||||
}
|
||||
long long streamLength = file->length();
|
||||
|
||||
if(file->hasID3v1Tag())
|
||||
streamLength -= 128;
|
||||
|
||||
if(file->hasID3v2Tag())
|
||||
streamLength -= file->ID3v2Tag()->header()->completeTagSize();
|
||||
|
||||
if(file->hasAPETag())
|
||||
streamLength -= file->APETag()->footer()->completeTagSize();
|
||||
|
||||
if(streamLength > 0)
|
||||
d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5);
|
||||
}
|
||||
|
||||
|
||||
d->sampleRate = firstHeader.sampleRate();
|
||||
d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2;
|
||||
d->version = firstHeader.version();
|
||||
d->layer = firstHeader.layer();
|
||||
d->sampleRate = firstHeader.sampleRate();
|
||||
d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2;
|
||||
d->version = firstHeader.version();
|
||||
d->layer = firstHeader.layer();
|
||||
d->protectionEnabled = firstHeader.protectionEnabled();
|
||||
d->channelMode = firstHeader.channelMode();
|
||||
d->isCopyrighted = firstHeader.isCopyrighted();
|
||||
d->isOriginal = firstHeader.isOriginal();
|
||||
d->channelMode = firstHeader.channelMode();
|
||||
d->isCopyrighted = firstHeader.isCopyrighted();
|
||||
d->isOriginal = firstHeader.isOriginal();
|
||||
}
|
||||
|
@ -59,18 +59,52 @@ 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;
|
||||
|
||||
/*!
|
||||
* Returns the sample rate in Hz.
|
||||
*/
|
||||
virtual int sampleRate() const;
|
||||
|
||||
/*!
|
||||
* Returns the number of audio channels.
|
||||
*/
|
||||
virtual int channels() const;
|
||||
|
||||
/*!
|
||||
* Returns a pointer to the XingHeader if one exists or null if no
|
||||
* XingHeader was found.
|
||||
* Returns a pointer to the Xing/VBRI header if one exists or null if no
|
||||
* Xing/VBRI header was found.
|
||||
*/
|
||||
|
||||
const XingHeader *xingHeader() const;
|
||||
|
||||
/*!
|
||||
@ -107,7 +141,7 @@ namespace TagLib {
|
||||
Properties(const Properties &);
|
||||
Properties &operator=(const Properties &);
|
||||
|
||||
void read();
|
||||
void read(File *file);
|
||||
|
||||
class PropertiesPrivate;
|
||||
PropertiesPrivate *d;
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <tdebug.h>
|
||||
|
||||
#include "xingheader.h"
|
||||
#include "mpegfile.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
@ -37,17 +38,21 @@ public:
|
||||
XingHeaderPrivate() :
|
||||
frames(0),
|
||||
size(0),
|
||||
valid(false)
|
||||
{}
|
||||
type(MPEG::XingHeader::Invalid) {}
|
||||
|
||||
uint frames;
|
||||
uint size;
|
||||
bool valid;
|
||||
|
||||
MPEG::XingHeader::HeaderType type;
|
||||
};
|
||||
|
||||
MPEG::XingHeader::XingHeader(const ByteVector &data)
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MPEG::XingHeader::XingHeader(const ByteVector &data) :
|
||||
d(new XingHeaderPrivate())
|
||||
{
|
||||
d = new XingHeaderPrivate;
|
||||
parse(data);
|
||||
}
|
||||
|
||||
@ -58,7 +63,7 @@ MPEG::XingHeader::~XingHeader()
|
||||
|
||||
bool MPEG::XingHeader::isValid() const
|
||||
{
|
||||
return d->valid;
|
||||
return (d->type != Invalid && d->frames > 0 && d->size > 0);
|
||||
}
|
||||
|
||||
TagLib::uint MPEG::XingHeader::totalFrames() const
|
||||
@ -71,45 +76,65 @@ TagLib::uint MPEG::XingHeader::totalSize() const
|
||||
return d->size;
|
||||
}
|
||||
|
||||
int MPEG::XingHeader::xingHeaderOffset(TagLib::MPEG::Header::Version v,
|
||||
TagLib::MPEG::Header::ChannelMode c)
|
||||
MPEG::XingHeader::HeaderType MPEG::XingHeader::type() const
|
||||
{
|
||||
if(v == MPEG::Header::Version1) {
|
||||
if(c == MPEG::Header::SingleChannel)
|
||||
return 0x15;
|
||||
else
|
||||
return 0x24;
|
||||
}
|
||||
else {
|
||||
if(c == MPEG::Header::SingleChannel)
|
||||
return 0x0D;
|
||||
else
|
||||
return 0x15;
|
||||
}
|
||||
return d->type;
|
||||
}
|
||||
|
||||
int MPEG::XingHeader::xingHeaderOffset(TagLib::MPEG::Header::Version /*v*/,
|
||||
TagLib::MPEG::Header::ChannelMode /*c*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void MPEG::XingHeader::parse(const ByteVector &data)
|
||||
{
|
||||
// Check to see if a valid Xing header is available.
|
||||
// Look for a Xing header.
|
||||
|
||||
if(!data.startsWith("Xing") && !data.startsWith("Info"))
|
||||
return;
|
||||
long offset = data.find("Xing");
|
||||
if(offset < 0)
|
||||
offset = data.find("Info");
|
||||
|
||||
// If the XingHeader doesn't contain the number of frames and the total stream
|
||||
// info it's invalid.
|
||||
if(offset >= 0) {
|
||||
|
||||
if(!(data[7] & 0x01)) {
|
||||
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total number of frames.");
|
||||
return;
|
||||
// Xing header found.
|
||||
|
||||
if(data.size() < offset + 16) {
|
||||
debug("MPEG::XingHeader::parse() -- Xing header found but too short.");
|
||||
return;
|
||||
}
|
||||
|
||||
if((data[offset + 7] & 0x03) != 0x03) {
|
||||
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the required information.");
|
||||
return;
|
||||
}
|
||||
|
||||
d->frames = data.toUInt(offset + 8, true);
|
||||
d->size = data.toUInt(offset + 12, true);
|
||||
d->type = Xing;
|
||||
}
|
||||
else {
|
||||
|
||||
if(!(data[7] & 0x02)) {
|
||||
debug("MPEG::XingHeader::parse() -- Xing header doesn't contain the total stream size.");
|
||||
return;
|
||||
// Xing header not found. Then look for a VBRI header.
|
||||
|
||||
offset = data.find("VBRI");
|
||||
|
||||
if(offset >= 0) {
|
||||
|
||||
// VBRI header found.
|
||||
|
||||
if(data.size() < offset + 32) {
|
||||
debug("MPEG::XingHeader::parse() -- VBRI header found but too short.");
|
||||
return;
|
||||
}
|
||||
|
||||
d->frames = data.toUInt(offset + 14, true);
|
||||
d->size = data.toUInt(offset + 10, true);
|
||||
d->type = VBRI;
|
||||
}
|
||||
}
|
||||
|
||||
d->frames = data.toUInt(8U);
|
||||
d->size = data.toUInt(12U);
|
||||
|
||||
d->valid = true;
|
||||
}
|
||||
|
@ -35,24 +35,47 @@ namespace TagLib {
|
||||
|
||||
namespace MPEG {
|
||||
|
||||
//! An implementation of the Xing VBR headers
|
||||
class File;
|
||||
|
||||
//! An implementation of the Xing/VBRI headers
|
||||
|
||||
/*!
|
||||
* This is a minimalistic implementation of the Xing VBR headers. Xing
|
||||
* headers are often added to VBR (variable bit rate) MP3 streams to make it
|
||||
* easy to compute the length and quality of a VBR stream. Our implementation
|
||||
* is only concerned with the total size of the stream (so that we can
|
||||
* calculate the total playing time and the average bitrate). It uses
|
||||
* <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt">this text</a>
|
||||
* and the XMMS sources as references.
|
||||
* This is a minimalistic implementation of the Xing/VBRI VBR headers.
|
||||
* Xing/VBRI headers are often added to VBR (variable bit rate) MP3 streams
|
||||
* to make it easy to compute the length and quality of a VBR stream. Our
|
||||
* implementation is only concerned with the total size of the stream (so
|
||||
* that we can calculate the total playing time and the average bitrate).
|
||||
* It uses <a href="http://home.pcisys.net/~melanson/codecs/mp3extensions.txt">
|
||||
* this text</a> and the XMMS sources as references.
|
||||
*/
|
||||
|
||||
class TAGLIB_EXPORT XingHeader
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Parses a Xing header based on \a data. The data must be at least 16
|
||||
* bytes long (anything longer than this is discarded).
|
||||
* The type of the VBR header.
|
||||
*/
|
||||
enum HeaderType
|
||||
{
|
||||
/*!
|
||||
* Invalid header or no VBR header found.
|
||||
*/
|
||||
Invalid = 0,
|
||||
|
||||
/*!
|
||||
* Xing header.
|
||||
*/
|
||||
Xing = 1,
|
||||
|
||||
/*!
|
||||
* VBRI header.
|
||||
*/
|
||||
VBRI = 2,
|
||||
};
|
||||
|
||||
/*!
|
||||
* Parses an Xing/VBRI header based on \a data which contains the entire
|
||||
* first MPEG frame.
|
||||
*/
|
||||
XingHeader(const ByteVector &data);
|
||||
|
||||
@ -63,7 +86,7 @@ namespace TagLib {
|
||||
|
||||
/*!
|
||||
* Returns true if the data was parsed properly and if there is a valid
|
||||
* Xing header present.
|
||||
* Xing/VBRI header present.
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
@ -77,11 +100,17 @@ namespace TagLib {
|
||||
*/
|
||||
uint totalSize() const;
|
||||
|
||||
/*!
|
||||
* Returns the type of the VBR header.
|
||||
*/
|
||||
HeaderType type() const;
|
||||
|
||||
/*!
|
||||
* Returns the offset for the start of this Xing header, given the
|
||||
* version and channels of the frame
|
||||
*
|
||||
* \deprecated Always returns 0.
|
||||
*/
|
||||
// BIC: rename to offset()
|
||||
static int xingHeaderOffset(TagLib::MPEG::Header::Version v,
|
||||
TagLib::MPEG::Header::ChannelMode c);
|
||||
|
||||
|
BIN
tests/data/bladeenc.mp3
Normal file
BIN
tests/data/bladeenc.mp3
Normal file
Binary file not shown.
BIN
tests/data/lame_cbr.mp3
Normal file
BIN
tests/data/lame_cbr.mp3
Normal file
Binary file not shown.
BIN
tests/data/lame_vbr.mp3
Normal file
BIN
tests/data/lame_vbr.mp3
Normal file
Binary file not shown.
BIN
tests/data/vbri.mp3
Normal file
BIN
tests/data/vbri.mp3
Normal file
Binary file not shown.
@ -3,6 +3,9 @@
|
||||
#include <tstring.h>
|
||||
#include <mpegfile.h>
|
||||
#include <id3v2tag.h>
|
||||
#include <mpegproperties.h>
|
||||
#include <xingheader.h>
|
||||
#include <mpegheader.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include "utils.h"
|
||||
|
||||
@ -12,6 +15,10 @@ using namespace TagLib;
|
||||
class TestMPEG : public CppUnit::TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(TestMPEG);
|
||||
CPPUNIT_TEST(testAudioPropertiesXingHeaderCBR);
|
||||
CPPUNIT_TEST(testAudioPropertiesXingHeaderVBR);
|
||||
CPPUNIT_TEST(testAudioPropertiesVBRIHeader);
|
||||
CPPUNIT_TEST(testAudioPropertiesNoVBRHeaders);
|
||||
CPPUNIT_TEST(testVersion2DurationWithXingHeader);
|
||||
CPPUNIT_TEST(testSaveID3v24);
|
||||
CPPUNIT_TEST(testSaveID3v24WrongParam);
|
||||
@ -23,10 +30,81 @@ class TestMPEG : public CppUnit::TestFixture
|
||||
|
||||
public:
|
||||
|
||||
void testAudioPropertiesXingHeaderCBR()
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("lame_cbr.mp3"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->length());
|
||||
CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(1887164, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type());
|
||||
}
|
||||
|
||||
void testAudioPropertiesXingHeaderVBR()
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("lame_vbr.mp3"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->length());
|
||||
CPPUNIT_ASSERT_EQUAL(1887, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(1887164, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(70, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type());
|
||||
}
|
||||
|
||||
void testAudioPropertiesVBRIHeader()
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("vbri.mp3"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(222, f.audioProperties()->length());
|
||||
CPPUNIT_ASSERT_EQUAL(222, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(222198, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(233, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::VBRI, f.audioProperties()->xingHeader()->type());
|
||||
}
|
||||
|
||||
void testAudioPropertiesNoVBRHeaders()
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("bladeenc.mp3"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length());
|
||||
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(3553, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(64, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT(!f.audioProperties()->xingHeader()->isValid());
|
||||
|
||||
long last = f.lastFrameOffset();
|
||||
|
||||
f.seek(last);
|
||||
MPEG::Header lastHeader(f.readBlock(4));
|
||||
|
||||
while (!lastHeader.isValid()) {
|
||||
|
||||
last = f.previousFrameOffset(last);
|
||||
|
||||
f.seek(last);
|
||||
lastHeader = MPEG::Header(f.readBlock(4));
|
||||
}
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(28213L, last);
|
||||
CPPUNIT_ASSERT_EQUAL(209, lastHeader.frameLength());
|
||||
}
|
||||
|
||||
void testVersion2DurationWithXingHeader()
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("mpeg2.mp3"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(5387, f.audioProperties()->length());
|
||||
CPPUNIT_ASSERT_EQUAL(5387, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(5387285, f.audioProperties()->lengthInMilliseconds());
|
||||
}
|
||||
|
||||
void testSaveID3v24()
|
||||
|
Loading…
Reference in New Issue
Block a user