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)
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) {
220 std::uint64_t totalDuration = 0;
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;
255 std::uint32_t defaultSampleDuration = 0;
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) {
270 defaultSampleDuration =
reader().readUInt32BE();
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) {
323 totalDuration +=
reader().readUInt32BE();
325 totalDuration += defaultSampleDuration;
327 if (trunFlags & 0x000200) {
328 m_sampleSizes.push_back(
reader().readUInt32BE());
329 m_size += m_sampleSizes.back();
331 m_size += defaultSampleSize;
333 if (trunFlags & 0x000400) {
336 if (trunFlags & 0x000800) {
343 if (m_sampleSizes.empty() && defaultSampleSize) {
344 m_sampleSizes.push_back(defaultSampleSize);
359 std::uint64_t Mp4Track::accumulateSampleSizes(
size_t &sampleIndex,
size_t count,
Diagnostics &diag)
361 if (sampleIndex + count <= m_sampleSizes.size()) {
362 std::uint64_t sum = 0;
363 for (
size_t end = sampleIndex + count; sampleIndex < end; ++sampleIndex) {
364 sum += m_sampleSizes[sampleIndex];
367 }
else if (m_sampleSizes.size() == 1) {
368 sampleIndex += count;
369 return static_cast<std::uint64_t
>(m_sampleSizes.front()) * count;
371 diag.emplace_back(
DiagLevel::Critical,
"There are not as many sample size entries as samples.",
"reading chunk sizes of MP4 track");
372 throw InvalidDataException();
384 void Mp4Track::addChunkSizeEntries(
385 std::vector<std::uint64_t> &chunkSizeTable,
size_t count,
size_t &sampleIndex, std::uint32_t sampleCount, Diagnostics &diag)
387 for (
size_t i = 0; i < count; ++i) {
388 chunkSizeTable.push_back(accumulateSampleSizes(sampleIndex,
sampleCount, diag));
396 TrackHeaderInfo Mp4Track::verifyPresentTrackHeader()
const
398 TrackHeaderInfo info;
406 info.discardBuffer = m_tkhdAtom->
buffer() ==
nullptr;
407 if (info.discardBuffer) {
412 switch (info.version =
static_cast<std::uint8_t
>(m_tkhdAtom->
buffer()[m_tkhdAtom->
headerSize()])) {
414 info.additionalDataOffset = 32;
417 info.additionalDataOffset = 44;
420 info.additionalDataOffset = 44;
421 info.versionUnknown =
true;
425 if (info.additionalDataOffset + 48u <= m_tkhdAtom->
dataSize()) {
426 info.canUseExisting =
true;
428 info.truncated =
true;
429 info.canUseExisting = info.additionalDataOffset < m_tkhdAtom->
dataSize();
430 if (!info.canUseExisting && info.discardBuffer) {
436 info.requiredSize = m_tkhdAtom->
dataSize() + 8;
438 if ((info.version == 0)
439 && (
static_cast<std::uint64_t
>((
m_creationTime -
startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
441 ||
static_cast<std::uint64_t
>(
m_duration.totalSeconds() *
m_timeScale) > numeric_limits<std::uint32_t>::max())) {
442 info.requiredSize += 12;
445 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
446 info.requiredSize += 8;
460 static const string context(
"reading sample to chunk table of MP4 track");
462 diag.emplace_back(
DiagLevel::Critical,
"Track has not been parsed or is invalid.", context);
466 std::uint64_t actualTableSize = m_stscAtom->
dataSize();
467 if (actualTableSize < 20) {
468 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom is truncated. There are no \"sample to chunk\" entries present.", context);
471 actualTableSize -= 8;
474 std::uint64_t calculatedTableSize = actualSampleToChunkEntryCount * 12;
475 if (calculatedTableSize < actualTableSize) {
476 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom stores more entries as denoted. The additional entries will be ignored.", context);
477 }
else if (calculatedTableSize > actualTableSize) {
478 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom is truncated. It stores less entries as denoted.", context);
479 actualSampleToChunkEntryCount = actualTableSize / 12;
482 vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> sampleToChunkTable;
483 sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
485 for (std::uint32_t i = 0; i < actualSampleToChunkEntryCount; ++i) {
487 std::uint32_t firstChunk =
reader().readUInt32BE();
488 std::uint32_t samplesPerChunk =
reader().readUInt32BE();
489 std::uint32_t sampleDescriptionIndex =
reader().readUInt32BE();
490 sampleToChunkTable.emplace_back(firstChunk, samplesPerChunk, sampleDescriptionIndex);
492 return sampleToChunkTable;
509 static const string context(
"reading chunk sizes of MP4 track");
511 diag.emplace_back(
DiagLevel::Critical,
"Track has not been parsed or is invalid.", context);
517 vector<std::uint64_t> chunkSizes;
518 if (!sampleToChunkTable.empty()) {
520 auto tableIterator = sampleToChunkTable.cbegin();
521 chunkSizes.reserve(m_chunkCount);
523 size_t sampleIndex = 0;
524 std::uint32_t previousChunkIndex = get<0>(*tableIterator);
525 if (previousChunkIndex != 1) {
526 diag.emplace_back(
DiagLevel::Critical,
"The first chunk of the first \"sample to chunk\" entry must be 1.", context);
527 previousChunkIndex = 1;
529 std::uint32_t samplesPerChunk = get<1>(*tableIterator);
532 for (
const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
533 std::uint32_t firstChunkIndex = get<0>(*tableIterator);
534 if (firstChunkIndex > previousChunkIndex && firstChunkIndex <= m_chunkCount) {
535 addChunkSizeEntries(chunkSizes, firstChunkIndex - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
538 "The first chunk index of a \"sample to chunk\" entry must be greather than the first chunk of the previous entry and not "
539 "greather than the chunk count.",
543 previousChunkIndex = firstChunkIndex;
544 samplesPerChunk = get<1>(*tableIterator);
546 if (m_chunkCount >= previousChunkIndex) {
547 addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
560 static const string context(
"parsing MPEG-4 elementary stream descriptor");
561 using namespace Mpeg4ElementaryStreamObjectIds;
562 unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
566 if (
reader.readUInt32BE() != 0) {
580 esInfo = make_unique<Mpeg4ElementaryStreamInfo>();
581 esInfo->id =
reader.readUInt16BE();
582 esInfo->esDescFlags =
reader.readByte();
583 if (esInfo->dependencyFlag()) {
584 esInfo->dependsOnId =
reader.readUInt16BE();
586 if (esInfo->urlFlag()) {
589 if (esInfo->ocrFlag()) {
590 esInfo->ocrId =
reader.readUInt16BE();
594 esDescChild; esDescChild = esDescChild->
nextSibling()) {
595 esDescChild->parse(diag);
596 switch (esDescChild->id()) {
599 reader.stream()->seekg(
static_cast<streamoff
>(esDescChild->dataOffset()));
600 esInfo->objectTypeId =
reader.readByte();
601 esInfo->decCfgDescFlags =
reader.readByte();
602 esInfo->bufferSize =
reader.readUInt24BE();
603 esInfo->maxBitrate =
reader.readUInt32BE();
604 esInfo->averageBitrate =
reader.readUInt32BE();
606 decCfgDescChild = decCfgDescChild->
nextSibling()) {
607 decCfgDescChild->parse(diag);
608 switch (decCfgDescChild->id()) {
611 switch (esInfo->objectTypeId) {
618 esInfo->audioSpecificConfig
622 esInfo->videoSpecificConfig
637 diag.emplace_back(
DiagLevel::Critical,
"The MPEG-4 descriptor element structure is invalid.", context);
640 diag.emplace_back(
DiagLevel::Warning,
"Elementary stream descriptor atom (esds) is truncated.", context);
652 static const string context(
"parsing MPEG-4 audio specific config from elementary stream descriptor");
653 using namespace Mpeg4AudioObjectIds;
656 auto buff = make_unique<char[]>(
size);
657 stream.read(buff.get(),
static_cast<streamoff
>(
size));
658 BitReader bitReader(buff.get(),
size);
659 auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
662 auto getAudioObjectType = [&bitReader] {
663 std::uint8_t objType = bitReader.readBits<std::uint8_t>(5);
665 objType = 32 + bitReader.readBits<std::uint8_t>(6);
669 audioCfg->audioObjectType = getAudioObjectType();
671 if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
672 audioCfg->sampleFrequency = bitReader.readBits<std::uint32_t>(24);
675 audioCfg->channelConfiguration = bitReader.readBits<std::uint8_t>(4);
677 switch (audioCfg->audioObjectType) {
680 audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
681 audioCfg->sbrPresent =
true;
682 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
683 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
685 if ((audioCfg->audioObjectType = getAudioObjectType()) ==
ErBsac) {
686 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
690 switch (audioCfg->extensionAudioObjectType) {
692 audioCfg->psPresent =
true;
697 switch (audioCfg->audioObjectType) {
709 audioCfg->frameLengthFlag = bitReader.readBits<std::uint8_t>(1);
710 if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
711 audioCfg->coreCoderDelay = bitReader.readBits<std::uint8_t>(14);
713 audioCfg->extensionFlag = bitReader.readBit();
714 if (audioCfg->channelConfiguration == 0) {
717 switch (audioCfg->audioObjectType) {
720 audioCfg->layerNr = bitReader.readBits<std::uint8_t>(3);
724 if (audioCfg->extensionFlag == 1) {
725 switch (audioCfg->audioObjectType) {
727 audioCfg->numOfSubFrame = bitReader.readBits<std::uint8_t>(5);
728 audioCfg->layerLength = bitReader.readBits<std::uint16_t>(11);
734 audioCfg->resilienceFlags = bitReader.readBits<std::uint8_t>(3);
738 if (bitReader.readBit() == 1) {
747 switch (audioCfg->audioObjectType) {
759 switch (audioCfg->epConfig = bitReader.readBits<std::uint8_t>(2)) {
763 bitReader.skipBits(1);
770 if (audioCfg->extensionAudioObjectType !=
Sbr && audioCfg->extensionAudioObjectType !=
Ps && bitReader.bitsAvailable() >= 16) {
771 std::uint16_t syncExtensionType = bitReader.readBits<std::uint16_t>(11);
772 if (syncExtensionType == 0x2B7) {
773 if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) ==
Sbr) {
774 if ((audioCfg->sbrPresent = bitReader.readBit())) {
775 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
776 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
778 if (bitReader.bitsAvailable() >= 12) {
779 if ((syncExtensionType = bitReader.readBits<std::uint16_t>(11)) == 0x548) {
780 audioCfg->psPresent = bitReader.readBits<std::uint8_t>(1);
784 }
else if (audioCfg->extensionAudioObjectType ==
ErBsac) {
785 if ((audioCfg->sbrPresent = bitReader.readBit())) {
786 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
787 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
790 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
792 }
else if (syncExtensionType == 0x548) {
793 audioCfg->psPresent = bitReader.readBit();
798 }
catch (
const std::ios_base::failure &) {
804 diag.emplace_back(
DiagLevel::Critical,
"Audio specific configuration is truncated.", context);
817 static const string context(
"parsing MPEG-4 video specific config from elementary stream descriptor");
818 using namespace Mpeg4AudioObjectIds;
819 auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
822 if (
size > 3 && (
reader.readUInt24BE() == 1)) {
827 switch (
reader.readByte()) {
830 videoCfg->profile =
reader.readByte();
840 if ((buff1 =
reader.readUInt24BE()) != 1) {
841 reader.stream()->seekg(-2, ios_base::cur);
842 videoCfg->userData.push_back(
static_cast<char>(buff1 >> 16));
849 if (buff1 != 1 &&
size > 0) {
850 videoCfg->userData +=
reader.readString(
size);
858 if (
reader.readUInt24BE() != 1) {
859 reader.stream()->seekg(-2, ios_base::cur);
868 diag.emplace_back(
DiagLevel::Critical,
"\"Visual Object Sequence Header\" not found.", context);
895 if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
898 static const unsigned int stcoDataBegin = 8;
899 std::uint64_t startPos = m_stcoAtom->
dataOffset() + stcoDataBegin;
900 std::uint64_t endPos = startPos + m_stcoAtom->
dataSize() - stcoDataBegin;
901 m_istream->seekg(
static_cast<streamoff
>(startPos));
902 m_ostream->seekp(
static_cast<streamoff
>(startPos));
903 vector<std::int64_t>::size_type i;
904 vector<std::int64_t>::size_type
size;
905 auto currentPos =
static_cast<std::uint64_t
>(
m_istream->tellg());
906 switch (m_stcoAtom->
id()) {
909 while ((currentPos + 4) <= endPos) {
911 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
912 if (off >
static_cast<std::uint64_t
>(oldMdatOffsets[i])) {
913 off +=
static_cast<std::uint32_t
>(newMdatOffsets[i] - oldMdatOffsets[i]);
917 m_ostream->seekp(
static_cast<streamoff
>(currentPos));
919 currentPos +=
static_cast<std::uint64_t
>(
m_istream->gcount());
925 while ((currentPos + 8) <= endPos) {
927 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
928 if (off >
static_cast<std::uint64_t
>(oldMdatOffsets[i])) {
929 off +=
static_cast<std::uint64_t
>(newMdatOffsets[i] - oldMdatOffsets[i]);
933 m_ostream->seekp(
static_cast<streamoff
>(currentPos));
935 currentPos +=
static_cast<std::uint64_t
>(
m_istream->gcount());
966 switch (m_stcoAtom->
id()) {
968 for (
auto offset : chunkOffsets) {
969 m_writer.writeUInt32BE(
static_cast<std::uint32_t
>(offset));
973 for (
auto offset : chunkOffsets) {
1003 writer().writeUInt32BE(
static_cast<std::uint32_t
>(offset));
1006 writer().writeUInt64BE(offset);
1052 CPP_UTILITIES_UNUSED(av1Config)
1053 CPP_UTILITIES_UNUSED(track)
1065 CPP_UTILITIES_UNUSED(diag)
1074 trakChild->makeBuffer();
1078 childAtom->makeBuffer();
1088 CPP_UTILITIES_UNUSED(diag)
1092 std::uint64_t
size = 8;
1094 size += verifyPresentTrackHeader().requiredSize;
1100 size += trakChild->totalSize();
1103 if (
static_cast<std::uint64_t
>((
m_creationTime -
startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
1105 ||
static_cast<std::uint64_t
>(
m_duration.totalSeconds() *
m_timeScale) > numeric_limits<std::uint32_t>::max()) {
1115 bool dinfAtomWritten =
false;
1119 dinfAtomWritten =
true;
1121 size += childAtom->totalSize();
1124 if (!dinfAtomWritten) {
1142 ostream::pos_type trakStartOffset =
outputStream().tellp();
1154 trakChild->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1174 if (info.versionUnknown) {
1176 argsToString(
"The version of the present \"tkhd\"-atom (", info.version,
") is unknown. Assuming version 1."),
1177 argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1179 if (info.truncated) {
1181 DiagLevel::Critical, argsToString(
"The present \"tkhd\"-atom is truncated."), argsToString(
"making \"tkhd\"-atom of track ",
m_id));
1185 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
1186 writer().writeUInt32BE(1);
1188 writer().writeUInt64BE(info.requiredSize);
1190 writer().writeUInt32BE(
static_cast<std::uint32_t
>(info.requiredSize));
1198 const std::uint8_t
version = (info.version == 0)
1200 ||
duration > numeric_limits<std::uint32_t>::max())
1206 std::uint32_t
flags = 0;
1228 writer().writeUInt32BE(
static_cast<std::uint32_t
>(
m_id));
1229 writer().writeUInt32BE(0);
1235 writer().writeUInt32BE(0);
1236 writer().writeUInt32BE(0);
1239 if (info.canUseExisting) {
1242 static_cast<streamoff
>(m_tkhdAtom->
dataSize() - info.additionalDataOffset));
1244 if (info.discardBuffer) {
1249 diag.emplace_back(
DiagLevel::Warning,
"Writing some default values because the existing tkhd atom is truncated.",
"making tkhd atom");
1250 writer().writeInt16BE(0);
1251 writer().writeInt16BE(0);
1252 writer().writeFixed8BE(1.0);
1253 writer().writeUInt16BE(0);
1254 for (
const std::int32_t value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) {
1255 writer().writeInt32BE(value);
1257 writer().writeFixed16BE(1.0);
1258 writer().writeFixed16BE(1.0);
1268 ostream::pos_type mdiaStartOffset =
outputStream().tellp();
1269 writer().writeUInt32BE(0);
1276 ||
duration > numeric_limits<std::uint32_t>::max())
1282 writer().writeUInt24BE(0);
1298 std::uint16_t codedLanguage = 0;
1299 for (
size_t charIndex = 0; charIndex != 3; ++charIndex) {
1300 const char langChar = charIndex <
language.size() ?
language[charIndex] : 0;
1301 if (langChar >=
'a' && langChar <=
'z') {
1302 codedLanguage |=
static_cast<std::uint16_t
>((langChar - 0x60) << (0xA - charIndex * 0x5));
1313 DiagLevel::Warning,
"Assigned language \"" %
language +
"\" is of an invalid format. Setting language to undefined.",
"making mdhd atom");
1314 codedLanguage = 0x55C4;
1319 DiagLevel::Warning,
"Assigned language \"" %
language +
"\" is longer than 3 byte and hence will be truncated.",
"making mdhd atom");
1321 writer().writeUInt16BE(codedLanguage);
1322 writer().writeUInt16BE(0);
1324 writer().writeUInt32BE(33 +
static_cast<std::uint32_t
>(
m_name.size()));
1326 writer().writeUInt64BE(0);
1345 diag.emplace_back(
DiagLevel::Critical,
"Media type is invalid; keeping media type as-is.",
"making hdlr atom");
1347 writer().writeUInt32BE(m_rawMediaType);
1350 for (
int i = 0; i < 3; ++i)
1351 writer().writeUInt32BE(0);
1365 ostream::pos_type minfStartOffset =
outputStream().tellp();
1366 writer().writeUInt32BE(0);
1368 bool dinfAtomWritten =
false;
1376 dinfAtomWritten =
true;
1378 childAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1382 if (!dinfAtomWritten) {
1383 writer().writeUInt32BE(36);
1386 writer().writeUInt32BE(28);
1388 writer().writeUInt32BE(0);
1389 writer().writeUInt32BE(1);
1391 writer().writeUInt32BE(12);
1394 writer().writeUInt24BE(0x000001);
1398 bool stblAtomWritten =
false;
1401 stblAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1402 stblAtomWritten =
true;
1405 if (!stblAtomWritten) {
1407 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.",
"making stbl atom");
1422 writer().writeUInt32BE(0);
1430 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stsd atom from scratch.",
"making stsd atom");
1439 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stts atom from scratch.",
"making stts atom");
1480 CPP_UTILITIES_UNUSED(progress)
1482 static const string context(
"parsing MP4 track");
1483 using namespace Mp4AtomIds;
1491 if (!(m_tkhdAtom = m_trakAtom->childById(
TrackHeader, diag))) {
1495 if (!(m_mdiaAtom = m_trakAtom->childById(
Media, diag))) {
1499 if (!(m_mdhdAtom = m_mdiaAtom->childById(
MediaHeader, diag))) {
1511 if (!(m_stblAtom = m_minfAtom->childById(
SampleTable, diag))) {
1519 if (!(m_stcoAtom = m_stblAtom->childById(
ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(
ChunkOffset64, diag))) {
1523 if (!(m_stscAtom = m_stblAtom->childById(
SampleToChunk, diag))) {
1536 BinaryReader &
reader = m_trakAtom->reader();
1539 m_istream->seekg(
static_cast<streamoff
>(m_tkhdAtom->startOffset() + 8));
1540 auto atomVersion =
reader.readByte();
1545 switch (atomVersion) {
1558 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1566 m_istream->seekg(
static_cast<streamoff
>(m_mdhdAtom->dataOffset()));
1567 atomVersion =
reader.readByte();
1569 switch (atomVersion) {
1584 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be "
1590 std::uint16_t tmp =
reader.readUInt16BE();
1592 const char buff[] = {
1593 static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1594 static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1595 static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1604 m_istream->seekg(
static_cast<streamoff
>(m_hdlrAtom->dataOffset() + 8));
1606 switch (m_rawMediaType =
reader.readUInt32BE()) {
1627 if (
static_cast<std::uint64_t
>(tmp =
static_cast<std::uint8_t
>(
m_istream->peek())) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1633 m_name =
reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1638 m_istream->seekg(
static_cast<streamoff
>(m_stcoAtom->dataOffset() + 4));
1639 m_chunkCount =
reader.readUInt32BE();
1642 m_istream->seekg(
static_cast<streamoff
>(m_stsdAtom->dataOffset() + 4));
1643 const auto entryCount =
reader.readUInt32BE();
1644 Mp4Atom *esDescParentAtom =
nullptr;
1647 for (
Mp4Atom *codecConfigContainerAtom = m_stsdAtom->
firstChild(); codecConfigContainerAtom;
1648 codecConfigContainerAtom = codecConfigContainerAtom->
nextSibling()) {
1649 codecConfigContainerAtom->
parse(diag);
1652 m_formatId = interpretIntegerAsString<std::uint32_t>(codecConfigContainerAtom->id());
1656 m_istream->seekg(
static_cast<streamoff
>(codecConfigContainerAtom->dataOffset()));
1657 switch (codecConfigContainerAtom->id()) {
1673 tmp =
reader.readUInt16BE();
1689 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1692 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1695 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1697 if (!esDescParentAtom) {
1698 esDescParentAtom = codecConfigContainerAtom;
1715 m_istream->seekg(6 + 2 + 16, ios_base::cur);
1721 m_framesPerSample =
reader.readUInt16BE();
1726 }
else if (tmp < 32) {
1730 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1731 if (!esDescParentAtom) {
1732 esDescParentAtom = codecConfigContainerAtom;
1737 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1738 if (!esDescParentAtom) {
1739 esDescParentAtom = codecConfigContainerAtom;
1750 if (esDescParentAtom) {
1753 m_istream->seekg(
static_cast<streamoff
>(avcConfigAtom->dataOffset()));
1754 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1756 m_avcConfig->parse(
reader, avcConfigAtom->dataSize(), diag);
1767 m_istream->seekg(
static_cast<streamoff
>(av1ConfigAtom->dataOffset()));
1768 m_av1Config = make_unique<TagParser::Av1Configuration>();
1770 m_av1Config->parse(
reader, av1ConfigAtom->dataSize(), diag);
1790 m_bitrate =
static_cast<double>(m_esInfo->averageBitrate) / 1000;
1791 m_maxBitrate =
static_cast<double>(m_esInfo->maxBitrate) / 1000;
1792 if (m_esInfo->audioSpecificConfig) {
1795 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1796 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1801 diag.emplace_back(
DiagLevel::Warning,
"Audio specific config has invalid sample frequency index.", context);
1803 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1810 DiagLevel::Warning,
"Audio specific config has invalid extension sample frequency index.", context);
1812 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1815 if (m_esInfo->videoSpecificConfig) {
1818 m_format.
sub = m_esInfo->videoSpecificConfig->profile;
1819 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1821 m_formatId += m_esInfo->videoSpecificConfig->userData;
1830 m_istream->seekg(
static_cast<streamoff
>(m_stcoAtom->dataOffset() + 8));
1831 m_istream->seekg(
static_cast<streamoff
>(m_chunkOffsetSize == 8 ?
reader.readUInt64BE() :
reader.readUInt32BE()));
1844 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse child atoms of \"stsd\"-atom.", context);
1849 m_sampleSizes.clear();
1851 std::uint64_t actualSampleSizeTableSize = m_stszAtom->dataSize();
1852 if (actualSampleSizeTableSize < 12) {
1854 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1856 actualSampleSizeTableSize -= 12;
1857 m_istream->seekg(
static_cast<streamoff
>(m_stszAtom->dataOffset() + 4));
1858 std::uint32_t fieldSize;
1859 std::uint32_t constantSize;
1863 fieldSize =
reader.readByte();
1866 constantSize =
reader.readUInt32BE();
1871 m_sampleSizes.push_back(constantSize);
1875 const auto calculatedSampleSizeTableSize
1876 =
static_cast<std::uint64_t
>(std::ceil((0.125 * fieldSize) *
static_cast<double>(
m_sampleCount)));
1877 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1879 DiagLevel::Critical,
"The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1880 }
else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1881 diag.emplace_back(
DiagLevel::Critical,
"The stsz atom is truncated. It stores less entries as denoted.", context);
1882 actualSampleCount =
static_cast<std::uint64_t
>(floor(
static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize)));
1884 m_sampleSizes.reserve(actualSampleCount);
1885 std::uint32_t i = 1;
1886 switch (fieldSize) {
1888 for (; i <= actualSampleCount; i += 2) {
1889 std::uint8_t val =
reader.readByte();
1890 m_sampleSizes.push_back(val >> 4);
1891 m_sampleSizes.push_back(val & 0xF0);
1892 m_size += (val >> 4) + (val & 0xF0);
1894 if (i <= actualSampleCount + 1) {
1895 m_sampleSizes.push_back(
reader.readByte() >> 4);
1896 m_size += m_sampleSizes.back();
1900 for (; i <= actualSampleCount; ++i) {
1901 m_sampleSizes.push_back(
reader.readByte());
1902 m_size += m_sampleSizes.back();
1906 for (; i <= actualSampleCount; ++i) {
1907 m_sampleSizes.push_back(
reader.readUInt16BE());
1908 m_size += m_sampleSizes.back();
1912 for (; i <= actualSampleCount; ++i) {
1913 m_sampleSizes.push_back(
reader.readUInt32BE());
1914 m_size += m_sampleSizes.back();
1919 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1926 std::uint64_t totalDuration = 0;
1929 moofAtom->parse(diag);
1931 trafAtom->parse(diag);
1934 tfhdAtom->parse(diag);
1935 std::uint32_t calculatedDataSize = 0;
1936 if (tfhdAtom->dataSize() < calculatedDataSize) {
1939 m_istream->seekg(
static_cast<streamoff
>(tfhdAtom->dataOffset() + 1));
1940 std::uint32_t tfhdFlags =
reader.readUInt24BE();
1942 if (tfhdFlags & 0x000001) {
1943 calculatedDataSize += 8;
1945 if (tfhdFlags & 0x000002) {
1946 calculatedDataSize += 4;
1948 if (tfhdFlags & 0x000008) {
1949 calculatedDataSize += 4;
1951 if (tfhdFlags & 0x000010) {
1952 calculatedDataSize += 4;
1954 if (tfhdFlags & 0x000020) {
1955 calculatedDataSize += 4;
1959 std::uint32_t defaultSampleDuration = 0;
1960 std::uint32_t defaultSampleSize = 0;
1962 if (tfhdAtom->dataSize() < calculatedDataSize) {
1963 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
1965 if (tfhdFlags & 0x000001) {
1969 if (tfhdFlags & 0x000002) {
1973 if (tfhdFlags & 0x000008) {
1974 defaultSampleDuration =
reader.readUInt32BE();
1977 if (tfhdFlags & 0x000010) {
1978 defaultSampleSize =
reader.readUInt32BE();
1980 if (tfhdFlags & 0x000020) {
1987 std::uint32_t trunCalculatedDataSize = 8;
1988 if (trunAtom->dataSize() < trunCalculatedDataSize) {
1991 m_istream->seekg(
static_cast<streamoff
>(trunAtom->dataOffset() + 1));
1992 std::uint32_t trunFlags =
reader.readUInt24BE();
1995 if (trunFlags & 0x000001) {
1996 trunCalculatedDataSize += 4;
1998 if (trunFlags & 0x000004) {
1999 trunCalculatedDataSize += 4;
2001 std::uint32_t entrySize = 0;
2002 if (trunFlags & 0x000100) {
2005 if (trunFlags & 0x000200) {
2008 if (trunFlags & 0x000400) {
2011 if (trunFlags & 0x000800) {
2014 trunCalculatedDataSize += entrySize *
sampleCount;
2015 if (trunAtom->dataSize() < trunCalculatedDataSize) {
2016 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
2018 if (trunFlags & 0x000001) {
2022 if (trunFlags & 0x000004) {
2026 if (trunFlags & 0x000100) {
2027 totalDuration +=
reader.readUInt32BE();
2029 totalDuration += defaultSampleDuration;
2031 if (trunFlags & 0x000200) {
2032 m_sampleSizes.push_back(
reader.readUInt32BE());
2033 m_size += m_sampleSizes.back();
2035 m_size += defaultSampleSize;
2037 if (trunFlags & 0x000400) {
2040 if (trunFlags & 0x000800) {
2047 if (m_sampleSizes.empty() && defaultSampleSize) {
2048 m_sampleSizes.push_back(defaultSampleSize);
2063 m_duration = TimeSpan::fromSeconds(
static_cast<double>(totalDuration) /
static_cast<double>(
timeScale));
2068 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2073 m_istream->seekg(
static_cast<streamoff
>(m_stscAtom->dataOffset() + 4));
2074 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.
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