Tag Parser 10.3.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mp4track.cpp
Go to the documentation of this file.
1#include "./mp4track.h"
2#include "./mp4atom.h"
3#include "./mp4container.h"
4#include "./mp4ids.h"
5#include "./mpeg4descriptor.h"
6
7#include "../av1/av1configuration.h"
8
9#include "../avc/avcconfiguration.h"
10
11#include "../mpegaudio/mpegaudioframe.h"
12#include "../mpegaudio/mpegaudioframestream.h"
13
14#include "../exceptions.h"
15#include "../mediaformat.h"
16
17#include <c++utilities/conversion/stringbuilder.h>
18#include <c++utilities/io/binaryreader.h>
19#include <c++utilities/io/binarywriter.h>
20#include <c++utilities/io/bitreader.h>
21
22#include <cmath>
23#include <locale>
24
25using namespace std;
26using namespace CppUtilities;
27
28namespace TagParser {
29
37 friend class Mp4Track;
38
39private:
40 constexpr TrackHeaderInfo();
41
43 std::uint64_t requiredSize;
45 bool canUseExisting;
47 bool truncated;
49 std::uint8_t version;
51 bool versionUnknown;
53 std::uint8_t additionalDataOffset;
55 bool discardBuffer;
56};
57
58constexpr TrackHeaderInfo::TrackHeaderInfo()
59 : requiredSize(100)
60 , canUseExisting(false)
61 , truncated(false)
62 , version(0)
63 , versionUnknown(false)
64 , additionalDataOffset(0)
65 , discardBuffer(false)
66{
67}
68
70const DateTime startDate = DateTime::fromDate(1904, 1, 1);
71
79 : audioObjectType(0)
80 , sampleFrequencyIndex(0xF)
81 , sampleFrequency(0)
82 , channelConfiguration(0)
83 , extensionAudioObjectType(0)
84 , sbrPresent(false)
85 , psPresent(false)
86 , extensionSampleFrequencyIndex(0xF)
87 , extensionSampleFrequency(0)
88 , extensionChannelConfiguration(0)
89 , frameLengthFlag(false)
90 , dependsOnCoreCoder(false)
91 , coreCoderDelay(0)
92 , extensionFlag(0)
93 , layerNr(0)
94 , numOfSubFrame(0)
95 , layerLength(0)
96 , resilienceFlags(0)
97 , epConfig(0)
98{
99}
100
110 : profile(0)
111{
112}
113
131 : AbstractTrack(trakAtom.stream(), trakAtom.startOffset())
132 , m_trakAtom(&trakAtom)
133 , m_tkhdAtom(nullptr)
134 , m_mdiaAtom(nullptr)
135 , m_mdhdAtom(nullptr)
136 , m_hdlrAtom(nullptr)
137 , m_minfAtom(nullptr)
138 , m_stblAtom(nullptr)
139 , m_stsdAtom(nullptr)
140 , m_stscAtom(nullptr)
141 , m_stcoAtom(nullptr)
142 , m_stszAtom(nullptr)
143 , m_rawMediaType(0)
144 , m_framesPerSample(1)
145 , m_chunkOffsetSize(4)
146 , m_chunkCount(0)
147 , m_sampleToChunkEntryCount(0)
148{
149}
150
155{
156}
157
159{
160 return TrackType::Mp4Track;
161}
162
173std::vector<std::uint64_t> Mp4Track::readChunkOffsets(bool parseFragments, Diagnostics &diag)
174{
175 static const string context("reading chunk offset table of MP4 track");
176 if (!isHeaderValid() || !m_istream) {
177 diag.emplace_back(DiagLevel::Critical, "Track has not been parsed.", context);
178 throw InvalidDataException();
179 }
180 vector<std::uint64_t> offsets;
181 if (m_stcoAtom) {
182 // verify integrity of the chunk offset table
183 std::uint64_t actualTableSize = m_stcoAtom->dataSize();
184 if (actualTableSize < (8 + chunkOffsetSize())) {
185 diag.emplace_back(DiagLevel::Critical, "The stco atom is truncated. There are no chunk offsets present.", context);
186 throw InvalidDataException();
187 } else {
188 actualTableSize -= 8;
189 }
190 std::uint32_t actualChunkCount = chunkCount();
191 std::uint64_t calculatedTableSize = chunkCount() * chunkOffsetSize();
192 if (calculatedTableSize < actualTableSize) {
193 diag.emplace_back(
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())));
198 }
199 // read the table
200 offsets.reserve(actualChunkCount);
201 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
202 switch (chunkOffsetSize()) {
203 case 4:
204 for (std::uint32_t i = 0; i < actualChunkCount; ++i) {
205 offsets.push_back(reader().readUInt32BE());
206 }
207 break;
208 case 8:
209 for (std::uint32_t i = 0; i < actualChunkCount; ++i) {
210 offsets.push_back(reader().readUInt64BE());
211 }
212 break;
213 default:
214 diag.emplace_back(DiagLevel::Critical, "The determined chunk offset size is invalid.", context);
215 throw InvalidDataException();
216 }
217 }
218 // read sample offsets of fragments
219 if (parseFragments) {
220 std::uint64_t totalDuration = 0;
221 for (Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingByIdIncludingThis(Mp4AtomIds::MovieFragment, diag); moofAtom;
222 moofAtom = moofAtom->siblingById(Mp4AtomIds::MovieFragment, diag)) {
223 moofAtom->parse(diag);
224 for (Mp4Atom *trafAtom = moofAtom->childById(Mp4AtomIds::TrackFragment, diag); trafAtom;
225 trafAtom = trafAtom->siblingById(Mp4AtomIds::TrackFragment, diag)) {
226 trafAtom->parse(diag);
227 for (Mp4Atom *tfhdAtom = trafAtom->childById(Mp4AtomIds::TrackFragmentHeader, diag); tfhdAtom;
228 tfhdAtom = tfhdAtom->siblingById(Mp4AtomIds::TrackFragmentHeader, diag)) {
229 tfhdAtom->parse(diag);
230 std::uint32_t calculatedDataSize = 0;
231 if (tfhdAtom->dataSize() < calculatedDataSize) {
232 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated.", context);
233 } else {
234 inputStream().seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
235 const std::uint32_t flags = reader().readUInt24BE();
236 if (m_id == reader().readUInt32BE()) { // check track ID
237 if (flags & 0x000001) { // base-data-offset present
238 calculatedDataSize += 8;
239 }
240 if (flags & 0x000002) { // sample-description-index present
241 calculatedDataSize += 4;
242 }
243 if (flags & 0x000008) { // default-sample-duration present
244 calculatedDataSize += 4;
245 }
246 if (flags & 0x000010) { // default-sample-size present
247 calculatedDataSize += 4;
248 }
249 if (flags & 0x000020) { // default-sample-flags present
250 calculatedDataSize += 4;
251 }
252 // some variables are currently skipped because they are currently not interesting
253 //uint64 baseDataOffset = moofAtom->startOffset();
254 //uint32 defaultSampleDescriptionIndex = 0;
255 std::uint32_t defaultSampleDuration = 0;
256 std::uint32_t defaultSampleSize = 0;
257 //uint32 defaultSampleFlags = 0;
258 if (tfhdAtom->dataSize() < calculatedDataSize) {
259 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
260 } else {
261 if (flags & 0x000001) { // base-data-offset present
262 //baseDataOffset = reader.readUInt64();
263 inputStream().seekg(8, ios_base::cur);
264 }
265 if (flags & 0x000002) { // sample-description-index present
266 //defaultSampleDescriptionIndex = reader.readUInt32();
267 inputStream().seekg(4, ios_base::cur);
268 }
269 if (flags & 0x000008) { // default-sample-duration present
270 defaultSampleDuration = reader().readUInt32BE();
271 //inputStream().seekg(4, ios_base::cur);
272 }
273 if (flags & 0x000010) { // default-sample-size present
274 defaultSampleSize = reader().readUInt32BE();
275 }
276 if (flags & 0x000020) { // default-sample-flags present
277 //defaultSampleFlags = reader().readUInt32BE();
278 inputStream().seekg(4, ios_base::cur);
279 }
280 }
281 for (Mp4Atom *trunAtom = trafAtom->childById(Mp4AtomIds::TrackFragmentRun, diag); trunAtom;
282 trunAtom = trunAtom->siblingById(Mp4AtomIds::TrackFragmentRun, diag)) {
283 std::uint32_t trunCalculatedDataSize = 8;
284 if (trunAtom->dataSize() < trunCalculatedDataSize) {
285 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated.", context);
286 } else {
287 inputStream().seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
288 std::uint32_t trunFlags = reader().readUInt24BE();
289 std::uint32_t sampleCount = reader().readUInt32BE();
291 if (trunFlags & 0x000001) { // data offset present
292 trunCalculatedDataSize += 4;
293 }
294 if (trunFlags & 0x000004) { // first-sample-flags present
295 trunCalculatedDataSize += 4;
296 }
297 std::uint32_t entrySize = 0;
298 if (trunFlags & 0x000100) { // sample-duration present
299 entrySize += 4;
300 }
301 if (trunFlags & 0x000200) { // sample-size present
302 entrySize += 4;
303 }
304 if (trunFlags & 0x000400) { // sample-flags present
305 entrySize += 4;
306 }
307 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
308 entrySize += 4;
309 }
310 trunCalculatedDataSize += entrySize * sampleCount;
311 if (trunAtom->dataSize() < trunCalculatedDataSize) {
312 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated (presence of fields denoted).", context);
313 } else {
314 if (trunFlags & 0x000001) { // data offset present
315 inputStream().seekg(4, ios_base::cur);
316 //int32 dataOffset = reader().readInt32BE();
317 }
318 if (trunFlags & 0x000004) { // first-sample-flags present
319 inputStream().seekg(4, ios_base::cur);
320 }
321 for (std::uint32_t i = 0; i < sampleCount; ++i) {
322 if (trunFlags & 0x000100) { // sample-duration present
323 totalDuration += reader().readUInt32BE();
324 } else {
325 totalDuration += defaultSampleDuration;
326 }
327 if (trunFlags & 0x000200) { // sample-size present
328 m_sampleSizes.push_back(reader().readUInt32BE());
329 m_size += m_sampleSizes.back();
330 } else {
331 m_size += defaultSampleSize;
332 }
333 if (trunFlags & 0x000400) { // sample-flags present
334 inputStream().seekg(4, ios_base::cur);
335 }
336 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
337 inputStream().seekg(4, ios_base::cur);
338 }
339 }
340 }
341 }
342 }
343 if (m_sampleSizes.empty() && defaultSampleSize) {
344 m_sampleSizes.push_back(defaultSampleSize);
345 }
346 }
347 }
348 }
349 }
350 }
351 }
352 return offsets;
353}
354
359std::uint64_t Mp4Track::accumulateSampleSizes(size_t &sampleIndex, size_t count, Diagnostics &diag)
360{
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];
365 }
366 return sum;
367 } else if (m_sampleSizes.size() == 1) {
368 sampleIndex += count;
369 return static_cast<std::uint64_t>(m_sampleSizes.front()) * count;
370 } else {
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();
373 }
374}
375
384void Mp4Track::addChunkSizeEntries(
385 std::vector<std::uint64_t> &chunkSizeTable, size_t count, size_t &sampleIndex, std::uint32_t sampleCount, Diagnostics &diag)
386{
387 for (size_t i = 0; i < count; ++i) {
388 chunkSizeTable.push_back(accumulateSampleSizes(sampleIndex, sampleCount, diag));
389 }
390}
391
396TrackHeaderInfo Mp4Track::verifyPresentTrackHeader() const
397{
398 TrackHeaderInfo info;
399
400 // return the default TrackHeaderInfo in case there is no track header prsent
401 if (!m_tkhdAtom) {
402 return info;
403 }
404
405 // ensure the tkhd atom is buffered but mark the buffer to be discarded again if it has not been present
406 info.discardBuffer = m_tkhdAtom->buffer() == nullptr;
407 if (info.discardBuffer) {
408 m_tkhdAtom->makeBuffer();
409 }
410
411 // check the version of the existing tkhd atom to determine where additional data starts
412 switch (info.version = static_cast<std::uint8_t>(m_tkhdAtom->buffer()[m_tkhdAtom->headerSize()])) {
413 case 0:
414 info.additionalDataOffset = 32;
415 break;
416 case 1:
417 info.additionalDataOffset = 44;
418 break;
419 default:
420 info.additionalDataOffset = 44;
421 info.versionUnknown = true;
422 }
423
424 // check whether the existing tkhd atom is not truncated
425 if (info.additionalDataOffset + 48u <= m_tkhdAtom->dataSize()) {
426 info.canUseExisting = true;
427 } else {
428 info.truncated = true;
429 info.canUseExisting = info.additionalDataOffset < m_tkhdAtom->dataSize();
430 if (!info.canUseExisting && info.discardBuffer) {
431 m_tkhdAtom->discardBuffer();
432 }
433 }
434
435 // determine required size
436 info.requiredSize = m_tkhdAtom->dataSize() + 8;
437 // -> add 12 byte to size if update from version 0 to version 1 is required (which needs 12 byte more)
438 if ((info.version == 0)
439 && (static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
440 || static_cast<std::uint64_t>((m_modificationTime - 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;
443 }
444 // -> add 8 byte to the size because it must be denoted using a 64-bit integer
445 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
446 info.requiredSize += 8;
447 }
448 return info;
449}
450
458vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> Mp4Track::readSampleToChunkTable(Diagnostics &diag)
459{
460 static const string context("reading sample to chunk table of MP4 track");
461 if (!isHeaderValid() || !m_istream || !m_stscAtom) {
462 diag.emplace_back(DiagLevel::Critical, "Track has not been parsed or is invalid.", context);
463 throw InvalidDataException();
464 }
465 // verify integrity of the sample to chunk table
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);
469 throw InvalidDataException();
470 } else {
471 actualTableSize -= 8;
472 }
473 std::uint64_t actualSampleToChunkEntryCount = sampleToChunkEntryCount();
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;
480 }
481 // prepare reading
482 vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> sampleToChunkTable;
483 sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
484 m_istream->seekg(static_cast<streamoff>(m_stscAtom->dataOffset() + 8));
485 for (std::uint32_t i = 0; i < actualSampleToChunkEntryCount; ++i) {
486 // read entry
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);
491 }
492 return sampleToChunkTable;
493}
494
507vector<std::uint64_t> Mp4Track::readChunkSizes(Diagnostics &diag)
508{
509 static const string context("reading chunk sizes of MP4 track");
510 if (!isHeaderValid() || !m_istream || !m_stcoAtom) {
511 diag.emplace_back(DiagLevel::Critical, "Track has not been parsed or is invalid.", context);
512 throw InvalidDataException();
513 }
514 // read sample to chunk table
515 const auto sampleToChunkTable = readSampleToChunkTable(diag);
516 // accumulate chunk sizes from the table
517 vector<std::uint64_t> chunkSizes;
518 if (!sampleToChunkTable.empty()) {
519 // prepare reading
520 auto tableIterator = sampleToChunkTable.cbegin();
521 chunkSizes.reserve(m_chunkCount);
522 // read first entry
523 size_t sampleIndex = 0;
524 std::uint32_t previousChunkIndex = get<0>(*tableIterator); // the first chunk has the index 1 and not zero!
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; // try to read the entry anyway
528 }
529 std::uint32_t samplesPerChunk = get<1>(*tableIterator);
530 // read the following entries
531 ++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);
536 } else {
537 diag.emplace_back(DiagLevel::Critical,
538 "The first chunk index of a \"sample to chunk\" entry must be greater than the first chunk of the previous entry and not "
539 "greater than the chunk count.",
540 context);
541 throw InvalidDataException();
542 }
543 previousChunkIndex = firstChunkIndex;
544 samplesPerChunk = get<1>(*tableIterator);
545 }
546 if (m_chunkCount >= previousChunkIndex) {
547 addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
548 }
549 }
550 return chunkSizes;
551}
552
557std::unique_ptr<Mpeg4ElementaryStreamInfo> Mp4Track::parseMpeg4ElementaryStreamInfo(
558 CppUtilities::BinaryReader &reader, Mp4Atom *esDescAtom, Diagnostics &diag)
559{
560 static const string context("parsing MPEG-4 elementary stream descriptor");
561 using namespace Mpeg4ElementaryStreamObjectIds;
562 unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
563 if (esDescAtom->dataSize() >= 12) {
564 reader.stream()->seekg(static_cast<streamoff>(esDescAtom->dataOffset()));
565 // read version/flags
566 if (reader.readUInt32BE() != 0) {
567 diag.emplace_back(DiagLevel::Warning, "Unknown version/flags.", context);
568 }
569 // read extended descriptor
570 Mpeg4Descriptor esDesc(esDescAtom->container(), static_cast<std::uint64_t>(reader.stream()->tellg()), esDescAtom->dataSize() - 4);
571 try {
572 esDesc.parse(diag);
573 // check ID
575 diag.emplace_back(DiagLevel::Critical, "Invalid descriptor found.", context);
576 throw Failure();
577 }
578 // read stream info
579 reader.stream()->seekg(static_cast<streamoff>(esDesc.dataOffset()));
580 esInfo = make_unique<Mpeg4ElementaryStreamInfo>();
581 esInfo->id = reader.readUInt16BE();
582 esInfo->esDescFlags = reader.readByte();
583 if (esInfo->dependencyFlag()) {
584 esInfo->dependsOnId = reader.readUInt16BE();
585 }
586 if (esInfo->urlFlag()) {
587 esInfo->url = reader.readString(reader.readByte());
588 }
589 if (esInfo->ocrFlag()) {
590 esInfo->ocrId = reader.readUInt16BE();
591 }
592 for (Mpeg4Descriptor *esDescChild
593 = esDesc.denoteFirstChild(static_cast<std::uint32_t>(static_cast<std::uint64_t>(reader.stream()->tellg()) - esDesc.startOffset()));
594 esDescChild; esDescChild = esDescChild->nextSibling()) {
595 esDescChild->parse(diag);
596 switch (esDescChild->id()) {
598 // read decoder config descriptor
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();
605 for (Mpeg4Descriptor *decCfgDescChild = esDescChild->denoteFirstChild(esDescChild->headerSize() + 13); decCfgDescChild;
606 decCfgDescChild = decCfgDescChild->nextSibling()) {
607 decCfgDescChild->parse(diag);
608 switch (decCfgDescChild->id()) {
610 // read decoder specific info
611 switch (esInfo->objectTypeId) {
612 case Aac:
616 case Mpeg2Audio:
617 case Mpeg1Audio:
618 esInfo->audioSpecificConfig
619 = parseAudioSpecificConfig(*reader.stream(), decCfgDescChild->dataOffset(), decCfgDescChild->dataSize(), diag);
620 break;
621 case Mpeg4Visual:
622 esInfo->videoSpecificConfig
623 = parseVideoSpecificConfig(reader, decCfgDescChild->dataOffset(), decCfgDescChild->dataSize(), diag);
624 break;
625 default:; // TODO: cover more object types
626 }
627 break;
628 }
629 }
630 break;
632 // uninteresting
633 break;
634 }
635 }
636 } catch (const Failure &) {
637 diag.emplace_back(DiagLevel::Critical, "The MPEG-4 descriptor element structure is invalid.", context);
638 }
639 } else {
640 diag.emplace_back(DiagLevel::Warning, "Elementary stream descriptor atom (esds) is truncated.", context);
641 }
642 return esInfo;
643}
644
649unique_ptr<Mpeg4AudioSpecificConfig> Mp4Track::parseAudioSpecificConfig(
650 istream &stream, std::uint64_t startOffset, std::uint64_t size, Diagnostics &diag)
651{
652 static const string context("parsing MPEG-4 audio specific config from elementary stream descriptor");
653 using namespace Mpeg4AudioObjectIds;
654 // read config into buffer and construct BitReader for bitwise reading
655 stream.seekg(static_cast<streamoff>(startOffset));
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>();
660 try {
661 // read audio object type
662 auto getAudioObjectType = [&bitReader] {
663 std::uint8_t objType = bitReader.readBits<std::uint8_t>(5);
664 if (objType == 31) {
665 objType = 32 + bitReader.readBits<std::uint8_t>(6);
666 }
667 return objType;
668 };
669 audioCfg->audioObjectType = getAudioObjectType();
670 // read sampling frequency
671 if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
672 audioCfg->sampleFrequency = bitReader.readBits<std::uint32_t>(24);
673 }
674 // read channel config
675 audioCfg->channelConfiguration = bitReader.readBits<std::uint8_t>(4);
676 // read extension header
677 switch (audioCfg->audioObjectType) {
678 case Sbr:
679 case Ps:
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);
684 }
685 if ((audioCfg->audioObjectType = getAudioObjectType()) == ErBsac) {
686 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
687 }
688 break;
689 }
690 switch (audioCfg->extensionAudioObjectType) {
691 case Ps:
692 audioCfg->psPresent = true;
693 audioCfg->extensionChannelConfiguration = Mpeg4ChannelConfigs::FrontLeftFrontRight;
694 break;
695 }
696 // read GA specific config
697 switch (audioCfg->audioObjectType) {
698 case AacMain:
699 case AacLc:
700 case AacLtp:
701 case AacScalable:
702 case TwinVq:
703 case ErAacLc:
704 case ErAacLtp:
705 case ErAacScalable:
706 case ErTwinVq:
707 case ErBsac:
708 case ErAacLd:
709 audioCfg->frameLengthFlag = bitReader.readBits<std::uint8_t>(1);
710 if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
711 audioCfg->coreCoderDelay = bitReader.readBits<std::uint8_t>(14);
712 }
713 audioCfg->extensionFlag = bitReader.readBit();
714 if (audioCfg->channelConfiguration == 0) {
715 throw NotImplementedException(); // TODO: parse program_config_element
716 }
717 switch (audioCfg->audioObjectType) {
718 case AacScalable:
719 case ErAacScalable:
720 audioCfg->layerNr = bitReader.readBits<std::uint8_t>(3);
721 break;
722 default:;
723 }
724 if (audioCfg->extensionFlag == 1) {
725 switch (audioCfg->audioObjectType) {
726 case ErBsac:
727 audioCfg->numOfSubFrame = bitReader.readBits<std::uint8_t>(5);
728 audioCfg->layerLength = bitReader.readBits<std::uint16_t>(11);
729 break;
730 case ErAacLc:
731 case ErAacLtp:
732 case ErAacScalable:
733 case ErAacLd:
734 audioCfg->resilienceFlags = bitReader.readBits<std::uint8_t>(3);
735 break;
736 default:;
737 }
738 if (bitReader.readBit() == 1) { // extension flag 3
739 throw NotImplementedException(); // TODO
740 }
741 }
742 break;
743 default:
744 throw NotImplementedException(); // TODO: cover remaining object types
745 }
746 // read error specific config
747 switch (audioCfg->audioObjectType) {
748 case ErAacLc:
749 case ErAacLtp:
750 case ErAacScalable:
751 case ErTwinVq:
752 case ErBsac:
753 case ErAacLd:
754 case ErCelp:
755 case ErHvxc:
756 case ErHiln:
757 case ErParametric:
758 case ErAacEld:
759 switch (audioCfg->epConfig = bitReader.readBits<std::uint8_t>(2)) {
760 case 2:
761 break;
762 case 3:
763 bitReader.skipBits(1);
764 break;
765 default:
766 throw NotImplementedException(); // TODO
767 }
768 break;
769 }
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);
777 }
778 if (bitReader.bitsAvailable() >= 12) {
779 if ((syncExtensionType = bitReader.readBits<std::uint16_t>(11)) == 0x548) {
780 audioCfg->psPresent = bitReader.readBits<std::uint8_t>(1);
781 }
782 }
783 }
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);
788 }
789 }
790 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
791 }
792 } else if (syncExtensionType == 0x548) {
793 audioCfg->psPresent = bitReader.readBit();
794 }
795 }
796 } catch (const NotImplementedException &) {
797 diag.emplace_back(DiagLevel::Information, "Not implemented for the format of audio track.", context);
798 } catch (const std::ios_base::failure &) {
799 if (stream.fail()) {
800 // IO error caused by input stream
801 throw;
802 } else {
803 // IO error caused by bitReader
804 diag.emplace_back(DiagLevel::Critical, "Audio specific configuration is truncated.", context);
805 }
806 }
807 return audioCfg;
808}
809
814std::unique_ptr<Mpeg4VideoSpecificConfig> Mp4Track::parseVideoSpecificConfig(
815 BinaryReader &reader, std::uint64_t startOffset, std::uint64_t size, Diagnostics &diag)
816{
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>();
820 // seek to start
821 reader.stream()->seekg(static_cast<streamoff>(startOffset));
822 if (size > 3 && (reader.readUInt24BE() == 1)) {
823 size -= 3;
824 std::uint32_t buff1;
825 while (size) {
826 --size;
827 switch (reader.readByte()) { // read start code
829 if (size) {
830 videoCfg->profile = reader.readByte();
831 --size;
832 }
833 break;
835
836 break;
838 buff1 = 0;
839 while (size >= 3) {
840 if ((buff1 = reader.readUInt24BE()) != 1) {
841 reader.stream()->seekg(-2, ios_base::cur);
842 videoCfg->userData.push_back(static_cast<char>(buff1 >> 16));
843 --size;
844 } else {
845 size -= 3;
846 break;
847 }
848 }
849 if (buff1 != 1 && size > 0) {
850 videoCfg->userData += reader.readString(size);
851 size = 0;
852 }
853 break;
854 default:;
855 }
856 // skip remaining values to get the start of the next video object
857 while (size >= 3) {
858 if (reader.readUInt24BE() != 1) {
859 reader.stream()->seekg(-2, ios_base::cur);
860 --size;
861 } else {
862 size -= 3;
863 break;
864 }
865 }
866 }
867 } else {
868 diag.emplace_back(DiagLevel::Critical, "\"Visual Object Sequence Header\" not found.", context);
869 }
870 return videoCfg;
871}
872
890void Mp4Track::updateChunkOffsets(const vector<std::int64_t> &oldMdatOffsets, const vector<std::int64_t> &newMdatOffsets)
891{
892 if (!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
893 throw InvalidDataException();
894 }
895 if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
896 throw InvalidDataException();
897 }
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()) {
908 std::uint32_t off;
909 while ((currentPos + 4) <= endPos) {
910 off = m_reader.readUInt32BE();
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]);
914 break;
915 }
916 }
917 m_ostream->seekp(static_cast<streamoff>(currentPos));
918 m_writer.writeUInt32BE(off);
919 currentPos += static_cast<std::uint64_t>(m_istream->gcount());
920 }
921 break;
922 }
924 std::uint64_t off;
925 while ((currentPos + 8) <= endPos) {
926 off = m_reader.readUInt64BE();
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]);
930 break;
931 }
932 }
933 m_ostream->seekp(static_cast<streamoff>(currentPos));
934 m_writer.writeUInt64BE(off);
935 currentPos += static_cast<std::uint64_t>(m_istream->gcount());
936 }
937 break;
938 }
939 default:
940 throw InvalidDataException();
941 }
942}
943
957void Mp4Track::updateChunkOffsets(const std::vector<std::uint64_t> &chunkOffsets)
958{
959 if (!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
960 throw InvalidDataException();
961 }
962 if (chunkOffsets.size() != chunkCount()) {
963 throw InvalidDataException();
964 }
965 m_ostream->seekp(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
966 switch (m_stcoAtom->id()) {
968 for (auto offset : chunkOffsets) {
969 m_writer.writeUInt32BE(static_cast<std::uint32_t>(offset));
970 }
971 break;
973 for (auto offset : chunkOffsets) {
974 m_writer.writeUInt64BE(offset);
975 }
976 break;
977 default:
978 throw InvalidDataException();
979 }
980}
981
995void Mp4Track::updateChunkOffset(std::uint32_t chunkIndex, std::uint64_t offset)
996{
997 if (!isHeaderValid() || !m_istream || !m_stcoAtom || chunkIndex >= m_chunkCount) {
998 throw InvalidDataException();
999 }
1000 m_ostream->seekp(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8 + chunkOffsetSize() * chunkIndex));
1001 switch (chunkOffsetSize()) {
1002 case 4:
1003 writer().writeUInt32BE(static_cast<std::uint32_t>(offset));
1004 break;
1005 case 8:
1006 writer().writeUInt64BE(offset);
1007 break;
1008 default:
1009 throw InvalidDataException();
1010 }
1011}
1012
1017{
1018 if (!avcConfig.spsInfos.empty()) {
1019 const SpsInfo &spsInfo = avcConfig.spsInfos.back();
1020 track.m_format.sub = spsInfo.profileIndication;
1021 track.m_version = static_cast<double>(spsInfo.levelIndication) / 10;
1022 track.m_cropping = spsInfo.cropping;
1023 track.m_pixelSize = spsInfo.pictureSize;
1024 switch (spsInfo.chromaFormatIndication) {
1025 case 0:
1026 track.m_chromaFormat = "monochrome";
1027 break;
1028 case 1:
1029 track.m_chromaFormat = "YUV 4:2:0";
1030 break;
1031 case 2:
1032 track.m_chromaFormat = "YUV 4:2:2";
1033 break;
1034 case 3:
1035 track.m_chromaFormat = "YUV 4:4:4";
1036 break;
1037 default:;
1038 }
1039 track.m_pixelAspectRatio = spsInfo.pixelAspectRatio;
1040 } else {
1041 track.m_format.sub = avcConfig.profileIndication;
1042 track.m_version = static_cast<double>(avcConfig.levelIndication) / 10;
1043 }
1044}
1045
1051{
1052 CPP_UTILITIES_UNUSED(av1Config)
1053 CPP_UTILITIES_UNUSED(track)
1055}
1056
1064{
1065 CPP_UTILITIES_UNUSED(diag)
1066
1067 if (m_tkhdAtom) {
1068 m_tkhdAtom->makeBuffer();
1069 }
1070 for (Mp4Atom *trakChild = m_trakAtom->firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1071 if (trakChild->id() == Mp4AtomIds::Media) {
1072 continue;
1073 }
1074 trakChild->makeBuffer();
1075 }
1076 if (m_minfAtom) {
1077 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1078 childAtom->makeBuffer();
1079 }
1080 }
1081}
1082
1086std::uint64_t Mp4Track::requiredSize(Diagnostics &diag) const
1087{
1088 CPP_UTILITIES_UNUSED(diag)
1089
1090 // add size of
1091 // ... trak header
1092 std::uint64_t size = 8;
1093 // ... tkhd atom (TODO: buffer TrackHeaderInfo in next major release)
1094 size += verifyPresentTrackHeader().requiredSize;
1095 // ... children beside tkhd and mdia
1096 for (Mp4Atom *trakChild = m_trakAtom->firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1097 if (trakChild->id() == Mp4AtomIds::Media || trakChild->id() == Mp4AtomIds::TrackHeader) {
1098 continue;
1099 }
1100 size += trakChild->totalSize();
1101 }
1102 // ... mdhd total size
1103 if (static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
1104 || static_cast<std::uint64_t>((m_modificationTime - 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()) {
1106 // write version 1 where those fields are 64-bit
1107 size += 44;
1108 } else {
1109 // write version 0 where those fields are 32-bit
1110 size += 32;
1111 }
1112 // ... mdia header + hdlr total size + minf header
1113 size += 8 + (33 + m_name.size()) + 8;
1114 // ... minf children
1115 bool dinfAtomWritten = false;
1116 if (m_minfAtom) {
1117 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1118 if (childAtom->id() == Mp4AtomIds::DataInformation) {
1119 dinfAtomWritten = true;
1120 }
1121 size += childAtom->totalSize();
1122 }
1123 }
1124 if (!dinfAtomWritten) {
1125 // take 36 bytes for a self-made dinf atom into account if the file lacks one
1126 size += 36;
1127 }
1128 return size;
1129}
1130
1140{
1141 // write header
1142 ostream::pos_type trakStartOffset = outputStream().tellp();
1143 m_writer.writeUInt32BE(0); // write size later
1144 m_writer.writeUInt32BE(Mp4AtomIds::Track);
1145
1146 // write tkhd atom
1147 makeTrackHeader(diag);
1148
1149 // write children of trak atom except mdia
1150 for (Mp4Atom *trakChild = trakAtom().firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1151 if (trakChild->id() == Mp4AtomIds::Media || trakChild->id() == Mp4AtomIds::TrackHeader) {
1152 continue;
1153 }
1154 trakChild->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1155 }
1156
1157 // write mdia atom
1158 makeMedia(diag);
1159
1160 // write size (of trak atom)
1161 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), trakStartOffset, diag);
1162}
1163
1169{
1170 // verify the existing track header to make the new one based on it (if possible)
1171 const TrackHeaderInfo info(verifyPresentTrackHeader());
1172
1173 // add notifications in case the present track header could not be parsed
1174 if (info.versionUnknown) {
1175 diag.emplace_back(DiagLevel::Critical,
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));
1178 }
1179 if (info.truncated) {
1180 diag.emplace_back(
1181 DiagLevel::Critical, argsToString("The present \"tkhd\"-atom is truncated."), argsToString("making \"tkhd\"-atom of track ", m_id));
1182 }
1183
1184 // make size and element ID
1185 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
1186 writer().writeUInt32BE(1);
1187 writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
1188 writer().writeUInt64BE(info.requiredSize);
1189 } else {
1190 writer().writeUInt32BE(static_cast<std::uint32_t>(info.requiredSize));
1191 writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
1192 }
1193
1194 // determine time-related values and version
1195 const auto creationTime = static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds());
1196 const auto modificationTime = static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds());
1197 const auto duration = static_cast<std::uint64_t>(m_duration.totalSeconds() * m_timeScale);
1198 const std::uint8_t version = (info.version == 0)
1199 && (creationTime > numeric_limits<std::uint32_t>::max() || modificationTime > numeric_limits<std::uint32_t>::max()
1200 || duration > numeric_limits<std::uint32_t>::max())
1201 ? 1
1202 : info.version;
1203
1204 // make version and flags
1205 writer().writeByte(version);
1206 std::uint32_t flags = 0;
1207 if (isEnabled()) {
1208 flags |= 0x000001;
1209 }
1211 flags |= 0x000002;
1212 }
1214 flags |= 0x000004;
1215 }
1216 writer().writeUInt24BE(flags);
1217
1218 // make creation and modification time
1219 if (version != 0) {
1220 writer().writeUInt64BE(creationTime);
1221 writer().writeUInt64BE(modificationTime);
1222 } else {
1223 writer().writeUInt32BE(static_cast<std::uint32_t>(creationTime));
1224 writer().writeUInt32BE(static_cast<std::uint32_t>(modificationTime));
1225 }
1226
1227 // make track ID and duration
1228 writer().writeUInt32BE(static_cast<std::uint32_t>(m_id));
1229 writer().writeUInt32BE(0); // reserved
1230 if (version != 0) {
1231 writer().writeUInt64BE(duration);
1232 } else {
1233 writer().writeUInt32BE(static_cast<std::uint32_t>(duration));
1234 }
1235 writer().writeUInt32BE(0); // reserved
1236 writer().writeUInt32BE(0); // reserved
1237
1238 // make further values, either from existing tkhd atom or just some defaults
1239 if (info.canUseExisting) {
1240 // write all bytes after the previously determined additionalDataOffset
1241 m_ostream->write(m_tkhdAtom->buffer().get() + m_tkhdAtom->headerSize() + info.additionalDataOffset,
1242 static_cast<streamoff>(m_tkhdAtom->dataSize() - info.additionalDataOffset));
1243 // discard the buffer again if it wasn't present before
1244 if (info.discardBuffer) {
1245 m_tkhdAtom->discardBuffer();
1246 }
1247 } else {
1248 // write default values
1249 diag.emplace_back(DiagLevel::Warning, "Writing some default values because the existing tkhd atom is truncated.", "making tkhd atom");
1250 writer().writeInt16BE(0); // layer
1251 writer().writeInt16BE(0); // alternate group
1252 writer().writeFixed8BE(1.0); // volume (fixed 8.8 - 2 byte)
1253 writer().writeUInt16BE(0); // reserved
1254 for (const std::int32_t value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) { // unity matrix
1255 writer().writeInt32BE(value);
1256 }
1257 writer().writeFixed16BE(1.0); // width
1258 writer().writeFixed16BE(1.0); // height
1259 }
1260}
1261
1267{
1268 ostream::pos_type mdiaStartOffset = outputStream().tellp();
1269 writer().writeUInt32BE(0); // write size later
1270 writer().writeUInt32BE(Mp4AtomIds::Media);
1271 // write mdhd atom
1272 const auto creationTime = static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds());
1273 const auto modificationTime = static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds());
1274 const auto duration = static_cast<std::uint64_t>(m_duration.totalSeconds() * m_timeScale);
1275 const std::uint8_t version = (creationTime > numeric_limits<std::uint32_t>::max() || modificationTime > numeric_limits<std::uint32_t>::max()
1276 || duration > numeric_limits<std::uint32_t>::max())
1277 ? 1
1278 : 0;
1279 writer().writeUInt32BE(version != 0 ? 44 : 32); // size
1280 writer().writeUInt32BE(Mp4AtomIds::MediaHeader);
1281 writer().writeByte(version); // version
1282 writer().writeUInt24BE(0); // flags
1283 if (version != 0) {
1284 writer().writeUInt64BE(creationTime);
1285 writer().writeUInt64BE(modificationTime);
1286 } else {
1287 writer().writeUInt32BE(static_cast<std::uint32_t>(creationTime));
1288 writer().writeUInt32BE(static_cast<std::uint32_t>(modificationTime));
1289 }
1290 writer().writeUInt32BE(m_timeScale);
1291 if (version != 0) {
1292 writer().writeUInt64BE(duration);
1293 } else {
1294 writer().writeUInt32BE(static_cast<std::uint32_t>(duration));
1295 }
1296 // convert and write language
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));
1303 continue;
1304 }
1305
1306 // handle invalid characters
1307 if (language.empty()) {
1308 // preserve empty language field
1309 codedLanguage = 0;
1310 break;
1311 }
1312 diag.emplace_back(
1313 DiagLevel::Warning, "Assigned language \"" % language + "\" is of an invalid format. Setting language to undefined.", "making mdhd atom");
1314 codedLanguage = 0x55C4; // und(efined)
1315 break;
1316 }
1317 if (language.size() > 3) {
1318 diag.emplace_back(
1319 DiagLevel::Warning, "Assigned language \"" % language + "\" is longer than 3 byte and hence will be truncated.", "making mdhd atom");
1320 }
1321 writer().writeUInt16BE(codedLanguage);
1322 writer().writeUInt16BE(0); // pre defined
1323 // write hdlr atom
1324 writer().writeUInt32BE(33 + static_cast<std::uint32_t>(m_name.size())); // size
1325 writer().writeUInt32BE(Mp4AtomIds::HandlerReference);
1326 writer().writeUInt64BE(0); // version, flags, pre defined
1327 switch (m_mediaType) {
1328 case MediaType::Video:
1329 outputStream().write("vide", 4);
1330 break;
1331 case MediaType::Audio:
1332 outputStream().write("soun", 4);
1333 break;
1334 case MediaType::Hint:
1335 outputStream().write("hint", 4);
1336 break;
1337 case MediaType::Text:
1338 outputStream().write("text", 4);
1339 break;
1340 case MediaType::Meta:
1341 outputStream().write("meta", 4);
1342 break;
1343 default:
1345 diag.emplace_back(DiagLevel::Critical, "Media type is invalid; keeping media type as-is.", "making hdlr atom");
1346 }
1347 writer().writeUInt32BE(m_rawMediaType);
1348 break;
1349 }
1350 for (int i = 0; i < 3; ++i)
1351 writer().writeUInt32BE(0); // reserved
1352 writer().writeTerminatedString(m_name);
1353 // write minf atom
1354 makeMediaInfo(diag);
1355 // write size (of mdia atom)
1356 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), mdiaStartOffset, diag);
1357}
1358
1364{
1365 ostream::pos_type minfStartOffset = outputStream().tellp();
1366 writer().writeUInt32BE(0); // write size later
1367 writer().writeUInt32BE(Mp4AtomIds::MediaInformation);
1368 bool dinfAtomWritten = false;
1369 if (m_minfAtom) {
1370 // copy existing atoms except sample table which is handled separately
1371 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1372 if (childAtom->id() == Mp4AtomIds::SampleTable) {
1373 continue;
1374 }
1375 if (childAtom->id() == Mp4AtomIds::DataInformation) {
1376 dinfAtomWritten = true;
1377 }
1378 childAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1379 }
1380 }
1381 // write dinf atom if not written yet
1382 if (!dinfAtomWritten) {
1383 writer().writeUInt32BE(36); // size
1384 writer().writeUInt32BE(Mp4AtomIds::DataInformation);
1385 // write dref atom
1386 writer().writeUInt32BE(28); // size
1387 writer().writeUInt32BE(Mp4AtomIds::DataReference);
1388 writer().writeUInt32BE(0); // version and flags
1389 writer().writeUInt32BE(1); // entry count
1390 // write url atom
1391 writer().writeUInt32BE(12); // size
1392 writer().writeUInt32BE(Mp4AtomIds::DataEntryUrl);
1393 writer().writeByte(0); // version
1394 writer().writeUInt24BE(0x000001); // flags (media data is in the same file as the movie box)
1395 }
1396 // write stbl atom
1397 // -> just copy existing stbl atom because makeSampleTable() is not fully implemented (yet)
1398 bool stblAtomWritten = false;
1399 if (m_minfAtom) {
1400 if (Mp4Atom *const stblAtom = m_minfAtom->childById(Mp4AtomIds::SampleTable, diag)) {
1401 stblAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1402 stblAtomWritten = true;
1403 }
1404 }
1405 if (!stblAtomWritten) {
1406 diag.emplace_back(DiagLevel::Critical,
1407 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.", "making stbl atom");
1408 }
1409 // write size (of minf atom)
1410 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset, diag);
1411}
1412
1419{
1420 // ostream::pos_type stblStartOffset = outputStream().tellp(); (enable when function is fully implemented)
1421
1422 writer().writeUInt32BE(0); // write size later
1423 writer().writeUInt32BE(Mp4AtomIds::SampleTable);
1424 Mp4Atom *const stblAtom = m_minfAtom ? m_minfAtom->childById(Mp4AtomIds::SampleTable, diag) : nullptr;
1425 // write stsd atom
1426 if (m_stsdAtom) {
1427 // copy existing stsd atom
1428 m_stsdAtom->copyEntirely(outputStream(), diag, nullptr);
1429 } else {
1430 diag.emplace_back(DiagLevel::Critical, "Unable to make stsd atom from scratch.", "making stsd atom");
1432 }
1433 // write stts and ctts atoms
1434 Mp4Atom *const sttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::DecodingTimeToSample, diag) : nullptr;
1435 if (sttsAtom) {
1436 // copy existing stts atom
1437 sttsAtom->copyEntirely(outputStream(), diag, nullptr);
1438 } else {
1439 diag.emplace_back(DiagLevel::Critical, "Unable to make stts atom from scratch.", "making stts atom");
1441 }
1442 Mp4Atom *const cttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::CompositionTimeToSample, diag) : nullptr;
1443 if (cttsAtom) {
1444 // copy existing ctts atom
1445 cttsAtom->copyEntirely(outputStream(), diag, nullptr);
1446 }
1447 // write stsc atom (sample-to-chunk table)
1449
1450 // write stsz atom (sample sizes)
1451
1452 // write stz2 atom (compact sample sizes)
1453
1454 // write stco/co64 atom (chunk offset table)
1455
1456 // write stss atom (sync sample table)
1457
1458 // write stsh atom (shadow sync sample table)
1459
1460 // write padb atom (sample padding bits)
1461
1462 // write stdp atom (sample degradation priority)
1463
1464 // write sdtp atom (independent and disposable samples)
1465
1466 // write sbgp atom (sample group description)
1467
1468 // write sbgp atom (sample-to-group)
1469
1470 // write sgpd atom (sample group description)
1471
1472 // write subs atom (sub-sample information)
1473
1474 // write size of stbl atom (enable when function is fully implemented)
1475 // Mp4Atom::seekBackAndWriteAtomSize(outputStream(), stblStartOffset, diag);
1476}
1477
1479{
1480 CPP_UTILITIES_UNUSED(progress)
1481
1482 static const string context("parsing MP4 track");
1483 using namespace Mp4AtomIds;
1484 if (!m_trakAtom) {
1485 diag.emplace_back(DiagLevel::Critical, "\"trak\"-atom is null.", context);
1486 throw InvalidDataException();
1487 }
1488
1489 // get atoms
1490 try {
1491 if (!(m_tkhdAtom = m_trakAtom->childById(TrackHeader, diag))) {
1492 diag.emplace_back(DiagLevel::Critical, "No \"tkhd\"-atom found.", context);
1493 throw InvalidDataException();
1494 }
1495 if (!(m_mdiaAtom = m_trakAtom->childById(Media, diag))) {
1496 diag.emplace_back(DiagLevel::Critical, "No \"mdia\"-atom found.", context);
1497 throw InvalidDataException();
1498 }
1499 if (!(m_mdhdAtom = m_mdiaAtom->childById(MediaHeader, diag))) {
1500 diag.emplace_back(DiagLevel::Critical, "No \"mdhd\"-atom found.", context);
1501 throw InvalidDataException();
1502 }
1503 if (!(m_hdlrAtom = m_mdiaAtom->childById(HandlerReference, diag))) {
1504 diag.emplace_back(DiagLevel::Critical, "No \"hdlr\"-atom found.", context);
1505 throw InvalidDataException();
1506 }
1507 if (!(m_minfAtom = m_mdiaAtom->childById(MediaInformation, diag))) {
1508 diag.emplace_back(DiagLevel::Critical, "No \"minf\"-atom found.", context);
1509 throw InvalidDataException();
1510 }
1511 if (!(m_stblAtom = m_minfAtom->childById(SampleTable, diag))) {
1512 diag.emplace_back(DiagLevel::Critical, "No \"stbl\"-atom found.", context);
1513 throw InvalidDataException();
1514 }
1515 if (!(m_stsdAtom = m_stblAtom->childById(SampleDescription, diag))) {
1516 diag.emplace_back(DiagLevel::Critical, "No \"stsd\"-atom found.", context);
1517 throw InvalidDataException();
1518 }
1519 if (!(m_stcoAtom = m_stblAtom->childById(ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(ChunkOffset64, diag))) {
1520 diag.emplace_back(DiagLevel::Critical, "No \"stco\"/\"co64\"-atom found.", context);
1521 throw InvalidDataException();
1522 }
1523 if (!(m_stscAtom = m_stblAtom->childById(SampleToChunk, diag))) {
1524 diag.emplace_back(DiagLevel::Critical, "No \"stsc\"-atom found.", context);
1525 throw InvalidDataException();
1526 }
1527 if (!(m_stszAtom = m_stblAtom->childById(SampleSize, diag)) && !(m_stszAtom = m_stblAtom->childById(CompactSampleSize, diag))) {
1528 diag.emplace_back(DiagLevel::Critical, "No \"stsz\"/\"stz2\"-atom found.", context);
1529 throw InvalidDataException();
1530 }
1531 } catch (const Failure &) {
1532 diag.emplace_back(DiagLevel::Critical, "Unable to parse relevant atoms.", context);
1533 throw InvalidDataException();
1534 }
1535
1536 BinaryReader &reader = m_trakAtom->reader();
1537
1538 // read tkhd atom
1539 m_istream->seekg(static_cast<streamoff>(m_tkhdAtom->startOffset() + 8)); // seek to beg, skip size and name
1540 auto atomVersion = reader.readByte(); // read version
1541 const auto flags = reader.readUInt24BE();
1542 modFlagEnum(m_flags, TrackFlags::Enabled, flags & 0x000001);
1543 modFlagEnum(m_flags, TrackFlags::UsedInPresentation, flags & 0x000002);
1544 modFlagEnum(m_flags, TrackFlags::UsedWhenPreviewing, flags & 0x000004);
1545 switch (atomVersion) {
1546 case 0:
1547 m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1548 m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1549 m_id = reader.readUInt32BE();
1550 break;
1551 case 1:
1552 m_creationTime = startDate + TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()));
1553 m_modificationTime = startDate + TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()));
1554 m_id = reader.readUInt32BE();
1555 break;
1556 default:
1557 diag.emplace_back(DiagLevel::Critical,
1558 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1559 context);
1562 m_id = 0;
1563 }
1564
1565 // read mdhd atom
1566 m_istream->seekg(static_cast<streamoff>(m_mdhdAtom->dataOffset())); // seek to beg, skip size and name
1567 atomVersion = reader.readByte(); // read version
1568 m_istream->seekg(3, ios_base::cur); // skip flags
1569 switch (atomVersion) {
1570 case 0:
1571 m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1572 m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1573 m_timeScale = reader.readUInt32BE();
1574 m_duration = TimeSpan::fromSeconds(static_cast<double>(reader.readUInt32BE()) / static_cast<double>(m_timeScale));
1575 break;
1576 case 1:
1577 m_creationTime = startDate + TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()));
1578 m_modificationTime = startDate + TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()));
1579 m_timeScale = reader.readUInt32BE();
1580 m_duration = TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()) / static_cast<double>(m_timeScale));
1581 break;
1582 default:
1583 diag.emplace_back(DiagLevel::Warning,
1584 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be "
1585 "determined.",
1586 context);
1587 m_timeScale = 0;
1588 m_duration = TimeSpan();
1589 }
1590 std::uint16_t tmp = reader.readUInt16BE();
1591 if (tmp) {
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),
1596 };
1597 m_locale.emplace_back(std::string(buff, 3), LocaleFormat::ISO_639_2_T);
1598 } else {
1599 m_locale.clear();
1600 }
1601
1602 // read hdlr atom
1603 // -> seek to begin skipping size, name, version, flags and reserved bytes
1604 m_istream->seekg(static_cast<streamoff>(m_hdlrAtom->dataOffset() + 8));
1605 // -> track type
1606 switch (m_rawMediaType = reader.readUInt32BE()) {
1607 case 0x76696465:
1609 break;
1610 case 0x736F756E:
1612 break;
1613 case 0x68696E74:
1615 break;
1616 case 0x6D657461:
1618 break;
1619 case 0x74657874:
1621 break;
1622 default:
1624 }
1625 // -> name
1626 m_istream->seekg(12, ios_base::cur); // skip reserved bytes
1627 if (static_cast<std::uint64_t>(tmp = static_cast<std::uint8_t>(m_istream->peek())) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1628 // assume size prefixed string (seems to appear in QuickTime files)
1629 m_istream->seekg(1, ios_base::cur);
1630 m_name = reader.readString(tmp);
1631 } else {
1632 // assume null terminated string (appears in MP4 files)
1633 m_name = reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1634 }
1635
1636 // read stco atom (only chunk count)
1637 m_chunkOffsetSize = (m_stcoAtom->id() == Mp4AtomIds::ChunkOffset64) ? 8 : 4;
1638 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 4));
1639 m_chunkCount = reader.readUInt32BE();
1640
1641 // read stsd atom
1642 m_istream->seekg(static_cast<streamoff>(m_stsdAtom->dataOffset() + 4)); // seek to beg, skip size, name, version and flags
1643 const auto entryCount = reader.readUInt32BE();
1644 Mp4Atom *esDescParentAtom = nullptr;
1645 if (entryCount) {
1646 try {
1647 for (Mp4Atom *codecConfigContainerAtom = m_stsdAtom->firstChild(); codecConfigContainerAtom;
1648 codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1649 codecConfigContainerAtom->parse(diag);
1650
1651 // parse FOURCC
1652 m_formatId = interpretIntegerAsString<std::uint32_t>(codecConfigContainerAtom->id());
1653 m_format = FourccIds::fourccToMediaFormat(codecConfigContainerAtom->id());
1654
1655 // parse codecConfigContainerAtom
1656 m_istream->seekg(static_cast<streamoff>(codecConfigContainerAtom->dataOffset()));
1657 switch (codecConfigContainerAtom->id()) {
1660 case FourccIds::Amr:
1661 case FourccIds::Drms:
1662 case FourccIds::Alac:
1664 case FourccIds::Ac3:
1665 case FourccIds::EAc3:
1667 case FourccIds::Dts:
1668 case FourccIds::DtsH:
1669 case FourccIds::DtsE:
1670 case FourccIds::Flac:
1671 case FourccIds::Opus:
1672 m_istream->seekg(6 + 2, ios_base::cur); // skip reserved bytes, data reference index
1673 tmp = reader.readUInt16BE(); // read sound version
1674 m_istream->seekg(6, ios_base::cur);
1675 m_channelCount = reader.readUInt16BE();
1676 m_bitsPerSample = reader.readUInt16BE();
1677 m_istream->seekg(4, ios_base::cur); // skip reserved bytes (again)
1678 if (!m_samplingFrequency) {
1679 m_samplingFrequency = reader.readUInt32BE() >> 16;
1680 if (codecConfigContainerAtom->id() != FourccIds::DolbyMpl) {
1681 m_samplingFrequency >>= 16;
1682 }
1683 } else {
1684 m_istream->seekg(4, ios_base::cur);
1685 }
1686 if (codecConfigContainerAtom->id() != FourccIds::WindowsMediaAudio) {
1687 switch (tmp) {
1688 case 1:
1689 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1690 break;
1691 case 2:
1692 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1693 break;
1694 default:
1695 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1696 }
1697 if (!esDescParentAtom) {
1698 esDescParentAtom = codecConfigContainerAtom;
1699 }
1700 }
1701 break;
1705 case FourccIds::Avc1:
1706 case FourccIds::Avc2:
1707 case FourccIds::Avc3:
1708 case FourccIds::Avc4:
1709 case FourccIds::Drmi:
1710 case FourccIds::Hevc1:
1711 case FourccIds::Hevc2:
1712 case FourccIds::Av1_IVF:
1714 case FourccIds::Vp9_2:
1715 m_istream->seekg(6 + 2 + 16, ios_base::cur); // skip reserved bytes, data reference index, and reserved bytes (again)
1716 m_pixelSize.setWidth(reader.readUInt16BE());
1717 m_pixelSize.setHeight(reader.readUInt16BE());
1718 m_resolution.setWidth(static_cast<std::uint32_t>(reader.readFixed16BE()));
1719 m_resolution.setHeight(static_cast<std::uint32_t>(reader.readFixed16BE()));
1720 m_istream->seekg(4, ios_base::cur); // skip reserved bytes
1721 m_framesPerSample = reader.readUInt16BE();
1722 tmp = reader.readByte();
1723 m_compressorName = reader.readString(31);
1724 if (tmp == 0) {
1725 m_compressorName.clear();
1726 } else if (tmp < 32) {
1727 m_compressorName.resize(tmp);
1728 }
1729 m_depth = reader.readUInt16BE(); // 24: color without alpha
1730 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1731 if (!esDescParentAtom) {
1732 esDescParentAtom = codecConfigContainerAtom;
1733 }
1734 break;
1736 // skip reserved bytes and data reference index
1737 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1738 if (!esDescParentAtom) {
1739 esDescParentAtom = codecConfigContainerAtom;
1740 }
1741 break;
1743 break; // TODO
1745 break; // TODO
1746 default:;
1747 }
1748 }
1749
1750 if (esDescParentAtom) {
1751 // parse AVC configuration
1752 if (auto *const avcConfigAtom = esDescParentAtom->childById(Mp4AtomIds::AvcConfiguration, diag)) {
1753 m_istream->seekg(static_cast<streamoff>(avcConfigAtom->dataOffset()));
1754 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1755 try {
1756 m_avcConfig->parse(reader, avcConfigAtom->dataSize(), diag);
1757 addInfo(*m_avcConfig, *this);
1758 } catch (const TruncatedDataException &) {
1759 diag.emplace_back(DiagLevel::Critical, "AVC configuration is truncated.", context);
1760 } catch (const Failure &) {
1761 diag.emplace_back(DiagLevel::Critical, "AVC configuration is invalid.", context);
1762 }
1763 }
1764
1765 // parse AV1 configuration
1766 if (auto *const av1ConfigAtom = esDescParentAtom->childById(Mp4AtomIds::Av1Configuration, diag)) {
1767 m_istream->seekg(static_cast<streamoff>(av1ConfigAtom->dataOffset()));
1768 m_av1Config = make_unique<TagParser::Av1Configuration>();
1769 try {
1770 m_av1Config->parse(reader, av1ConfigAtom->dataSize(), diag);
1771 addInfo(*m_av1Config, *this);
1772 } catch (const NotImplementedException &) {
1773 diag.emplace_back(DiagLevel::Information, "Parsing AV1 configuration is not supported yet.", context);
1774 } catch (const TruncatedDataException &) {
1775 diag.emplace_back(DiagLevel::Critical, "AV1 configuration is truncated.", context);
1776 } catch (const Failure &) {
1777 diag.emplace_back(DiagLevel::Critical, "AV1 configuration is invalid.", context);
1778 }
1779 }
1780
1781 // parse MPEG-4 elementary stream descriptor
1782 auto *esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor, diag);
1783 if (!esDescAtom) {
1784 esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor2, diag);
1785 }
1786 if (esDescAtom) {
1787 try {
1788 if ((m_esInfo = parseMpeg4ElementaryStreamInfo(m_reader, esDescAtom, 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) {
1793 // check the audio specific config for useful information
1794 m_format += Mpeg4AudioObjectIds::idToMediaFormat(m_esInfo->audioSpecificConfig->audioObjectType,
1795 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1796 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1797 m_samplingFrequency = m_esInfo->audioSpecificConfig->sampleFrequency;
1798 } else if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1799 m_samplingFrequency = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->sampleFrequencyIndex];
1800 } else {
1801 diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid sample frequency index.", context);
1802 }
1803 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1804 m_extensionSamplingFrequency = m_esInfo->audioSpecificConfig->extensionSampleFrequency;
1805 } else if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1807 = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex];
1808 } else {
1809 diag.emplace_back(
1810 DiagLevel::Warning, "Audio specific config has invalid extension sample frequency index.", context);
1811 }
1812 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1813 m_extensionChannelConfig = m_esInfo->audioSpecificConfig->extensionChannelConfiguration;
1814 }
1815 if (m_esInfo->videoSpecificConfig) {
1816 // check the video specific config for useful information
1817 if (m_format.general == GeneralMediaFormat::Mpeg4Video && m_esInfo->videoSpecificConfig->profile) {
1818 m_format.sub = m_esInfo->videoSpecificConfig->profile;
1819 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1820 m_formatId += " / ";
1821 m_formatId += m_esInfo->videoSpecificConfig->userData;
1822 }
1823 }
1824 }
1825 // check the stream data for missing information
1826 switch (m_format.general) {
1829 MpegAudioFrame frame;
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()));
1832 frame.parseHeader(reader, diag);
1833 MpegAudioFrameStream::addInfo(frame, *this);
1834 break;
1835 }
1836 default:;
1837 }
1838 }
1839 } catch (const Failure &) {
1840 }
1841 }
1842 }
1843 } catch (const Failure &) {
1844 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of \"stsd\"-atom.", context);
1845 }
1846 }
1847
1848 // read stsz atom which holds the sample size table
1849 m_sampleSizes.clear();
1850 m_size = m_sampleCount = 0;
1851 std::uint64_t actualSampleSizeTableSize = m_stszAtom->dataSize();
1852 if (actualSampleSizeTableSize < 12) {
1853 diag.emplace_back(DiagLevel::Critical,
1854 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1855 } else {
1856 actualSampleSizeTableSize -= 12; // subtract size of version and flags
1857 m_istream->seekg(static_cast<streamoff>(m_stszAtom->dataOffset() + 4)); // seek to beg, skip size, name, version and flags
1858 std::uint32_t fieldSize;
1859 std::uint32_t constantSize;
1860 if (m_stszAtom->id() == Mp4AtomIds::CompactSampleSize) {
1861 constantSize = 0;
1862 m_istream->seekg(3, ios_base::cur); // seek reserved bytes
1863 fieldSize = reader.readByte();
1864 m_sampleCount = reader.readUInt32BE();
1865 } else {
1866 constantSize = reader.readUInt32BE();
1867 m_sampleCount = reader.readUInt32BE();
1868 fieldSize = 32;
1869 }
1870 if (constantSize) {
1871 m_sampleSizes.push_back(constantSize);
1872 m_size = constantSize * m_sampleCount;
1873 } else {
1874 auto actualSampleCount = m_sampleCount;
1875 const auto calculatedSampleSizeTableSize
1876 = static_cast<std::uint64_t>(std::ceil((0.125 * fieldSize) * static_cast<double>(m_sampleCount)));
1877 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1878 diag.emplace_back(
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)));
1883 }
1884 m_sampleSizes.reserve(actualSampleCount);
1885 std::uint32_t i = 1;
1886 switch (fieldSize) {
1887 case 4:
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);
1893 }
1894 if (i <= actualSampleCount + 1) {
1895 m_sampleSizes.push_back(reader.readByte() >> 4);
1896 m_size += m_sampleSizes.back();
1897 }
1898 break;
1899 case 8:
1900 for (; i <= actualSampleCount; ++i) {
1901 m_sampleSizes.push_back(reader.readByte());
1902 m_size += m_sampleSizes.back();
1903 }
1904 break;
1905 case 16:
1906 for (; i <= actualSampleCount; ++i) {
1907 m_sampleSizes.push_back(reader.readUInt16BE());
1908 m_size += m_sampleSizes.back();
1909 }
1910 break;
1911 case 32:
1912 for (; i <= actualSampleCount; ++i) {
1913 m_sampleSizes.push_back(reader.readUInt32BE());
1914 m_size += m_sampleSizes.back();
1915 }
1916 break;
1917 default:
1918 diag.emplace_back(DiagLevel::Critical,
1919 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1920 context);
1921 }
1922 }
1923 }
1924
1925 // no sample sizes found, search for trun atoms
1926 std::uint64_t totalDuration = 0;
1927 for (Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingByIdIncludingThis(MovieFragment, diag); moofAtom;
1928 moofAtom = moofAtom->siblingById(MovieFragment, diag)) {
1929 moofAtom->parse(diag);
1930 for (Mp4Atom *trafAtom = moofAtom->childById(TrackFragment, diag); trafAtom; trafAtom = trafAtom->siblingById(TrackFragment, diag)) {
1931 trafAtom->parse(diag);
1932 for (Mp4Atom *tfhdAtom = trafAtom->childById(TrackFragmentHeader, diag); tfhdAtom;
1933 tfhdAtom = tfhdAtom->siblingById(TrackFragmentHeader, diag)) {
1934 tfhdAtom->parse(diag);
1935 std::uint32_t calculatedDataSize = 0;
1936 if (tfhdAtom->dataSize() < calculatedDataSize) {
1937 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated.", context);
1938 } else {
1939 m_istream->seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
1940 std::uint32_t tfhdFlags = reader.readUInt24BE();
1941 if (m_id == reader.readUInt32BE()) { // check track ID
1942 if (tfhdFlags & 0x000001) { // base-data-offset present
1943 calculatedDataSize += 8;
1944 }
1945 if (tfhdFlags & 0x000002) { // sample-description-index present
1946 calculatedDataSize += 4;
1947 }
1948 if (tfhdFlags & 0x000008) { // default-sample-duration present
1949 calculatedDataSize += 4;
1950 }
1951 if (tfhdFlags & 0x000010) { // default-sample-size present
1952 calculatedDataSize += 4;
1953 }
1954 if (tfhdFlags & 0x000020) { // default-sample-flags present
1955 calculatedDataSize += 4;
1956 }
1957 //uint64 baseDataOffset = moofAtom->startOffset();
1958 //uint32 defaultSampleDescriptionIndex = 0;
1959 std::uint32_t defaultSampleDuration = 0;
1960 std::uint32_t defaultSampleSize = 0;
1961 //uint32 defaultSampleFlags = 0;
1962 if (tfhdAtom->dataSize() < calculatedDataSize) {
1963 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
1964 } else {
1965 if (tfhdFlags & 0x000001) { // base-data-offset present
1966 //baseDataOffset = reader.readUInt64();
1967 m_istream->seekg(8, ios_base::cur);
1968 }
1969 if (tfhdFlags & 0x000002) { // sample-description-index present
1970 //defaultSampleDescriptionIndex = reader.readUInt32();
1971 m_istream->seekg(4, ios_base::cur);
1972 }
1973 if (tfhdFlags & 0x000008) { // default-sample-duration present
1974 defaultSampleDuration = reader.readUInt32BE();
1975 //m_istream->seekg(4, ios_base::cur);
1976 }
1977 if (tfhdFlags & 0x000010) { // default-sample-size present
1978 defaultSampleSize = reader.readUInt32BE();
1979 }
1980 if (tfhdFlags & 0x000020) { // default-sample-flags present
1981 //defaultSampleFlags = reader.readUInt32BE();
1982 m_istream->seekg(4, ios_base::cur);
1983 }
1984 }
1985 for (Mp4Atom *trunAtom = trafAtom->childById(TrackFragmentRun, diag); trunAtom;
1986 trunAtom = trunAtom->siblingById(TrackFragmentRun, diag)) {
1987 std::uint32_t trunCalculatedDataSize = 8;
1988 if (trunAtom->dataSize() < trunCalculatedDataSize) {
1989 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated.", context);
1990 } else {
1991 m_istream->seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
1992 std::uint32_t trunFlags = reader.readUInt24BE();
1993 std::uint32_t sampleCount = reader.readUInt32BE();
1995 if (trunFlags & 0x000001) { // data offset present
1996 trunCalculatedDataSize += 4;
1997 }
1998 if (trunFlags & 0x000004) { // first-sample-flags present
1999 trunCalculatedDataSize += 4;
2000 }
2001 std::uint32_t entrySize = 0;
2002 if (trunFlags & 0x000100) { // sample-duration present
2003 entrySize += 4;
2004 }
2005 if (trunFlags & 0x000200) { // sample-size present
2006 entrySize += 4;
2007 }
2008 if (trunFlags & 0x000400) { // sample-flags present
2009 entrySize += 4;
2010 }
2011 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
2012 entrySize += 4;
2013 }
2014 trunCalculatedDataSize += entrySize * sampleCount;
2015 if (trunAtom->dataSize() < trunCalculatedDataSize) {
2016 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated (presence of fields denoted).", context);
2017 } else {
2018 if (trunFlags & 0x000001) { // data offset present
2019 m_istream->seekg(4, ios_base::cur);
2020 //int32 dataOffset = reader.readInt32();
2021 }
2022 if (trunFlags & 0x000004) { // first-sample-flags present
2023 m_istream->seekg(4, ios_base::cur);
2024 }
2025 for (std::uint32_t i = 0; i < sampleCount; ++i) {
2026 if (trunFlags & 0x000100) { // sample-duration present
2027 totalDuration += reader.readUInt32BE();
2028 } else {
2029 totalDuration += defaultSampleDuration;
2030 }
2031 if (trunFlags & 0x000200) { // sample-size present
2032 m_sampleSizes.push_back(reader.readUInt32BE());
2033 m_size += m_sampleSizes.back();
2034 } else {
2035 m_size += defaultSampleSize;
2036 }
2037 if (trunFlags & 0x000400) { // sample-flags present
2038 m_istream->seekg(4, ios_base::cur);
2039 }
2040 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
2041 m_istream->seekg(4, ios_base::cur);
2042 }
2043 }
2044 }
2045 }
2046 }
2047 if (m_sampleSizes.empty() && defaultSampleSize) {
2048 m_sampleSizes.push_back(defaultSampleSize);
2049 }
2050 }
2051 }
2052 }
2053 }
2054 }
2055
2056 // set duration from "trun-information" if the duration has not been determined yet
2057 if (m_duration.isNull() && totalDuration) {
2058 std::uint32_t timeScale = m_timeScale;
2059 if (!timeScale) {
2060 timeScale = trakAtom().container().timeScale();
2061 }
2062 if (timeScale) {
2063 m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(timeScale));
2064 }
2065 }
2066
2067 // calculate average bitrate
2068 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2069 m_bitrate = (static_cast<double>(m_size) * 0.0078125) / m_duration.totalSeconds();
2070 }
2071
2072 // read stsc atom (only number of entries)
2073 m_istream->seekg(static_cast<streamoff>(m_stscAtom->dataOffset() + 4));
2074 m_sampleToChunkEntryCount = reader.readUInt32BE();
2075}
2076
2077} // namespace TagParser
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...
Definition: abstracttrack.h:65
std::ostream * m_ostream
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.
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::istream * m_istream
std::uint32_t m_samplingFrequency
CppUtilities::BinaryWriter & writer()
Returns a binary writer for the associated stream.
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
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...
Definition: exceptions.h:25
GeneralMediaFormat general
Definition: mediaformat.h:259
The Mp4Atom class helps to parse MP4 files.
Definition: mp4atom.h:38
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.
Definition: mp4atom.cpp:133
Implementation of TagParser::AbstractTrack for the MP4 container.
Definition: mp4track.h:118
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.
Definition: mp4track.cpp:814
static std::unique_ptr< Mpeg4ElementaryStreamInfo > parseMpeg4ElementaryStreamInfo(CppUtilities::BinaryReader &reader, Mp4Atom *esDescAtom, Diagnostics &diag)
Reads the MPEG-4 elementary stream descriptor for the track.
Definition: mp4track.cpp:557
std::uint32_t chunkCount() const
Returns the number of chunks denoted by the stco atom.
Definition: mp4track.h:229
std::vector< std::tuple< std::uint32_t, std::uint32_t, std::uint32_t > > readSampleToChunkTable(Diagnostics &diag)
Reads the sample to chunk table.
Definition: mp4track.cpp:458
static void addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
Adds the information from the specified avcConfig to the specified track.
Definition: mp4track.cpp:1016
std::vector< std::uint64_t > readChunkSizes(TagParser::Diagnostics &diag)
Reads the chunk sizes from the stsz (sample sizes) and stsc (samples per chunk) atom.
Definition: mp4track.cpp:507
void updateChunkOffsets(const std::vector< std::int64_t > &oldMdatOffsets, const std::vector< std::int64_t > &newMdatOffsets)
Updates the chunk offsets of the track.
Definition: mp4track.cpp:890
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
This method is internally called to parse header information.
Definition: mp4track.cpp:1478
void makeSampleTable(Diagnostics &diag)
Makes the sample table (stbl atom) for the track.
Definition: mp4track.cpp:1418
std::uint64_t requiredSize(Diagnostics &diag) const
Returns the number of bytes written when calling makeTrack().
Definition: mp4track.cpp:1086
TrackType type() const override
Returns the type of the track if known; otherwise returns TrackType::Unspecified.
Definition: mp4track.cpp:158
std::uint32_t sampleToChunkEntryCount() const
Returns the number of "sample to chunk" entries within the stsc atom.
Definition: mp4track.h:237
void makeMedia(Diagnostics &diag)
Makes the media information (mdia atom) for the track.
Definition: mp4track.cpp:1266
std::vector< std::uint64_t > readChunkOffsets(bool parseFragments, Diagnostics &diag)
Reads the chunk offsets from the stco atom and fragments if parseFragments is true.
Definition: mp4track.cpp:173
unsigned int chunkOffsetSize() const
Returns the size of a single chunk offset denotation within the stco atom.
Definition: mp4track.h:221
~Mp4Track() override
Destroys the track.
Definition: mp4track.cpp:154
void makeTrackHeader(Diagnostics &diag)
Makes the track header (tkhd atom) for the track.
Definition: mp4track.cpp:1168
void bufferTrackAtoms(Diagnostics &diag)
Buffers all atoms required by the makeTrack() method.
Definition: mp4track.cpp:1063
void makeMediaInfo(Diagnostics &diag)
Makes a media information (minf atom) for the track.
Definition: mp4track.cpp:1363
Mp4Atom & trakAtom()
Returns the trak atom for the current instance.
Definition: mp4track.h:199
void makeTrack(Diagnostics &diag)
Makes the track entry ("trak"-atom) for the track.
Definition: mp4track.cpp:1139
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.
Definition: mp4track.cpp:649
void updateChunkOffset(std::uint32_t chunkIndex, std::uint64_t offset)
Updates a particular chunk offset.
Definition: mp4track.cpp:995
The Mpeg4Descriptor class helps to parse MPEG-4 descriptors.
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.
Definition: exceptions.h:60
void setWidth(std::uint32_t value)
Sets the width.
Definition: size.h:76
void setHeight(std::uint32_t value)
Sets the height.
Definition: size.h:84
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
TAG_PARSER_EXPORT MediaFormat fourccToMediaFormat(std::uint32_t fourccId)
Definition: mp4ids.cpp:47
constexpr TAG_PARSER_EXPORT std::string_view language()
TAG_PARSER_EXPORT MediaFormat idToMediaFormat(std::uint8_t mpeg4AudioObjectId, bool sbrPresent=false, bool psPresent=false)
Definition: mp4ids.cpp:367
TAG_PARSER_EXPORT MediaFormat streamObjectTypeFormat(std::uint8_t streamObjectTypeId)
Returns the TagParser::MediaFormat denoted by the specified MPEG-4 stream ID.
Definition: mp4ids.cpp:215
constexpr TAG_PARSER_EXPORT std::string_view version()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
const DateTime startDate
Dates within MP4 tracks are expressed as the number of seconds since this date.
Definition: mp4track.cpp:70
std::uint32_t mpeg4SamplingFrequencyTable[13]
Definition: mp4ids.cpp:423
TrackType
The TrackType enum specifies the underlying file type of a track and the concrete class of the track ...
Definition: abstracttrack.h:31
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
const LocaleDetail & abbreviatedName(LocaleFormat format) const
Returns the abbreviated name of the specified format.
The SpsInfo struct holds the sequence parameter set.
Definition: avcinfo.h:71
AspectRatio pixelAspectRatio
Definition: avcinfo.h:87
Margin cropping
Definition: avcinfo.h:89
std::uint8_t profileIndication
Definition: avcinfo.h:74
std::uint8_t levelIndication
Definition: avcinfo.h:76
ugolomb chromaFormatIndication
Definition: avcinfo.h:77
The TrackHeaderInfo struct holds information about the present track header (tkhd atom) and informati...
Definition: mp4track.cpp:36