7 #include "../avc/avcconfiguration.h" 9 #include "../mpegaudio/mpegaudioframe.h" 10 #include "../mpegaudio/mpegaudioframestream.h" 12 #include "../exceptions.h" 13 #include "../mediaformat.h" 15 #include <c++utilities/conversion/stringbuilder.h> 16 #include <c++utilities/io/binaryreader.h> 17 #include <c++utilities/io/binarywriter.h> 18 #include <c++utilities/io/bitreader.h> 19 #include <c++utilities/io/catchiofailure.h> 54 byte additionalDataOffset;
59 inline TrackHeaderInfo::TrackHeaderInfo()
61 , canUseExisting(false)
64 , versionUnknown(false)
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)
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 = 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) {
237 const uint32 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) {
290 uint32 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;
438 if (info.version == 0) {
440 info.requiredSize += 12;
442 if (info.requiredSize > numeric_limits<uint32>::max()) {
444 info.requiredSize += 8;
458 static const string context(
"reading sample to chunk table of MP4 track");
460 diag.emplace_back(
DiagLevel::Critical,
"Track has not been parsed or is invalid.", context);
464 uint64 actualTableSize = m_stscAtom->
dataSize();
465 if (actualTableSize < 20) {
466 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom is truncated. There are no \"sample to chunk\" entries present.", context);
469 actualTableSize -= 8;
472 uint64 calculatedTableSize = actualSampleToChunkEntryCount * 12;
473 if (calculatedTableSize < actualTableSize) {
474 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom stores more entries as denoted. The additional entries will be ignored.", context);
475 }
else if (calculatedTableSize > actualTableSize) {
476 diag.emplace_back(
DiagLevel::Critical,
"The stsc atom is truncated. It stores less entries as denoted.", context);
478 actualSampleToChunkEntryCount = floor(static_cast<double>(actualTableSize) / 12.0);
481 vector<tuple<uint32, uint32, uint32>> sampleToChunkTable;
482 sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
484 for (uint32 i = 0; i < actualSampleToChunkEntryCount; ++i) {
486 uint32 firstChunk =
reader().readUInt32BE();
487 uint32 samplesPerChunk =
reader().readUInt32BE();
488 uint32 sampleDescriptionIndex =
reader().readUInt32BE();
489 sampleToChunkTable.emplace_back(firstChunk, samplesPerChunk, sampleDescriptionIndex);
491 return sampleToChunkTable;
508 static const string context(
"reading chunk sizes of MP4 track");
510 diag.emplace_back(
DiagLevel::Critical,
"Track has not been parsed or is invalid.", context);
516 vector<uint64> chunkSizes;
517 if (!sampleToChunkTable.empty()) {
519 auto tableIterator = sampleToChunkTable.cbegin();
520 chunkSizes.reserve(m_chunkCount);
522 size_t sampleIndex = 0;
523 uint32 previousChunkIndex = get<0>(*tableIterator);
524 if (previousChunkIndex != 1) {
525 diag.emplace_back(
DiagLevel::Critical,
"The first chunk of the first \"sample to chunk\" entry must be 1.", context);
526 previousChunkIndex = 1;
528 uint32 samplesPerChunk = get<1>(*tableIterator);
531 for (
const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
532 uint32 firstChunkIndex = get<0>(*tableIterator);
533 if (firstChunkIndex > previousChunkIndex && firstChunkIndex <= m_chunkCount) {
534 addChunkSizeEntries(chunkSizes, firstChunkIndex - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
537 "The first chunk index of a \"sample to chunk\" entry must be greather than the first chunk of the previous entry and not " 538 "greather than the chunk count.",
542 previousChunkIndex = firstChunkIndex;
543 samplesPerChunk = get<1>(*tableIterator);
545 if (m_chunkCount >= previousChunkIndex) {
546 addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
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) {
580 reader.stream()->seekg(esDesc.dataOffset());
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();
594 esDescChild; esDescChild = esDescChild->nextSibling()) {
595 esDescChild->parse(diag);
596 switch (esDescChild->id()) {
599 reader.stream()->seekg(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);
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(),
size);
659 BitReader bitReader(buff.get(),
size);
660 auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
663 auto getAudioObjectType = [&bitReader] {
664 byte objType = bitReader.readBits<byte>(5);
666 objType = 32 + bitReader.readBits<byte>(6);
670 audioCfg->audioObjectType = getAudioObjectType();
672 if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
673 audioCfg->sampleFrequency = bitReader.readBits<uint32>(24);
676 audioCfg->channelConfiguration = bitReader.readBits<byte>(4);
678 switch (audioCfg->audioObjectType) {
681 audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
682 audioCfg->sbrPresent =
true;
683 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
684 audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
686 if ((audioCfg->audioObjectType = getAudioObjectType()) ==
ErBsac) {
687 audioCfg->extensionChannelConfiguration = bitReader.readBits<byte>(4);
691 switch (audioCfg->extensionAudioObjectType) {
693 audioCfg->psPresent =
true;
698 switch (audioCfg->audioObjectType) {
710 audioCfg->frameLengthFlag = bitReader.readBits<byte>(1);
711 if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
712 audioCfg->coreCoderDelay = bitReader.readBits<byte>(14);
714 audioCfg->extensionFlag = bitReader.readBit();
715 if (audioCfg->channelConfiguration == 0) {
718 switch (audioCfg->audioObjectType) {
721 audioCfg->layerNr = bitReader.readBits<byte>(3);
725 if (audioCfg->extensionFlag == 1) {
726 switch (audioCfg->audioObjectType) {
728 audioCfg->numOfSubFrame = bitReader.readBits<byte>(5);
729 audioCfg->layerLength = bitReader.readBits<uint16>(11);
735 audioCfg->resilienceFlags = bitReader.readBits<byte>(3);
739 if (bitReader.readBit() == 1) {
748 switch (audioCfg->audioObjectType) {
760 switch (audioCfg->epConfig = bitReader.readBits<byte>(2)) {
764 bitReader.skipBits(1);
771 if (audioCfg->extensionAudioObjectType !=
Sbr && audioCfg->extensionAudioObjectType !=
Ps && bitReader.bitsAvailable() >= 16) {
772 uint16 syncExtensionType = bitReader.readBits<uint16>(11);
773 if (syncExtensionType == 0x2B7) {
774 if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) ==
Sbr) {
775 if ((audioCfg->sbrPresent = bitReader.readBit())) {
776 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
777 audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
779 if (bitReader.bitsAvailable() >= 12) {
780 if ((syncExtensionType = bitReader.readBits<uint16>(11)) == 0x548) {
781 audioCfg->psPresent = bitReader.readBits<byte>(1);
785 }
else if (audioCfg->extensionAudioObjectType ==
ErBsac) {
786 if ((audioCfg->sbrPresent = bitReader.readBit())) {
787 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
788 audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
791 audioCfg->extensionChannelConfiguration = bitReader.readBits<byte>(4);
793 }
else if (syncExtensionType == 0x548) {
794 audioCfg->psPresent = bitReader.readBit();
800 const char *what = catchIoFailure();
803 throwIoFailure(what);
806 diag.emplace_back(
DiagLevel::Critical,
"Audio specific configuration is truncated.", context);
820 static const string context(
"parsing MPEG-4 video specific config from elementary stream descriptor");
821 using namespace Mpeg4AudioObjectIds;
822 auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
825 if (
size > 3 && (
reader.readUInt24BE() == 1)) {
830 switch (
reader.readByte()) {
833 videoCfg->profile =
reader.readByte();
843 if ((buff1 =
reader.readUInt24BE()) != 1) {
844 reader.stream()->seekg(-2, ios_base::cur);
845 videoCfg->userData.push_back(buff1 >> 16);
852 if (buff1 != 1 &&
size > 0) {
853 videoCfg->userData +=
reader.readString(
size);
861 if (
reader.readUInt24BE() != 1) {
862 reader.stream()->seekg(-2, ios_base::cur);
871 diag.emplace_back(
DiagLevel::Critical,
"\"Visual Object Sequence Header\" not found.", context);
898 if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
901 static const unsigned int stcoDataBegin = 8;
902 uint64 startPos = m_stcoAtom->
dataOffset() + stcoDataBegin;
903 uint64 endPos = startPos + m_stcoAtom->
dataSize() - stcoDataBegin;
906 vector<int64>::size_type i;
907 vector<int64>::size_type
size;
909 switch (m_stcoAtom->
id()) {
912 while ((currentPos + 4) <= endPos) {
914 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
915 if (off > static_cast<uint64>(oldMdatOffsets[i])) {
916 off += (newMdatOffsets[i] - oldMdatOffsets[i]);
928 while ((currentPos + 8) <= endPos) {
930 for (i = 0,
size = oldMdatOffsets.size(); i <
size; ++i) {
931 if (off > static_cast<uint64>(oldMdatOffsets[i])) {
932 off += (newMdatOffsets[i] - oldMdatOffsets[i]);
968 switch (m_stcoAtom->
id()) {
970 for (
auto offset : chunkOffsets) {
975 for (
auto offset : chunkOffsets) {
1003 writer().writeUInt32BE(offset);
1006 writer().writeUInt64BE(offset);
1058 trefAtom->makeBuffer();
1061 edtsAtom->makeBuffer();
1065 vmhdAtom->makeBuffer();
1068 smhdAtom->makeBuffer();
1071 hmhdAtom->makeBuffer();
1074 nmhdAtom->makeBuffer();
1077 dinfAtom->makeBuffer();
1080 stblAtom->makeBuffer();
1094 size += verifyPresentTrackHeader().requiredSize;
1097 size += trefAtom->totalSize();
1101 size += edtsAtom->totalSize();
1106 bool dinfAtomWritten =
false;
1109 size += vmhdAtom->totalSize();
1112 size += smhdAtom->totalSize();
1115 size += hmhdAtom->totalSize();
1118 size += nmhdAtom->totalSize();
1121 size += dinfAtom->totalSize();
1122 dinfAtomWritten =
true;
1125 size += stblAtom->totalSize();
1128 if (!dinfAtomWritten) {
1145 ostream::pos_type trakStartOffset =
outputStream().tellp();
1152 trefAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1156 edtsAtom->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<uint32>::max()) {
1186 writer().writeUInt32BE(1);
1188 writer().writeUInt64BE(info.requiredSize);
1190 writer().writeUInt32BE(static_cast<uint32>(info.requiredSize));
1206 writer().writeUInt24BE(flags);
1213 writer().writeUInt32BE(static_cast<uint32>(
m_id));
1214 writer().writeUInt32BE(0);
1216 writer().writeUInt32BE(0);
1217 writer().writeUInt32BE(0);
1220 if (info.canUseExisting) {
1223 m_tkhdAtom->
buffer().get() + m_tkhdAtom->
headerSize() + info.additionalDataOffset, m_tkhdAtom->
dataSize() - info.additionalDataOffset);
1225 if (info.discardBuffer) {
1230 writer().writeInt16BE(0);
1231 writer().writeInt16BE(0);
1232 writer().writeFixed8BE(1.0);
1233 writer().writeUInt16BE(0);
1234 for (
const int32 value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) {
1235 writer().writeInt32BE(value);
1237 writer().writeFixed16BE(1.0);
1238 writer().writeFixed16BE(1.0);
1248 ostream::pos_type mdiaStartOffset =
outputStream().tellp();
1249 writer().writeUInt32BE(0);
1252 writer().writeUInt32BE(44);
1255 writer().writeUInt24BE(0);
1262 for (
size_t charIndex = 0; charIndex != 3; ++charIndex) {
1264 if (langChar >=
'a' && langChar <=
'z') {
1265 language |=
static_cast<uint16
>(langChar - 0x60) << (0xA - charIndex * 0x5);
1275 DiagLevel::Warning,
"Assigned language \"" %
m_language +
"\" is longer than 3 byte and hence will be truncated.",
"making mdhd atom");
1278 writer().writeUInt16BE(0);
1282 writer().writeUInt64BE(0);
1297 diag.emplace_back(
DiagLevel::Critical,
"Media type is invalid; The media type video is assumed.",
"making hdlr atom");
1301 for (
int i = 0; i < 3; ++i)
1302 writer().writeUInt32BE(0);
1316 ostream::pos_type minfStartOffset =
outputStream().tellp();
1317 writer().writeUInt32BE(0);
1319 bool dinfAtomWritten =
false;
1323 vmhdAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1327 smhdAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1331 hmhdAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1335 nmhdAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1339 dinfAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1340 dinfAtomWritten =
true;
1344 if (!dinfAtomWritten) {
1345 writer().writeUInt32BE(36);
1348 writer().writeUInt32BE(28);
1350 writer().writeUInt32BE(0);
1351 writer().writeUInt32BE(1);
1353 writer().writeUInt32BE(12);
1356 writer().writeUInt24BE(0x000001);
1360 bool stblAtomWritten =
false;
1363 stblAtom->copyPreferablyFromBuffer(
outputStream(), diag,
nullptr);
1364 stblAtomWritten =
true;
1367 if (!stblAtomWritten) {
1369 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.",
"making stbl atom");
1382 ostream::pos_type stblStartOffset =
outputStream().tellp();
1383 writer().writeUInt32BE(0);
1391 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stsd atom from scratch.",
"making stsd atom");
1400 diag.emplace_back(
DiagLevel::Critical,
"Unable to make stts atom from scratch.",
"making stts atom");
1441 static const string context(
"parsing MP4 track");
1442 using namespace Mp4AtomIds;
1450 if (!(m_tkhdAtom = m_trakAtom->childById(
TrackHeader, diag))) {
1454 if (!(m_mdiaAtom = m_trakAtom->childById(
Media, diag))) {
1458 if (!(m_mdhdAtom = m_mdiaAtom->childById(
MediaHeader, diag))) {
1470 if (!(m_stblAtom = m_minfAtom->childById(
SampleTable, diag))) {
1478 if (!(m_stcoAtom = m_stblAtom->childById(
ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(
ChunkOffset64, diag))) {
1482 if (!(m_stscAtom = m_stblAtom->childById(
SampleToChunk, diag))) {
1495 BinaryReader &
reader = m_trakAtom->reader();
1498 m_istream->seekg(m_tkhdAtom->startOffset() + 8);
1499 byte atomVersion =
reader.readByte();
1500 uint32 flags =
reader.readUInt24BE();
1504 switch (atomVersion) {
1517 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1525 m_istream->seekg(m_mdhdAtom->dataOffset());
1526 atomVersion =
reader.readByte();
1528 switch (atomVersion) {
1543 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be " 1549 uint16 tmp =
reader.readUInt16BE();
1551 const char buff[] = {
1552 static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1553 static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1554 static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1563 m_istream->seekg(m_hdlrAtom->dataOffset() + 8);
1565 switch (
reader.readUInt32BE()) {
1584 if ((tmp =
m_istream->peek()) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1590 m_name =
reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1595 m_istream->seekg(m_stcoAtom->dataOffset() + 4);
1596 m_chunkCount =
reader.readUInt32BE();
1599 m_istream->seekg(m_stsdAtom->dataOffset() + 4);
1600 uint32 entryCount =
reader.readUInt32BE();
1601 Mp4Atom *esDescParentAtom =
nullptr;
1604 for (
Mp4Atom *codecConfigContainerAtom = m_stsdAtom->
firstChild(); codecConfigContainerAtom;
1605 codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1606 codecConfigContainerAtom->
parse(diag);
1608 m_formatId = interpretIntegerAsString<uint32>(codecConfigContainerAtom->id());
1611 m_istream->seekg(codecConfigContainerAtom->dataOffset());
1612 switch (codecConfigContainerAtom->id()) {
1626 tmp =
reader.readUInt16BE();
1642 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1645 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1648 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1650 if (!esDescParentAtom) {
1651 esDescParentAtom = codecConfigContainerAtom;
1665 m_istream->seekg(6 + 2 + 16, ios_base::cur);
1671 m_framesPerSample =
reader.readUInt16BE();
1676 }
else if (tmp < 32) {
1680 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1681 if (!esDescParentAtom) {
1682 esDescParentAtom = codecConfigContainerAtom;
1687 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1688 if (!esDescParentAtom) {
1689 esDescParentAtom = codecConfigContainerAtom;
1700 if (esDescParentAtom) {
1703 m_istream->seekg(avcConfigAtom->dataOffset());
1704 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1706 m_avcConfig->parse(
reader, avcConfigAtom->dataSize());
1724 m_bitrate =
static_cast<double>(m_esInfo->averageBitrate) / 1000;
1725 m_maxBitrate =
static_cast<double>(m_esInfo->maxBitrate) / 1000;
1726 if (m_esInfo->audioSpecificConfig) {
1729 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1730 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1735 diag.emplace_back(
DiagLevel::Warning,
"Audio specific config has invalid sample frequency index.", context);
1737 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1744 DiagLevel::Warning,
"Audio specific config has invalid extension sample frequency index.", context);
1746 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1749 if (m_esInfo->videoSpecificConfig) {
1752 m_format.
sub = m_esInfo->videoSpecificConfig->profile;
1753 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1755 m_formatId += m_esInfo->videoSpecificConfig->userData;
1764 m_istream->seekg(m_stcoAtom->dataOffset() + 8);
1778 diag.emplace_back(
DiagLevel::Critical,
"Unable to parse child atoms of \"stsd\"-atom.", context);
1783 m_sampleSizes.clear();
1785 uint64 actualSampleSizeTableSize = m_stszAtom->dataSize();
1786 if (actualSampleSizeTableSize < 12) {
1788 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1790 actualSampleSizeTableSize -= 12;
1791 m_istream->seekg(m_stszAtom->dataOffset() + 4);
1793 uint32 constantSize;
1797 fieldSize =
reader.readByte();
1800 constantSize =
reader.readUInt32BE();
1805 m_sampleSizes.push_back(constantSize);
1809 uint64 calculatedSampleSizeTableSize = ceil((0.125 * fieldSize) *
m_sampleCount);
1810 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1812 DiagLevel::Critical,
"The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1813 }
else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1814 diag.emplace_back(
DiagLevel::Critical,
"The stsz atom is truncated. It stores less entries as denoted.", context);
1815 actualSampleCount = floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize));
1817 m_sampleSizes.reserve(actualSampleCount);
1819 switch (fieldSize) {
1821 for (; i <= actualSampleCount; i += 2) {
1822 byte val =
reader.readByte();
1823 m_sampleSizes.push_back(val >> 4);
1824 m_sampleSizes.push_back(val & 0xF0);
1825 m_size += (val >> 4) + (val & 0xF0);
1827 if (i <= actualSampleCount + 1) {
1828 m_sampleSizes.push_back(
reader.readByte() >> 4);
1829 m_size += m_sampleSizes.back();
1833 for (; i <= actualSampleCount; ++i) {
1834 m_sampleSizes.push_back(
reader.readByte());
1835 m_size += m_sampleSizes.back();
1839 for (; i <= actualSampleCount; ++i) {
1840 m_sampleSizes.push_back(
reader.readUInt16BE());
1841 m_size += m_sampleSizes.back();
1845 for (; i <= actualSampleCount; ++i) {
1846 m_sampleSizes.push_back(
reader.readUInt32BE());
1847 m_size += m_sampleSizes.back();
1852 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1859 uint64 totalDuration = 0;
1862 moofAtom->parse(diag);
1864 trafAtom->parse(diag);
1867 tfhdAtom->parse(diag);
1868 uint32 calculatedDataSize = 0;
1869 if (tfhdAtom->dataSize() < calculatedDataSize) {
1872 m_istream->seekg(tfhdAtom->dataOffset() + 1);
1873 uint32 flags =
reader.readUInt24BE();
1875 if (flags & 0x000001) {
1876 calculatedDataSize += 8;
1878 if (flags & 0x000002) {
1879 calculatedDataSize += 4;
1881 if (flags & 0x000008) {
1882 calculatedDataSize += 4;
1884 if (flags & 0x000010) {
1885 calculatedDataSize += 4;
1887 if (flags & 0x000020) {
1888 calculatedDataSize += 4;
1892 uint32 defaultSampleDuration = 0;
1893 uint32 defaultSampleSize = 0;
1895 if (tfhdAtom->dataSize() < calculatedDataSize) {
1896 diag.emplace_back(
DiagLevel::Critical,
"tfhd atom is truncated (presence of fields denoted).", context);
1898 if (flags & 0x000001) {
1902 if (flags & 0x000002) {
1906 if (flags & 0x000008) {
1907 defaultSampleDuration =
reader.readUInt32BE();
1910 if (flags & 0x000010) {
1911 defaultSampleSize =
reader.readUInt32BE();
1913 if (flags & 0x000020) {
1920 uint32 calculatedDataSize = 8;
1921 if (trunAtom->dataSize() < calculatedDataSize) {
1924 m_istream->seekg(trunAtom->dataOffset() + 1);
1925 uint32 flags =
reader.readUInt24BE();
1928 if (flags & 0x000001) {
1929 calculatedDataSize += 4;
1931 if (flags & 0x000004) {
1932 calculatedDataSize += 4;
1934 uint32 entrySize = 0;
1935 if (flags & 0x000100) {
1938 if (flags & 0x000200) {
1941 if (flags & 0x000400) {
1944 if (flags & 0x000800) {
1948 if (trunAtom->dataSize() < calculatedDataSize) {
1949 diag.emplace_back(
DiagLevel::Critical,
"trun atom is truncated (presence of fields denoted).", context);
1951 if (flags & 0x000001) {
1955 if (flags & 0x000004) {
1959 if (flags & 0x000100) {
1960 totalDuration +=
reader.readUInt32BE();
1962 totalDuration += defaultSampleDuration;
1964 if (flags & 0x000200) {
1965 m_sampleSizes.push_back(
reader.readUInt32BE());
1966 m_size += m_sampleSizes.back();
1968 m_size += defaultSampleSize;
1970 if (flags & 0x000400) {
1973 if (flags & 0x000800) {
1980 if (m_sampleSizes.empty() && defaultSampleSize) {
1981 m_sampleSizes.push_back(defaultSampleSize);
1996 m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(
timeScale));
2001 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2006 m_istream->seekg(m_stscAtom->dataOffset() + 4);
2007 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
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
void parseHeader(IoUtilities::BinaryReader &reader)
Parses the header read using the specified reader.
void updateChunkOffsets(const std::vector< int64 > &oldMdatOffsets, const std::vector< int64 > &newMdatOffsets)
Updates the chunk offsets of the track.
std::vector< uint64 > readChunkSizes(TagParser::Diagnostics &diag)
Reads the chunk sizes from the stsz (sample sizes) and stsc (samples per chunk) atom.
void setWidth(uint32 value)
Sets the width.
void copyEntirely(std::ostream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all childs to the specified targetStream.
const char * m_chromaFormat
AspectRatio pixelAspectRatio
uint32 timeScale() const
Returns the time scale if known; otherwise returns 0.
void makeBuffer()
Buffers the element (header and data).
const std::unique_ptr< char[]> & buffer()
Returns buffered data.
ImplementationType * firstChild()
Returns the first child of the element.
std::string m_compressorName
static void addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
Adds the information from the specified avcConfig to the specified track.
std::ostream & outputStream()
Returns the associated output stream.
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.
unsigned int chunkOffsetSize() const
Returns the size of a single chunk offset denotation within the stco atom.
Mpeg4VideoSpecificConfig()
void makeTrackHeader(Diagnostics &diag)
Makes the track header (tkhd atom) for the track.
static std::unique_ptr< Mpeg4AudioSpecificConfig > parseAudioSpecificConfig(std::istream &stream, uint64 startOffset, uint64 size, Diagnostics &diag)
Parses the audio specific configuration for the track.
void setHeight(uint32 value)
Sets the height.
TAG_PARSER_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent=false, bool psPresent=false)
Mpeg4AudioSpecificConfig()
uint64 dataOffset() const
Returns the data offset of the element in the related stream.
The AvcConfiguration struct provides a parser for AVC configuration.
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
static void seekBackAndWriteAtomSize(std::ostream &stream, const std::ostream::pos_type &startOffset)
This function helps to write the atom size after writing an atom to a stream.
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 Media::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.
static void addInfo(const MpegAudioFrame &frame, AbstractTrack &track)
Adds the information from the specified frame to the specified track.
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.
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.
IoUtilities::BinaryWriter & writer()
Returns a binary writer for the associated stream.
TrackType
Specifies the track type.
uint32 mpeg4SamplingFrequencyTable[13]
~Mp4Track() override
Destroys the track.