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;
58constexpr 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)
144 , m_framesPerSample(1)
145 , m_chunkOffsetSize(4)
147 , m_sampleToChunkEntryCount(0)
175 static const string context(
"reading chunk offset table of MP4 track");
180 vector<std::uint64_t> offsets;
183 std::uint64_t actualTableSize = m_stcoAtom->
dataSize();
185 diag.emplace_back(
DiagLevel::Critical,
"The stco atom is truncated. There are no chunk offsets present.", context);
188 actualTableSize -= 8;
190 std::uint32_t actualChunkCount =
chunkCount();
192 if (calculatedTableSize < actualTableSize) {
194 DiagLevel::Critical,
"The stco atom stores more chunk offsets as denoted. The additional chunk offsets will be ignored.", context);
195 }
else if (calculatedTableSize > actualTableSize) {
196 diag.emplace_back(
DiagLevel::Critical,
"The stco atom is truncated. It stores less chunk offsets as denoted.", context);
197 actualChunkCount =
static_cast<std::uint32_t
>(floor(
static_cast<double>(actualTableSize) /
static_cast<double>(
chunkOffsetSize())));
200 offsets.reserve(actualChunkCount);
204 for (std::uint32_t i = 0; i < actualChunkCount; ++i) {
205 offsets.push_back(
reader().readUInt32BE());
209 for (std::uint32_t i = 0; i < actualChunkCount; ++i) {
210 offsets.push_back(
reader().readUInt64BE());
214 diag.emplace_back(
DiagLevel::Critical,
"The determined chunk offset size is invalid.", context);
219 if (parseFragments) {
223 moofAtom->parse(diag);
226 trafAtom->parse(diag);
229 tfhdAtom->parse(diag);
230 std::uint32_t calculatedDataSize = 0;
231 if (tfhdAtom->dataSize() < calculatedDataSize) {
234 inputStream().seekg(
static_cast<streamoff
>(tfhdAtom->dataOffset() + 1));
235 const std::uint32_t
flags =
reader().readUInt24BE();
237 if (
flags & 0x000001) {
238 calculatedDataSize += 8;
240 if (
flags & 0x000002) {
241 calculatedDataSize += 4;
243 if (
flags & 0x000008) {
244 calculatedDataSize += 4;
246 if (
flags & 0x000010) {
247 calculatedDataSize += 4;
249 if (
flags & 0x000020) {
250 calculatedDataSize += 4;
256 std::uint32_t defaultSampleSize = 0;
258 if (tfhdAtom->dataSize() < calculatedDataSize) {
259 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
261 if (
flags & 0x000001) {
265 if (
flags & 0x000002) {
269 if (
flags & 0x000008) {
273 if (
flags & 0x000010) {
274 defaultSampleSize =
reader().readUInt32BE();
276 if (
flags & 0x000020) {
283 std::uint32_t trunCalculatedDataSize = 8;
284 if (trunAtom->dataSize() < trunCalculatedDataSize) {
287 inputStream().seekg(
static_cast<streamoff
>(trunAtom->dataOffset() + 1));
288 std::uint32_t trunFlags =
reader().readUInt24BE();
291 if (trunFlags & 0x000001) {
292 trunCalculatedDataSize += 4;
294 if (trunFlags & 0x000004) {
295 trunCalculatedDataSize += 4;
297 std::uint32_t entrySize = 0;
298 if (trunFlags & 0x000100) {
301 if (trunFlags & 0x000200) {
304 if (trunFlags & 0x000400) {
307 if (trunFlags & 0x000800) {
311 if (trunAtom->dataSize() < trunCalculatedDataSize) {
312 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
314 if (trunFlags & 0x000001) {
318 if (trunFlags & 0x000004) {
322 if (trunFlags & 0x000100) {
328 if (trunFlags & 0x000200) {
329 m_sampleSizes.push_back(
reader().readUInt32BE());
330 m_size += m_sampleSizes.back();
332 m_size += defaultSampleSize;
334 if (trunFlags & 0x000400) {
337 if (trunFlags & 0x000800) {
344 if (m_sampleSizes.empty() && defaultSampleSize) {
345 m_sampleSizes.push_back(defaultSampleSize);
360std::uint64_t Mp4Track::accumulateSampleSizes(
size_t &sampleIndex,
size_t count,
Diagnostics &diag)
362 if (sampleIndex + count <= m_sampleSizes.size()) {
363 std::uint64_t sum = 0;
364 for (
size_t end = sampleIndex + count; sampleIndex < end; ++sampleIndex) {
365 sum += m_sampleSizes[sampleIndex];
368 }
else if (m_sampleSizes.size() == 1) {
369 sampleIndex += count;
370 return static_cast<std::uint64_t
>(m_sampleSizes.front()) * count;
372 diag.emplace_back(
DiagLevel::Critical,
"There are not as many sample size entries as samples.",
"reading chunk sizes of MP4 track");
373 throw InvalidDataException();
385void Mp4Track::addChunkSizeEntries(
386 std::vector<std::uint64_t> &chunkSizeTable,
size_t count,
size_t &sampleIndex, std::uint32_t sampleCount, Diagnostics &diag)
388 for (
size_t i = 0; i < count; ++i) {
389 chunkSizeTable.push_back(accumulateSampleSizes(sampleIndex,
sampleCount, diag));
397TrackHeaderInfo Mp4Track::verifyPresentTrackHeader()
const
399 TrackHeaderInfo info;
407 info.discardBuffer = m_tkhdAtom->
buffer() ==
nullptr;
408 if (info.discardBuffer) {
413 switch (info.version =
static_cast<std::uint8_t
>(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 std::uint64_t 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<std::uint32_t, std::uint32_t, std::uint32_t>> sampleToChunkTable;
484 sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
486 for (std::uint32_t i = 0; i < actualSampleToChunkEntryCount; ++i) {
488 std::uint32_t firstChunk =
reader().readUInt32BE();
489 std::uint32_t samplesPerChunk =
reader().readUInt32BE();
490 std::uint32_t 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<std::uint64_t> chunkSizes;
519 if (!sampleToChunkTable.empty()) {
521 auto tableIterator = sampleToChunkTable.cbegin();
522 chunkSizes.reserve(m_chunkCount);
524 size_t sampleIndex = 0;
525 std::uint32_t 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 std::uint32_t samplesPerChunk = get<1>(*tableIterator);
533 for (
const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
534 std::uint32_t 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 greater than the first chunk of the previous entry and not "
540 "greater 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);
561 static const string context(
"parsing MPEG-4 elementary stream descriptor");
562 using namespace Mpeg4ElementaryStreamObjectIds;
563 unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
567 if (
reader.readUInt32BE() != 0) {
581 esInfo = make_unique<Mpeg4ElementaryStreamInfo>();
582 esInfo->id =
reader.readUInt16BE();
583 esInfo->esDescFlags =
reader.readByte();
584 if (esInfo->dependencyFlag()) {
585 esInfo->dependsOnId =
reader.readUInt16BE();
587 if (esInfo->urlFlag()) {
590 if (esInfo->ocrFlag()) {
591 esInfo->ocrId =
reader.readUInt16BE();
595 esDescChild; esDescChild = esDescChild->
nextSibling()) {
596 esDescChild->parse(diag);
597 switch (esDescChild->id()) {
600 reader.stream()->seekg(
static_cast<streamoff
>(esDescChild->dataOffset()));
601 esInfo->objectTypeId =
reader.readByte();
602 esInfo->decCfgDescFlags =
reader.readByte();
603 esInfo->bufferSize =
reader.readUInt24BE();
604 esInfo->maxBitrate =
reader.readUInt32BE();
605 esInfo->averageBitrate =
reader.readUInt32BE();
607 decCfgDescChild = decCfgDescChild->
nextSibling()) {
608 decCfgDescChild->parse(diag);
609 switch (decCfgDescChild->id()) {
612 switch (esInfo->objectTypeId) {
619 esInfo->audioSpecificConfig
623 esInfo->videoSpecificConfig
638 diag.emplace_back(
DiagLevel::Critical,
"The MPEG-4 descriptor element structure is invalid.", context);
641 diag.emplace_back(
DiagLevel::Warning,
"Elementary stream descriptor atom (esds) is truncated.", context);
651 istream &stream, std::uint64_t startOffset, std::uint64_t size,
Diagnostics &diag)
653 static const string context(
"parsing MPEG-4 audio specific config from elementary stream descriptor");
654 using namespace Mpeg4AudioObjectIds;
657 auto buff = make_unique<char[]>(
size);
658 stream.read(buff.get(),
static_cast<streamoff
>(
size));
659 BitReader bitReader(buff.get(),
size);
660 auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
663 auto getAudioObjectType = [&bitReader] {
664 std::uint8_t objType = bitReader.readBits<std::uint8_t>(5);
666 objType = 32 + bitReader.readBits<std::uint8_t>(6);
670 audioCfg->audioObjectType = getAudioObjectType();
672 if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
673 audioCfg->sampleFrequency = bitReader.readBits<std::uint32_t>(24);
676 audioCfg->channelConfiguration = bitReader.readBits<std::uint8_t>(4);
678 switch (audioCfg->audioObjectType) {
681 audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
682 audioCfg->sbrPresent =
true;
683 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
684 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
686 if ((audioCfg->audioObjectType = getAudioObjectType()) ==
ErBsac) {
687 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
691 switch (audioCfg->extensionAudioObjectType) {
693 audioCfg->psPresent =
true;
698 switch (audioCfg->audioObjectType) {
710 audioCfg->frameLengthFlag = bitReader.readBits<std::uint8_t>(1);
711 if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
712 audioCfg->coreCoderDelay = bitReader.readBits<std::uint8_t>(14);
714 audioCfg->extensionFlag = bitReader.readBit();
715 if (audioCfg->channelConfiguration == 0) {
718 switch (audioCfg->audioObjectType) {
721 audioCfg->layerNr = bitReader.readBits<std::uint8_t>(3);
725 if (audioCfg->extensionFlag == 1) {
726 switch (audioCfg->audioObjectType) {
728 audioCfg->numOfSubFrame = bitReader.readBits<std::uint8_t>(5);
729 audioCfg->layerLength = bitReader.readBits<std::uint16_t>(11);
735 audioCfg->resilienceFlags = bitReader.readBits<std::uint8_t>(3);
739 if (bitReader.readBit() == 1) {
748 switch (audioCfg->audioObjectType) {
760 switch (audioCfg->epConfig = bitReader.readBits<std::uint8_t>(2)) {
764 bitReader.skipBits(1);
771 if (audioCfg->extensionAudioObjectType !=
Sbr && audioCfg->extensionAudioObjectType !=
Ps && bitReader.bitsAvailable() >= 16) {
772 std::uint16_t syncExtensionType = bitReader.readBits<std::uint16_t>(11);
773 if (syncExtensionType == 0x2B7) {
774 if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) ==
Sbr) {
775 if ((audioCfg->sbrPresent = bitReader.readBit())) {
776 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
777 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
779 if (bitReader.bitsAvailable() >= 12) {
780 if ((syncExtensionType = bitReader.readBits<std::uint16_t>(11)) == 0x548) {
781 audioCfg->psPresent = bitReader.readBits<std::uint8_t>(1);
785 }
else if (audioCfg->extensionAudioObjectType ==
ErBsac) {
786 if ((audioCfg->sbrPresent = bitReader.readBit())) {
787 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
788 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
791 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
793 }
else if (syncExtensionType == 0x548) {
794 audioCfg->psPresent = bitReader.readBit();
799 }
catch (
const std::ios_base::failure &) {
805 diag.emplace_back(
DiagLevel::Critical,
"Audio specific configuration is truncated.", context);
816 BinaryReader &reader, std::uint64_t startOffset, std::uint64_t size,
Diagnostics &diag)
818 static const string context(
"parsing MPEG-4 video specific config from elementary stream descriptor");
819 using namespace Mpeg4AudioObjectIds;
820 auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
823 if (
size > 3 && (
reader.readUInt24BE() == 1)) {
828 switch (
reader.readByte()) {
831 videoCfg->profile =
reader.readByte();
841 if ((buff1 =
reader.readUInt24BE()) != 1) {
842 reader.stream()->seekg(-2, ios_base::cur);
843 videoCfg->userData.push_back(
static_cast<char>(buff1 >> 16));
850 if (buff1 != 1 &&
size > 0) {
851 videoCfg->userData +=
reader.readString(
size);
859 if (
reader.readUInt24BE() != 1) {
860 reader.stream()->seekg(-2, ios_base::cur);
869 diag.emplace_back(
DiagLevel::Critical,
"\"Visual Object Sequence Header\" not found.", context);
896 if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
899 static const unsigned int stcoDataBegin = 8;
900 std::uint64_t startPos = m_stcoAtom->
dataOffset() + stcoDataBegin;
901 std::uint64_t endPos = startPos + m_stcoAtom->
dataSize() - stcoDataBegin;
902 m_istream->seekg(
static_cast<streamoff
>(startPos));
903 m_ostream->seekp(
static_cast<streamoff
>(startPos));
904 vector<std::int64_t>::size_type i;
905 vector<std::int64_t>::size_type
size;
906 auto currentPos =
static_cast<std::uint64_t
>(
m_istream->tellg());
907 switch (m_stcoAtom->
id()) {
910 while ((currentPos + 4) <= endPos) {
912 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
913 if (off >
static_cast<std::uint64_t
>(oldMdatOffsets[i])) {
914 off +=
static_cast<std::uint32_t
>(newMdatOffsets[i] - oldMdatOffsets[i]);
918 m_ostream->seekp(
static_cast<streamoff
>(currentPos));
920 currentPos +=
static_cast<std::uint64_t
>(
m_istream->gcount());
926 while ((currentPos + 8) <= endPos) {
928 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
929 if (off >
static_cast<std::uint64_t
>(oldMdatOffsets[i])) {
930 off +=
static_cast<std::uint64_t
>(newMdatOffsets[i] - oldMdatOffsets[i]);
934 m_ostream->seekp(
static_cast<streamoff
>(currentPos));
936 currentPos +=
static_cast<std::uint64_t
>(
m_istream->gcount());
967 switch (m_stcoAtom->
id()) {
969 for (
auto offset : chunkOffsets) {
970 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(offset));
974 for (
auto offset : chunkOffsets) {
1004 writer().writeUInt32BE(
static_cast<std::uint32_t
>(offset));
1007 writer().writeUInt64BE(offset);
1053 CPP_UTILITIES_UNUSED(av1Config)
1054 CPP_UTILITIES_UNUSED(track)
1066 CPP_UTILITIES_UNUSED(diag)
1075 trakChild->makeBuffer();
1079 childAtom->makeBuffer();
1089 CPP_UTILITIES_UNUSED(diag)
1093 std::uint64_t
size = 8;
1095 size += verifyPresentTrackHeader().requiredSize;
1101 size += trakChild->totalSize();
1104 if (
static_cast<std::uint64_t
>((
m_creationTime -
startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
1106 ||
static_cast<std::uint64_t
>(
m_duration.totalSeconds() *
m_timeScale) > numeric_limits<std::uint32_t>::max()) {
1116 bool dinfAtomWritten =
false;
1120 dinfAtomWritten =
true;
1122 size += childAtom->totalSize();
1125 if (!dinfAtomWritten) {
1143 ostream::pos_type trakStartOffset =
outputStream().tellp();
1155 trakChild->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1175 if (info.versionUnknown) {
1177 argsToString(
"The version of the present \"tkhd\"-atom (", info.version,
") is unknown. Assuming version 1."),
1178 argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1180 if (info.truncated) {
1182 DiagLevel::Critical, argsToString(
"The present \"tkhd\"-atom is truncated."), argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1186 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
1187 writer().writeUInt32BE(1);
1189 writer().writeUInt64BE(info.requiredSize);
1191 writer().writeUInt32BE(
static_cast<std::uint32_t
>(info.requiredSize));
1199 const std::uint8_t
version = (info.version == 0)
1201 ||
duration > numeric_limits<std::uint32_t>::max())
1207 std::uint32_t
flags = 0;
1229 writer().writeUInt32BE(
static_cast<std::uint32_t
>(
m_id));
1230 writer().writeUInt32BE(0);
1236 writer().writeUInt32BE(0);
1237 writer().writeUInt32BE(0);
1240 if (info.canUseExisting) {
1243 static_cast<streamoff
>(m_tkhdAtom->
dataSize() - info.additionalDataOffset));
1245 if (info.discardBuffer) {
1250 diag.emplace_back(
DiagLevel::Warning,
"Writing some default values because the existing tkhd atom is truncated.",
"making tkhd atom");
1251 writer().writeInt16BE(0);
1252 writer().writeInt16BE(0);
1253 writer().writeFixed8BE(1.0);
1254 writer().writeUInt16BE(0);
1255 for (
const std::int32_t value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) {
1256 writer().writeInt32BE(value);
1258 writer().writeFixed16BE(1.0);
1259 writer().writeFixed16BE(1.0);
1269 ostream::pos_type mdiaStartOffset =
outputStream().tellp();
1270 writer().writeUInt32BE(0);
1277 ||
duration > numeric_limits<std::uint32_t>::max())
1283 writer().writeUInt24BE(0);
1299 std::uint16_t codedLanguage = 0;
1300 for (
size_t charIndex = 0; charIndex != 3; ++charIndex) {
1301 const char langChar = charIndex <
language.size() ?
language[charIndex] : 0;
1302 if (langChar >=
'a' && langChar <=
'z') {
1303 codedLanguage |=
static_cast<std::uint16_t
>((langChar - 0x60) << (0xA - charIndex * 0x5));
1314 DiagLevel::Warning,
"Assigned language \"" %
language +
"\" is of an invalid format. Setting language to undefined.",
"making mdhd atom");
1315 codedLanguage = 0x55C4;
1320 DiagLevel::Warning,
"Assigned language \"" %
language +
"\" is longer than 3 byte and hence will be truncated.",
"making mdhd atom");
1322 writer().writeUInt16BE(codedLanguage);
1323 writer().writeUInt16BE(0);
1325 writer().writeUInt32BE(33 +
static_cast<std::uint32_t
>(
m_name.size()));
1327 writer().writeUInt64BE(0);
1346 diag.emplace_back(
DiagLevel::Critical,
"Media type is invalid; keeping media type as-is.",
"making hdlr atom");
1348 writer().writeUInt32BE(m_rawMediaType);
1351 for (
int i = 0; i < 3; ++i)
1352 writer().writeUInt32BE(0);
1366 ostream::pos_type minfStartOffset =
outputStream().tellp();
1367 writer().writeUInt32BE(0);
1369 bool dinfAtomWritten =
false;
1377 dinfAtomWritten =
true;
1379 childAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1383 if (!dinfAtomWritten) {
1384 writer().writeUInt32BE(36);
1387 writer().writeUInt32BE(28);
1389 writer().writeUInt32BE(0);
1390 writer().writeUInt32BE(1);
1392 writer().writeUInt32BE(12);
1395 writer().writeUInt24BE(0x000001);
1399 bool stblAtomWritten =
false;
1402 stblAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1403 stblAtomWritten =
true;
1406 if (!stblAtomWritten) {
1408 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.",
"making stbl atom");
1423 writer().writeUInt32BE(0);
1431 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stsd atom from scratch.",
"making stsd atom");
1440 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stts atom from scratch.",
"making stts atom");
1481 CPP_UTILITIES_UNUSED(progress)
1483 static const string context(
"parsing MP4 track");
1484 using namespace Mp4AtomIds;
1541 auto atomVersion =
reader.readByte();
1546 switch (atomVersion) {
1559 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1568 atomVersion =
reader.readByte();
1570 switch (atomVersion) {
1585 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be "
1591 std::uint16_t tmp =
reader.readUInt16BE();
1593 const char buff[] = {
1594 static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1595 static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1596 static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1607 switch (m_rawMediaType =
reader.readUInt32BE()) {
1628 if (
static_cast<std::uint64_t
>(tmp =
static_cast<std::uint8_t
>(
m_istream->peek())) == m_hdlrAtom->
dataSize() - 12 - 4 - 8 - 1) {
1640 m_chunkCount =
reader.readUInt32BE();
1644 const auto entryCount =
reader.readUInt32BE();
1645 Mp4Atom *esDescParentAtom =
nullptr;
1648 for (
Mp4Atom *codecConfigContainerAtom = m_stsdAtom->
firstChild(); codecConfigContainerAtom;
1649 codecConfigContainerAtom = codecConfigContainerAtom->
nextSibling()) {
1650 codecConfigContainerAtom->
parse(diag);
1653 m_formatId = interpretIntegerAsString<std::uint32_t>(codecConfigContainerAtom->id());
1657 m_istream->seekg(
static_cast<streamoff
>(codecConfigContainerAtom->dataOffset()));
1658 switch (codecConfigContainerAtom->id()) {
1674 tmp =
reader.readUInt16BE();
1690 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1693 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1696 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1698 if (!esDescParentAtom) {
1699 esDescParentAtom = codecConfigContainerAtom;
1716 m_istream->seekg(6 + 2 + 16, ios_base::cur);
1722 m_framesPerSample =
reader.readUInt16BE();
1727 }
else if (tmp < 32) {
1731 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1732 if (!esDescParentAtom) {
1733 esDescParentAtom = codecConfigContainerAtom;
1738 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1739 if (!esDescParentAtom) {
1740 esDescParentAtom = codecConfigContainerAtom;
1751 if (esDescParentAtom) {
1754 m_istream->seekg(
static_cast<streamoff
>(avcConfigAtom->dataOffset()));
1755 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1757 m_avcConfig->parse(
reader, avcConfigAtom->dataSize(), diag);
1768 m_istream->seekg(
static_cast<streamoff
>(av1ConfigAtom->dataOffset()));
1769 m_av1Config = make_unique<TagParser::Av1Configuration>();
1771 m_av1Config->parse(
reader, av1ConfigAtom->dataSize(), diag);
1791 m_bitrate =
static_cast<double>(m_esInfo->averageBitrate) / 1000;
1792 m_maxBitrate =
static_cast<double>(m_esInfo->maxBitrate) / 1000;
1793 if (m_esInfo->audioSpecificConfig) {
1796 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1797 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1802 diag.emplace_back(
DiagLevel::Warning,
"Audio specific config has invalid sample frequency index.", context);
1804 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1811 DiagLevel::Warning,
"Audio specific config has invalid extension sample frequency index.", context);
1813 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1816 if (m_esInfo->videoSpecificConfig) {
1819 m_format.
sub = m_esInfo->videoSpecificConfig->profile;
1820 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1822 m_formatId += m_esInfo->videoSpecificConfig->userData;
1832 m_istream->seekg(
static_cast<streamoff
>(m_chunkOffsetSize == 8 ?
reader.readUInt64BE() :
reader.readUInt32BE()));
1845 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse child atoms of \"stsd\"-atom.", context);
1850 m_sampleSizes.clear();
1852 std::uint64_t actualSampleSizeTableSize = m_stszAtom->
dataSize();
1853 if (actualSampleSizeTableSize < 12) {
1855 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1857 actualSampleSizeTableSize -= 12;
1859 std::uint32_t fieldSize;
1860 std::uint32_t constantSize;
1864 fieldSize =
reader.readByte();
1867 constantSize =
reader.readUInt32BE();
1872 m_sampleSizes.push_back(constantSize);
1876 const auto calculatedSampleSizeTableSize
1877 =
static_cast<std::uint64_t
>(std::ceil((0.125 * fieldSize) *
static_cast<double>(
m_sampleCount)));
1878 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1880 DiagLevel::Critical,
"The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1881 }
else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1882 diag.emplace_back(
DiagLevel::Critical,
"The stsz atom is truncated. It stores less entries as denoted.", context);
1883 actualSampleCount =
static_cast<std::uint64_t
>(floor(
static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize)));
1885 m_sampleSizes.reserve(actualSampleCount);
1886 std::uint32_t i = 1;
1887 switch (fieldSize) {
1889 for (; i <= actualSampleCount; i += 2) {
1890 std::uint8_t val =
reader.readByte();
1891 m_sampleSizes.push_back(val >> 4);
1892 m_sampleSizes.push_back(val & 0xF0);
1893 m_size += (val >> 4) + (val & 0xF0);
1895 if (i <= actualSampleCount + 1) {
1896 m_sampleSizes.push_back(
reader.readByte() >> 4);
1897 m_size += m_sampleSizes.back();
1901 for (; i <= actualSampleCount; ++i) {
1902 m_sampleSizes.push_back(
reader.readByte());
1903 m_size += m_sampleSizes.back();
1907 for (; i <= actualSampleCount; ++i) {
1908 m_sampleSizes.push_back(
reader.readUInt16BE());
1909 m_size += m_sampleSizes.back();
1913 for (; i <= actualSampleCount; ++i) {
1914 m_sampleSizes.push_back(
reader.readUInt32BE());
1915 m_size += m_sampleSizes.back();
1920 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1927 std::uint64_t totalDuration = 0;
1930 moofAtom->parse(diag);
1932 trafAtom->parse(diag);
1935 tfhdAtom->parse(diag);
1936 std::uint32_t calculatedDataSize = 0;
1937 if (tfhdAtom->dataSize() < calculatedDataSize) {
1940 m_istream->seekg(
static_cast<streamoff
>(tfhdAtom->dataOffset() + 1));
1941 std::uint32_t tfhdFlags =
reader.readUInt24BE();
1943 if (tfhdFlags & 0x000001) {
1944 calculatedDataSize += 8;
1946 if (tfhdFlags & 0x000002) {
1947 calculatedDataSize += 4;
1949 if (tfhdFlags & 0x000008) {
1950 calculatedDataSize += 4;
1952 if (tfhdFlags & 0x000010) {
1953 calculatedDataSize += 4;
1955 if (tfhdFlags & 0x000020) {
1956 calculatedDataSize += 4;
1960 std::uint32_t defaultSampleDuration = 0;
1961 std::uint32_t defaultSampleSize = 0;
1963 if (tfhdAtom->dataSize() < calculatedDataSize) {
1964 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
1966 if (tfhdFlags & 0x000001) {
1970 if (tfhdFlags & 0x000002) {
1974 if (tfhdFlags & 0x000008) {
1975 defaultSampleDuration =
reader.readUInt32BE();
1978 if (tfhdFlags & 0x000010) {
1979 defaultSampleSize =
reader.readUInt32BE();
1981 if (tfhdFlags & 0x000020) {
1988 std::uint32_t trunCalculatedDataSize = 8;
1989 if (trunAtom->dataSize() < trunCalculatedDataSize) {
1992 m_istream->seekg(
static_cast<streamoff
>(trunAtom->dataOffset() + 1));
1993 std::uint32_t trunFlags =
reader.readUInt24BE();
1996 if (trunFlags & 0x000001) {
1997 trunCalculatedDataSize += 4;
1999 if (trunFlags & 0x000004) {
2000 trunCalculatedDataSize += 4;
2002 std::uint32_t entrySize = 0;
2003 if (trunFlags & 0x000100) {
2006 if (trunFlags & 0x000200) {
2009 if (trunFlags & 0x000400) {
2012 if (trunFlags & 0x000800) {
2015 trunCalculatedDataSize += entrySize *
sampleCount;
2016 if (trunAtom->dataSize() < trunCalculatedDataSize) {
2017 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
2019 if (trunFlags & 0x000001) {
2023 if (trunFlags & 0x000004) {
2027 if (trunFlags & 0x000100) {
2028 totalDuration +=
reader.readUInt32BE();
2030 totalDuration += defaultSampleDuration;
2032 if (trunFlags & 0x000200) {
2033 m_sampleSizes.push_back(
reader.readUInt32BE());
2034 m_size += m_sampleSizes.back();
2036 m_size += defaultSampleSize;
2038 if (trunFlags & 0x000400) {
2041 if (trunFlags & 0x000800) {
2048 if (m_sampleSizes.empty() && defaultSampleSize) {
2049 m_sampleSizes.push_back(defaultSampleSize);
2064 m_duration = TimeSpan::fromSeconds(
static_cast<double>(totalDuration) /
static_cast<double>(
timeScale));
2069 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2075 m_sampleToChunkEntryCount =
reader.readUInt32BE();
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
std::uint64_t size() const
Returns the size in bytes if known; otherwise returns 0.
std::uint32_t timeScale() const
Returns the time scale if known; otherwise returns 0.
const CppUtilities::DateTime & modificationTime() const
Returns the time of the last modification if known; otherwise returns a DateTime of zero ticks.
std::uint8_t m_extensionChannelConfig
std::string_view m_chromaFormat
std::uint64_t m_sampleCount
std::uint64_t startOffset() const
Returns the start offset of the track in the associated stream.
const CppUtilities::DateTime & creationTime() const
Returns the creation time if known; otherwise returns a DateTime of zero ticks.
AspectRatio m_pixelAspectRatio
double version() const
Returns the version/level of the track if known; otherwise returns 0.
std::uint16_t m_bitsPerSample
std::istream & inputStream()
Returns the associated input stream.
bool isEnabled() const
Returns true if the track is marked as enabled; otherwise returns false.
std::uint16_t m_channelCount
std::uint64_t sampleCount() const
Returns the number of samples/frames if known; otherwise returns 0.
std::uint8_t m_channelConfig
CppUtilities::BinaryReader & reader()
Returns a binary reader for the associated stream.
CppUtilities::TimeSpan m_duration
CppUtilities::BinaryReader m_reader
TrackFlags flags() const
Returns flags (various boolean properties) of this track.
std::uint32_t m_timeScale
CppUtilities::DateTime m_modificationTime
CppUtilities::BinaryWriter m_writer
bool isHeaderValid() const
Returns an indication whether the track header is valid.
std::ostream & outputStream()
Returns the associated output stream.
const CppUtilities::TimeSpan & duration() const
Returns the duration if known; otherwise returns a TimeSpan of zero ticks.
std::uint32_t m_extensionSamplingFrequency
CppUtilities::DateTime m_creationTime
std::string m_compressorName
std::uint32_t m_samplingFrequency
CppUtilities::BinaryWriter & writer()
Returns a binary writer for the associated stream.
The Diagnostics class is a container for DiagMessage.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void discardBuffer()
Discards buffered data.
void copyEntirely(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all children to the specified targetStream.
const std::unique_ptr< char[]> & buffer()
Returns buffered data.
std::uint32_t headerSize() const
Returns the header size of the element in byte.
const IdentifierType & id() const
Returns the element ID.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
ImplementationType * nextSibling()
Returns the next sibling of the element.
ImplementationType * denoteFirstChild(std::uint32_t offset)
Denotes the first child to start at the specified offset (relative to the start offset of this descri...
ImplementationType * firstChild()
Returns the first child of the element.
DataSizeType dataSize() const
Returns the data size of the element in byte.
void parse(Diagnostics &diag)
Parses the header information of the element which is read from the related stream at the start offse...
std::uint64_t dataOffset() const
Returns the data offset of the element in the related stream.
ContainerType & container()
Returns the related container.
CppUtilities::BinaryReader & reader()
Returns the related BinaryReader.
void makeBuffer()
Buffers the element (header and data).
ImplementationType * siblingById(const IdentifierType &id, Diagnostics &diag)
Returns the first sibling with the specified id.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
The Mp4Atom class helps to parse MP4 files.
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.
Implementation of TagParser::AbstractTrack for the MP4 container.
static std::unique_ptr< Mpeg4VideoSpecificConfig > parseVideoSpecificConfig(CppUtilities::BinaryReader &reader, std::uint64_t startOffset, std::uint64_t size, Diagnostics &diag)
Parses the video specific configuration for the track.
static std::unique_ptr< Mpeg4ElementaryStreamInfo > parseMpeg4ElementaryStreamInfo(CppUtilities::BinaryReader &reader, Mp4Atom *esDescAtom, Diagnostics &diag)
Reads the MPEG-4 elementary stream descriptor for the track.
std::uint32_t chunkCount() const
Returns the number of chunks denoted by the stco atom.
std::vector< std::tuple< std::uint32_t, std::uint32_t, std::uint32_t > > readSampleToChunkTable(Diagnostics &diag)
Reads the sample to chunk table.
static void addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
Adds the information from the specified avcConfig to the specified track.
std::vector< std::uint64_t > readChunkSizes(TagParser::Diagnostics &diag)
Reads the chunk sizes from the stsz (sample sizes) and stsc (samples per chunk) atom.
void updateChunkOffsets(const std::vector< std::int64_t > &oldMdatOffsets, const std::vector< std::int64_t > &newMdatOffsets)
Updates the chunk offsets of the track.
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
This method is internally called to parse header information.
void makeSampleTable(Diagnostics &diag)
Makes the sample table (stbl atom) for the track.
std::uint64_t requiredSize(Diagnostics &diag) const
Returns the number of bytes written when calling makeTrack().
TrackType type() const override
Returns the type of the track if known; otherwise returns TrackType::Unspecified.
std::uint32_t sampleToChunkEntryCount() const
Returns the number of "sample to chunk" entries within the stsc atom.
void makeMedia(Diagnostics &diag)
Makes the media information (mdia atom) for the track.
std::vector< std::uint64_t > readChunkOffsets(bool parseFragments, Diagnostics &diag)
Reads the chunk offsets from the stco atom and fragments if parseFragments is true.
unsigned int chunkOffsetSize() const
Returns the size of a single chunk offset denotation within the stco atom.
~Mp4Track() override
Destroys the track.
void makeTrackHeader(Diagnostics &diag)
Makes the track header (tkhd atom) for the track.
void bufferTrackAtoms(Diagnostics &diag)
Buffers all atoms required by the makeTrack() method.
void makeMediaInfo(Diagnostics &diag)
Makes a media information (minf atom) for the track.
Mp4Atom & trakAtom()
Returns the trak atom for the current instance.
void makeTrack(Diagnostics &diag)
Makes the track entry ("trak"-atom) for the track.
static std::unique_ptr< Mpeg4AudioSpecificConfig > parseAudioSpecificConfig(std::istream &stream, std::uint64_t startOffset, std::uint64_t size, Diagnostics &diag)
Parses the audio specific configuration for the track.
void updateChunkOffset(std::uint32_t chunkIndex, std::uint64_t offset)
Updates a particular chunk offset.
Mpeg4AudioSpecificConfig()
The Mpeg4Descriptor class helps to parse MPEG-4 descriptors.
Mpeg4VideoSpecificConfig()
static void addInfo(const MpegAudioFrame &frame, AbstractTrack &track)
Adds the information from the specified frame to the specified track.
The MpegAudioFrame class is used to parse MPEG audio frames.
void parseHeader(CppUtilities::BinaryReader &reader, Diagnostics &diag)
Parses the header read using the specified reader.
This exception is thrown when the an operation is invoked that has not been implemented yet.
void setWidth(std::uint32_t value)
Sets the width.
void setHeight(std::uint32_t value)
Sets the height.
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
TAG_PARSER_EXPORT MediaFormat fourccToMediaFormat(std::uint32_t fourccId)
constexpr TAG_PARSER_EXPORT std::string_view language()
@ CompositionTimeToSample
@ Mpeg4ElementaryStreamDescriptor
@ Mpeg4ElementaryStreamDescriptor2
TAG_PARSER_EXPORT MediaFormat idToMediaFormat(std::uint8_t mpeg4AudioObjectId, bool sbrPresent=false, bool psPresent=false)
@ Mpeg2AacLowComplexityProfile
@ Mpeg2AacScaleableSamplingRateProfile
TAG_PARSER_EXPORT MediaFormat streamObjectTypeFormat(std::uint8_t streamObjectTypeId)
Returns the TagParser::MediaFormat denoted by the specified MPEG-4 stream ID.
@ VisualObjectSequenceStart
Contains all classes and functions of the TagInfo library.
const DateTime startDate
Dates within MP4 tracks are expressed as the number of seconds since this date.
std::uint32_t mpeg4SamplingFrequencyTable[13]
TrackType
The TrackType enum specifies the underlying file type of a track and the concrete class of the track ...
The Av1Configuration struct provides a parser for AV1 configuration found in ISOBMFF files.
The AvcConfiguration struct provides a parser for AVC configuration.
std::vector< SpsInfo > spsInfos
std::uint8_t profileIndication
std::uint8_t levelIndication
const LocaleDetail & abbreviatedName(LocaleFormat format) const
Returns the abbreviated name of the specified format.
The SpsInfo struct holds the sequence parameter set.
AspectRatio pixelAspectRatio
std::uint8_t profileIndication
std::uint8_t levelIndication
ugolomb chromaFormatIndication