Skip to content

Commit

Permalink
Raw AAC (ADTS) support (taglib#508)
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: Urs Fleisch <[email protected]>
  • Loading branch information
3 people authored Nov 3, 2023
1 parent a7c0b53 commit 3869aa1
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 84 deletions.
3 changes: 2 additions & 1 deletion taglib/fileref.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion taglib/mpeg/mpegfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
180 changes: 123 additions & 57 deletions taglib/mpeg/mpegheader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class MPEG::Header::HeaderPrivate
int sampleRate { 0 };
bool isPadded { false };
ChannelMode channelMode { Stereo };
ChannelConfiguration channelConfiguration { Custom };
bool isCopyrighted { false };
bool isOriginal { false };
int frameLength { 0 };
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
d->bitrate = static_cast<int>(d->frameLength * d->sampleRate / 1024.0 + 0.5) * 8 / 1024;
}
}
else {
// Not ADTS

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
};
// Set the bitrate

// The sample rate index is encoded as two bits in the 3rd byte, i.e. xxxx11xx
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) & 0x03;
const int versionIndex = (d->version == Version1) ? 0 : 1;
const int layerIndex = (d->layer > 0) ? d->layer - 1 : 0;

d->sampleRate = sampleRates[d->version][samplerateIndex];
// The bitrate index is encoded as the first 4 bits of the 3rd byte,
// i.e. 1111xxxx

if(d->sampleRate == 0) {
return;
}
const int bitrateIndex = (static_cast<unsigned char>(data[2]) >> 4) & 0x0F;

d->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex];

if(d->bitrate == 0)
return;

// 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 channel mode is encoded as a 2 bit value at the end of the 3rd byte,
// i.e. xxxxxx11
// The sample rate index is encoded as two bits in the 3rd byte, i.e. xxxx11xx

d->channelMode = static_cast<ChannelMode>((static_cast<unsigned char>(data[3]) >> 6) & 0x03);
const int samplerateIndex = (static_cast<unsigned char>(data[2]) >> 2) & 0x03;

// TODO: Add mode extension for completeness
d->sampleRate = sampleRates[d->version][samplerateIndex];

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

// Samples per frame
// TODO: Add mode extension for completeness

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

d->samplesPerFrame = versionIndex ? samplesPerFrame[layerIndex].second : samplesPerFrame[layerIndex].first;
// Samples per frame

// Calculate the frame length
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
};

static constexpr std::array paddingSize { 4, 1, 1 };
d->samplesPerFrame = versionIndex ? samplesPerFrame[layerIndex].second : samplesPerFrame[layerIndex].first;

d->frameLength = d->samplesPerFrame * d->bitrate * 125 / d->sampleRate;
// Calculate the frame length

if(d->isPadded)
d->frameLength += paddingSize[layerIndex];
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) {

Expand Down
29 changes: 28 additions & 1 deletion taglib/mpeg/mpegheader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

/*!
Expand Down Expand Up @@ -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.
*/
Expand Down
Loading

0 comments on commit 3869aa1

Please sign in to comment.