7 #include "../av1/av1configuration.h" 9 #include "../avc/avcconfiguration.h" 11 #include "../mpegaudio/mpegaudioframe.h" 12 #include "../mpegaudio/mpegaudioframestream.h" 14 #include "../exceptions.h" 15 #include "../mediaformat.h" 17 #include <c++utilities/conversion/stringbuilder.h> 18 #include <c++utilities/io/binaryreader.h> 19 #include <c++utilities/io/binarywriter.h> 20 #include <c++utilities/io/bitreader.h> 21 #include <c++utilities/io/catchiofailure.h> 56 byte additionalDataOffset;
61 constexpr TrackHeaderInfo::TrackHeaderInfo()
63 , canUseExisting(false)
66 , versionUnknown(false)
67 , additionalDataOffset(0)
68 , discardBuffer(false)
83 , sampleFrequencyIndex(0xF)
85 , channelConfiguration(0)
86 , extensionAudioObjectType(0)
89 , extensionSampleFrequencyIndex(0xF)
90 , extensionSampleFrequency(0)
91 , extensionChannelConfiguration(0)
92 , frameLengthFlag(false)
93 , dependsOnCoreCoder(false)
135 , m_trakAtom(&trakAtom)
136 , m_tkhdAtom(nullptr)
137 , m_mdiaAtom(nullptr)
138 , m_mdhdAtom(nullptr)
139 , m_hdlrAtom(nullptr)
140 , m_minfAtom(nullptr)
141 , m_stblAtom(nullptr)
142 , m_stsdAtom(nullptr)
143 , m_stscAtom(nullptr)
144 , m_stcoAtom(nullptr)
145 , m_stszAtom(nullptr)
150 , m_chunkOffsetSize(4)
152 , m_sampleToChunkEntryCount(0)
180 static const string context(
"reading chunk offset table of MP4 track");
185 vector<uint64> offsets;
188 uint64 actualTableSize = m_stcoAtom->
dataSize();
190 diag.emplace_back(
DiagLevel::Critical,
"The stco atom is truncated. There are no chunk offsets present.", context);
193 actualTableSize -= 8;
197 if (calculatedTableSize < actualTableSize) {
199 DiagLevel::Critical,
"The stco atom stores more chunk offsets as denoted. The additional chunk offsets will be ignored.", context);
200 }
else if (calculatedTableSize > actualTableSize) {
201 diag.emplace_back(
DiagLevel::Critical,
"The stco atom is truncated. It stores less chunk offsets as denoted.", context);
202 actualChunkCount =
static_cast<uint32
>(floor(static_cast<double>(actualTableSize) / static_cast<double>(
chunkOffsetSize())));
205 offsets.reserve(actualChunkCount);
209 for (uint32 i = 0; i < actualChunkCount; ++i) {
210 offsets.push_back(
reader().readUInt32BE());
214 for (uint32 i = 0; i < actualChunkCount; ++i) {
215 offsets.push_back(
reader().readUInt64BE());
219 diag.emplace_back(
DiagLevel::Critical,
"The determined chunk offset size is invalid.", context);
224 if (parseFragments) {
225 uint64 totalDuration = 0;
228 moofAtom->parse(diag);
231 trafAtom->parse(diag);
234 tfhdAtom->parse(diag);
235 uint32 calculatedDataSize = 0;
236 if (tfhdAtom->dataSize() < calculatedDataSize) {
240 const uint32 flags =
reader().readUInt24BE();
242 if (flags & 0x000001) {
243 calculatedDataSize += 8;
245 if (flags & 0x000002) {
246 calculatedDataSize += 4;
248 if (flags & 0x000008) {
249 calculatedDataSize += 4;
251 if (flags & 0x000010) {
252 calculatedDataSize += 4;
254 if (flags & 0x000020) {
255 calculatedDataSize += 4;
260 uint32 defaultSampleDuration = 0;
261 uint32 defaultSampleSize = 0;
263 if (tfhdAtom->dataSize() < calculatedDataSize) {
264 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
266 if (flags & 0x000001) {
270 if (flags & 0x000002) {
274 if (flags & 0x000008) {
275 defaultSampleDuration =
reader().readUInt32BE();
278 if (flags & 0x000010) {
279 defaultSampleSize =
reader().readUInt32BE();
281 if (flags & 0x000020) {
288 uint32 calculatedDataSize = 8;
289 if (trunAtom->dataSize() < calculatedDataSize) {
293 uint32 flags =
reader().readUInt24BE();
296 if (flags & 0x000001) {
297 calculatedDataSize += 4;
299 if (flags & 0x000004) {
300 calculatedDataSize += 4;
302 uint32 entrySize = 0;
303 if (flags & 0x000100) {
306 if (flags & 0x000200) {
309 if (flags & 0x000400) {
312 if (flags & 0x000800) {
316 if (trunAtom->dataSize() < calculatedDataSize) {
317 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
319 if (flags & 0x000001) {
323 if (flags & 0x000004) {
327 if (flags & 0x000100) {
328 totalDuration +=
reader().readUInt32BE();
330 totalDuration += defaultSampleDuration;
332 if (flags & 0x000200) {
333 m_sampleSizes.push_back(
reader().readUInt32BE());
334 m_size += m_sampleSizes.back();
336 m_size += defaultSampleSize;
338 if (flags & 0x000400) {
341 if (flags & 0x000800) {
348 if (m_sampleSizes.empty() && defaultSampleSize) {
349 m_sampleSizes.push_back(defaultSampleSize);
364 uint64 Mp4Track::accumulateSampleSizes(
size_t &sampleIndex,
size_t count,
Diagnostics &diag)
366 if (sampleIndex + count <= m_sampleSizes.size()) {
368 for (
size_t end = sampleIndex + count; sampleIndex < end; ++sampleIndex) {
369 sum += m_sampleSizes[sampleIndex];
372 }
else if (m_sampleSizes.size() == 1) {
373 sampleIndex += count;
374 return static_cast<uint64
>(m_sampleSizes.front()) * count;
376 diag.emplace_back(
DiagLevel::Critical,
"There are not as many sample size entries as samples.",
"reading chunk sizes of MP4 track");
377 throw InvalidDataException();
389 void Mp4Track::addChunkSizeEntries(std::vector<uint64> &chunkSizeTable,
size_t count,
size_t &sampleIndex, uint32 sampleCount, Diagnostics &diag)
391 for (
size_t i = 0; i < count; ++i) {
392 chunkSizeTable.push_back(accumulateSampleSizes(sampleIndex,
sampleCount, diag));
400 TrackHeaderInfo Mp4Track::verifyPresentTrackHeader()
const 402 TrackHeaderInfo info;
410 info.discardBuffer = m_tkhdAtom->
buffer() ==
nullptr;
411 if (info.discardBuffer) {
416 switch (info.version = static_cast<byte>(m_tkhdAtom->
buffer()[m_tkhdAtom->
headerSize()])) {
418 info.additionalDataOffset = 32;
421 info.additionalDataOffset = 44;
424 info.additionalDataOffset = 44;
425 info.versionUnknown =
true;
429 if (info.additionalDataOffset + 48u <= m_tkhdAtom->dataSize()) {
430 info.canUseExisting =
true;
432 info.truncated =
true;
433 info.canUseExisting = info.additionalDataOffset < m_tkhdAtom->
dataSize();
434 if (!info.canUseExisting && info.discardBuffer) {
440 info.requiredSize = m_tkhdAtom->
dataSize() + 8;
441 if (info.version == 0) {
443 info.requiredSize += 12;
445 if (info.requiredSize > numeric_limits<uint32>::max()) {
447 info.requiredSize += 8;
461 static const string context(
"reading sample to chunk table of MP4 track");
463 diag.emplace_back(
DiagLevel::Critical,
"Track has not been parsed or is invalid.", context);
467 uint64 actualTableSize = m_stscAtom->
dataSize();
468 if (actualTableSize < 20) {
469 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom is truncated. There are no \"sample to chunk\" entries present.", context);
472 actualTableSize -= 8;
475 uint64 calculatedTableSize = actualSampleToChunkEntryCount * 12;
476 if (calculatedTableSize < actualTableSize) {
477 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom stores more entries as denoted. The additional entries will be ignored.", context);
478 }
else if (calculatedTableSize > actualTableSize) {
479 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom is truncated. It stores less entries as denoted.", context);
481 actualSampleToChunkEntryCount = floor(static_cast<double>(actualTableSize) / 12.0);
484 vector<tuple<uint32, uint32, uint32>> sampleToChunkTable;
485 sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
487 for (uint32 i = 0; i < actualSampleToChunkEntryCount; ++i) {
489 uint32 firstChunk =
reader().readUInt32BE();
490 uint32 samplesPerChunk =
reader().readUInt32BE();
491 uint32 sampleDescriptionIndex =
reader().readUInt32BE();
492 sampleToChunkTable.emplace_back(firstChunk, samplesPerChunk, sampleDescriptionIndex);
494 return sampleToChunkTable;
511 static const string context(
"reading chunk sizes of MP4 track");
513 diag.emplace_back(
DiagLevel::Critical,
"Track has not been parsed or is invalid.", context);
519 vector<uint64> chunkSizes;
520 if (!sampleToChunkTable.empty()) {
522 auto tableIterator = sampleToChunkTable.cbegin();
523 chunkSizes.reserve(m_chunkCount);
525 size_t sampleIndex = 0;
526 uint32 previousChunkIndex = get<0>(*tableIterator);
527 if (previousChunkIndex != 1) {
528 diag.emplace_back(
DiagLevel::Critical,
"The first chunk of the first \"sample to chunk\" entry must be 1.", context);
529 previousChunkIndex = 1;
531 uint32 samplesPerChunk = get<1>(*tableIterator);
534 for (
const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
535 uint32 firstChunkIndex = get<0>(*tableIterator);
536 if (firstChunkIndex > previousChunkIndex && firstChunkIndex <= m_chunkCount) {
537 addChunkSizeEntries(chunkSizes, firstChunkIndex - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
540 "The first chunk index of a \"sample to chunk\" entry must be greather than the first chunk of the previous entry and not " 541 "greather than the chunk count.",
545 previousChunkIndex = firstChunkIndex;
546 samplesPerChunk = get<1>(*tableIterator);
548 if (m_chunkCount >= previousChunkIndex) {
549 addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
564 static const string context(
"parsing MPEG-4 elementary stream descriptor");
565 using namespace Mpeg4ElementaryStreamObjectIds;
566 unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
570 if (
reader.readUInt32BE() != 0) {
583 reader.stream()->seekg(esDesc.dataOffset());
584 esInfo = make_unique<Mpeg4ElementaryStreamInfo>();
585 esInfo->id =
reader.readUInt16BE();
586 esInfo->esDescFlags =
reader.readByte();
587 if (esInfo->dependencyFlag()) {
588 esInfo->dependsOnId =
reader.readUInt16BE();
590 if (esInfo->urlFlag()) {
593 if (esInfo->ocrFlag()) {
594 esInfo->ocrId =
reader.readUInt16BE();
597 esDescChild; esDescChild = esDescChild->nextSibling()) {
598 esDescChild->parse(diag);
599 switch (esDescChild->id()) {
602 reader.stream()->seekg(esDescChild->dataOffset());
603 esInfo->objectTypeId =
reader.readByte();
604 esInfo->decCfgDescFlags =
reader.readByte();
605 esInfo->bufferSize =
reader.readUInt24BE();
606 esInfo->maxBitrate =
reader.readUInt32BE();
607 esInfo->averageBitrate =
reader.readUInt32BE();
609 decCfgDescChild = decCfgDescChild->nextSibling()) {
610 decCfgDescChild->parse(diag);
611 switch (decCfgDescChild->id()) {
614 switch (esInfo->objectTypeId) {
621 esInfo->audioSpecificConfig
625 esInfo->videoSpecificConfig
640 diag.emplace_back(
DiagLevel::Critical,
"The MPEG-4 descriptor element structure is invalid.", context);
643 diag.emplace_back(
DiagLevel::Warning,
"Elementary stream descriptor atom (esds) is truncated.", context);
656 static const string context(
"parsing MPEG-4 audio specific config from elementary stream descriptor");
657 using namespace Mpeg4AudioObjectIds;
660 auto buff = make_unique<char[]>(
size);
661 stream.read(buff.get(),
size);
662 BitReader bitReader(buff.get(),
size);
663 auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
666 auto getAudioObjectType = [&bitReader] {
667 byte objType = bitReader.readBits<byte>(5);
669 objType = 32 + bitReader.readBits<byte>(6);
673 audioCfg->audioObjectType = getAudioObjectType();
675 if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
676 audioCfg->sampleFrequency = bitReader.readBits<uint32>(24);
679 audioCfg->channelConfiguration = bitReader.readBits<byte>(4);
681 switch (audioCfg->audioObjectType) {
684 audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
685 audioCfg->sbrPresent =
true;
686 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
687 audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
689 if ((audioCfg->audioObjectType = getAudioObjectType()) ==
ErBsac) {
690 audioCfg->extensionChannelConfiguration = bitReader.readBits<byte>(4);
694 switch (audioCfg->extensionAudioObjectType) {
696 audioCfg->psPresent =
true;
701 switch (audioCfg->audioObjectType) {
713 audioCfg->frameLengthFlag = bitReader.readBits<byte>(1);
714 if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
715 audioCfg->coreCoderDelay = bitReader.readBits<byte>(14);
717 audioCfg->extensionFlag = bitReader.readBit();
718 if (audioCfg->channelConfiguration == 0) {
721 switch (audioCfg->audioObjectType) {
724 audioCfg->layerNr = bitReader.readBits<byte>(3);
728 if (audioCfg->extensionFlag == 1) {
729 switch (audioCfg->audioObjectType) {
731 audioCfg->numOfSubFrame = bitReader.readBits<byte>(5);
732 audioCfg->layerLength = bitReader.readBits<uint16>(11);
738 audioCfg->resilienceFlags = bitReader.readBits<byte>(3);
742 if (bitReader.readBit() == 1) {
751 switch (audioCfg->audioObjectType) {
763 switch (audioCfg->epConfig = bitReader.readBits<byte>(2)) {
767 bitReader.skipBits(1);
774 if (audioCfg->extensionAudioObjectType !=
Sbr && audioCfg->extensionAudioObjectType !=
Ps && bitReader.bitsAvailable() >= 16) {
775 uint16 syncExtensionType = bitReader.readBits<uint16>(11);
776 if (syncExtensionType == 0x2B7) {
777 if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) ==
Sbr) {
778 if ((audioCfg->sbrPresent = bitReader.readBit())) {
779 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
780 audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
782 if (bitReader.bitsAvailable() >= 12) {
783 if ((syncExtensionType = bitReader.readBits<uint16>(11)) == 0x548) {
784 audioCfg->psPresent = bitReader.readBits<byte>(1);
788 }
else if (audioCfg->extensionAudioObjectType ==
ErBsac) {
789 if ((audioCfg->sbrPresent = bitReader.readBit())) {
790 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
791 audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
794 audioCfg->extensionChannelConfiguration = bitReader.readBits<byte>(4);
796 }
else if (syncExtensionType == 0x548) {
797 audioCfg->psPresent = bitReader.readBit();
803 const char *what = catchIoFailure();
806 throwIoFailure(what);
809 diag.emplace_back(
DiagLevel::Critical,
"Audio specific configuration is truncated.", context);
823 static const string context(
"parsing MPEG-4 video specific config from elementary stream descriptor");
824 using namespace Mpeg4AudioObjectIds;
825 auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
828 if (
size > 3 && (
reader.readUInt24BE() == 1)) {
833 switch (
reader.readByte()) {
836 videoCfg->profile =
reader.readByte();
846 if ((buff1 =
reader.readUInt24BE()) != 1) {
847 reader.stream()->seekg(-2, ios_base::cur);
848 videoCfg->userData.push_back(buff1 >> 16);
855 if (buff1 != 1 &&
size > 0) {
856 videoCfg->userData +=
reader.readString(
size);
864 if (
reader.readUInt24BE() != 1) {
865 reader.stream()->seekg(-2, ios_base::cur);
874 diag.emplace_back(
DiagLevel::Critical,
"\"Visual Object Sequence Header\" not found.", context);
901 if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
904 static const unsigned int stcoDataBegin = 8;
905 uint64 startPos = m_stcoAtom->
dataOffset() + stcoDataBegin;
906 uint64 endPos = startPos + m_stcoAtom->
dataSize() - stcoDataBegin;
909 vector<int64>::size_type i;
910 vector<int64>::size_type
size;
912 switch (m_stcoAtom->
id()) {
915 while ((currentPos + 4) <= endPos) {
917 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
918 if (off > static_cast<uint64>(oldMdatOffsets[i])) {
919 off += (newMdatOffsets[i] - oldMdatOffsets[i]);
931 while ((currentPos + 8) <= endPos) {
933 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
934 if (off > static_cast<uint64>(oldMdatOffsets[i])) {
935 off += (newMdatOffsets[i] - oldMdatOffsets[i]);
971 switch (m_stcoAtom->
id()) {
973 for (
auto offset : chunkOffsets) {
978 for (
auto offset : chunkOffsets) {
1007 writer().writeUInt32BE(offset);
1010 writer().writeUInt64BE(offset);
1056 VAR_UNUSED(av1Config)
1073 trefAtom->makeBuffer();
1076 edtsAtom->makeBuffer();
1080 vmhdAtom->makeBuffer();
1083 smhdAtom->makeBuffer();
1086 hmhdAtom->makeBuffer();
1089 nmhdAtom->makeBuffer();
1092 dinfAtom->makeBuffer();
1095 stblAtom->makeBuffer();
1109 size += verifyPresentTrackHeader().requiredSize;
1112 size += trefAtom->totalSize();
1116 size += edtsAtom->totalSize();
1121 bool dinfAtomWritten =
false;
1124 size += vmhdAtom->totalSize();
1127 size += smhdAtom->totalSize();
1130 size += hmhdAtom->totalSize();
1133 size += nmhdAtom->totalSize();
1136 size += dinfAtom->totalSize();
1137 dinfAtomWritten =
true;
1140 size += stblAtom->totalSize();
1143 if (!dinfAtomWritten) {
1160 ostream::pos_type trakStartOffset =
outputStream().tellp();
1167 trefAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1171 edtsAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1189 if (info.versionUnknown) {
1191 argsToString(
"The version of the present \"tkhd\"-atom (", info.version,
") is unknown. Assuming version 1."),
1192 argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1194 if (info.truncated) {
1196 DiagLevel::Critical, argsToString(
"The present \"tkhd\"-atom is truncated."), argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1200 if (info.requiredSize > numeric_limits<uint32>::max()) {
1201 writer().writeUInt32BE(1);
1203 writer().writeUInt64BE(info.requiredSize);
1205 writer().writeUInt32BE(static_cast<uint32>(info.requiredSize));
1221 writer().writeUInt24BE(flags);
1228 writer().writeUInt32BE(static_cast<uint32>(
m_id));
1229 writer().writeUInt32BE(0);
1231 writer().writeUInt32BE(0);
1232 writer().writeUInt32BE(0);
1235 if (info.canUseExisting) {
1238 static_cast<streamoff
>(m_tkhdAtom->
dataSize() - info.additionalDataOffset));
1240 if (info.discardBuffer) {
1245 writer().writeInt16BE(0);
1246 writer().writeInt16BE(0);
1247 writer().writeFixed8BE(1.0);
1248 writer().writeUInt16BE(0);
1249 for (
const int32 value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) {
1250 writer().writeInt32BE(value);
1252 writer().writeFixed16BE(1.0);
1253 writer().writeFixed16BE(1.0);
1263 ostream::pos_type mdiaStartOffset =
outputStream().tellp();
1264 writer().writeUInt32BE(0);
1267 writer().writeUInt32BE(44);
1270 writer().writeUInt24BE(0);
1277 for (
size_t charIndex = 0; charIndex != 3; ++charIndex) {
1279 if (langChar >=
'a' && langChar <=
'z') {
1280 language |=
static_cast<uint16
>(langChar - 0x60) << (0xA - charIndex * 0x5);
1292 DiagLevel::Warning,
"Assigned language \"" %
m_language +
"\" is longer than 3 byte and hence will be truncated.",
"making mdhd atom");
1295 writer().writeUInt16BE(0);
1299 writer().writeUInt64BE(0);
1314 diag.emplace_back(
DiagLevel::Critical,
"Media type is invalid; The media type video is assumed.",
"making hdlr atom");
1318 for (
int i = 0; i < 3; ++i)
1319 writer().writeUInt32BE(0);
1333 ostream::pos_type minfStartOffset =
outputStream().tellp();
1334 writer().writeUInt32BE(0);
1336 bool dinfAtomWritten =
false;
1340 vmhdAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1344 smhdAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1348 hmhdAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1352 nmhdAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1356 dinfAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1357 dinfAtomWritten =
true;
1361 if (!dinfAtomWritten) {
1362 writer().writeUInt32BE(36);
1365 writer().writeUInt32BE(28);
1367 writer().writeUInt32BE(0);
1368 writer().writeUInt32BE(1);
1370 writer().writeUInt32BE(12);
1373 writer().writeUInt24BE(0x000001);
1377 bool stblAtomWritten =
false;
1380 stblAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1381 stblAtomWritten =
true;
1384 if (!stblAtomWritten) {
1386 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.",
"making stbl atom");
1399 ostream::pos_type stblStartOffset =
outputStream().tellp();
1400 writer().writeUInt32BE(0);
1408 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stsd atom from scratch.",
"making stsd atom");
1417 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stts atom from scratch.",
"making stts atom");
1458 static const string context(
"parsing MP4 track");
1459 using namespace Mp4AtomIds;
1467 if (!(m_tkhdAtom = m_trakAtom->childById(
TrackHeader, diag))) {
1471 if (!(m_mdiaAtom = m_trakAtom->childById(
Media, diag))) {
1475 if (!(m_mdhdAtom = m_mdiaAtom->childById(
MediaHeader, diag))) {
1487 if (!(m_stblAtom = m_minfAtom->childById(
SampleTable, diag))) {
1495 if (!(m_stcoAtom = m_stblAtom->childById(
ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(
ChunkOffset64, diag))) {
1499 if (!(m_stscAtom = m_stblAtom->childById(
SampleToChunk, diag))) {
1512 BinaryReader &
reader = m_trakAtom->reader();
1515 m_istream->seekg(m_tkhdAtom->startOffset() + 8);
1516 auto atomVersion =
reader.readByte();
1517 const auto flags =
reader.readUInt24BE();
1521 switch (atomVersion) {
1534 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1542 m_istream->seekg(m_mdhdAtom->dataOffset());
1543 atomVersion =
reader.readByte();
1545 switch (atomVersion) {
1560 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be " 1566 uint16 tmp =
reader.readUInt16BE();
1568 const char buff[] = {
1569 static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1570 static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1571 static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1580 m_istream->seekg(m_hdlrAtom->dataOffset() + 8);
1582 switch (
reader.readUInt32BE()) {
1601 if ((tmp =
m_istream->peek()) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1607 m_name =
reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1612 m_istream->seekg(m_stcoAtom->dataOffset() + 4);
1613 m_chunkCount =
reader.readUInt32BE();
1616 m_istream->seekg(m_stsdAtom->dataOffset() + 4);
1617 const auto entryCount =
reader.readUInt32BE();
1618 Mp4Atom *esDescParentAtom =
nullptr;
1621 for (
Mp4Atom *codecConfigContainerAtom = m_stsdAtom->
firstChild(); codecConfigContainerAtom;
1622 codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1623 codecConfigContainerAtom->
parse(diag);
1625 m_formatId = interpretIntegerAsString<uint32>(codecConfigContainerAtom->id());
1628 m_istream->seekg(codecConfigContainerAtom->dataOffset());
1629 switch (codecConfigContainerAtom->id()) {
1643 tmp =
reader.readUInt16BE();
1659 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1662 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1665 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1667 if (!esDescParentAtom) {
1668 esDescParentAtom = codecConfigContainerAtom;
1684 m_istream->seekg(6 + 2 + 16, ios_base::cur);
1690 m_framesPerSample =
reader.readUInt16BE();
1695 }
else if (tmp < 32) {
1699 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1700 if (!esDescParentAtom) {
1701 esDescParentAtom = codecConfigContainerAtom;
1706 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1707 if (!esDescParentAtom) {
1708 esDescParentAtom = codecConfigContainerAtom;
1719 if (esDescParentAtom) {
1722 m_istream->seekg(avcConfigAtom->dataOffset());
1723 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1725 m_avcConfig->parse(
reader, avcConfigAtom->dataSize(), diag);
1736 m_istream->seekg(av1ConfigAtom->dataOffset());
1737 m_av1Config = make_unique<TagParser::Av1Configuration>();
1739 m_av1Config->parse(
reader, av1ConfigAtom->dataSize(), diag);
1742 diag.emplace_back(
DiagLevel::Critical,
"Parsing AV1 configuration is not supported yet.", context);
1759 m_bitrate =
static_cast<double>(m_esInfo->averageBitrate) / 1000;
1760 m_maxBitrate =
static_cast<double>(m_esInfo->maxBitrate) / 1000;
1761 if (m_esInfo->audioSpecificConfig) {
1764 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1765 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1770 diag.emplace_back(
DiagLevel::Warning,
"Audio specific config has invalid sample frequency index.", context);
1772 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1779 DiagLevel::Warning,
"Audio specific config has invalid extension sample frequency index.", context);
1781 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1784 if (m_esInfo->videoSpecificConfig) {
1787 m_format.
sub = m_esInfo->videoSpecificConfig->profile;
1788 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1790 m_formatId += m_esInfo->videoSpecificConfig->userData;
1799 m_istream->seekg(m_stcoAtom->dataOffset() + 8);
1813 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse child atoms of \"stsd\"-atom.", context);
1818 m_sampleSizes.clear();
1820 uint64 actualSampleSizeTableSize = m_stszAtom->dataSize();
1821 if (actualSampleSizeTableSize < 12) {
1823 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1825 actualSampleSizeTableSize -= 12;
1826 m_istream->seekg(m_stszAtom->dataOffset() + 4);
1828 uint32 constantSize;
1832 fieldSize =
reader.readByte();
1835 constantSize =
reader.readUInt32BE();
1840 m_sampleSizes.push_back(constantSize);
1844 const auto calculatedSampleSizeTableSize =
static_cast<uint64
>(ceil((0.125 * fieldSize) *
m_sampleCount));
1845 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1847 DiagLevel::Critical,
"The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1848 }
else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1849 diag.emplace_back(
DiagLevel::Critical,
"The stsz atom is truncated. It stores less entries as denoted.", context);
1850 actualSampleCount =
static_cast<uint64
>(floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize)));
1852 m_sampleSizes.reserve(actualSampleCount);
1854 switch (fieldSize) {
1856 for (; i <= actualSampleCount; i += 2) {
1857 byte val =
reader.readByte();
1858 m_sampleSizes.push_back(val >> 4);
1859 m_sampleSizes.push_back(val & 0xF0);
1860 m_size += (val >> 4) + (val & 0xF0);
1862 if (i <= actualSampleCount + 1) {
1863 m_sampleSizes.push_back(
reader.readByte() >> 4);
1864 m_size += m_sampleSizes.back();
1868 for (; i <= actualSampleCount; ++i) {
1869 m_sampleSizes.push_back(
reader.readByte());
1870 m_size += m_sampleSizes.back();
1874 for (; i <= actualSampleCount; ++i) {
1875 m_sampleSizes.push_back(
reader.readUInt16BE());
1876 m_size += m_sampleSizes.back();
1880 for (; i <= actualSampleCount; ++i) {
1881 m_sampleSizes.push_back(
reader.readUInt32BE());
1882 m_size += m_sampleSizes.back();
1887 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1894 uint64 totalDuration = 0;
1897 moofAtom->parse(diag);
1899 trafAtom->parse(diag);
1902 tfhdAtom->parse(diag);
1903 uint32 calculatedDataSize = 0;
1904 if (tfhdAtom->dataSize() < calculatedDataSize) {
1907 m_istream->seekg(tfhdAtom->dataOffset() + 1);
1908 uint32 flags =
reader.readUInt24BE();
1910 if (flags & 0x000001) {
1911 calculatedDataSize += 8;
1913 if (flags & 0x000002) {
1914 calculatedDataSize += 4;
1916 if (flags & 0x000008) {
1917 calculatedDataSize += 4;
1919 if (flags & 0x000010) {
1920 calculatedDataSize += 4;
1922 if (flags & 0x000020) {
1923 calculatedDataSize += 4;
1927 uint32 defaultSampleDuration = 0;
1928 uint32 defaultSampleSize = 0;
1930 if (tfhdAtom->dataSize() < calculatedDataSize) {
1931 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
1933 if (flags & 0x000001) {
1937 if (flags & 0x000002) {
1941 if (flags & 0x000008) {
1942 defaultSampleDuration =
reader.readUInt32BE();
1945 if (flags & 0x000010) {
1946 defaultSampleSize =
reader.readUInt32BE();
1948 if (flags & 0x000020) {
1955 uint32 calculatedDataSize = 8;
1956 if (trunAtom->dataSize() < calculatedDataSize) {
1959 m_istream->seekg(trunAtom->dataOffset() + 1);
1960 uint32 flags =
reader.readUInt24BE();
1963 if (flags & 0x000001) {
1964 calculatedDataSize += 4;
1966 if (flags & 0x000004) {
1967 calculatedDataSize += 4;
1969 uint32 entrySize = 0;
1970 if (flags & 0x000100) {
1973 if (flags & 0x000200) {
1976 if (flags & 0x000400) {
1979 if (flags & 0x000800) {
1983 if (trunAtom->dataSize() < calculatedDataSize) {
1984 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
1986 if (flags & 0x000001) {
1990 if (flags & 0x000004) {
1994 if (flags & 0x000100) {
1995 totalDuration +=
reader.readUInt32BE();
1997 totalDuration += defaultSampleDuration;
1999 if (flags & 0x000200) {
2000 m_sampleSizes.push_back(
reader.readUInt32BE());
2001 m_size += m_sampleSizes.back();
2003 m_size += defaultSampleSize;
2005 if (flags & 0x000400) {
2008 if (flags & 0x000800) {
2015 if (m_sampleSizes.empty() && defaultSampleSize) {
2016 m_sampleSizes.push_back(defaultSampleSize);
2031 m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(
timeScale));
2036 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2041 m_istream->seekg(m_stscAtom->dataOffset() + 4);
2042 m_sampleToChunkEntryCount =
reader.readUInt32BE();
const std::string & language() const
Returns the language of the track if known; otherwise returns an empty string.
std::vector< uint64 > readChunkOffsets(bool parseFragments, Diagnostics &diag)
Reads the chunk offsets from the stco atom and fragments if parseFragments is true.
IoUtilities::BinaryWriter m_writer
This exception is thrown when the an operation is invoked that has not been implemented yet...
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
ugolomb chromaFormatIndication
IoUtilities::BinaryReader m_reader
const DateTime startDate
Dates within MP4 tracks are expressed as the number of seconds since this date.
byte m_extensionChannelConfig
The MpegAudioFrame class is used to parse MPEG audio frames.
void updateChunkOffsets(const std::vector< int64 > &oldMdatOffsets, const std::vector< int64 > &newMdatOffsets)
Updates the chunk offsets of the track.
std::vector< uint64 > readChunkSizes(TagParser::Diagnostics &diag)
Reads the chunk sizes from the stsz (sample sizes) and stsc (samples per chunk) atom.
void setWidth(uint32 value)
Sets the width.
void copyEntirely(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all childs to the specified targetStream.
const char * m_chromaFormat
AspectRatio pixelAspectRatio
uint32 timeScale() const
Returns the time scale if known; otherwise returns 0.
void makeBuffer()
Buffers the element (header and data).
const std::unique_ptr< char[]> & buffer()
Returns buffered data.
ImplementationType * firstChild()
Returns the first child of the element.
std::string m_compressorName
static void addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
Adds the information from the specified avcConfig to the specified track.
std::ostream & outputStream()
Returns the associated output stream.
The Mpeg4Descriptor class helps to parse MPEG-4 descriptors.
DataSizeType dataSize() const
Returns the data size of the element in byte.
uint32 chunkCount() const
Returns the number of chunks denoted by the stco atom.
void updateChunkOffset(uint32 chunkIndex, uint64 offset)
Updates a particular chunk offset.
IoUtilities::BinaryReader & reader()
Returns a binary reader for the associated stream.
std::vector< SpsInfo > spsInfos
static std::unique_ptr< Mpeg4VideoSpecificConfig > parseVideoSpecificConfig(IoUtilities::BinaryReader &reader, uint64 startOffset, uint64 size, Diagnostics &diag)
Parses the video specific configuration for the track.
uint32 m_extensionSamplingFrequency
void bufferTrackAtoms(Diagnostics &diag)
Buffers all atoms required by the makeTrack() method.
The Av1Configuration struct provides a parser for AV1 configuration found in ISOBMFF files...
The Mp4Atom class helps to parse MP4 files.
unsigned int chunkOffsetSize() const
Returns the size of a single chunk offset denotation within the stco atom.
Mpeg4VideoSpecificConfig()
void makeTrackHeader(Diagnostics &diag)
Makes the track header (tkhd atom) for the track.
static std::unique_ptr< Mpeg4AudioSpecificConfig > parseAudioSpecificConfig(std::istream &stream, uint64 startOffset, uint64 size, Diagnostics &diag)
Parses the audio specific configuration for the track.
void setHeight(uint32 value)
Sets the height.
TAG_PARSER_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent=false, bool psPresent=false)
Mpeg4AudioSpecificConfig()
static void seekBackAndWriteAtomSize(std::ostream &stream, const std::ostream::pos_type &startOffset, Diagnostics &diag)
This function helps to write the atom size after writing an atom to a stream.
uint64 dataOffset() const
Returns the data offset of the element in the related stream.
The SpsInfo struct holds the sequence parameter set.
The AvcConfiguration struct provides a parser for AVC configuration.
Implementation of TagParser::AbstractTrack for the MP4 container.
void discardBuffer()
Discards buffered data.
Mp4Track(Mp4Atom &trakAtom)
Constructs a new track for the specified trakAtom.
void makeSampleTable(Diagnostics &diag)
Makes the sample table (stbl atom) for the track.
Contains utility classes helping to read and write streams.
AspectRatio m_pixelAspectRatio
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
ImplementationType * denoteFirstChild(uint32 offset)
Denotes the first child to start at the specified offset (relative to the start offset of this descri...
void makeMedia(Diagnostics &diag)
Makes the media information (mdia atom) for the track.
void makeMediaInfo(Diagnostics &diag)
Makes a media information (minf atom) for the track.
uint64 startOffset() const
Returns the start offset of the track in the associated stream.
ContainerType & container()
Returns the related container.
ChronoUtilities::TimeSpan m_duration
uint64 size() const
Returns the size in bytes if known; otherwise returns 0.
bool m_usedInPresentation
void parseHeader(IoUtilities::BinaryReader &reader, Diagnostics &diag)
Parses the header read using the specified reader.
std::istream & inputStream()
Returns the associated input stream.
TAG_PARSER_EXPORT MediaFormat fourccToMediaFormat(uint32 fourccId)
bool m_usedWhenPreviewing
static std::unique_ptr< Mpeg4ElementaryStreamInfo > parseMpeg4ElementaryStreamInfo(IoUtilities::BinaryReader &reader, Mp4Atom *esDescAtom, Diagnostics &diag)
Reads the MPEG-4 elementary stream descriptor for the track.
uint32 headerSize() const
Returns the header size of the element in byte.
Mp4Atom & trakAtom()
Returns the trak atom for the current instance.
TAG_PARSER_EXPORT MediaFormat streamObjectTypeFormat(byte streamObjectTypeId)
Returns the TagParser::MediaFormat denoted by the specified MPEG-4 stream ID.
void internalParseHeader(Diagnostics &diag) override
This method is internally called to parse header information.
uint64 sampleCount() const
Returns the number of samples/frames if known; otherwise returns 0.
ChronoUtilities::DateTime m_creationTime
void makeTrack(Diagnostics &diag)
Makes the track entry ("trak"-atom) for the track.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
static void addInfo(const MpegAudioFrame &frame, AbstractTrack &track)
Adds the information from the specified frame to the specified track.
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
bool isHeaderValid() const
Returns an indication whether the track header is valid.
uint64 requiredSize(Diagnostics &diag) const
Returns the number of bytes written when calling makeTrack().
uint32 sampleToChunkEntryCount() const
Returns the number of "sample to chunk" entries within the stsc atom.
ChronoUtilities::DateTime m_modificationTime
TrackType type() const override
Returns the type of the track if known; otherwise returns TrackType::Unspecified. ...
std::vector< std::tuple< uint32, uint32, uint32 > > readSampleToChunkTable(Diagnostics &diag)
Reads the sample to chunk table.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
const IdentifierType & id() const
Returns the element ID.
uint32 m_samplingFrequency
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
Contains all classes and functions of the TagInfo library.
IoUtilities::BinaryWriter & writer()
Returns a binary writer for the associated stream.
TrackType
Specifies the track type.
The Diagnostics class is a container for DiagMessage.
uint32 mpeg4SamplingFrequencyTable[13]
~Mp4Track() override
Destroys the track.