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> 28 using namespace ConversionUtilities;
29 using namespace ChronoUtilities;
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)
146 , m_framesPerSample(1)
147 , m_chunkOffsetSize(4)
149 , m_sampleToChunkEntryCount(0)
177 static const string context(
"reading chunk offset table of MP4 track");
182 vector<uint64> offsets;
185 uint64 actualTableSize = m_stcoAtom->
dataSize();
187 diag.emplace_back(
DiagLevel::Critical,
"The stco atom is truncated. There are no chunk offsets present.", context);
190 actualTableSize -= 8;
194 if (calculatedTableSize < actualTableSize) {
196 DiagLevel::Critical,
"The stco atom stores more chunk offsets as denoted. The additional chunk offsets will be ignored.", context);
197 }
else if (calculatedTableSize > actualTableSize) {
198 diag.emplace_back(
DiagLevel::Critical,
"The stco atom is truncated. It stores less chunk offsets as denoted.", context);
199 actualChunkCount = static_cast<uint32>(floor(static_cast<double>(actualTableSize) / static_cast<double>(
chunkOffsetSize())));
202 offsets.reserve(actualChunkCount);
206 for (uint32 i = 0; i < actualChunkCount; ++i) {
207 offsets.push_back(
reader().readUInt32BE());
211 for (uint32 i = 0; i < actualChunkCount; ++i) {
212 offsets.push_back(
reader().readUInt64BE());
216 diag.emplace_back(
DiagLevel::Critical,
"The determined chunk offset size is invalid.", context);
221 if (parseFragments) {
222 uint64 totalDuration = 0;
225 moofAtom->parse(diag);
228 trafAtom->parse(diag);
231 tfhdAtom->parse(diag);
232 uint32 calculatedDataSize = 0;
233 if (tfhdAtom->dataSize() < calculatedDataSize) {
236 inputStream().seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
237 const std::uint32_t flags =
reader().readUInt24BE();
239 if (flags & 0x000001) {
240 calculatedDataSize += 8;
242 if (flags & 0x000002) {
243 calculatedDataSize += 4;
245 if (flags & 0x000008) {
246 calculatedDataSize += 4;
248 if (flags & 0x000010) {
249 calculatedDataSize += 4;
251 if (flags & 0x000020) {
252 calculatedDataSize += 4;
257 uint32 defaultSampleDuration = 0;
258 uint32 defaultSampleSize = 0;
260 if (tfhdAtom->dataSize() < calculatedDataSize) {
261 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
263 if (flags & 0x000001) {
267 if (flags & 0x000002) {
271 if (flags & 0x000008) {
272 defaultSampleDuration =
reader().readUInt32BE();
275 if (flags & 0x000010) {
276 defaultSampleSize =
reader().readUInt32BE();
278 if (flags & 0x000020) {
285 uint32 calculatedDataSize = 8;
286 if (trunAtom->dataSize() < calculatedDataSize) {
289 inputStream().seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
290 std::uint32_t flags =
reader().readUInt24BE();
293 if (flags & 0x000001) {
294 calculatedDataSize += 4;
296 if (flags & 0x000004) {
297 calculatedDataSize += 4;
299 uint32 entrySize = 0;
300 if (flags & 0x000100) {
303 if (flags & 0x000200) {
306 if (flags & 0x000400) {
309 if (flags & 0x000800) {
313 if (trunAtom->dataSize() < calculatedDataSize) {
314 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
316 if (flags & 0x000001) {
320 if (flags & 0x000004) {
324 if (flags & 0x000100) {
325 totalDuration +=
reader().readUInt32BE();
327 totalDuration += defaultSampleDuration;
329 if (flags & 0x000200) {
330 m_sampleSizes.push_back(
reader().readUInt32BE());
331 m_size += m_sampleSizes.back();
333 m_size += defaultSampleSize;
335 if (flags & 0x000400) {
338 if (flags & 0x000800) {
345 if (m_sampleSizes.empty() && defaultSampleSize) {
346 m_sampleSizes.push_back(defaultSampleSize);
361 uint64 Mp4Track::accumulateSampleSizes(
size_t &sampleIndex,
size_t count,
Diagnostics &diag)
363 if (sampleIndex + count <= m_sampleSizes.size()) {
365 for (
size_t end = sampleIndex + count; sampleIndex < end; ++sampleIndex) {
366 sum += m_sampleSizes[sampleIndex];
369 }
else if (m_sampleSizes.size() == 1) {
370 sampleIndex += count;
371 return static_cast<uint64>(m_sampleSizes.front()) * count;
373 diag.emplace_back(
DiagLevel::Critical,
"There are not as many sample size entries as samples.",
"reading chunk sizes of MP4 track");
374 throw InvalidDataException();
386 void Mp4Track::addChunkSizeEntries(std::vector<uint64> &chunkSizeTable,
size_t count,
size_t &sampleIndex, uint32 sampleCount, Diagnostics &diag)
388 for (
size_t i = 0; i < count; ++i) {
389 chunkSizeTable.push_back(accumulateSampleSizes(sampleIndex,
sampleCount, diag));
397 TrackHeaderInfo Mp4Track::verifyPresentTrackHeader()
const 399 TrackHeaderInfo info;
407 info.discardBuffer = m_tkhdAtom->
buffer() ==
nullptr;
408 if (info.discardBuffer) {
413 switch (info.version = static_cast<byte>(m_tkhdAtom->
buffer()[m_tkhdAtom->
headerSize()])) {
415 info.additionalDataOffset = 32;
418 info.additionalDataOffset = 44;
421 info.additionalDataOffset = 44;
422 info.versionUnknown =
true;
426 if (info.additionalDataOffset + 48u <= m_tkhdAtom->dataSize()) {
427 info.canUseExisting =
true;
429 info.truncated =
true;
430 info.canUseExisting = info.additionalDataOffset < m_tkhdAtom->
dataSize();
431 if (!info.canUseExisting && info.discardBuffer) {
437 info.requiredSize = m_tkhdAtom->
dataSize() + 8;
439 if ((info.version == 0)
440 && (static_cast<std::uint64_t>((
m_creationTime -
startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
442 || static_cast<std::uint64_t>(
m_duration.totalSeconds() *
m_timeScale) > numeric_limits<std::uint32_t>::max())) {
443 info.requiredSize += 12;
446 if (info.requiredSize > numeric_limits<std::uint32_t>::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 std::uint64_t 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);
480 actualSampleToChunkEntryCount = actualTableSize / 12;
483 vector<tuple<uint32, uint32, uint32>> sampleToChunkTable;
484 sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
486 for (std::uint32_t i = 0; i < actualSampleToChunkEntryCount; ++i) {
488 uint32 firstChunk =
reader().readUInt32BE();
489 uint32 samplesPerChunk =
reader().readUInt32BE();
490 uint32 sampleDescriptionIndex =
reader().readUInt32BE();
491 sampleToChunkTable.emplace_back(firstChunk, samplesPerChunk, sampleDescriptionIndex);
493 return sampleToChunkTable;
510 static const string context(
"reading chunk sizes of MP4 track");
512 diag.emplace_back(
DiagLevel::Critical,
"Track has not been parsed or is invalid.", context);
518 vector<uint64> chunkSizes;
519 if (!sampleToChunkTable.empty()) {
521 auto tableIterator = sampleToChunkTable.cbegin();
522 chunkSizes.reserve(m_chunkCount);
524 size_t sampleIndex = 0;
525 uint32 previousChunkIndex = get<0>(*tableIterator);
526 if (previousChunkIndex != 1) {
527 diag.emplace_back(
DiagLevel::Critical,
"The first chunk of the first \"sample to chunk\" entry must be 1.", context);
528 previousChunkIndex = 1;
530 uint32 samplesPerChunk = get<1>(*tableIterator);
533 for (
const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
534 uint32 firstChunkIndex = get<0>(*tableIterator);
535 if (firstChunkIndex > previousChunkIndex && firstChunkIndex <= m_chunkCount) {
536 addChunkSizeEntries(chunkSizes, firstChunkIndex - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
539 "The first chunk index of a \"sample to chunk\" entry must be greather than the first chunk of the previous entry and not " 540 "greather than the chunk count.",
544 previousChunkIndex = firstChunkIndex;
545 samplesPerChunk = get<1>(*tableIterator);
547 if (m_chunkCount >= previousChunkIndex) {
548 addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
563 static const string context(
"parsing MPEG-4 elementary stream descriptor");
564 using namespace Mpeg4ElementaryStreamObjectIds;
565 unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
569 if (
reader.readUInt32BE() != 0) {
582 reader.stream()->seekg(static_cast<streamoff>(esDesc.dataOffset()));
583 esInfo = make_unique<Mpeg4ElementaryStreamInfo>();
584 esInfo->id =
reader.readUInt16BE();
585 esInfo->esDescFlags =
reader.readByte();
586 if (esInfo->dependencyFlag()) {
587 esInfo->dependsOnId =
reader.readUInt16BE();
589 if (esInfo->urlFlag()) {
592 if (esInfo->ocrFlag()) {
593 esInfo->ocrId =
reader.readUInt16BE();
596 esDescChild; esDescChild = esDescChild->nextSibling()) {
597 esDescChild->parse(diag);
598 switch (esDescChild->id()) {
601 reader.stream()->seekg(static_cast<streamoff>(esDescChild->dataOffset()));
602 esInfo->objectTypeId =
reader.readByte();
603 esInfo->decCfgDescFlags =
reader.readByte();
604 esInfo->bufferSize =
reader.readUInt24BE();
605 esInfo->maxBitrate =
reader.readUInt32BE();
606 esInfo->averageBitrate =
reader.readUInt32BE();
608 decCfgDescChild = decCfgDescChild->nextSibling()) {
609 decCfgDescChild->parse(diag);
610 switch (decCfgDescChild->id()) {
613 switch (esInfo->objectTypeId) {
620 esInfo->audioSpecificConfig
624 esInfo->videoSpecificConfig
639 diag.emplace_back(
DiagLevel::Critical,
"The MPEG-4 descriptor element structure is invalid.", context);
642 diag.emplace_back(
DiagLevel::Warning,
"Elementary stream descriptor atom (esds) is truncated.", context);
655 static const string context(
"parsing MPEG-4 audio specific config from elementary stream descriptor");
656 using namespace Mpeg4AudioObjectIds;
659 auto buff = make_unique<char[]>(
size);
660 stream.read(buff.get(), static_cast<streamoff>(
size));
661 BitReader bitReader(buff.get(),
size);
662 auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
665 auto getAudioObjectType = [&bitReader] {
666 byte objType = bitReader.readBits<
byte>(5);
668 objType = 32 + bitReader.readBits<
byte>(6);
672 audioCfg->audioObjectType = getAudioObjectType();
674 if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<
byte>(4)) == 0xF) {
675 audioCfg->sampleFrequency = bitReader.readBits<uint32>(24);
678 audioCfg->channelConfiguration = bitReader.readBits<
byte>(4);
680 switch (audioCfg->audioObjectType) {
683 audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
684 audioCfg->sbrPresent =
true;
685 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<
byte>(4)) == 0xF) {
686 audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
688 if ((audioCfg->audioObjectType = getAudioObjectType()) ==
ErBsac) {
689 audioCfg->extensionChannelConfiguration = bitReader.readBits<
byte>(4);
693 switch (audioCfg->extensionAudioObjectType) {
695 audioCfg->psPresent =
true;
700 switch (audioCfg->audioObjectType) {
712 audioCfg->frameLengthFlag = bitReader.readBits<
byte>(1);
713 if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
714 audioCfg->coreCoderDelay = bitReader.readBits<
byte>(14);
716 audioCfg->extensionFlag = bitReader.readBit();
717 if (audioCfg->channelConfiguration == 0) {
720 switch (audioCfg->audioObjectType) {
723 audioCfg->layerNr = bitReader.readBits<
byte>(3);
727 if (audioCfg->extensionFlag == 1) {
728 switch (audioCfg->audioObjectType) {
730 audioCfg->numOfSubFrame = bitReader.readBits<
byte>(5);
731 audioCfg->layerLength = bitReader.readBits<uint16>(11);
737 audioCfg->resilienceFlags = bitReader.readBits<
byte>(3);
741 if (bitReader.readBit() == 1) {
750 switch (audioCfg->audioObjectType) {
762 switch (audioCfg->epConfig = bitReader.readBits<
byte>(2)) {
766 bitReader.skipBits(1);
773 if (audioCfg->extensionAudioObjectType !=
Sbr && audioCfg->extensionAudioObjectType !=
Ps && bitReader.bitsAvailable() >= 16) {
774 uint16 syncExtensionType = bitReader.readBits<uint16>(11);
775 if (syncExtensionType == 0x2B7) {
776 if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) ==
Sbr) {
777 if ((audioCfg->sbrPresent = bitReader.readBit())) {
778 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<
byte>(4)) == 0xF) {
779 audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
781 if (bitReader.bitsAvailable() >= 12) {
782 if ((syncExtensionType = bitReader.readBits<uint16>(11)) == 0x548) {
783 audioCfg->psPresent = bitReader.readBits<
byte>(1);
787 }
else if (audioCfg->extensionAudioObjectType ==
ErBsac) {
788 if ((audioCfg->sbrPresent = bitReader.readBit())) {
789 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<
byte>(4)) == 0xF) {
790 audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
793 audioCfg->extensionChannelConfiguration = bitReader.readBits<
byte>(4);
795 }
else if (syncExtensionType == 0x548) {
796 audioCfg->psPresent = bitReader.readBit();
802 const char *what = catchIoFailure();
805 throwIoFailure(what);
808 diag.emplace_back(
DiagLevel::Critical,
"Audio specific configuration is truncated.", context);
822 static const string context(
"parsing MPEG-4 video specific config from elementary stream descriptor");
823 using namespace Mpeg4AudioObjectIds;
824 auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
827 if (
size > 3 && (
reader.readUInt24BE() == 1)) {
832 switch (
reader.readByte()) {
835 videoCfg->profile =
reader.readByte();
845 if ((buff1 =
reader.readUInt24BE()) != 1) {
846 reader.stream()->seekg(-2, ios_base::cur);
847 videoCfg->userData.push_back(static_cast<char>(buff1 >> 16));
854 if (buff1 != 1 &&
size > 0) {
855 videoCfg->userData +=
reader.readString(
size);
863 if (
reader.readUInt24BE() != 1) {
864 reader.stream()->seekg(-2, ios_base::cur);
873 diag.emplace_back(
DiagLevel::Critical,
"\"Visual Object Sequence Header\" not found.", context);
900 if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
903 static const unsigned int stcoDataBegin = 8;
904 std::uint64_t startPos = m_stcoAtom->
dataOffset() + stcoDataBegin;
905 std::uint64_t endPos = startPos + m_stcoAtom->
dataSize() - stcoDataBegin;
906 m_istream->seekg(static_cast<streamoff>(startPos));
907 m_ostream->seekp(static_cast<streamoff>(startPos));
908 vector<std::int64_t>::size_type i;
909 vector<std::int64_t>::size_type
size;
910 auto currentPos = static_cast<std::uint64_t>(
m_istream->tellg());
911 switch (m_stcoAtom->
id()) {
914 while ((currentPos + 4) <= endPos) {
916 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
917 if (off > static_cast<uint64>(oldMdatOffsets[i])) {
918 off += (newMdatOffsets[i] - oldMdatOffsets[i]);
922 m_ostream->seekp(static_cast<streamoff>(currentPos));
924 currentPos += static_cast<std::uint64_t>(
m_istream->gcount());
930 while ((currentPos + 8) <= endPos) {
932 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
933 if (off > static_cast<uint64>(oldMdatOffsets[i])) {
934 off += (newMdatOffsets[i] - oldMdatOffsets[i]);
938 m_ostream->seekp(static_cast<streamoff>(currentPos));
940 currentPos += static_cast<std::uint64_t>(
m_istream->gcount());
971 switch (m_stcoAtom->
id()) {
973 for (
auto offset : chunkOffsets) {
974 m_writer.writeUInt32BE(static_cast<std::uint32_t>(offset));
978 for (
auto offset : chunkOffsets) {
1008 writer().writeUInt32BE(static_cast<std::uint32_t>(offset));
1011 writer().writeUInt64BE(offset);
1057 VAR_UNUSED(av1Config)
1075 for (
Mp4Atom *trakChild = m_trakAtom->
firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1079 trakChild->makeBuffer();
1082 for (
Mp4Atom *childAtom = m_minfAtom->
firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1083 childAtom->makeBuffer();
1097 std::uint64_t
size = 8;
1099 size += verifyPresentTrackHeader().requiredSize;
1101 for (
Mp4Atom *trakChild = m_trakAtom->
firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1105 size += trakChild->totalSize();
1108 if (static_cast<std::uint64_t>((
m_creationTime -
startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
1110 || static_cast<std::uint64_t>(
m_duration.totalSeconds() *
m_timeScale) > numeric_limits<std::uint32_t>::max()) {
1120 bool dinfAtomWritten =
false;
1122 for (
Mp4Atom *childAtom = m_minfAtom->
firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1124 dinfAtomWritten =
true;
1126 size += childAtom->totalSize();
1129 if (!dinfAtomWritten) {
1147 ostream::pos_type trakStartOffset =
outputStream().tellp();
1159 trakChild->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1179 if (info.versionUnknown) {
1181 argsToString(
"The version of the present \"tkhd\"-atom (", info.version,
") is unknown. Assuming version 1."),
1182 argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1184 if (info.truncated) {
1186 DiagLevel::Critical, argsToString(
"The present \"tkhd\"-atom is truncated."), argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1190 if (info.requiredSize > numeric_limits<uint32>::max()) {
1191 writer().writeUInt32BE(1);
1193 writer().writeUInt64BE(info.requiredSize);
1195 writer().writeUInt32BE(static_cast<uint32>(info.requiredSize));
1203 const std::uint8_t
version = (info.version == 0) && (
creationTime > numeric_limits<std::uint32_t>::max()
1205 ||
duration > numeric_limits<std::uint32_t>::max()) ? 1 : info.version;
1209 std::uint32_t flags = 0;
1219 writer().writeUInt24BE(flags);
1231 writer().writeUInt32BE(static_cast<uint32>(
m_id));
1232 writer().writeUInt32BE(0);
1238 writer().writeUInt32BE(0);
1239 writer().writeUInt32BE(0);
1242 if (info.canUseExisting) {
1245 static_cast<streamoff>(m_tkhdAtom->
dataSize() - info.additionalDataOffset));
1247 if (info.discardBuffer) {
1252 diag.emplace_back(
DiagLevel::Warning,
"Writing some default values because the existing tkhd atom is truncated.",
"making tkhd atom");
1253 writer().writeInt16BE(0);
1254 writer().writeInt16BE(0);
1255 writer().writeFixed8BE(1.0);
1256 writer().writeUInt16BE(0);
1257 for (
const int32 value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) {
1258 writer().writeInt32BE(value);
1260 writer().writeFixed16BE(1.0);
1261 writer().writeFixed16BE(1.0);
1271 ostream::pos_type mdiaStartOffset =
outputStream().tellp();
1272 writer().writeUInt32BE(0);
1280 ||
duration > numeric_limits<std::uint32_t>::max()) ? 1 : 0;
1284 writer().writeUInt24BE(0);
1300 for (
size_t charIndex = 0; charIndex != 3; ++charIndex) {
1302 if (langChar >=
'a' && langChar <=
'z') {
1303 language |= static_cast<std::uint16_t>(langChar - 0x60) << (0xA - charIndex * 0x5);
1313 diag.emplace_back(
DiagLevel::Warning,
"Assigned language \"" %
m_language +
"\" is of an invalid format. Setting language to undefined.",
"making mdhd atom");
1319 DiagLevel::Warning,
"Assigned language \"" %
m_language +
"\" is longer than 3 byte and hence will be truncated.",
"making mdhd atom");
1322 writer().writeUInt16BE(0);
1326 writer().writeUInt64BE(0);
1344 diag.emplace_back(
DiagLevel::Critical,
"Media type is invalid; The media type video is assumed.",
"making hdlr atom");
1348 for (
int i = 0; i < 3; ++i)
1349 writer().writeUInt32BE(0);
1363 ostream::pos_type minfStartOffset =
outputStream().tellp();
1364 writer().writeUInt32BE(0);
1366 bool dinfAtomWritten =
false;
1369 for (
Mp4Atom *childAtom = m_minfAtom->
firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1374 dinfAtomWritten =
true;
1376 childAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1380 if (!dinfAtomWritten) {
1381 writer().writeUInt32BE(36);
1384 writer().writeUInt32BE(28);
1386 writer().writeUInt32BE(0);
1387 writer().writeUInt32BE(1);
1389 writer().writeUInt32BE(12);
1392 writer().writeUInt24BE(0x000001);
1396 bool stblAtomWritten =
false;
1399 stblAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1400 stblAtomWritten =
true;
1403 if (!stblAtomWritten) {
1405 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.",
"making stbl atom");
1420 writer().writeUInt32BE(0);
1428 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stsd atom from scratch.",
"making stsd atom");
1437 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stts atom from scratch.",
"making stts atom");
1478 static const string context(
"parsing MP4 track");
1479 using namespace Mp4AtomIds;
1487 if (!(m_tkhdAtom = m_trakAtom->childById(
TrackHeader, diag))) {
1491 if (!(m_mdiaAtom = m_trakAtom->childById(
Media, diag))) {
1495 if (!(m_mdhdAtom = m_mdiaAtom->childById(
MediaHeader, diag))) {
1507 if (!(m_stblAtom = m_minfAtom->childById(
SampleTable, diag))) {
1515 if (!(m_stcoAtom = m_stblAtom->childById(
ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(
ChunkOffset64, diag))) {
1519 if (!(m_stscAtom = m_stblAtom->childById(
SampleToChunk, diag))) {
1532 BinaryReader &
reader = m_trakAtom->reader();
1535 m_istream->seekg(static_cast<streamoff>(m_tkhdAtom->startOffset() + 8));
1536 auto atomVersion =
reader.readByte();
1537 const auto flags =
reader.readUInt24BE();
1541 switch (atomVersion) {
1554 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1562 m_istream->seekg(static_cast<streamoff>(m_mdhdAtom->dataOffset()));
1563 atomVersion =
reader.readByte();
1565 switch (atomVersion) {
1580 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be " 1586 uint16 tmp =
reader.readUInt16BE();
1588 const char buff[] = {
1589 static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1590 static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1591 static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1600 m_istream->seekg(static_cast<streamoff>(m_hdlrAtom->dataOffset() + 8));
1602 switch (
reader.readUInt32BE()) {
1624 if (static_cast<std::uint64_t>(tmp = static_cast<std::uint8_t>(
m_istream->peek())) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1630 m_name =
reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1635 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 4));
1636 m_chunkCount =
reader.readUInt32BE();
1639 m_istream->seekg(static_cast<streamoff>(m_stsdAtom->dataOffset() + 4));
1640 const auto entryCount =
reader.readUInt32BE();
1641 Mp4Atom *esDescParentAtom =
nullptr;
1644 for (
Mp4Atom *codecConfigContainerAtom = m_stsdAtom->
firstChild(); codecConfigContainerAtom;
1645 codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1646 codecConfigContainerAtom->
parse(diag);
1648 m_formatId = interpretIntegerAsString<uint32>(codecConfigContainerAtom->id());
1651 m_istream->seekg(static_cast<streamoff>(codecConfigContainerAtom->dataOffset()));
1652 switch (codecConfigContainerAtom->id()) {
1666 tmp =
reader.readUInt16BE();
1682 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1685 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1688 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1690 if (!esDescParentAtom) {
1691 esDescParentAtom = codecConfigContainerAtom;
1707 m_istream->seekg(6 + 2 + 16, ios_base::cur);
1713 m_framesPerSample =
reader.readUInt16BE();
1718 }
else if (tmp < 32) {
1722 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1723 if (!esDescParentAtom) {
1724 esDescParentAtom = codecConfigContainerAtom;
1729 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1730 if (!esDescParentAtom) {
1731 esDescParentAtom = codecConfigContainerAtom;
1742 if (esDescParentAtom) {
1745 m_istream->seekg(static_cast<streamoff>(avcConfigAtom->dataOffset()));
1746 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1748 m_avcConfig->parse(
reader, avcConfigAtom->dataSize(), diag);
1759 m_istream->seekg(static_cast<streamoff>(av1ConfigAtom->dataOffset()));
1760 m_av1Config = make_unique<TagParser::Av1Configuration>();
1762 m_av1Config->parse(
reader, av1ConfigAtom->dataSize(), diag);
1782 m_bitrate = static_cast<double>(m_esInfo->averageBitrate) / 1000;
1783 m_maxBitrate = static_cast<double>(m_esInfo->maxBitrate) / 1000;
1784 if (m_esInfo->audioSpecificConfig) {
1787 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1788 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1793 diag.emplace_back(
DiagLevel::Warning,
"Audio specific config has invalid sample frequency index.", context);
1795 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1802 DiagLevel::Warning,
"Audio specific config has invalid extension sample frequency index.", context);
1804 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1807 if (m_esInfo->videoSpecificConfig) {
1810 m_format.
sub = m_esInfo->videoSpecificConfig->profile;
1811 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1813 m_formatId += m_esInfo->videoSpecificConfig->userData;
1822 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
1823 m_istream->seekg(static_cast<streamoff>(m_chunkOffsetSize == 8 ?
reader.readUInt64BE() :
reader.readUInt32BE()));
1836 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse child atoms of \"stsd\"-atom.", context);
1841 m_sampleSizes.clear();
1843 uint64 actualSampleSizeTableSize = m_stszAtom->dataSize();
1844 if (actualSampleSizeTableSize < 12) {
1846 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1848 actualSampleSizeTableSize -= 12;
1849 m_istream->seekg(static_cast<streamoff>(m_stszAtom->dataOffset() + 4));
1850 std::uint32_t fieldSize;
1851 std::uint32_t constantSize;
1855 fieldSize =
reader.readByte();
1858 constantSize =
reader.readUInt32BE();
1863 m_sampleSizes.push_back(constantSize);
1867 const auto calculatedSampleSizeTableSize = static_cast<uint64>(ceil((0.125 * fieldSize) *
m_sampleCount));
1868 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1870 DiagLevel::Critical,
"The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1871 }
else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1872 diag.emplace_back(
DiagLevel::Critical,
"The stsz atom is truncated. It stores less entries as denoted.", context);
1873 actualSampleCount = static_cast<uint64>(floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize)));
1875 m_sampleSizes.reserve(actualSampleCount);
1877 switch (fieldSize) {
1879 for (; i <= actualSampleCount; i += 2) {
1880 byte val =
reader.readByte();
1881 m_sampleSizes.push_back(val >> 4);
1882 m_sampleSizes.push_back(val & 0xF0);
1883 m_size += (val >> 4) + (val & 0xF0);
1885 if (i <= actualSampleCount + 1) {
1886 m_sampleSizes.push_back(
reader.readByte() >> 4);
1887 m_size += m_sampleSizes.back();
1891 for (; i <= actualSampleCount; ++i) {
1892 m_sampleSizes.push_back(
reader.readByte());
1893 m_size += m_sampleSizes.back();
1897 for (; i <= actualSampleCount; ++i) {
1898 m_sampleSizes.push_back(
reader.readUInt16BE());
1899 m_size += m_sampleSizes.back();
1903 for (; i <= actualSampleCount; ++i) {
1904 m_sampleSizes.push_back(
reader.readUInt32BE());
1905 m_size += m_sampleSizes.back();
1910 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1917 uint64 totalDuration = 0;
1920 moofAtom->parse(diag);
1922 trafAtom->parse(diag);
1925 tfhdAtom->parse(diag);
1926 uint32 calculatedDataSize = 0;
1927 if (tfhdAtom->dataSize() < calculatedDataSize) {
1930 m_istream->seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
1931 std::uint32_t flags =
reader.readUInt24BE();
1933 if (flags & 0x000001) {
1934 calculatedDataSize += 8;
1936 if (flags & 0x000002) {
1937 calculatedDataSize += 4;
1939 if (flags & 0x000008) {
1940 calculatedDataSize += 4;
1942 if (flags & 0x000010) {
1943 calculatedDataSize += 4;
1945 if (flags & 0x000020) {
1946 calculatedDataSize += 4;
1950 uint32 defaultSampleDuration = 0;
1951 uint32 defaultSampleSize = 0;
1953 if (tfhdAtom->dataSize() < calculatedDataSize) {
1954 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
1956 if (flags & 0x000001) {
1960 if (flags & 0x000002) {
1964 if (flags & 0x000008) {
1965 defaultSampleDuration =
reader.readUInt32BE();
1968 if (flags & 0x000010) {
1969 defaultSampleSize =
reader.readUInt32BE();
1971 if (flags & 0x000020) {
1978 uint32 calculatedDataSize = 8;
1979 if (trunAtom->dataSize() < calculatedDataSize) {
1982 m_istream->seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
1983 std::uint32_t flags =
reader.readUInt24BE();
1986 if (flags & 0x000001) {
1987 calculatedDataSize += 4;
1989 if (flags & 0x000004) {
1990 calculatedDataSize += 4;
1992 uint32 entrySize = 0;
1993 if (flags & 0x000100) {
1996 if (flags & 0x000200) {
1999 if (flags & 0x000400) {
2002 if (flags & 0x000800) {
2006 if (trunAtom->dataSize() < calculatedDataSize) {
2007 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
2009 if (flags & 0x000001) {
2013 if (flags & 0x000004) {
2017 if (flags & 0x000100) {
2018 totalDuration +=
reader.readUInt32BE();
2020 totalDuration += defaultSampleDuration;
2022 if (flags & 0x000200) {
2023 m_sampleSizes.push_back(
reader.readUInt32BE());
2024 m_size += m_sampleSizes.back();
2026 m_size += defaultSampleSize;
2028 if (flags & 0x000400) {
2031 if (flags & 0x000800) {
2038 if (m_sampleSizes.empty() && defaultSampleSize) {
2039 m_sampleSizes.push_back(defaultSampleSize);
2054 m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(
timeScale));
2059 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2064 m_istream->seekg(static_cast<streamoff>(m_stscAtom->dataOffset() + 4));
2065 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 * nextSibling()
Returns the next sibling of the element.
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.
const ChronoUtilities::DateTime & creationTime() const
Returns the creation time if known; otherwise returns a DateTime of zero ticks.
void setHeight(uint32 value)
Sets the height.
TAG_PARSER_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent=false, bool psPresent=false)
Mpeg4AudioSpecificConfig()
double version() const
Returns the version/level of the track if known; otherwise returns 0.
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...
const ChronoUtilities::TimeSpan & duration() const
Returns the duration if known; otherwise returns a TimeSpan of zero ticks.
const ChronoUtilities::DateTime & modificationTime() const
Returns the time of the last modification if known; otherwise returns a DateTime of zero ticks.
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.