mirror of
https://github.com/taglib/taglib.git
synced 2026-08-14 06:17:00 -04:00
Adds MP4::Properties::Codec enum values AC3, EAC3, FLAC, DTS, Opus and a MP4::Properties::codecId() method. Parse high-res FLAC bitrate from MP4 dfLa box. Parse E-AC-3 bitrate from MP4 dec3 box. The test files were generated using ffmpeg: ffmpeg -hide_banner -y -f lavfi \ -i "sine=frequency=440:sample_rate=48000:duration=1" \ -c:a flac -ac 2 -flags +bitexact -fflags +bitexact \ -metadata:s:a:0 encoder= -metadata encoder= \ -f mp4 tests/data/flac.m4a ffmpeg -hide_banner -y -f lavfi \ -i "sine=frequency=440:sample_rate=48000:duration=1" \ -c:a libopus -ac 2 -flags +bitexact -fflags +bitexact \ -metadata:s:a:0 encoder= -metadata encoder= \ -f mp4 tests/data/opus.m4a ffmpeg -hide_banner -y -f lavfi \ -i "sine=frequency=440:sample_rate=48000:duration=1" \ -c:a ac3 -b:a 128k -ac 2 -strict experimental \ -flags +bitexact -fflags +bitexact \ -metadata:s:a:0 encoder= -metadata encoder= \ -f mp4 tests/data/ac3.m4a ffmpeg -hide_banner -y -f lavfi \ -i "sine=frequency=440:sample_rate=48000:duration=1" \ -c:a eac3 -b:a 128k -ac 2 -strict experimental \ -flags +bitexact -fflags +bitexact \ -metadata:s:a:0 encoder= -metadata encoder= \ -f mp4 tests/data/eac3.m4a ffmpeg -hide_banner -y -f lavfi \ -i "sine=frequency=440:sample_rate=96000:duration=1" \ -c:a flac -sample_fmt s32 -ac 2 -flags +bitexact -fflags +bitexact \ -metadata:s:a:0 encoder= -metadata encoder= \ -f mp4 tests/data/flac96.m4a
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "tdebug.h"
|
||||
#include "tstring.h"
|
||||
#include "tmap.h"
|
||||
#include "mp4file.h"
|
||||
#include "mp4atom.h"
|
||||
|
||||
@@ -63,6 +64,7 @@ public:
|
||||
int bitsPerSample { 0 };
|
||||
bool encrypted { false };
|
||||
Codec codec { MP4::Properties::Unknown };
|
||||
String codecId;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -120,6 +122,12 @@ MP4::Properties::codec() const
|
||||
return d->codec;
|
||||
}
|
||||
|
||||
String
|
||||
MP4::Properties::codecId() const
|
||||
{
|
||||
return d->codecId;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// private members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -205,6 +213,12 @@ MP4::Properties::read(File *file, const Atoms *atoms)
|
||||
|
||||
file->seek(atom->offset());
|
||||
data = file->readBlock(atom->length());
|
||||
|
||||
// The four character code of the first sample entry identifies the codec and
|
||||
// is exposed directly, also for codecs not represented by the Codec enum.
|
||||
if(data.size() >= 24)
|
||||
d->codecId = String(data.mid(20, 4));
|
||||
|
||||
if(data.containsAt("mp4a", 20)) {
|
||||
d->codec = AAC;
|
||||
d->channels = data.toShort(40U);
|
||||
@@ -249,6 +263,53 @@ MP4::Properties::read(File *file, const Atoms *atoms)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(data.size() >= 50) {
|
||||
// Other audio codecs use the same AudioSampleEntry layout as above, so the
|
||||
// basic properties can be read from the same offsets. The nominal bitrate
|
||||
// is not parsed from the codec specific configuration box; it is estimated
|
||||
// from the audio data size and the duration.
|
||||
static const Map<ByteVector, Codec> codecMap {
|
||||
{"ac-3", AC3},
|
||||
{"ec-3", EAC3},
|
||||
{"fLaC", FLAC},
|
||||
{"Opus", Opus},
|
||||
{"dtsc", DTS},
|
||||
{"dtse", DTS},
|
||||
{"dtsh", DTS},
|
||||
{"dtsl", DTS}
|
||||
};
|
||||
d->codec = codecMap.value(data.mid(20, 4), Unknown);
|
||||
d->channels = data.toShort(40U);
|
||||
d->bitsPerSample = data.toShort(42U);
|
||||
d->sampleRate = data.toUInt(46U);
|
||||
|
||||
if(d->codec == FLAC) {
|
||||
// The AudioSampleEntry sample rate field is only 16 bits wide, so read
|
||||
// the exact values from the FLAC STREAMINFO metadata block in the 'dfLa'
|
||||
// box. This is required for high resolution files (e.g. 96 kHz, 24 bit).
|
||||
const auto dflaPos = data.find("dfLa");
|
||||
if(const auto dflaOffset = static_cast<unsigned int>(dflaPos);
|
||||
dflaPos >= 0 && data.size() >= dflaOffset + 26 &&
|
||||
(static_cast<unsigned char>(data.at(dflaOffset + 8)) & 0x7f) == 0) {
|
||||
const auto streamInfo = data.toUInt(dflaOffset + 22);
|
||||
if(const auto sampleRate = streamInfo >> 12; sampleRate != 0)
|
||||
d->sampleRate = sampleRate;
|
||||
d->channels = ((streamInfo >> 9) & 0x7) + 1;
|
||||
d->bitsPerSample = ((streamInfo >> 4) & 0x1f) + 1;
|
||||
}
|
||||
}
|
||||
else if(d->codec == EAC3) {
|
||||
// The 'dec3' box (EC3SpecificBox) starts with the nominal data rate in
|
||||
// kbit/s as a 13 bit value.
|
||||
const auto dec3Pos = data.find("dec3");
|
||||
if(const auto dec3Offset = static_cast<unsigned int>(dec3Pos);
|
||||
dec3Pos >= 0 && data.size() >= dec3Offset + 6)
|
||||
d->bitrate = data.toUShort(dec3Offset + 4) >> 3;
|
||||
}
|
||||
|
||||
if(d->bitrate == 0 && d->length > 0)
|
||||
d->bitrate = static_cast<int>(calculateMdatLength(atoms->atoms()) * 8 / d->length);
|
||||
}
|
||||
|
||||
if(atom->find("drms")) {
|
||||
d->encrypted = true;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#define TAGLIB_MP4PROPERTIES_H
|
||||
|
||||
#include "taglib_export.h"
|
||||
#include "tstring.h"
|
||||
#include "audioproperties.h"
|
||||
|
||||
namespace TagLib {
|
||||
@@ -41,7 +42,12 @@ namespace TagLib {
|
||||
enum Codec {
|
||||
Unknown = 0,
|
||||
AAC,
|
||||
ALAC
|
||||
ALAC,
|
||||
AC3,
|
||||
EAC3,
|
||||
FLAC,
|
||||
DTS,
|
||||
Opus
|
||||
};
|
||||
|
||||
Properties(File *file, const Atoms *atoms, ReadStyle style = Average);
|
||||
@@ -87,6 +93,13 @@ namespace TagLib {
|
||||
*/
|
||||
Codec codec() const;
|
||||
|
||||
/*!
|
||||
* Returns the four character code identifying the codec, as found in the
|
||||
* sample description atom (e.g. "mp4a", "alac", "ec-3"). This is useful
|
||||
* for codecs which are not represented by a value of the Codec enum.
|
||||
*/
|
||||
String codecId() const;
|
||||
|
||||
private:
|
||||
void read(File *file, const Atoms *atoms);
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -130,6 +130,11 @@ class TestMP4 : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testPropertiesALAC);
|
||||
CPPUNIT_TEST(testPropertiesALACWithoutBitrate);
|
||||
CPPUNIT_TEST(testPropertiesAACWithoutLength);
|
||||
CPPUNIT_TEST(testPropertiesAC3);
|
||||
CPPUNIT_TEST(testPropertiesEAC3);
|
||||
CPPUNIT_TEST(testPropertiesFLAC);
|
||||
CPPUNIT_TEST(testPropertiesFLACHighResolution);
|
||||
CPPUNIT_TEST(testPropertiesOpus);
|
||||
CPPUNIT_TEST(testPropertiesM4V);
|
||||
CPPUNIT_TEST(testFreeForm);
|
||||
CPPUNIT_TEST(testCheckValid);
|
||||
@@ -271,6 +276,75 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec());
|
||||
}
|
||||
|
||||
void testPropertiesAC3()
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("ac3.m4a"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT(f.audioProperties()->bitrate() > 0);
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted());
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::AC3, f.audioProperties()->codec());
|
||||
CPPUNIT_ASSERT_EQUAL(String("ac-3"), f.audioProperties()->codecId());
|
||||
}
|
||||
|
||||
void testPropertiesEAC3()
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("eac3.m4a"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(128, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted());
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::EAC3, f.audioProperties()->codec());
|
||||
CPPUNIT_ASSERT_EQUAL(String("ec-3"), f.audioProperties()->codecId());
|
||||
}
|
||||
|
||||
void testPropertiesFLAC()
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("flac.m4a"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample());
|
||||
CPPUNIT_ASSERT(f.audioProperties()->bitrate() > 0);
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted());
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::FLAC, f.audioProperties()->codec());
|
||||
CPPUNIT_ASSERT_EQUAL(String("fLaC"), f.audioProperties()->codecId());
|
||||
}
|
||||
|
||||
void testPropertiesFLACHighResolution()
|
||||
{
|
||||
// The AudioSampleEntry sample rate field cannot hold rates above 65535 Hz,
|
||||
// so the exact values must come from the FLAC STREAMINFO in the 'dfLa' box.
|
||||
MP4::File f(TEST_FILE_PATH_C("flac96.m4a"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(96000, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT_EQUAL(24, f.audioProperties()->bitsPerSample());
|
||||
CPPUNIT_ASSERT(f.audioProperties()->bitrate() > 0);
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted());
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::FLAC, f.audioProperties()->codec());
|
||||
CPPUNIT_ASSERT_EQUAL(String("fLaC"), f.audioProperties()->codecId());
|
||||
}
|
||||
|
||||
void testPropertiesOpus()
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("opus.m4a"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT(f.audioProperties()->bitrate() > 0);
|
||||
CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted());
|
||||
CPPUNIT_ASSERT_EQUAL(MP4::Properties::Opus, f.audioProperties()->codec());
|
||||
CPPUNIT_ASSERT_EQUAL(String("Opus"), f.audioProperties()->codecId());
|
||||
}
|
||||
|
||||
void testPropertiesM4V()
|
||||
{
|
||||
MP4::File f(TEST_FILE_PATH_C("blank_video.m4v"));
|
||||
|
||||
Reference in New Issue
Block a user