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>
43 std::uint64_t requiredSize;
53 std::uint8_t additionalDataOffset;
58 constexpr TrackHeaderInfo::TrackHeaderInfo()
60 , canUseExisting(false)
63 , versionUnknown(false)
64 , additionalDataOffset(0)
65 , discardBuffer(false)
80 , sampleFrequencyIndex(0xF)
82 , channelConfiguration(0)
83 , extensionAudioObjectType(0)
86 , extensionSampleFrequencyIndex(0xF)
87 , extensionSampleFrequency(0)
88 , extensionChannelConfiguration(0)
89 , frameLengthFlag(false)
90 , dependsOnCoreCoder(false)
132 , m_trakAtom(&trakAtom)
133 , m_tkhdAtom(nullptr)
134 , m_mdiaAtom(nullptr)
135 , m_mdhdAtom(nullptr)
136 , m_hdlrAtom(nullptr)
137 , m_minfAtom(nullptr)
138 , m_stblAtom(nullptr)
139 , m_stsdAtom(nullptr)
140 , m_stscAtom(nullptr)
141 , m_stcoAtom(nullptr)
142 , m_stszAtom(nullptr)
143 , m_framesPerSample(1)
144 , m_chunkOffsetSize(4)
146 , m_sampleToChunkEntryCount(0)
174 static const string context(
"reading chunk offset table of MP4 track");
179 vector<std::uint64_t> offsets;
182 std::uint64_t actualTableSize = m_stcoAtom->
dataSize();
184 diag.emplace_back(
DiagLevel::Critical,
"The stco atom is truncated. There are no chunk offsets present.", context);
187 actualTableSize -= 8;
189 std::uint32_t actualChunkCount =
chunkCount();
191 if (calculatedTableSize < actualTableSize) {
193 DiagLevel::Critical,
"The stco atom stores more chunk offsets as denoted. The additional chunk offsets will be ignored.", context);
194 }
else if (calculatedTableSize > actualTableSize) {
195 diag.emplace_back(
DiagLevel::Critical,
"The stco atom is truncated. It stores less chunk offsets as denoted.", context);
196 actualChunkCount = static_cast<std::uint32_t>(floor(static_cast<double>(actualTableSize) / static_cast<double>(
chunkOffsetSize())));
199 offsets.reserve(actualChunkCount);
203 for (std::uint32_t i = 0; i < actualChunkCount; ++i) {
204 offsets.push_back(
reader().readUInt32BE());
208 for (std::uint32_t i = 0; i < actualChunkCount; ++i) {
209 offsets.push_back(
reader().readUInt64BE());
213 diag.emplace_back(
DiagLevel::Critical,
"The determined chunk offset size is invalid.", context);
218 if (parseFragments) {
219 std::uint64_t totalDuration = 0;
222 moofAtom->parse(diag);
225 trafAtom->parse(diag);
228 tfhdAtom->parse(diag);
229 std::uint32_t calculatedDataSize = 0;
230 if (tfhdAtom->dataSize() < calculatedDataSize) {
233 inputStream().seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
234 const std::uint32_t flags =
reader().readUInt24BE();
236 if (flags & 0x000001) {
237 calculatedDataSize += 8;
239 if (flags & 0x000002) {
240 calculatedDataSize += 4;
242 if (flags & 0x000008) {
243 calculatedDataSize += 4;
245 if (flags & 0x000010) {
246 calculatedDataSize += 4;
248 if (flags & 0x000020) {
249 calculatedDataSize += 4;
254 std::uint32_t defaultSampleDuration = 0;
255 std::uint32_t defaultSampleSize = 0;
257 if (tfhdAtom->dataSize() < calculatedDataSize) {
258 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
260 if (flags & 0x000001) {
264 if (flags & 0x000002) {
268 if (flags & 0x000008) {
269 defaultSampleDuration =
reader().readUInt32BE();
272 if (flags & 0x000010) {
273 defaultSampleSize =
reader().readUInt32BE();
275 if (flags & 0x000020) {
282 std::uint32_t calculatedDataSize = 8;
283 if (trunAtom->dataSize() < calculatedDataSize) {
286 inputStream().seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
287 std::uint32_t flags =
reader().readUInt24BE();
290 if (flags & 0x000001) {
291 calculatedDataSize += 4;
293 if (flags & 0x000004) {
294 calculatedDataSize += 4;
296 std::uint32_t entrySize = 0;
297 if (flags & 0x000100) {
300 if (flags & 0x000200) {
303 if (flags & 0x000400) {
306 if (flags & 0x000800) {
310 if (trunAtom->dataSize() < calculatedDataSize) {
311 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
313 if (flags & 0x000001) {
317 if (flags & 0x000004) {
321 if (flags & 0x000100) {
322 totalDuration +=
reader().readUInt32BE();
324 totalDuration += defaultSampleDuration;
326 if (flags & 0x000200) {
327 m_sampleSizes.push_back(
reader().readUInt32BE());
328 m_size += m_sampleSizes.back();
330 m_size += defaultSampleSize;
332 if (flags & 0x000400) {
335 if (flags & 0x000800) {
342 if (m_sampleSizes.empty() && defaultSampleSize) {
343 m_sampleSizes.push_back(defaultSampleSize);
358 std::uint64_t Mp4Track::accumulateSampleSizes(
size_t &sampleIndex,
size_t count,
Diagnostics &diag)
360 if (sampleIndex + count <= m_sampleSizes.size()) {
361 std::uint64_t sum = 0;
362 for (
size_t end = sampleIndex + count; sampleIndex < end; ++sampleIndex) {
363 sum += m_sampleSizes[sampleIndex];
366 }
else if (m_sampleSizes.size() == 1) {
367 sampleIndex += count;
368 return static_cast<std::uint64_t>(m_sampleSizes.front()) * count;
370 diag.emplace_back(
DiagLevel::Critical,
"There are not as many sample size entries as samples.",
"reading chunk sizes of MP4 track");
371 throw InvalidDataException();
383 void Mp4Track::addChunkSizeEntries(
384 std::vector<std::uint64_t> &chunkSizeTable,
size_t count,
size_t &sampleIndex, std::uint32_t sampleCount, Diagnostics &diag)
386 for (
size_t i = 0; i < count; ++i) {
387 chunkSizeTable.push_back(accumulateSampleSizes(sampleIndex,
sampleCount, diag));
395 TrackHeaderInfo Mp4Track::verifyPresentTrackHeader()
const
397 TrackHeaderInfo info;
405 info.discardBuffer = m_tkhdAtom->
buffer() ==
nullptr;
406 if (info.discardBuffer) {
411 switch (info.version = static_cast<std::uint8_t>(m_tkhdAtom->
buffer()[m_tkhdAtom->
headerSize()])) {
413 info.additionalDataOffset = 32;
416 info.additionalDataOffset = 44;
419 info.additionalDataOffset = 44;
420 info.versionUnknown =
true;
424 if (info.additionalDataOffset + 48u <= m_tkhdAtom->dataSize()) {
425 info.canUseExisting =
true;
427 info.truncated =
true;
428 info.canUseExisting = info.additionalDataOffset < m_tkhdAtom->
dataSize();
429 if (!info.canUseExisting && info.discardBuffer) {
435 info.requiredSize = m_tkhdAtom->
dataSize() + 8;
437 if ((info.version == 0)
438 && (static_cast<std::uint64_t>((
m_creationTime -
startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
440 || static_cast<std::uint64_t>(
m_duration.totalSeconds() *
m_timeScale) > numeric_limits<std::uint32_t>::max())) {
441 info.requiredSize += 12;
444 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
445 info.requiredSize += 8;
459 static const string context(
"reading sample to chunk table of MP4 track");
461 diag.emplace_back(
DiagLevel::Critical,
"Track has not been parsed or is invalid.", context);
465 std::uint64_t actualTableSize = m_stscAtom->
dataSize();
466 if (actualTableSize < 20) {
467 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom is truncated. There are no \"sample to chunk\" entries present.", context);
470 actualTableSize -= 8;
473 std::uint64_t calculatedTableSize = actualSampleToChunkEntryCount * 12;
474 if (calculatedTableSize < actualTableSize) {
475 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom stores more entries as denoted. The additional entries will be ignored.", context);
476 }
else if (calculatedTableSize > actualTableSize) {
477 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom is truncated. It stores less entries as denoted.", context);
478 actualSampleToChunkEntryCount = actualTableSize / 12;
481 vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> sampleToChunkTable;
482 sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
484 for (std::uint32_t i = 0; i < actualSampleToChunkEntryCount; ++i) {
486 std::uint32_t firstChunk =
reader().readUInt32BE();
487 std::uint32_t samplesPerChunk =
reader().readUInt32BE();
488 std::uint32_t sampleDescriptionIndex =
reader().readUInt32BE();
489 sampleToChunkTable.emplace_back(firstChunk, samplesPerChunk, sampleDescriptionIndex);
491 return sampleToChunkTable;
508 static const string context(
"reading chunk sizes of MP4 track");
510 diag.emplace_back(
DiagLevel::Critical,
"Track has not been parsed or is invalid.", context);
516 vector<std::uint64_t> chunkSizes;
517 if (!sampleToChunkTable.empty()) {
519 auto tableIterator = sampleToChunkTable.cbegin();
520 chunkSizes.reserve(m_chunkCount);
522 size_t sampleIndex = 0;
523 std::uint32_t previousChunkIndex = get<0>(*tableIterator);
524 if (previousChunkIndex != 1) {
525 diag.emplace_back(
DiagLevel::Critical,
"The first chunk of the first \"sample to chunk\" entry must be 1.", context);
526 previousChunkIndex = 1;
528 std::uint32_t samplesPerChunk = get<1>(*tableIterator);
531 for (
const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
532 std::uint32_t firstChunkIndex = get<0>(*tableIterator);
533 if (firstChunkIndex > previousChunkIndex && firstChunkIndex <= m_chunkCount) {
534 addChunkSizeEntries(chunkSizes, firstChunkIndex - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
537 "The first chunk index of a \"sample to chunk\" entry must be greather than the first chunk of the previous entry and not "
538 "greather than the chunk count.",
542 previousChunkIndex = firstChunkIndex;
543 samplesPerChunk = get<1>(*tableIterator);
545 if (m_chunkCount >= previousChunkIndex) {
546 addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
559 static const string context(
"parsing MPEG-4 elementary stream descriptor");
560 using namespace Mpeg4ElementaryStreamObjectIds;
561 unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
565 if (
reader.readUInt32BE() != 0) {
578 reader.stream()->seekg(static_cast<streamoff>(esDesc.dataOffset()));
579 esInfo = make_unique<Mpeg4ElementaryStreamInfo>();
580 esInfo->id =
reader.readUInt16BE();
581 esInfo->esDescFlags =
reader.readByte();
582 if (esInfo->dependencyFlag()) {
583 esInfo->dependsOnId =
reader.readUInt16BE();
585 if (esInfo->urlFlag()) {
588 if (esInfo->ocrFlag()) {
589 esInfo->ocrId =
reader.readUInt16BE();
592 = esDesc.
denoteFirstChild(static_cast<std::uint32_t>(static_cast<std::uint64_t>(
reader.stream()->tellg()) - esDesc.startOffset()));
593 esDescChild; esDescChild = esDescChild->nextSibling()) {
594 esDescChild->parse(diag);
595 switch (esDescChild->id()) {
598 reader.stream()->seekg(static_cast<streamoff>(esDescChild->dataOffset()));
599 esInfo->objectTypeId =
reader.readByte();
600 esInfo->decCfgDescFlags =
reader.readByte();
601 esInfo->bufferSize =
reader.readUInt24BE();
602 esInfo->maxBitrate =
reader.readUInt32BE();
603 esInfo->averageBitrate =
reader.readUInt32BE();
605 decCfgDescChild = decCfgDescChild->nextSibling()) {
606 decCfgDescChild->parse(diag);
607 switch (decCfgDescChild->id()) {
610 switch (esInfo->objectTypeId) {
617 esInfo->audioSpecificConfig
621 esInfo->videoSpecificConfig
636 diag.emplace_back(
DiagLevel::Critical,
"The MPEG-4 descriptor element structure is invalid.", context);
639 diag.emplace_back(
DiagLevel::Warning,
"Elementary stream descriptor atom (esds) is truncated.", context);
651 static const string context(
"parsing MPEG-4 audio specific config from elementary stream descriptor");
652 using namespace Mpeg4AudioObjectIds;
655 auto buff = make_unique<char[]>(
size);
656 stream.read(buff.get(), static_cast<streamoff>(
size));
657 BitReader bitReader(buff.get(),
size);
658 auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
661 auto getAudioObjectType = [&bitReader] {
662 std::uint8_t objType = bitReader.readBits<std::uint8_t>(5);
664 objType = 32 + bitReader.readBits<std::uint8_t>(6);
668 audioCfg->audioObjectType = getAudioObjectType();
670 if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
671 audioCfg->sampleFrequency = bitReader.readBits<std::uint32_t>(24);
674 audioCfg->channelConfiguration = bitReader.readBits<std::uint8_t>(4);
676 switch (audioCfg->audioObjectType) {
679 audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
680 audioCfg->sbrPresent =
true;
681 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
682 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
684 if ((audioCfg->audioObjectType = getAudioObjectType()) ==
ErBsac) {
685 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
689 switch (audioCfg->extensionAudioObjectType) {
691 audioCfg->psPresent =
true;
696 switch (audioCfg->audioObjectType) {
708 audioCfg->frameLengthFlag = bitReader.readBits<std::uint8_t>(1);
709 if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
710 audioCfg->coreCoderDelay = bitReader.readBits<std::uint8_t>(14);
712 audioCfg->extensionFlag = bitReader.readBit();
713 if (audioCfg->channelConfiguration == 0) {
716 switch (audioCfg->audioObjectType) {
719 audioCfg->layerNr = bitReader.readBits<std::uint8_t>(3);
723 if (audioCfg->extensionFlag == 1) {
724 switch (audioCfg->audioObjectType) {
726 audioCfg->numOfSubFrame = bitReader.readBits<std::uint8_t>(5);
727 audioCfg->layerLength = bitReader.readBits<std::uint16_t>(11);
733 audioCfg->resilienceFlags = bitReader.readBits<std::uint8_t>(3);
737 if (bitReader.readBit() == 1) {
746 switch (audioCfg->audioObjectType) {
758 switch (audioCfg->epConfig = bitReader.readBits<std::uint8_t>(2)) {
762 bitReader.skipBits(1);
769 if (audioCfg->extensionAudioObjectType !=
Sbr && audioCfg->extensionAudioObjectType !=
Ps && bitReader.bitsAvailable() >= 16) {
770 std::uint16_t syncExtensionType = bitReader.readBits<std::uint16_t>(11);
771 if (syncExtensionType == 0x2B7) {
772 if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) ==
Sbr) {
773 if ((audioCfg->sbrPresent = bitReader.readBit())) {
774 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
775 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
777 if (bitReader.bitsAvailable() >= 12) {
778 if ((syncExtensionType = bitReader.readBits<std::uint16_t>(11)) == 0x548) {
779 audioCfg->psPresent = bitReader.readBits<std::uint8_t>(1);
783 }
else if (audioCfg->extensionAudioObjectType ==
ErBsac) {
784 if ((audioCfg->sbrPresent = bitReader.readBit())) {
785 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
786 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
789 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
791 }
else if (syncExtensionType == 0x548) {
792 audioCfg->psPresent = bitReader.readBit();
797 }
catch (
const std::ios_base::failure &) {
803 diag.emplace_back(
DiagLevel::Critical,
"Audio specific configuration is truncated.", context);
816 static const string context(
"parsing MPEG-4 video specific config from elementary stream descriptor");
817 using namespace Mpeg4AudioObjectIds;
818 auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
821 if (
size > 3 && (
reader.readUInt24BE() == 1)) {
826 switch (
reader.readByte()) {
829 videoCfg->profile =
reader.readByte();
839 if ((buff1 =
reader.readUInt24BE()) != 1) {
840 reader.stream()->seekg(-2, ios_base::cur);
841 videoCfg->userData.push_back(static_cast<char>(buff1 >> 16));
848 if (buff1 != 1 &&
size > 0) {
849 videoCfg->userData +=
reader.readString(
size);
857 if (
reader.readUInt24BE() != 1) {
858 reader.stream()->seekg(-2, ios_base::cur);
867 diag.emplace_back(
DiagLevel::Critical,
"\"Visual Object Sequence Header\" not found.", context);
894 if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
897 static const unsigned int stcoDataBegin = 8;
898 std::uint64_t startPos = m_stcoAtom->
dataOffset() + stcoDataBegin;
899 std::uint64_t endPos = startPos + m_stcoAtom->
dataSize() - stcoDataBegin;
900 m_istream->seekg(static_cast<streamoff>(startPos));
901 m_ostream->seekp(static_cast<streamoff>(startPos));
902 vector<std::int64_t>::size_type i;
903 vector<std::int64_t>::size_type
size;
904 auto currentPos = static_cast<std::uint64_t>(
m_istream->tellg());
905 switch (m_stcoAtom->
id()) {
908 while ((currentPos + 4) <= endPos) {
910 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
911 if (off > static_cast<std::uint64_t>(oldMdatOffsets[i])) {
912 off += (newMdatOffsets[i] - oldMdatOffsets[i]);
916 m_ostream->seekp(static_cast<streamoff>(currentPos));
918 currentPos += static_cast<std::uint64_t>(
m_istream->gcount());
924 while ((currentPos + 8) <= endPos) {
926 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
927 if (off > static_cast<std::uint64_t>(oldMdatOffsets[i])) {
928 off += (newMdatOffsets[i] - oldMdatOffsets[i]);
932 m_ostream->seekp(static_cast<streamoff>(currentPos));
934 currentPos += static_cast<std::uint64_t>(
m_istream->gcount());
965 switch (m_stcoAtom->
id()) {
967 for (
auto offset : chunkOffsets) {
968 m_writer.writeUInt32BE(static_cast<std::uint32_t>(offset));
972 for (
auto offset : chunkOffsets) {
1002 writer().writeUInt32BE(static_cast<std::uint32_t>(offset));
1005 writer().writeUInt64BE(offset);
1051 CPP_UTILITIES_UNUSED(av1Config)
1052 CPP_UTILITIES_UNUSED(track)
1064 CPP_UTILITIES_UNUSED(diag)
1069 for (
Mp4Atom *trakChild = m_trakAtom->
firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1073 trakChild->makeBuffer();
1076 for (
Mp4Atom *childAtom = m_minfAtom->
firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1077 childAtom->makeBuffer();
1087 CPP_UTILITIES_UNUSED(diag)
1091 std::uint64_t
size = 8;
1093 size += verifyPresentTrackHeader().requiredSize;
1095 for (
Mp4Atom *trakChild = m_trakAtom->
firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1099 size += trakChild->totalSize();
1102 if (static_cast<std::uint64_t>((
m_creationTime -
startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
1104 || static_cast<std::uint64_t>(
m_duration.totalSeconds() *
m_timeScale) > numeric_limits<std::uint32_t>::max()) {
1114 bool dinfAtomWritten =
false;
1116 for (
Mp4Atom *childAtom = m_minfAtom->
firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1118 dinfAtomWritten =
true;
1120 size += childAtom->totalSize();
1123 if (!dinfAtomWritten) {
1141 ostream::pos_type trakStartOffset =
outputStream().tellp();
1153 trakChild->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1173 if (info.versionUnknown) {
1175 argsToString(
"The version of the present \"tkhd\"-atom (", info.version,
") is unknown. Assuming version 1."),
1176 argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1178 if (info.truncated) {
1180 DiagLevel::Critical, argsToString(
"The present \"tkhd\"-atom is truncated."), argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1184 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
1185 writer().writeUInt32BE(1);
1187 writer().writeUInt64BE(info.requiredSize);
1189 writer().writeUInt32BE(static_cast<std::uint32_t>(info.requiredSize));
1197 const std::uint8_t
version = (info.version == 0)
1199 ||
duration > numeric_limits<std::uint32_t>::max())
1205 std::uint32_t flags = 0;
1215 writer().writeUInt24BE(flags);
1227 writer().writeUInt32BE(static_cast<std::uint32_t>(
m_id));
1228 writer().writeUInt32BE(0);
1234 writer().writeUInt32BE(0);
1235 writer().writeUInt32BE(0);
1238 if (info.canUseExisting) {
1241 static_cast<streamoff>(m_tkhdAtom->
dataSize() - info.additionalDataOffset));
1243 if (info.discardBuffer) {
1248 diag.emplace_back(
DiagLevel::Warning,
"Writing some default values because the existing tkhd atom is truncated.",
"making tkhd atom");
1249 writer().writeInt16BE(0);
1250 writer().writeInt16BE(0);
1251 writer().writeFixed8BE(1.0);
1252 writer().writeUInt16BE(0);
1253 for (
const std::int32_t value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) {
1254 writer().writeInt32BE(value);
1256 writer().writeFixed16BE(1.0);
1257 writer().writeFixed16BE(1.0);
1267 ostream::pos_type mdiaStartOffset =
outputStream().tellp();
1268 writer().writeUInt32BE(0);
1275 ||
duration > numeric_limits<std::uint32_t>::max())
1281 writer().writeUInt24BE(0);
1297 for (
size_t charIndex = 0; charIndex != 3; ++charIndex) {
1299 if (langChar >=
'a' && langChar <=
'z') {
1300 language |= static_cast<std::uint16_t>(langChar - 0x60) << (0xA - charIndex * 0x5);
1310 diag.emplace_back(
DiagLevel::Warning,
"Assigned language \"" %
m_language +
"\" is of an invalid format. Setting language to undefined.",
1311 "making mdhd atom");
1317 DiagLevel::Warning,
"Assigned language \"" %
m_language +
"\" is longer than 3 byte and hence will be truncated.",
"making mdhd atom");
1320 writer().writeUInt16BE(0);
1324 writer().writeUInt64BE(0);
1342 diag.emplace_back(
DiagLevel::Critical,
"Media type is invalid; The media type video is assumed.",
"making hdlr atom");
1346 for (
int i = 0; i < 3; ++i)
1347 writer().writeUInt32BE(0);
1361 ostream::pos_type minfStartOffset =
outputStream().tellp();
1362 writer().writeUInt32BE(0);
1364 bool dinfAtomWritten =
false;
1367 for (
Mp4Atom *childAtom = m_minfAtom->
firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1372 dinfAtomWritten =
true;
1374 childAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1378 if (!dinfAtomWritten) {
1379 writer().writeUInt32BE(36);
1382 writer().writeUInt32BE(28);
1384 writer().writeUInt32BE(0);
1385 writer().writeUInt32BE(1);
1387 writer().writeUInt32BE(12);
1390 writer().writeUInt24BE(0x000001);
1394 bool stblAtomWritten =
false;
1397 stblAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1398 stblAtomWritten =
true;
1401 if (!stblAtomWritten) {
1403 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.",
"making stbl atom");
1418 writer().writeUInt32BE(0);
1426 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stsd atom from scratch.",
"making stsd atom");
1435 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stts atom from scratch.",
"making stts atom");
1476 static const string context(
"parsing MP4 track");
1477 using namespace Mp4AtomIds;
1485 if (!(m_tkhdAtom = m_trakAtom->childById(
TrackHeader, diag))) {
1489 if (!(m_mdiaAtom = m_trakAtom->childById(
Media, diag))) {
1493 if (!(m_mdhdAtom = m_mdiaAtom->childById(
MediaHeader, diag))) {
1505 if (!(m_stblAtom = m_minfAtom->childById(
SampleTable, diag))) {
1513 if (!(m_stcoAtom = m_stblAtom->childById(
ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(
ChunkOffset64, diag))) {
1517 if (!(m_stscAtom = m_stblAtom->childById(
SampleToChunk, diag))) {
1530 BinaryReader &
reader = m_trakAtom->reader();
1533 m_istream->seekg(static_cast<streamoff>(m_tkhdAtom->startOffset() + 8));
1534 auto atomVersion =
reader.readByte();
1535 const auto flags =
reader.readUInt24BE();
1539 switch (atomVersion) {
1552 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1560 m_istream->seekg(static_cast<streamoff>(m_mdhdAtom->dataOffset()));
1561 atomVersion =
reader.readByte();
1563 switch (atomVersion) {
1578 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be "
1584 std::uint16_t tmp =
reader.readUInt16BE();
1586 const char buff[] = {
1587 static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1588 static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1589 static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1598 m_istream->seekg(static_cast<streamoff>(m_hdlrAtom->dataOffset() + 8));
1600 switch (
reader.readUInt32BE()) {
1622 if (static_cast<std::uint64_t>(tmp = static_cast<std::uint8_t>(
m_istream->peek())) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1628 m_name =
reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1633 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 4));
1634 m_chunkCount =
reader.readUInt32BE();
1637 m_istream->seekg(static_cast<streamoff>(m_stsdAtom->dataOffset() + 4));
1638 const auto entryCount =
reader.readUInt32BE();
1639 Mp4Atom *esDescParentAtom =
nullptr;
1642 for (
Mp4Atom *codecConfigContainerAtom = m_stsdAtom->
firstChild(); codecConfigContainerAtom;
1643 codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1644 codecConfigContainerAtom->
parse(diag);
1646 m_formatId = interpretIntegerAsString<std::uint32_t>(codecConfigContainerAtom->id());
1649 m_istream->seekg(static_cast<streamoff>(codecConfigContainerAtom->dataOffset()));
1650 switch (codecConfigContainerAtom->id()) {
1664 tmp =
reader.readUInt16BE();
1680 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1683 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1686 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1688 if (!esDescParentAtom) {
1689 esDescParentAtom = codecConfigContainerAtom;
1705 m_istream->seekg(6 + 2 + 16, ios_base::cur);
1711 m_framesPerSample =
reader.readUInt16BE();
1716 }
else if (tmp < 32) {
1720 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1721 if (!esDescParentAtom) {
1722 esDescParentAtom = codecConfigContainerAtom;
1727 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1728 if (!esDescParentAtom) {
1729 esDescParentAtom = codecConfigContainerAtom;
1740 if (esDescParentAtom) {
1743 m_istream->seekg(static_cast<streamoff>(avcConfigAtom->dataOffset()));
1744 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1746 m_avcConfig->parse(
reader, avcConfigAtom->dataSize(), diag);
1757 m_istream->seekg(static_cast<streamoff>(av1ConfigAtom->dataOffset()));
1758 m_av1Config = make_unique<TagParser::Av1Configuration>();
1760 m_av1Config->parse(
reader, av1ConfigAtom->dataSize(), diag);
1780 m_bitrate = static_cast<double>(m_esInfo->averageBitrate) / 1000;
1781 m_maxBitrate = static_cast<double>(m_esInfo->maxBitrate) / 1000;
1782 if (m_esInfo->audioSpecificConfig) {
1785 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1786 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1791 diag.emplace_back(
DiagLevel::Warning,
"Audio specific config has invalid sample frequency index.", context);
1793 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1800 DiagLevel::Warning,
"Audio specific config has invalid extension sample frequency index.", context);
1802 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1805 if (m_esInfo->videoSpecificConfig) {
1808 m_format.
sub = m_esInfo->videoSpecificConfig->profile;
1809 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1811 m_formatId += m_esInfo->videoSpecificConfig->userData;
1820 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
1821 m_istream->seekg(static_cast<streamoff>(m_chunkOffsetSize == 8 ?
reader.readUInt64BE() :
reader.readUInt32BE()));
1834 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse child atoms of \"stsd\"-atom.", context);
1839 m_sampleSizes.clear();
1841 std::uint64_t actualSampleSizeTableSize = m_stszAtom->dataSize();
1842 if (actualSampleSizeTableSize < 12) {
1844 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1846 actualSampleSizeTableSize -= 12;
1847 m_istream->seekg(static_cast<streamoff>(m_stszAtom->dataOffset() + 4));
1848 std::uint32_t fieldSize;
1849 std::uint32_t constantSize;
1853 fieldSize =
reader.readByte();
1856 constantSize =
reader.readUInt32BE();
1861 m_sampleSizes.push_back(constantSize);
1865 const auto calculatedSampleSizeTableSize = static_cast<std::uint64_t>(ceil((0.125 * fieldSize) *
m_sampleCount));
1866 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1868 DiagLevel::Critical,
"The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1869 }
else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1870 diag.emplace_back(
DiagLevel::Critical,
"The stsz atom is truncated. It stores less entries as denoted.", context);
1871 actualSampleCount = static_cast<std::uint64_t>(floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize)));
1873 m_sampleSizes.reserve(actualSampleCount);
1874 std::uint32_t i = 1;
1875 switch (fieldSize) {
1877 for (; i <= actualSampleCount; i += 2) {
1878 std::uint8_t val =
reader.readByte();
1879 m_sampleSizes.push_back(val >> 4);
1880 m_sampleSizes.push_back(val & 0xF0);
1881 m_size += (val >> 4) + (val & 0xF0);
1883 if (i <= actualSampleCount + 1) {
1884 m_sampleSizes.push_back(
reader.readByte() >> 4);
1885 m_size += m_sampleSizes.back();
1889 for (; i <= actualSampleCount; ++i) {
1890 m_sampleSizes.push_back(
reader.readByte());
1891 m_size += m_sampleSizes.back();
1895 for (; i <= actualSampleCount; ++i) {
1896 m_sampleSizes.push_back(
reader.readUInt16BE());
1897 m_size += m_sampleSizes.back();
1901 for (; i <= actualSampleCount; ++i) {
1902 m_sampleSizes.push_back(
reader.readUInt32BE());
1903 m_size += m_sampleSizes.back();
1908 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1915 std::uint64_t totalDuration = 0;
1918 moofAtom->parse(diag);
1920 trafAtom->parse(diag);
1923 tfhdAtom->parse(diag);
1924 std::uint32_t calculatedDataSize = 0;
1925 if (tfhdAtom->dataSize() < calculatedDataSize) {
1928 m_istream->seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
1929 std::uint32_t flags =
reader.readUInt24BE();
1931 if (flags & 0x000001) {
1932 calculatedDataSize += 8;
1934 if (flags & 0x000002) {
1935 calculatedDataSize += 4;
1937 if (flags & 0x000008) {
1938 calculatedDataSize += 4;
1940 if (flags & 0x000010) {
1941 calculatedDataSize += 4;
1943 if (flags & 0x000020) {
1944 calculatedDataSize += 4;
1948 std::uint32_t defaultSampleDuration = 0;
1949 std::uint32_t defaultSampleSize = 0;
1951 if (tfhdAtom->dataSize() < calculatedDataSize) {
1952 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
1954 if (flags & 0x000001) {
1958 if (flags & 0x000002) {
1962 if (flags & 0x000008) {
1963 defaultSampleDuration =
reader.readUInt32BE();
1966 if (flags & 0x000010) {
1967 defaultSampleSize =
reader.readUInt32BE();
1969 if (flags & 0x000020) {
1976 std::uint32_t calculatedDataSize = 8;
1977 if (trunAtom->dataSize() < calculatedDataSize) {
1980 m_istream->seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
1981 std::uint32_t flags =
reader.readUInt24BE();
1984 if (flags & 0x000001) {
1985 calculatedDataSize += 4;
1987 if (flags & 0x000004) {
1988 calculatedDataSize += 4;
1990 std::uint32_t entrySize = 0;
1991 if (flags & 0x000100) {
1994 if (flags & 0x000200) {
1997 if (flags & 0x000400) {
2000 if (flags & 0x000800) {
2004 if (trunAtom->dataSize() < calculatedDataSize) {
2005 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
2007 if (flags & 0x000001) {
2011 if (flags & 0x000004) {
2015 if (flags & 0x000100) {
2016 totalDuration +=
reader.readUInt32BE();
2018 totalDuration += defaultSampleDuration;
2020 if (flags & 0x000200) {
2021 m_sampleSizes.push_back(
reader.readUInt32BE());
2022 m_size += m_sampleSizes.back();
2024 m_size += defaultSampleSize;
2026 if (flags & 0x000400) {
2029 if (flags & 0x000800) {
2036 if (m_sampleSizes.empty() && defaultSampleSize) {
2037 m_sampleSizes.push_back(defaultSampleSize);
2052 m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(
timeScale));
2057 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2062 m_istream->seekg(static_cast<streamoff>(m_stscAtom->dataOffset() + 4));
2063 m_sampleToChunkEntryCount =
reader.readUInt32BE();