diff options
-rw-r--r-- | taglib/mp4/mp4properties.cpp | 37 | ||||
-rw-r--r-- | tests/test_mp4.cpp | 48 |
2 files changed, 84 insertions, 1 deletions
diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp index faa43c27..6c6976fa 100644 --- a/taglib/mp4/mp4properties.cpp +++ b/taglib/mp4/mp4properties.cpp @@ -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; + } } } diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 9e52de2b..ba38c0f0 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -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")); |