Raw AAC (ADTS) support (#508)

Detect ADTS MPEG header to use it also for AAC.

The test file empty1s.aac was generated using
ffmpeg -f lavfi -i anullsrc=r=11025:cl=mono -t 1 -acodec aac empty1s.aac

---------

Co-authored-by: Nick Shaforostov <mshaforostov@airmusictech.com>
Co-authored-by: Urs Fleisch <ufleisch@users.sourceforge.net>
This commit is contained in:
Nick Shaforostoff 2023-11-03 05:17:39 +01:00 committed by GitHub
parent a7c0b53f7a
commit 3869aa189f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 325 additions and 89 deletions

View File

@ -134,7 +134,7 @@ namespace
File *file = nullptr;
if(ext == "MP3")
if(ext == "MP3" || ext == "AAC")
file = new MPEG::File(stream, ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle);
else if(ext == "OGG")
file = new Ogg::Vorbis::File(stream, readAudioProperties, audioPropertiesStyle);
@ -410,6 +410,7 @@ StringList FileRef::defaultFileExtensions()
l.append("wv");
l.append("spx");
l.append("tta");
l.append("aac");
l.append("m4a");
l.append("m4r");
l.append("m4b");

View File

@ -479,7 +479,7 @@ void MPEG::File::read(bool readProperties, Properties::ReadStyle readStyle)
}
if(readProperties)
d->properties = std::make_unique<Properties>(this);
d->properties = std::make_unique<Properties>(this, readStyle);
// Make sure that we have our default tag types available.

View File

@ -45,6 +45,7 @@ public:
int sampleRate { 0 };
bool isPadded { false };
ChannelMode channelMode { Stereo };
ChannelConfiguration channelConfiguration { Custom };
bool isCopyrighted { false };
bool isOriginal { false };
int frameLength { 0 };
@ -104,6 +105,17 @@ MPEG::Header::ChannelMode MPEG::Header::channelMode() const
return d->channelMode;
}
MPEG::Header::ChannelConfiguration MPEG::Header::channelConfiguration() const
{
return d->channelConfiguration;
}
bool MPEG::Header::isADTS() const
{
// See detection in parse().
return d->layer == 0 && (d->version == Version2 || d->version == Version4);
}
bool MPEG::Header::isCopyrighted() const
{
return d->isCopyrighted;
@ -177,89 +189,143 @@ void MPEG::Header::parse(File *file, offset_t offset, bool checkLength)
d->layer = 2;
else if(layerBits == 3)
d->layer = 1;
else
return;
else {
// layerBits == 0 is reserved in the
// <a href="http://www.mp3-tech.org/programmer/frame_header.html">
// MPEG Audio Layer I/II/III frame header</a>, for
// <a href="https://wiki.multimedia.cx/index.php/ADTS">ADTS</a>
// they are always set to 0.
// Bit 1 of versionBits is bit 4 of the 2nd header word. For ADTS
// it must be set to 1, therefore these three bits are used to detect
// that this header is from an ADTS file.
if(versionBits == 2) {
d->version = Version4;
d->layer = 0;
}
else if(versionBits == 3) {
d->version = Version2;
d->layer = 0;
}
else {
return;
}
}
d->protectionEnabled = (static_cast<unsigned char>(data[1] & 0x01) == 0);
// Set the bitrate
if(isADTS()) {
static constexpr std::array sampleRates {
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
16000, 12000, 11025, 8000, 7350, 0, 0, 0
};
static constexpr std::array bitrates {
std::array {
// Version 1
std::array { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, // layer 1
std::array { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, // layer 2
std::array { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 } // layer 3
},
std::array {
// Version 2 or 2.5
std::array { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 }, // layer 1
std::array { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // layer 2
std::array { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 } // layer 3
},
};
const int sampleRateIndex = (static_cast<unsigned char>(data[2]) >> 2) & 0x0F;
d->sampleRate = sampleRates[sampleRateIndex];
d->samplesPerFrame = 1024;
const int versionIndex = (d->version == Version1) ? 0 : 1;
const int layerIndex = (d->layer > 0) ? d->layer - 1 : 0;
d->channelConfiguration = static_cast<ChannelConfiguration>(
((static_cast<unsigned char>(data[3]) >> 6) & 0x03) |
((static_cast<unsigned char>(data[2]) << 2) & 0x04));
d->channelMode = d->channelConfiguration == FrontCenter ? SingleChannel : Stereo;
// The bitrate index is encoded as the first 4 bits of the 3rd byte,
// i.e. 1111xxxx
// TODO: Add mode extension for completeness
const int bitrateIndex = (static_cast<unsigned char>(data[2]) >> 4) & 0x0F;
d->isOriginal = (static_cast<unsigned char>(data[3]) & 0x20) != 0;
d->isCopyrighted = (static_cast<unsigned char>(data[3]) & 0x04) != 0;
d->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex];
// Calculate the frame length
const ByteVector frameLengthData = file->readBlock(2);
if(d->bitrate == 0)
return;
if(frameLengthData.size() >= 2) {
d->frameLength = (static_cast<unsigned char>(data[3]) & 0x3) << 11 |
(static_cast<unsigned char>(frameLengthData[0]) << 3) |
(static_cast<unsigned char>(frameLengthData[1]) >> 5);
// Set the sample rate
static constexpr std::array sampleRates {
std::array { 44100, 48000, 32000, 0 }, // Version 1
std::array { 22050, 24000, 16000, 0 }, // Version 2
std::array { 11025, 12000, 8000, 0 } // Version 2.5
};
// The sample rate index is encoded as two bits in the 3rd byte, i.e. xxxx11xx
const int samplerateIndex = (static_cast<unsigned char>(data[2]) >> 2) & 0x03;
d->sampleRate = sampleRates[d->version][samplerateIndex];
if(d->sampleRate == 0) {
return;
d->bitrate = static_cast<int>(d->frameLength * d->sampleRate / 1024.0 + 0.5) * 8 / 1024;
}
}
else {
// Not ADTS
// The channel mode is encoded as a 2 bit value at the end of the 3rd byte,
// i.e. xxxxxx11
// Set the bitrate
d->channelMode = static_cast<ChannelMode>((static_cast<unsigned char>(data[3]) >> 6) & 0x03);
static constexpr std::array bitrates {
std::array {
// Version 1
std::array { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, // layer 1
std::array { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, // layer 2
std::array { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 } // layer 3
},
std::array {
// Version 2 or 2.5
std::array { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 }, // layer 1
std::array { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // layer 2
std::array { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 } // layer 3
},
};
// TODO: Add mode extension for completeness
const int versionIndex = (d->version == Version1) ? 0 : 1;
const int layerIndex = (d->layer > 0) ? d->layer - 1 : 0;
d->isOriginal = ((static_cast<unsigned char>(data[3]) & 0x04) != 0);
d->isCopyrighted = ((static_cast<unsigned char>(data[3]) & 0x08) != 0);
d->isPadded = ((static_cast<unsigned char>(data[2]) & 0x02) != 0);
// The bitrate index is encoded as the first 4 bits of the 3rd byte,
// i.e. 1111xxxx
// Samples per frame
const int bitrateIndex = (static_cast<unsigned char>(data[2]) >> 4) & 0x0F;
static constexpr std::array samplesPerFrame {
// MPEG1, 2/2.5
std::pair(384, 384), // Layer I
std::pair(1152, 1152), // Layer II
std::pair(1152, 576), // Layer III
};
d->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex];
d->samplesPerFrame = versionIndex ? samplesPerFrame[layerIndex].second : samplesPerFrame[layerIndex].first;
if(d->bitrate == 0)
return;
// Calculate the frame length
// Set the sample rate
static constexpr std::array paddingSize { 4, 1, 1 };
static constexpr std::array sampleRates {
std::array { 44100, 48000, 32000, 0 }, // Version 1
std::array { 22050, 24000, 16000, 0 }, // Version 2
std::array { 11025, 12000, 8000, 0 } // Version 2.5
};
d->frameLength = d->samplesPerFrame * d->bitrate * 125 / d->sampleRate;
// The sample rate index is encoded as two bits in the 3rd byte, i.e. xxxx11xx
if(d->isPadded)
d->frameLength += paddingSize[layerIndex];
const int samplerateIndex = (static_cast<unsigned char>(data[2]) >> 2) & 0x03;
d->sampleRate = sampleRates[d->version][samplerateIndex];
if(d->sampleRate == 0) {
return;
}
// The channel mode is encoded as a 2 bit value at the end of the 3rd byte,
// i.e. xxxxxx11
d->channelMode = static_cast<ChannelMode>((static_cast<unsigned char>(data[3]) >> 6) & 0x03);
// TODO: Add mode extension for completeness
d->isOriginal = ((static_cast<unsigned char>(data[3]) & 0x04) != 0);
d->isCopyrighted = ((static_cast<unsigned char>(data[3]) & 0x08) != 0);
d->isPadded = ((static_cast<unsigned char>(data[2]) & 0x02) != 0);
// Samples per frame
static constexpr std::array samplesPerFrame {
// MPEG1, 2/2.5
std::pair(384, 384), // Layer I
std::pair(1152, 1152), // Layer II
std::pair(1152, 576), // Layer III
};
d->samplesPerFrame = versionIndex ? samplesPerFrame[layerIndex].second : samplesPerFrame[layerIndex].first;
// Calculate the frame length
static constexpr std::array paddingSize { 4, 1, 1 };
d->frameLength = d->samplesPerFrame * d->bitrate * 125 / d->sampleRate;
if(d->isPadded)
d->frameLength += paddingSize[layerIndex];
}
if(checkLength) {

View File

@ -84,7 +84,9 @@ namespace TagLib {
//! MPEG Version 2
Version2 = 1,
//! MPEG Version 2.5
Version2_5 = 2
Version2_5 = 2,
//! MPEG Version 4
Version4 = 3
};
/*!
@ -137,6 +139,31 @@ namespace TagLib {
*/
ChannelMode channelMode() const;
/*!
* MPEG-4 channel configuration.
*/
enum ChannelConfiguration {
Custom = 0,
FrontCenter = 1,
FrontLeftRight = 2,
FrontCenterLeftRight = 3,
FrontCenterLeftRightBackCenter = 4,
FrontCenterLeftRightBackLeftRight = 5,
FrontCenterLeftRightBackLeftRightLFE = 6,
FrontCenterLeftRightSideLeftRightBackLeftRightLFE = 7
};
/*!
* Returns the MPEG-4 channel configuration.
*/
ChannelConfiguration channelConfiguration() const;
/*!
* Returns true if this is the header of an Audio Data Transport Stream
* (ADTS), usually AAC.
*/
bool isADTS() const;
/*!
* Returns true if the copyrighted bit is set.
*/

View File

@ -44,6 +44,7 @@ public:
int layer { 0 };
Header::Version version { Header::Version1 };
Header::ChannelMode channelMode { Header::Stereo };
Header::ChannelConfiguration channelConfiguration { Header::Custom };
bool protectionEnabled { false };
bool isCopyrighted { false };
bool isOriginal { false };
@ -57,7 +58,7 @@ MPEG::Properties::Properties(File *file, ReadStyle style) :
AudioProperties(style),
d(std::make_unique<PropertiesPrivate>())
{
read(file);
read(file, style);
}
MPEG::Properties::~Properties() = default;
@ -107,6 +108,17 @@ MPEG::Header::ChannelMode MPEG::Properties::channelMode() const
return d->channelMode;
}
MPEG::Header::ChannelConfiguration MPEG::Properties::channelConfiguration() const
{
return d->channelConfiguration;
}
bool MPEG::Properties::isADTS() const
{
return d->layer == 0 &&
(d->version == Header::Version2 || d->version == Header::Version4);
}
bool MPEG::Properties::isCopyrighted() const
{
return d->isCopyrighted;
@ -121,7 +133,7 @@ bool MPEG::Properties::isOriginal() const
// private members
////////////////////////////////////////////////////////////////////////////////
void MPEG::Properties::read(File *file)
void MPEG::Properties::read(File *file, ReadStyle readStyle)
{
// Only the first valid frame is required if we have a VBR header.
@ -152,33 +164,113 @@ void MPEG::Properties::read(File *file)
d->length = static_cast<int>(length + 0.5);
d->bitrate = static_cast<int>(d->xingHeader->totalSize() * 8.0 / length + 0.5);
}
else if(firstHeader.bitrate() > 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.
d->bitrate = firstHeader.bitrate();
// Look for the last MPEG audio frame to calculate the stream length.
const offset_t lastFrameOffset = file->lastFrameOffset();
if(lastFrameOffset < 0) {
debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream.");
else {
int bitrate = firstHeader.bitrate();
if(firstHeader.isADTS()) {
// ADTS is probably VBR, so to get the real length, we would have to go
// through all frames, count the frames in numFrames and sum their
// header.frameLength() to totalFrameSize, and finally calculate
// d->length = 1000LL * numFrames * firstHeader.samplesPerFrame() / firstHeader.sampleRate();
// d->bitrate = d->length > 0 ? totalFrameSize * 8 / d->length : 0;
//
// With Fast read style, we do not try to estimate the length and just set
// it and the bitrate to zero.
// With Average read style, in order to come faster to an estimate which
// is accurate enough, we stop when the average bytes/frame rate is stable
// for 10 frames and then calculate the length from the estimated bitrate
// and the stream length.
if(readStyle == Fast) {
bitrate = 0;
d->length = 0;
}
else {
Header header(firstHeader);
unsigned long long totalFrameSize = header.frameLength();
unsigned long long bytesPerFrame = 0;
unsigned long long lastBytesPerFrame = 0;
offset_t offset = firstFrameOffset;
offset_t nextOffset;
int numFrames = 1;
int sameBytesPerFrameCount = 0;
while((nextOffset = file->nextFrameOffset(offset + header.frameLength())) > offset) {
offset = nextOffset;
header = Header(file, offset, false);
totalFrameSize += header.frameLength();
++numFrames;
bytesPerFrame = totalFrameSize / numFrames;
if(readStyle != Accurate) {
if(bytesPerFrame == lastBytesPerFrame) {
if(++sameBytesPerFrameCount >= 10) {
break;
}
}
else {
sameBytesPerFrameCount = 0;
}
lastBytesPerFrame = bytesPerFrame;
}
}
bitrate = firstHeader.samplesPerFrame() != 0
? static_cast<int>((bytesPerFrame * 8 * firstHeader.sampleRate())
/ 1000 / firstHeader.samplesPerFrame())
: 0;
}
}
else
{
const Header lastHeader(file, lastFrameOffset, false);
const offset_t streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength();
if (streamLength > 0)
d->length = static_cast<int>(streamLength * 8.0 / d->bitrate + 0.5);
else if(firstHeader.bitrate() > 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.
bitrate = firstHeader.bitrate();
}
if(bitrate > 0) {
d->bitrate = bitrate;
// Look for the last MPEG audio frame to calculate the stream length.
const offset_t lastFrameOffset = file->lastFrameOffset();
if(lastFrameOffset < 0) {
debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream.");
}
else
{
const Header lastHeader(file, lastFrameOffset, false);
const offset_t streamLength = lastFrameOffset - firstFrameOffset + lastHeader.frameLength();
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->channelConfiguration = firstHeader.channelConfiguration();
switch(d->channelConfiguration) {
case Header::FrontCenter:
d->channels = 1;
break;
case Header::FrontLeftRight:
d->channels = 2;
break;
case Header::FrontCenterLeftRight:
d->channels = 3;
break;
case Header::FrontCenterLeftRightBackCenter:
d->channels = 4;
break;
case Header::FrontCenterLeftRightBackLeftRight:
d->channels = 5;
break;
case Header::FrontCenterLeftRightBackLeftRightLFE:
d->channels = 6;
break;
case Header::FrontCenterLeftRightSideLeftRightBackLeftRightLFE:
d->channels = 8;
break;
case Header::Custom:
default:
d->channels = firstHeader.channelMode() == Header::SingleChannel ? 1 : 2;
}
d->version = firstHeader.version();
d->layer = firstHeader.layer();
d->protectionEnabled = firstHeader.protectionEnabled();

View File

@ -109,6 +109,16 @@ namespace TagLib {
*/
Header::ChannelMode channelMode() const;
/*!
* Returns the MPEG-4 channel configuration.
*/
Header::ChannelConfiguration channelConfiguration() const;
/*!
* Returns true for an Audio Data Transport Stream (ADTS), usually AAC.
*/
bool isADTS() const;
/*!
* Returns true if the copyrighted bit is set.
*/
@ -120,7 +130,7 @@ namespace TagLib {
bool isOriginal() const;
private:
void read(File *file);
void read(File *file, ReadStyle readStyle);
class PropertiesPrivate;
std::unique_ptr<PropertiesPrivate> d;

BIN
tests/data/empty1s.aac Normal file

Binary file not shown.

View File

@ -25,6 +25,7 @@
#include <string>
#include <cstdio>
#include <array>
#include "tstring.h"
#include "tpropertymap.h"
@ -49,6 +50,7 @@ class TestMPEG : public CppUnit::TestFixture
CPPUNIT_TEST(testAudioPropertiesXingHeaderVBR);
CPPUNIT_TEST(testAudioPropertiesVBRIHeader);
CPPUNIT_TEST(testAudioPropertiesNoVBRHeaders);
CPPUNIT_TEST(testAudioPropertiesADTS);
CPPUNIT_TEST(testSkipInvalidFrames1);
CPPUNIT_TEST(testSkipInvalidFrames2);
CPPUNIT_TEST(testSkipInvalidFrames3);
@ -83,6 +85,7 @@ public:
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type());
CPPUNIT_ASSERT(!f.audioProperties()->isADTS());
}
void testAudioPropertiesXingHeaderVBR()
@ -95,6 +98,7 @@ public:
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::Xing, f.audioProperties()->xingHeader()->type());
CPPUNIT_ASSERT(!f.audioProperties()->isADTS());
}
void testAudioPropertiesVBRIHeader()
@ -107,6 +111,7 @@ public:
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT_EQUAL(MPEG::XingHeader::VBRI, f.audioProperties()->xingHeader()->type());
CPPUNIT_ASSERT(!f.audioProperties()->isADTS());
}
void testAudioPropertiesNoVBRHeaders()
@ -119,6 +124,7 @@ public:
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT(!f.audioProperties()->xingHeader());
CPPUNIT_ASSERT(!f.audioProperties()->isADTS());
const offset_t last = f.lastFrameOffset();
const MPEG::Header lastHeader(&f, last, false);
@ -127,6 +133,37 @@ public:
CPPUNIT_ASSERT_EQUAL(209, lastHeader.frameLength());
}
void testAudioPropertiesADTS()
{
const std::array readStyles = {
MPEG::Properties::Fast,
MPEG::Properties::Average,
MPEG::Properties::Accurate
};
for(auto readStyle : readStyles) {
MPEG::File f(TEST_FILE_PATH_C("empty1s.aac"), true, readStyle);
CPPUNIT_ASSERT(f.audioProperties());
CPPUNIT_ASSERT_EQUAL(readStyle == MPEG::Properties::Fast ? 0 : 1,
f.audioProperties()->lengthInSeconds());
CPPUNIT_ASSERT_EQUAL(readStyle == MPEG::Properties::Fast ? 0 : 1176,
f.audioProperties()->lengthInMilliseconds());
CPPUNIT_ASSERT_EQUAL(readStyle == MPEG::Properties::Fast ? 0 : 1,
f.audioProperties()->bitrate());
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(MPEG::Header::FrontCenter,
f.audioProperties()->channelConfiguration());
CPPUNIT_ASSERT_EQUAL(11025, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT(!f.audioProperties()->xingHeader());
CPPUNIT_ASSERT(f.audioProperties()->isADTS());
const offset_t last = f.lastFrameOffset();
const MPEG::Header lastHeader(&f, last, false);
CPPUNIT_ASSERT_EQUAL(static_cast<offset_t>(136), last);
CPPUNIT_ASSERT_EQUAL(11, lastHeader.frameLength());
}
}
void testSkipInvalidFrames1()
{
MPEG::File f(TEST_FILE_PATH_C("invalid-frames1.mp3"));
@ -137,6 +174,7 @@ public:
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT(!f.audioProperties()->xingHeader());
CPPUNIT_ASSERT(!f.audioProperties()->isADTS());
}
void testSkipInvalidFrames2()
@ -149,6 +187,7 @@ public:
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT(!f.audioProperties()->xingHeader());
CPPUNIT_ASSERT(!f.audioProperties()->isADTS());
}
void testSkipInvalidFrames3()
@ -161,6 +200,7 @@ public:
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT(!f.audioProperties()->xingHeader());
CPPUNIT_ASSERT(!f.audioProperties()->isADTS());
}
void testVersion2DurationWithXingHeader()