Merge pull request #981 from ufleisch/ufleisch/alac-without-bitrate

Calculate bitrate for ALAC files without it (#961)
This commit is contained in:
Urs Fleisch 2021-01-01 11:45:12 +01:00
commit 54f5c66b65
2 changed files with 84 additions and 1 deletions

View File

@ -31,6 +31,27 @@
using namespace TagLib;
namespace
{
// Calculate the total bytes used by audio data, used to calculate the bitrate
long long calculateMdatLength(const MP4::AtomList &list)
{
long long totalLength = 0;
for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) {
long length = (*it)->length;
if(length == 0)
return 0; // for safety, see checkValid() in mp4file.cpp
if((*it)->name == "mdat")
totalLength += length;
totalLength += calculateMdatLength((*it)->children);
}
return totalLength;
}
}
class MP4::Properties::PropertiesPrivate
{
public:
@ -213,7 +234,14 @@ MP4::Properties::read(File *file, Atoms *atoms)
pos += 3;
}
pos += 10;
d->bitrate = static_cast<int>((data.toUInt(pos) + 500) / 1000.0 + 0.5);
const unsigned int bitrateValue = data.toUInt(pos);
if(bitrateValue != 0 || d->length <= 0) {
d->bitrate = static_cast<int>((bitrateValue + 500) / 1000.0 + 0.5);
}
else {
d->bitrate = static_cast<int>(
(calculateMdatLength(atoms->atoms) * 8) / d->length);
}
}
}
}
@ -224,6 +252,13 @@ MP4::Properties::read(File *file, Atoms *atoms)
d->channels = data.at(73);
d->bitrate = static_cast<int>(data.toUInt(80U) / 1000.0 + 0.5);
d->sampleRate = data.toUInt(84U);
if(d->bitrate == 0 && d->length > 0) {
// There are files which do not contain a nominal bitrate, e.g. those
// generated by refalac64.exe. Calculate the bitrate from the audio
// data size (mdat atoms) and the duration.
d->bitrate = (calculateMdatLength(atoms->atoms) * 8) / d->length;
}
}
}

View File

@ -28,10 +28,12 @@
#include <tag.h>
#include <mp4tag.h>
#include <tbytevectorlist.h>
#include <tbytevectorstream.h>
#include <tpropertymap.h>
#include <mp4atom.h>
#include <mp4file.h>
#include <cppunit/extensions/HelperMacros.h>
#include "plainfile.h"
#include "utils.h"
using namespace std;
@ -41,7 +43,9 @@ class TestMP4 : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestMP4);
CPPUNIT_TEST(testPropertiesAAC);
CPPUNIT_TEST(testPropertiesAACWithoutBitrate);
CPPUNIT_TEST(testPropertiesALAC);
CPPUNIT_TEST(testPropertiesALACWithoutBitrate);
CPPUNIT_TEST(testPropertiesM4V);
CPPUNIT_TEST(testFreeForm);
CPPUNIT_TEST(testCheckValid);
@ -78,6 +82,28 @@ public:
CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec());
}
void testPropertiesAACWithoutBitrate()
{
ByteVector aacData = PlainFile(TEST_FILE_PATH_C("has-tags.m4a")).readAll();
CPPUNIT_ASSERT_GREATER(1960U, aacData.size());
CPPUNIT_ASSERT_EQUAL(ByteVector("mp4a"), aacData.mid(1890, 4));
// Set the bitrate to zero
for (int offset = 1956; offset < 1960; ++offset) {
aacData[offset] = 0;
}
ByteVectorStream aacStream(aacData);
MP4::File f(&aacStream);
CPPUNIT_ASSERT(f.audioProperties());
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds());
CPPUNIT_ASSERT_EQUAL(3708, f.audioProperties()->lengthInMilliseconds());
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate());
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample());
CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted());
CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec());
}
void testPropertiesALAC()
{
MP4::File f(TEST_FILE_PATH_C("empty_alac.m4a"));
@ -92,6 +118,28 @@ public:
CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec());
}
void testPropertiesALACWithoutBitrate()
{
ByteVector alacData = PlainFile(TEST_FILE_PATH_C("empty_alac.m4a")).readAll();
CPPUNIT_ASSERT_GREATER(474U, alacData.size());
CPPUNIT_ASSERT_EQUAL(ByteVector("alac"), alacData.mid(446, 4));
// Set the bitrate to zero
for (int offset = 470; offset < 474; ++offset) {
alacData[offset] = 0;
}
ByteVectorStream alacStream(alacData);
MP4::File f(&alacStream);
CPPUNIT_ASSERT(f.audioProperties());
CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds());
CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds());
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->bitrate());
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample());
CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted());
CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec());
}
void testPropertiesM4V()
{
MP4::File f(TEST_FILE_PATH_C("blank_video.m4v"));