Tag Parser 10.4.0
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 //std::uint64_t baseDataOffset = moofAtom->startOffset();
254 //std::uint32_t defaultSampleDescriptionIndex = 0;
255 //std::uint32_t defaultSampleDuration = 0;
256 std::uint32_t defaultSampleSize = 0;
257 //std::uint32_t 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 inputStream().seekg(4, ios_base::cur);
325 } else {
326 //totalDuration += defaultSampleDuration;
327 }
328 if (trunFlags & 0x000200) { // sample-size present
329 m_sampleSizes.push_back(reader().readUInt32BE());
330 m_size += m_sampleSizes.back();
331 } else {
332 m_size += defaultSampleSize;
333 }
334 if (trunFlags & 0x000400) { // sample-flags present
335 inputStream().seekg(4, ios_base::cur);
336 }
337 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
338 inputStream().seekg(4, ios_base::cur);
339 }
340 }
341 }
342 }
343 }
344 if (m_sampleSizes.empty() && defaultSampleSize) {
345 m_sampleSizes.push_back(defaultSampleSize);
346 }
347 }
348 }
349 }
350 }
351 }
352 }
353 return offsets;
354}
355
360std::uint64_t Mp4Track::accumulateSampleSizes(size_t &sampleIndex, size_t count, Diagnostics &diag)
361{
362 if (sampleIndex + count <= m_sampleSizes.size()) {
363 std::uint64_t sum = 0;
364 for (size_t end = sampleIndex + count; sampleIndex < end; ++sampleIndex) {
365 sum += m_sampleSizes[sampleIndex];
366 }
367 return sum;
368 } else if (m_sampleSizes.size() == 1) {
369 sampleIndex += count;
370 return static_cast<std::uint64_t>(m_sampleSizes.front()) * count;
371 } else {
372 diag.emplace_back(DiagLevel::Critical, "There are not as many sample size entries as samples.", "reading chunk sizes of MP4 track");
373 throw InvalidDataException();
374 }
375}
376
385void Mp4Track::addChunkSizeEntries(
386 std::vector<std::uint64_t> &chunkSizeTable, size_t count, size_t &sampleIndex, std::uint32_t sampleCount, Diagnostics &diag)
387{
388 for (size_t i = 0; i < count; ++i) {
389 chunkSizeTable.push_back(accumulateSampleSizes(sampleIndex, sampleCount, diag));
390 }
391}
392
397TrackHeaderInfo Mp4Track::verifyPresentTrackHeader() const
398{
399 TrackHeaderInfo info;
400
401 // return the default TrackHeaderInfo in case there is no track header prsent
402 if (!m_tkhdAtom) {
403 return info;
404 }
405
406 // ensure the tkhd atom is buffered but mark the buffer to be discarded again if it has not been present
407 info.discardBuffer = m_tkhdAtom->buffer() == nullptr;
408 if (info.discardBuffer) {
409 m_tkhdAtom->makeBuffer();
410 }
411
412 // check the version of the existing tkhd atom to determine where additional data starts
413 switch (info.version = static_cast<std::uint8_t>(m_tkhdAtom->buffer()[m_tkhdAtom->headerSize()])) {
414 case 0:
415 info.additionalDataOffset = 32;
416 break;
417 case 1:
418 info.additionalDataOffset = 44;
419 break;
420 default:
421 info.additionalDataOffset = 44;
422 info.versionUnknown = true;
423 }
424
425 // check whether the existing tkhd atom is not truncated
426 if (info.additionalDataOffset + 48u <= m_tkhdAtom->dataSize()) {
427 info.canUseExisting = true;
428 } else {
429 info.truncated = true;
430 info.canUseExisting = info.additionalDataOffset < m_tkhdAtom->dataSize();
431 if (!info.canUseExisting && info.discardBuffer) {
432 m_tkhdAtom->discardBuffer();
433 }
434 }
435
436 // determine required size
437 info.requiredSize = m_tkhdAtom->dataSize() + 8;
438 // -> add 12 byte to size if update from version 0 to version 1 is required (which needs 12 byte more)
439 if ((info.version == 0)
440 && (static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
441 || static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
442 || static_cast<std::uint64_t>(m_duration.totalSeconds() * m_timeScale) > numeric_limits<std::uint32_t>::max())) {
443 info.requiredSize += 12;
444 }
445 // -> add 8 byte to the size because it must be denoted using a 64-bit integer
446 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
447 info.requiredSize += 8;
448 }
449 return info;
450}
451
459vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> Mp4Track::readSampleToChunkTable(Diagnostics &diag)
460{
461 static const string context("reading sample to chunk table of MP4 track");
462 if (!isHeaderValid() || !m_istream || !m_stscAtom) {
463 diag.emplace_back(DiagLevel::Critical, "Track has not been parsed or is invalid.", context);
464 throw InvalidDataException();
465 }
466 // verify integrity of the sample to chunk table
467 std::uint64_t actualTableSize = m_stscAtom->dataSize();
468 if (actualTableSize < 20) {
469 diag.emplace_back(DiagLevel::Critical, "The stsc atom is truncated. There are no \"sample to chunk\" entries present.", context);
470 throw InvalidDataException();
471 } else {
472 actualTableSize -= 8;
473 }
474 std::uint64_t actualSampleToChunkEntryCount = sampleToChunkEntryCount();
475 std::uint64_t calculatedTableSize = actualSampleToChunkEntryCount * 12;
476 if (calculatedTableSize < actualTableSize) {
477 diag.emplace_back(DiagLevel::Critical, "The stsc atom stores more entries as denoted. The additional entries will be ignored.", context);
478 } else if (calculatedTableSize > actualTableSize) {
479 diag.emplace_back(DiagLevel::Critical, "The stsc atom is truncated. It stores less entries as denoted.", context);
480 actualSampleToChunkEntryCount = actualTableSize / 12;
481 }
482 // prepare reading
483 vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> sampleToChunkTable;
484 sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
485 m_istream->seekg(static_cast<streamoff>(m_stscAtom->dataOffset() + 8));
486 for (std::uint32_t i = 0; i < actualSampleToChunkEntryCount; ++i) {
487 // read entry
488 std::uint32_t firstChunk = reader().readUInt32BE();
489 std::uint32_t samplesPerChunk = reader().readUInt32BE();
490 std::uint32_t sampleDescriptionIndex = reader().readUInt32BE();
491 sampleToChunkTable.emplace_back(firstChunk, samplesPerChunk, sampleDescriptionIndex);
492 }
493 return sampleToChunkTable;
494}
495
508vector<std::uint64_t> Mp4Track::readChunkSizes(Diagnostics &diag)
509{
510 static const string context("reading chunk sizes of MP4 track");
511 if (!isHeaderValid() || !m_istream || !m_stcoAtom) {
512 diag.emplace_back(DiagLevel::Critical, "Track has not been parsed or is invalid.", context);
513 throw InvalidDataException();
514 }
515 // read sample to chunk table
516 const auto sampleToChunkTable = readSampleToChunkTable(diag);
517 // accumulate chunk sizes from the table
518 vector<std::uint64_t> chunkSizes;
519 if (!sampleToChunkTable.empty()) {
520 // prepare reading
521 auto tableIterator = sampleToChunkTable.cbegin();
522 chunkSizes.reserve(m_chunkCount);
523 // read first entry
524 size_t sampleIndex = 0;
525 std::uint32_t previousChunkIndex = get<0>(*tableIterator); // the first chunk has the index 1 and not zero!
526 if (previousChunkIndex != 1) {
527 diag.emplace_back(DiagLevel::Critical, "The first chunk of the first \"sample to chunk\" entry must be 1.", context);
528 previousChunkIndex = 1; // try to read the entry anyway
529 }
530 std::uint32_t samplesPerChunk = get<1>(*tableIterator);
531 // read the following entries
532 ++tableIterator;
533 for (const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
534 std::uint32_t firstChunkIndex = get<0>(*tableIterator);
535 if (firstChunkIndex > previousChunkIndex && firstChunkIndex <= m_chunkCount) {
536 addChunkSizeEntries(chunkSizes, firstChunkIndex - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
537 } else {
538 diag.emplace_back(DiagLevel::Critical,
539 "The first chunk index of a \"sample to chunk\" entry must be greater than the first chunk of the previous entry and not "
540 "greater than the chunk count.",
541 context);
542 throw InvalidDataException();
543 }
544 previousChunkIndex = firstChunkIndex;
545 samplesPerChunk = get<1>(*tableIterator);
546 }
547 if (m_chunkCount >= previousChunkIndex) {
548 addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
549 }
550 }
551 return chunkSizes;
552}
553
558std::unique_ptr<Mpeg4ElementaryStreamInfo> Mp4Track::parseMpeg4ElementaryStreamInfo(
559 CppUtilities::BinaryReader &reader, Mp4Atom *esDescAtom, Diagnostics &diag)
560{
561 static const string context("parsing MPEG-4 elementary stream descriptor");
562 using namespace Mpeg4ElementaryStreamObjectIds;
563 unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
564 if (esDescAtom->dataSize() >= 12) {
565 reader.stream()->seekg(static_cast<streamoff>(esDescAtom->dataOffset()));
566 // read version/flags
567 if (reader.readUInt32BE() != 0) {
568 diag.emplace_back(DiagLevel::Warning, "Unknown version/flags.", context);
569 }
570 // read extended descriptor
571 Mpeg4Descriptor esDesc(esDescAtom->container(), static_cast<std::uint64_t>(reader.stream()->tellg()), esDescAtom->dataSize() - 4);
572 try {
573 esDesc.parse(diag);
574 // check ID
576 diag.emplace_back(DiagLevel::Critical, "Invalid descriptor found.", context);
577 throw Failure();
578 }
579 // read stream info
580 reader.stream()->seekg(static_cast<streamoff>(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();
586 }
587 if (esInfo->urlFlag()) {
588 esInfo->url = reader.readString(reader.readByte());
589 }
590 if (esInfo->ocrFlag()) {
591 esInfo->ocrId = reader.readUInt16BE();
592 }
593 for (Mpeg4Descriptor *esDescChild
594 = esDesc.denoteFirstChild(static_cast<std::uint32_t>(static_cast<std::uint64_t>(reader.stream()->tellg()) - esDesc.startOffset()));
595 esDescChild; esDescChild = esDescChild->nextSibling()) {
596 esDescChild->parse(diag);
597 switch (esDescChild->id()) {
599 // read decoder config descriptor
600 reader.stream()->seekg(static_cast<streamoff>(esDescChild->dataOffset()));
601 esInfo->objectTypeId = reader.readByte();
602 esInfo->decCfgDescFlags = reader.readByte();
603 esInfo->bufferSize = reader.readUInt24BE();
604 esInfo->maxBitrate = reader.readUInt32BE();
605 esInfo->averageBitrate = reader.readUInt32BE();
606 for (Mpeg4Descriptor *decCfgDescChild = esDescChild->denoteFirstChild(esDescChild->headerSize() + 13); decCfgDescChild;
607 decCfgDescChild = decCfgDescChild->nextSibling()) {
608 decCfgDescChild->parse(diag);
609 switch (decCfgDescChild->id()) {
611 // read decoder specific info
612 switch (esInfo->objectTypeId) {
613 case Aac:
617 case Mpeg2Audio:
618 case Mpeg1Audio:
619 esInfo->audioSpecificConfig
620 = parseAudioSpecificConfig(*reader.stream(), decCfgDescChild->dataOffset(), decCfgDescChild->dataSize(), diag);
621 break;
622 case Mpeg4Visual:
623 esInfo->videoSpecificConfig
624 = parseVideoSpecificConfig(reader, decCfgDescChild->dataOffset(), decCfgDescChild->dataSize(), diag);
625 break;
626 default:; // TODO: cover more object types
627 }
628 break;
629 }
630 }
631 break;
633 // uninteresting
634 break;
635 }
636 }
637 } catch (const Failure &) {
638 diag.emplace_back(DiagLevel::Critical, "The MPEG-4 descriptor element structure is invalid.", context);
639 }
640 } else {
641 diag.emplace_back(DiagLevel::Warning, "Elementary stream descriptor atom (esds) is truncated.", context);
642 }
643 return esInfo;
644}
645
650unique_ptr<Mpeg4AudioSpecificConfig> Mp4Track::parseAudioSpecificConfig(
651 istream &stream, std::uint64_t startOffset, std::uint64_t size, Diagnostics &diag)
652{
653 static const string context("parsing MPEG-4 audio specific config from elementary stream descriptor");
654 using namespace Mpeg4AudioObjectIds;
655 // read config into buffer and construct BitReader for bitwise reading
656 stream.seekg(static_cast<streamoff>(startOffset));
657 auto buff = make_unique<char[]>(size);
658 stream.read(buff.get(), static_cast<streamoff>(size));
659 BitReader bitReader(buff.get(), size);
660 auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
661 try {
662 // read audio object type
663 auto getAudioObjectType = [&bitReader] {
664 std::uint8_t objType = bitReader.readBits<std::uint8_t>(5);
665 if (objType == 31) {
666 objType = 32 + bitReader.readBits<std::uint8_t>(6);
667 }
668 return objType;
669 };
670 audioCfg->audioObjectType = getAudioObjectType();
671 // read sampling frequency
672 if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
673 audioCfg->sampleFrequency = bitReader.readBits<std::uint32_t>(24);
674 }
675 // read channel config
676 audioCfg->channelConfiguration = bitReader.readBits<std::uint8_t>(4);
677 // read extension header
678 switch (audioCfg->audioObjectType) {
679 case Sbr:
680 case Ps:
681 audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
682 audioCfg->sbrPresent = true;
683 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
684 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
685 }
686 if ((audioCfg->audioObjectType = getAudioObjectType()) == ErBsac) {
687 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
688 }
689 break;
690 }
691 switch (audioCfg->extensionAudioObjectType) {
692 case Ps:
693 audioCfg->psPresent = true;
694 audioCfg->extensionChannelConfiguration = Mpeg4ChannelConfigs::FrontLeftFrontRight;
695 break;
696 }
697 // read GA specific config
698 switch (audioCfg->audioObjectType) {
699 case AacMain:
700 case AacLc:
701 case AacLtp:
702 case AacScalable:
703 case TwinVq:
704 case ErAacLc:
705 case ErAacLtp:
706 case ErAacScalable:
707 case ErTwinVq:
708 case ErBsac:
709 case ErAacLd:
710 audioCfg->frameLengthFlag = bitReader.readBits<std::uint8_t>(1);
711 if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
712 audioCfg->coreCoderDelay = bitReader.readBits<std::uint8_t>(14);
713 }
714 audioCfg->extensionFlag = bitReader.readBit();
715 if (audioCfg->channelConfiguration == 0) {
716 throw NotImplementedException(); // TODO: parse program_config_element
717 }
718 switch (audioCfg->audioObjectType) {
719 case AacScalable:
720 case ErAacScalable:
721 audioCfg->layerNr = bitReader.readBits<std::uint8_t>(3);
722 break;
723 default:;
724 }
725 if (audioCfg->extensionFlag == 1) {
726 switch (audioCfg->audioObjectType) {
727 case ErBsac:
728 audioCfg->numOfSubFrame = bitReader.readBits<std::uint8_t>(5);
729 audioCfg->layerLength = bitReader.readBits<std::uint16_t>(11);
730 break;
731 case ErAacLc:
732 case ErAacLtp:
733 case ErAacScalable:
734 case ErAacLd:
735 audioCfg->resilienceFlags = bitReader.readBits<std::uint8_t>(3);
736 break;
737 default:;
738 }
739 if (bitReader.readBit() == 1) { // extension flag 3
740 throw NotImplementedException(); // TODO
741 }
742 }
743 break;
744 default:
745 throw NotImplementedException(); // TODO: cover remaining object types
746 }
747 // read error specific config
748 switch (audioCfg->audioObjectType) {
749 case ErAacLc:
750 case ErAacLtp:
751 case ErAacScalable:
752 case ErTwinVq:
753 case ErBsac:
754 case ErAacLd:
755 case ErCelp:
756 case ErHvxc:
757 case ErHiln:
758 case ErParametric:
759 case ErAacEld:
760 switch (audioCfg->epConfig = bitReader.readBits<std::uint8_t>(2)) {
761 case 2:
762 break;
763 case 3:
764 bitReader.skipBits(1);
765 break;
766 default:
767 throw NotImplementedException(); // TODO
768 }
769 break;
770 }
771 if (audioCfg->extensionAudioObjectType != Sbr && audioCfg->extensionAudioObjectType != Ps && bitReader.bitsAvailable() >= 16) {
772 std::uint16_t syncExtensionType = bitReader.readBits<std::uint16_t>(11);
773 if (syncExtensionType == 0x2B7) {
774 if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) == Sbr) {
775 if ((audioCfg->sbrPresent = bitReader.readBit())) {
776 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
777 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
778 }
779 if (bitReader.bitsAvailable() >= 12) {
780 if ((syncExtensionType = bitReader.readBits<std::uint16_t>(11)) == 0x548) {
781 audioCfg->psPresent = bitReader.readBits<std::uint8_t>(1);
782 }
783 }
784 }
785 } else if (audioCfg->extensionAudioObjectType == ErBsac) {
786 if ((audioCfg->sbrPresent = bitReader.readBit())) {
787 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
788 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
789 }
790 }
791 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
792 }
793 } else if (syncExtensionType == 0x548) {
794 audioCfg->psPresent = bitReader.readBit();
795 }
796 }
797 } catch (const NotImplementedException &) {
798 diag.emplace_back(DiagLevel::Information, "Not implemented for the format of audio track.", context);
799 } catch (const std::ios_base::failure &) {
800 if (stream.fail()) {
801 // IO error caused by input stream
802 throw;
803 } else {
804 // IO error caused by bitReader
805 diag.emplace_back(DiagLevel::Critical, "Audio specific configuration is truncated.", context);
806 }
807 }
808 return audioCfg;
809}
810
815std::unique_ptr<Mpeg4VideoSpecificConfig> Mp4Track::parseVideoSpecificConfig(
816 BinaryReader &reader, std::uint64_t startOffset, std::uint64_t size, Diagnostics &diag)
817{
818 static const string context("parsing MPEG-4 video specific config from elementary stream descriptor");
819 using namespace Mpeg4AudioObjectIds;
820 auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
821 // seek to start
822 reader.stream()->seekg(static_cast<streamoff>(startOffset));
823 if (size > 3 && (reader.readUInt24BE() == 1)) {
824 size -= 3;
825 std::uint32_t buff1;
826 while (size) {
827 --size;
828 switch (reader.readByte()) { // read start code
830 if (size) {
831 videoCfg->profile = reader.readByte();
832 --size;
833 }
834 break;
836
837 break;
839 buff1 = 0;
840 while (size >= 3) {
841 if ((buff1 = reader.readUInt24BE()) != 1) {
842 reader.stream()->seekg(-2, ios_base::cur);
843 videoCfg->userData.push_back(static_cast<char>(buff1 >> 16));
844 --size;
845 } else {
846 size -= 3;
847 break;
848 }
849 }
850 if (buff1 != 1 && size > 0) {
851 videoCfg->userData += reader.readString(size);
852 size = 0;
853 }
854 break;
855 default:;
856 }
857 // skip remaining values to get the start of the next video object
858 while (size >= 3) {
859 if (reader.readUInt24BE() != 1) {
860 reader.stream()->seekg(-2, ios_base::cur);
861 --size;
862 } else {
863 size -= 3;
864 break;
865 }
866 }
867 }
868 } else {
869 diag.emplace_back(DiagLevel::Critical, "\"Visual Object Sequence Header\" not found.", context);
870 }
871 return videoCfg;
872}
873
891void Mp4Track::updateChunkOffsets(const vector<std::int64_t> &oldMdatOffsets, const vector<std::int64_t> &newMdatOffsets)
892{
893 if (!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
894 throw InvalidDataException();
895 }
896 if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
897 throw InvalidDataException();
898 }
899 static const unsigned int stcoDataBegin = 8;
900 std::uint64_t startPos = m_stcoAtom->dataOffset() + stcoDataBegin;
901 std::uint64_t endPos = startPos + m_stcoAtom->dataSize() - stcoDataBegin;
902 m_istream->seekg(static_cast<streamoff>(startPos));
903 m_ostream->seekp(static_cast<streamoff>(startPos));
904 vector<std::int64_t>::size_type i;
905 vector<std::int64_t>::size_type size;
906 auto currentPos = static_cast<std::uint64_t>(m_istream->tellg());
907 switch (m_stcoAtom->id()) {
909 std::uint32_t off;
910 while ((currentPos + 4) <= endPos) {
911 off = m_reader.readUInt32BE();
912 for (i = 0, size = oldMdatOffsets.size(); i < size; ++i) {
913 if (off > static_cast<std::uint64_t>(oldMdatOffsets[i])) {
914 off += static_cast<std::uint32_t>(newMdatOffsets[i] - oldMdatOffsets[i]);
915 break;
916 }
917 }
918 m_ostream->seekp(static_cast<streamoff>(currentPos));
919 m_writer.writeUInt32BE(off);
920 currentPos += static_cast<std::uint64_t>(m_istream->gcount());
921 }
922 break;
923 }
925 std::uint64_t off;
926 while ((currentPos + 8) <= endPos) {
927 off = m_reader.readUInt64BE();
928 for (i = 0, size = oldMdatOffsets.size(); i < size; ++i) {
929 if (off > static_cast<std::uint64_t>(oldMdatOffsets[i])) {
930 off += static_cast<std::uint64_t>(newMdatOffsets[i] - oldMdatOffsets[i]);
931 break;
932 }
933 }
934 m_ostream->seekp(static_cast<streamoff>(currentPos));
935 m_writer.writeUInt64BE(off);
936 currentPos += static_cast<std::uint64_t>(m_istream->gcount());
937 }
938 break;
939 }
940 default:
941 throw InvalidDataException();
942 }
943}
944
958void Mp4Track::updateChunkOffsets(const std::vector<std::uint64_t> &chunkOffsets)
959{
960 if (!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
961 throw InvalidDataException();
962 }
963 if (chunkOffsets.size() != chunkCount()) {
964 throw InvalidDataException();
965 }
966 m_ostream->seekp(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
967 switch (m_stcoAtom->id()) {
969 for (auto offset : chunkOffsets) {
970 m_writer.writeUInt32BE(static_cast<std::uint32_t>(offset));
971 }
972 break;
974 for (auto offset : chunkOffsets) {
975 m_writer.writeUInt64BE(offset);
976 }
977 break;
978 default:
979 throw InvalidDataException();
980 }
981}
982
996void Mp4Track::updateChunkOffset(std::uint32_t chunkIndex, std::uint64_t offset)
997{
998 if (!isHeaderValid() || !m_istream || !m_stcoAtom || chunkIndex >= m_chunkCount) {
999 throw InvalidDataException();
1000 }
1001 m_ostream->seekp(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8 + chunkOffsetSize() * chunkIndex));
1002 switch (chunkOffsetSize()) {
1003 case 4:
1004 writer().writeUInt32BE(static_cast<std::uint32_t>(offset));
1005 break;
1006 case 8:
1007 writer().writeUInt64BE(offset);
1008 break;
1009 default:
1010 throw InvalidDataException();
1011 }
1012}
1013
1018{
1019 if (!avcConfig.spsInfos.empty()) {
1020 const SpsInfo &spsInfo = avcConfig.spsInfos.back();
1021 track.m_format.sub = spsInfo.profileIndication;
1022 track.m_version = static_cast<double>(spsInfo.levelIndication) / 10;
1023 track.m_cropping = spsInfo.cropping;
1024 track.m_pixelSize = spsInfo.pictureSize;
1025 switch (spsInfo.chromaFormatIndication) {
1026 case 0:
1027 track.m_chromaFormat = "monochrome";
1028 break;
1029 case 1:
1030 track.m_chromaFormat = "YUV 4:2:0";
1031 break;
1032 case 2:
1033 track.m_chromaFormat = "YUV 4:2:2";
1034 break;
1035 case 3:
1036 track.m_chromaFormat = "YUV 4:4:4";
1037 break;
1038 default:;
1039 }
1040 track.m_pixelAspectRatio = spsInfo.pixelAspectRatio;
1041 } else {
1042 track.m_format.sub = avcConfig.profileIndication;
1043 track.m_version = static_cast<double>(avcConfig.levelIndication) / 10;
1044 }
1045}
1046
1052{
1053 CPP_UTILITIES_UNUSED(av1Config)
1054 CPP_UTILITIES_UNUSED(track)
1056}
1057
1065{
1066 CPP_UTILITIES_UNUSED(diag)
1067
1068 if (m_tkhdAtom) {
1069 m_tkhdAtom->makeBuffer();
1070 }
1071 for (Mp4Atom *trakChild = m_trakAtom->firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1072 if (trakChild->id() == Mp4AtomIds::Media) {
1073 continue;
1074 }
1075 trakChild->makeBuffer();
1076 }
1077 if (m_minfAtom) {
1078 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1079 childAtom->makeBuffer();
1080 }
1081 }
1082}
1083
1087std::uint64_t Mp4Track::requiredSize(Diagnostics &diag) const
1088{
1089 CPP_UTILITIES_UNUSED(diag)
1090
1091 // add size of
1092 // ... trak header
1093 std::uint64_t size = 8;
1094 // ... tkhd atom (TODO: buffer TrackHeaderInfo in next major release)
1095 size += verifyPresentTrackHeader().requiredSize;
1096 // ... children beside tkhd and mdia
1097 for (Mp4Atom *trakChild = m_trakAtom->firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1098 if (trakChild->id() == Mp4AtomIds::Media || trakChild->id() == Mp4AtomIds::TrackHeader) {
1099 continue;
1100 }
1101 size += trakChild->totalSize();
1102 }
1103 // ... mdhd total size
1104 if (static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
1105 || static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
1106 || static_cast<std::uint64_t>(m_duration.totalSeconds() * m_timeScale) > numeric_limits<std::uint32_t>::max()) {
1107 // write version 1 where those fields are 64-bit
1108 size += 44;
1109 } else {
1110 // write version 0 where those fields are 32-bit
1111 size += 32;
1112 }
1113 // ... mdia header + hdlr total size + minf header
1114 size += 8 + (33 + m_name.size()) + 8;
1115 // ... minf children
1116 bool dinfAtomWritten = false;
1117 if (m_minfAtom) {
1118 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1119 if (childAtom->id() == Mp4AtomIds::DataInformation) {
1120 dinfAtomWritten = true;
1121 }
1122 size += childAtom->totalSize();
1123 }
1124 }
1125 if (!dinfAtomWritten) {
1126 // take 36 bytes for a self-made dinf atom into account if the file lacks one
1127 size += 36;
1128 }
1129 return size;
1130}
1131
1141{
1142 // write header
1143 ostream::pos_type trakStartOffset = outputStream().tellp();
1144 m_writer.writeUInt32BE(0); // write size later
1145 m_writer.writeUInt32BE(Mp4AtomIds::Track);
1146
1147 // write tkhd atom
1148 makeTrackHeader(diag);
1149
1150 // write children of trak atom except mdia
1151 for (Mp4Atom *trakChild = trakAtom().firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1152 if (trakChild->id() == Mp4AtomIds::Media || trakChild->id() == Mp4AtomIds::TrackHeader) {
1153 continue;
1154 }
1155 trakChild->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1156 }
1157
1158 // write mdia atom
1159 makeMedia(diag);
1160
1161 // write size (of trak atom)
1162 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), trakStartOffset, diag);
1163}
1164
1170{
1171 // verify the existing track header to make the new one based on it (if possible)
1172 const TrackHeaderInfo info(verifyPresentTrackHeader());
1173
1174 // add notifications in case the present track header could not be parsed
1175 if (info.versionUnknown) {
1176 diag.emplace_back(DiagLevel::Critical,
1177 argsToString("The version of the present \"tkhd\"-atom (", info.version, ") is unknown. Assuming version 1."),
1178 argsToString("making \"tkhd\"-atom of track ", m_id));
1179 }
1180 if (info.truncated) {
1181 diag.emplace_back(
1182 DiagLevel::Critical, argsToString("The present \"tkhd\"-atom is truncated."), argsToString("making \"tkhd\"-atom of track ", m_id));
1183 }
1184
1185 // make size and element ID
1186 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
1187 writer().writeUInt32BE(1);
1188 writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
1189 writer().writeUInt64BE(info.requiredSize);
1190 } else {
1191 writer().writeUInt32BE(static_cast<std::uint32_t>(info.requiredSize));
1192 writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
1193 }
1194
1195 // determine time-related values and version
1196 const auto creationTime = static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds());
1197 const auto modificationTime = static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds());
1198 const auto duration = static_cast<std::uint64_t>(m_duration.totalSeconds() * m_timeScale);
1199 const std::uint8_t version = (info.version == 0)
1200 && (creationTime > numeric_limits<std::uint32_t>::max() || modificationTime > numeric_limits<std::uint32_t>::max()
1201 || duration > numeric_limits<std::uint32_t>::max())
1202 ? 1
1203 : info.version;
1204
1205 // make version and flags
1206 writer().writeByte(version);
1207 std::uint32_t flags = 0;
1208 if (isEnabled()) {
1209 flags |= 0x000001;
1210 }
1212 flags |= 0x000002;
1213 }
1215 flags |= 0x000004;
1216 }
1217 writer().writeUInt24BE(flags);
1218
1219 // make creation and modification time
1220 if (version != 0) {
1221 writer().writeUInt64BE(creationTime);
1222 writer().writeUInt64BE(modificationTime);
1223 } else {
1224 writer().writeUInt32BE(static_cast<std::uint32_t>(creationTime));
1225 writer().writeUInt32BE(static_cast<std::uint32_t>(modificationTime));
1226 }
1227
1228 // make track ID and duration
1229 writer().writeUInt32BE(static_cast<std::uint32_t>(m_id));
1230 writer().writeUInt32BE(0); // reserved
1231 if (version != 0) {
1232 writer().writeUInt64BE(duration);
1233 } else {
1234 writer().writeUInt32BE(static_cast<std::uint32_t>(duration));
1235 }
1236 writer().writeUInt32BE(0); // reserved
1237 writer().writeUInt32BE(0); // reserved
1238
1239 // make further values, either from existing tkhd atom or just some defaults
1240 if (info.canUseExisting) {
1241 // write all bytes after the previously determined additionalDataOffset
1242 m_ostream->write(m_tkhdAtom->buffer().get() + m_tkhdAtom->headerSize() + info.additionalDataOffset,
1243 static_cast<streamoff>(m_tkhdAtom->dataSize() - info.additionalDataOffset));
1244 // discard the buffer again if it wasn't present before
1245 if (info.discardBuffer) {
1246 m_tkhdAtom->discardBuffer();
1247 }
1248 } else {
1249 // write default values
1250 diag.emplace_back(DiagLevel::Warning, "Writing some default values because the existing tkhd atom is truncated.", "making tkhd atom");
1251 writer().writeInt16BE(0); // layer
1252 writer().writeInt16BE(0); // alternate group
1253 writer().writeFixed8BE(1.0); // volume (fixed 8.8 - 2 byte)
1254 writer().writeUInt16BE(0); // reserved
1255 for (const std::int32_t value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) { // unity matrix
1256 writer().writeInt32BE(value);
1257 }
1258 writer().writeFixed16BE(1.0); // width
1259 writer().writeFixed16BE(1.0); // height
1260 }
1261}
1262
1268{
1269 ostream::pos_type mdiaStartOffset = outputStream().tellp();
1270 writer().writeUInt32BE(0); // write size later
1271 writer().writeUInt32BE(Mp4AtomIds::Media);
1272 // write mdhd atom
1273 const auto creationTime = static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds());
1274 const auto modificationTime = static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds());
1275 const auto duration = static_cast<std::uint64_t>(m_duration.totalSeconds() * m_timeScale);
1276 const std::uint8_t version = (creationTime > numeric_limits<std::uint32_t>::max() || modificationTime > numeric_limits<std::uint32_t>::max()
1277 || duration > numeric_limits<std::uint32_t>::max())
1278 ? 1
1279 : 0;
1280 writer().writeUInt32BE(version != 0 ? 44 : 32); // size
1281 writer().writeUInt32BE(Mp4AtomIds::MediaHeader);
1282 writer().writeByte(version); // version
1283 writer().writeUInt24BE(0); // flags
1284 if (version != 0) {
1285 writer().writeUInt64BE(creationTime);
1286 writer().writeUInt64BE(modificationTime);
1287 } else {
1288 writer().writeUInt32BE(static_cast<std::uint32_t>(creationTime));
1289 writer().writeUInt32BE(static_cast<std::uint32_t>(modificationTime));
1290 }
1291 writer().writeUInt32BE(m_timeScale);
1292 if (version != 0) {
1293 writer().writeUInt64BE(duration);
1294 } else {
1295 writer().writeUInt32BE(static_cast<std::uint32_t>(duration));
1296 }
1297 // convert and write language
1299 std::uint16_t codedLanguage = 0;
1300 for (size_t charIndex = 0; charIndex != 3; ++charIndex) {
1301 const char langChar = charIndex < language.size() ? language[charIndex] : 0;
1302 if (langChar >= 'a' && langChar <= 'z') {
1303 codedLanguage |= static_cast<std::uint16_t>((langChar - 0x60) << (0xA - charIndex * 0x5));
1304 continue;
1305 }
1306
1307 // handle invalid characters
1308 if (language.empty()) {
1309 // preserve empty language field
1310 codedLanguage = 0;
1311 break;
1312 }
1313 diag.emplace_back(
1314 DiagLevel::Warning, "Assigned language \"" % language + "\" is of an invalid format. Setting language to undefined.", "making mdhd atom");
1315 codedLanguage = 0x55C4; // und(efined)
1316 break;
1317 }
1318 if (language.size() > 3) {
1319 diag.emplace_back(
1320 DiagLevel::Warning, "Assigned language \"" % language + "\" is longer than 3 byte and hence will be truncated.", "making mdhd atom");
1321 }
1322 writer().writeUInt16BE(codedLanguage);
1323 writer().writeUInt16BE(0); // pre defined
1324 // write hdlr atom
1325 writer().writeUInt32BE(33 + static_cast<std::uint32_t>(m_name.size())); // size
1326 writer().writeUInt32BE(Mp4AtomIds::HandlerReference);
1327 writer().writeUInt64BE(0); // version, flags, pre defined
1328 switch (m_mediaType) {
1329 case MediaType::Video:
1330 outputStream().write("vide", 4);
1331 break;
1332 case MediaType::Audio:
1333 outputStream().write("soun", 4);
1334 break;
1335 case MediaType::Hint:
1336 outputStream().write("hint", 4);
1337 break;
1338 case MediaType::Text:
1339 outputStream().write("text", 4);
1340 break;
1341 case MediaType::Meta:
1342 outputStream().write("meta", 4);
1343 break;
1344 default:
1346 diag.emplace_back(DiagLevel::Critical, "Media type is invalid; keeping media type as-is.", "making hdlr atom");
1347 }
1348 writer().writeUInt32BE(m_rawMediaType);
1349 break;
1350 }
1351 for (int i = 0; i < 3; ++i)
1352 writer().writeUInt32BE(0); // reserved
1353 writer().writeTerminatedString(m_name);
1354 // write minf atom
1355 makeMediaInfo(diag);
1356 // write size (of mdia atom)
1357 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), mdiaStartOffset, diag);
1358}
1359
1365{
1366 ostream::pos_type minfStartOffset = outputStream().tellp();
1367 writer().writeUInt32BE(0); // write size later
1368 writer().writeUInt32BE(Mp4AtomIds::MediaInformation);
1369 bool dinfAtomWritten = false;
1370 if (m_minfAtom) {
1371 // copy existing atoms except sample table which is handled separately
1372 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1373 if (childAtom->id() == Mp4AtomIds::SampleTable) {
1374 continue;
1375 }
1376 if (childAtom->id() == Mp4AtomIds::DataInformation) {
1377 dinfAtomWritten = true;
1378 }
1379 childAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1380 }
1381 }
1382 // write dinf atom if not written yet
1383 if (!dinfAtomWritten) {
1384 writer().writeUInt32BE(36); // size
1385 writer().writeUInt32BE(Mp4AtomIds::DataInformation);
1386 // write dref atom
1387 writer().writeUInt32BE(28); // size
1388 writer().writeUInt32BE(Mp4AtomIds::DataReference);
1389 writer().writeUInt32BE(0); // version and flags
1390 writer().writeUInt32BE(1); // entry count
1391 // write url atom
1392 writer().writeUInt32BE(12); // size
1393 writer().writeUInt32BE(Mp4AtomIds::DataEntryUrl);
1394 writer().writeByte(0); // version
1395 writer().writeUInt24BE(0x000001); // flags (media data is in the same file as the movie box)
1396 }
1397 // write stbl atom
1398 // -> just copy existing stbl atom because makeSampleTable() is not fully implemented (yet)
1399 bool stblAtomWritten = false;
1400 if (m_minfAtom) {
1401 if (Mp4Atom *const stblAtom = m_minfAtom->childById(Mp4AtomIds::SampleTable, diag)) {
1402 stblAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1403 stblAtomWritten = true;
1404 }
1405 }
1406 if (!stblAtomWritten) {
1407 diag.emplace_back(DiagLevel::Critical,
1408 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.", "making stbl atom");
1409 }
1410 // write size (of minf atom)
1411 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset, diag);
1412}
1413
1420{
1421 // ostream::pos_type stblStartOffset = outputStream().tellp(); (enable when function is fully implemented)
1422
1423 writer().writeUInt32BE(0); // write size later
1424 writer().writeUInt32BE(Mp4AtomIds::SampleTable);
1425 Mp4Atom *const stblAtom = m_minfAtom ? m_minfAtom->childById(Mp4AtomIds::SampleTable, diag) : nullptr;
1426 // write stsd atom
1427 if (m_stsdAtom) {
1428 // copy existing stsd atom
1429 m_stsdAtom->copyEntirely(outputStream(), diag, nullptr);
1430 } else {
1431 diag.emplace_back(DiagLevel::Critical, "Unable to make stsd atom from scratch.", "making stsd atom");
1433 }
1434 // write stts and ctts atoms
1435 Mp4Atom *const sttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::DecodingTimeToSample, diag) : nullptr;
1436 if (sttsAtom) {
1437 // copy existing stts atom
1438 sttsAtom->copyEntirely(outputStream(), diag, nullptr);
1439 } else {
1440 diag.emplace_back(DiagLevel::Critical, "Unable to make stts atom from scratch.", "making stts atom");
1442 }
1443 Mp4Atom *const cttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::CompositionTimeToSample, diag) : nullptr;
1444 if (cttsAtom) {
1445 // copy existing ctts atom
1446 cttsAtom->copyEntirely(outputStream(), diag, nullptr);
1447 }
1448 // write stsc atom (sample-to-chunk table)
1450
1451 // write stsz atom (sample sizes)
1452
1453 // write stz2 atom (compact sample sizes)
1454
1455 // write stco/co64 atom (chunk offset table)
1456
1457 // write stss atom (sync sample table)
1458
1459 // write stsh atom (shadow sync sample table)
1460
1461 // write padb atom (sample padding bits)
1462
1463 // write stdp atom (sample degradation priority)
1464
1465 // write sdtp atom (independent and disposable samples)
1466
1467 // write sbgp atom (sample group description)
1468
1469 // write sbgp atom (sample-to-group)
1470
1471 // write sgpd atom (sample group description)
1472
1473 // write subs atom (sub-sample information)
1474
1475 // write size of stbl atom (enable when function is fully implemented)
1476 // Mp4Atom::seekBackAndWriteAtomSize(outputStream(), stblStartOffset, diag);
1477}
1478
1480{
1481 CPP_UTILITIES_UNUSED(progress)
1482
1483 static const string context("parsing MP4 track");
1484 using namespace Mp4AtomIds;
1485 if (!m_trakAtom) {
1486 diag.emplace_back(DiagLevel::Critical, "\"trak\"-atom is null.", context);
1487 throw InvalidDataException();
1488 }
1489
1490 // get atoms
1491 try {
1492 if (!(m_tkhdAtom = m_trakAtom->childById(TrackHeader, diag))) {
1493 diag.emplace_back(DiagLevel::Critical, "No \"tkhd\"-atom found.", context);
1494 throw InvalidDataException();
1495 }
1496 if (!(m_mdiaAtom = m_trakAtom->childById(Media, diag))) {
1497 diag.emplace_back(DiagLevel::Critical, "No \"mdia\"-atom found.", context);
1498 throw InvalidDataException();
1499 }
1500 if (!(m_mdhdAtom = m_mdiaAtom->childById(MediaHeader, diag))) {
1501 diag.emplace_back(DiagLevel::Critical, "No \"mdhd\"-atom found.", context);
1502 throw InvalidDataException();
1503 }
1504 if (!(m_hdlrAtom = m_mdiaAtom->childById(HandlerReference, diag))) {
1505 diag.emplace_back(DiagLevel::Critical, "No \"hdlr\"-atom found.", context);
1506 throw InvalidDataException();
1507 }
1508 if (!(m_minfAtom = m_mdiaAtom->childById(MediaInformation, diag))) {
1509 diag.emplace_back(DiagLevel::Critical, "No \"minf\"-atom found.", context);
1510 throw InvalidDataException();
1511 }
1512 if (!(m_stblAtom = m_minfAtom->childById(SampleTable, diag))) {
1513 diag.emplace_back(DiagLevel::Critical, "No \"stbl\"-atom found.", context);
1514 throw InvalidDataException();
1515 }
1516 if (!(m_stsdAtom = m_stblAtom->childById(SampleDescription, diag))) {
1517 diag.emplace_back(DiagLevel::Critical, "No \"stsd\"-atom found.", context);
1518 throw InvalidDataException();
1519 }
1520 if (!(m_stcoAtom = m_stblAtom->childById(ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(ChunkOffset64, diag))) {
1521 diag.emplace_back(DiagLevel::Critical, "No \"stco\"/\"co64\"-atom found.", context);
1522 throw InvalidDataException();
1523 }
1524 if (!(m_stscAtom = m_stblAtom->childById(SampleToChunk, diag))) {
1525 diag.emplace_back(DiagLevel::Critical, "No \"stsc\"-atom found.", context);
1526 throw InvalidDataException();
1527 }
1528 if (!(m_stszAtom = m_stblAtom->childById(SampleSize, diag)) && !(m_stszAtom = m_stblAtom->childById(CompactSampleSize, diag))) {
1529 diag.emplace_back(DiagLevel::Critical, "No \"stsz\"/\"stz2\"-atom found.", context);
1530 throw InvalidDataException();
1531 }
1532 } catch (const Failure &) {
1533 diag.emplace_back(DiagLevel::Critical, "Unable to parse relevant atoms.", context);
1534 throw InvalidDataException();
1535 }
1536
1537 BinaryReader &reader = m_trakAtom->reader();
1538
1539 // read tkhd atom
1540 m_istream->seekg(static_cast<streamoff>(m_tkhdAtom->startOffset() + 8)); // seek to beg, skip size and name
1541 auto atomVersion = reader.readByte(); // read version
1542 const auto flags = reader.readUInt24BE();
1543 modFlagEnum(m_flags, TrackFlags::Enabled, flags & 0x000001);
1544 modFlagEnum(m_flags, TrackFlags::UsedInPresentation, flags & 0x000002);
1545 modFlagEnum(m_flags, TrackFlags::UsedWhenPreviewing, flags & 0x000004);
1546 switch (atomVersion) {
1547 case 0:
1548 m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1549 m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1550 m_id = reader.readUInt32BE();
1551 break;
1552 case 1:
1553 m_creationTime = startDate + TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()));
1554 m_modificationTime = startDate + TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()));
1555 m_id = reader.readUInt32BE();
1556 break;
1557 default:
1558 diag.emplace_back(DiagLevel::Critical,
1559 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1560 context);
1563 m_id = 0;
1564 }
1565
1566 // read mdhd atom
1567 m_istream->seekg(static_cast<streamoff>(m_mdhdAtom->dataOffset())); // seek to beg, skip size and name
1568 atomVersion = reader.readByte(); // read version
1569 m_istream->seekg(3, ios_base::cur); // skip flags
1570 switch (atomVersion) {
1571 case 0:
1572 m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1573 m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1574 m_timeScale = reader.readUInt32BE();
1575 m_duration = TimeSpan::fromSeconds(static_cast<double>(reader.readUInt32BE()) / static_cast<double>(m_timeScale));
1576 break;
1577 case 1:
1578 m_creationTime = startDate + TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()));
1579 m_modificationTime = startDate + TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()));
1580 m_timeScale = reader.readUInt32BE();
1581 m_duration = TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()) / static_cast<double>(m_timeScale));
1582 break;
1583 default:
1584 diag.emplace_back(DiagLevel::Warning,
1585 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be "
1586 "determined.",
1587 context);
1588 m_timeScale = 0;
1589 m_duration = TimeSpan();
1590 }
1591 std::uint16_t tmp = reader.readUInt16BE();
1592 if (tmp) {
1593 const char buff[] = {
1594 static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1595 static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1596 static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1597 };
1598 m_locale.emplace_back(std::string(buff, 3), LocaleFormat::ISO_639_2_T);
1599 } else {
1600 m_locale.clear();
1601 }
1602
1603 // read hdlr atom
1604 // -> seek to begin skipping size, name, version, flags and reserved bytes
1605 m_istream->seekg(static_cast<streamoff>(m_hdlrAtom->dataOffset() + 8));
1606 // -> track type
1607 switch (m_rawMediaType = reader.readUInt32BE()) {
1608 case 0x76696465:
1610 break;
1611 case 0x736F756E:
1613 break;
1614 case 0x68696E74:
1616 break;
1617 case 0x6D657461:
1619 break;
1620 case 0x74657874:
1622 break;
1623 default:
1625 }
1626 // -> name
1627 m_istream->seekg(12, ios_base::cur); // skip reserved bytes
1628 if (static_cast<std::uint64_t>(tmp = static_cast<std::uint8_t>(m_istream->peek())) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1629 // assume size prefixed string (seems to appear in QuickTime files)
1630 m_istream->seekg(1, ios_base::cur);
1631 m_name = reader.readString(tmp);
1632 } else {
1633 // assume null terminated string (appears in MP4 files)
1634 m_name = reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1635 }
1636
1637 // read stco atom (only chunk count)
1638 m_chunkOffsetSize = (m_stcoAtom->id() == Mp4AtomIds::ChunkOffset64) ? 8 : 4;
1639 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 4));
1640 m_chunkCount = reader.readUInt32BE();
1641
1642 // read stsd atom
1643 m_istream->seekg(static_cast<streamoff>(m_stsdAtom->dataOffset() + 4)); // seek to beg, skip size, name, version and flags
1644 const auto entryCount = reader.readUInt32BE();
1645 Mp4Atom *esDescParentAtom = nullptr;
1646 if (entryCount) {
1647 try {
1648 for (Mp4Atom *codecConfigContainerAtom = m_stsdAtom->firstChild(); codecConfigContainerAtom;
1649 codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1650 codecConfigContainerAtom->parse(diag);
1651
1652 // parse FOURCC
1653 m_formatId = interpretIntegerAsString<std::uint32_t>(codecConfigContainerAtom->id());
1654 m_format = FourccIds::fourccToMediaFormat(codecConfigContainerAtom->id());
1655
1656 // parse codecConfigContainerAtom
1657 m_istream->seekg(static_cast<streamoff>(codecConfigContainerAtom->dataOffset()));
1658 switch (codecConfigContainerAtom->id()) {
1661 case FourccIds::Amr:
1662 case FourccIds::Drms:
1663 case FourccIds::Alac:
1665 case FourccIds::Ac3:
1666 case FourccIds::EAc3:
1668 case FourccIds::Dts:
1669 case FourccIds::DtsH:
1670 case FourccIds::DtsE:
1671 case FourccIds::Flac:
1672 case FourccIds::Opus:
1673 m_istream->seekg(6 + 2, ios_base::cur); // skip reserved bytes, data reference index
1674 tmp = reader.readUInt16BE(); // read sound version
1675 m_istream->seekg(6, ios_base::cur);
1676 m_channelCount = reader.readUInt16BE();
1677 m_bitsPerSample = reader.readUInt16BE();
1678 m_istream->seekg(4, ios_base::cur); // skip reserved bytes (again)
1679 if (!m_samplingFrequency) {
1680 m_samplingFrequency = reader.readUInt32BE() >> 16;
1681 if (codecConfigContainerAtom->id() != FourccIds::DolbyMpl) {
1682 m_samplingFrequency >>= 16;
1683 }
1684 } else {
1685 m_istream->seekg(4, ios_base::cur);
1686 }
1687 if (codecConfigContainerAtom->id() != FourccIds::WindowsMediaAudio) {
1688 switch (tmp) {
1689 case 1:
1690 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1691 break;
1692 case 2:
1693 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1694 break;
1695 default:
1696 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1697 }
1698 if (!esDescParentAtom) {
1699 esDescParentAtom = codecConfigContainerAtom;
1700 }
1701 }
1702 break;
1706 case FourccIds::Avc1:
1707 case FourccIds::Avc2:
1708 case FourccIds::Avc3:
1709 case FourccIds::Avc4:
1710 case FourccIds::Drmi:
1711 case FourccIds::Hevc1:
1712 case FourccIds::Hevc2:
1713 case FourccIds::Av1_IVF:
1715 case FourccIds::Vp9_2:
1716 m_istream->seekg(6 + 2 + 16, ios_base::cur); // skip reserved bytes, data reference index, and reserved bytes (again)
1717 m_pixelSize.setWidth(reader.readUInt16BE());
1718 m_pixelSize.setHeight(reader.readUInt16BE());
1719 m_resolution.setWidth(static_cast<std::uint32_t>(reader.readFixed16BE()));
1720 m_resolution.setHeight(static_cast<std::uint32_t>(reader.readFixed16BE()));
1721 m_istream->seekg(4, ios_base::cur); // skip reserved bytes
1722 m_framesPerSample = reader.readUInt16BE();
1723 tmp = reader.readByte();
1724 m_compressorName = reader.readString(31);
1725 if (tmp == 0) {
1726 m_compressorName.clear();
1727 } else if (tmp < 32) {
1728 m_compressorName.resize(tmp);
1729 }
1730 m_depth = reader.readUInt16BE(); // 24: color without alpha
1731 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1732 if (!esDescParentAtom) {
1733 esDescParentAtom = codecConfigContainerAtom;
1734 }
1735 break;
1737 // skip reserved bytes and data reference index
1738 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1739 if (!esDescParentAtom) {
1740 esDescParentAtom = codecConfigContainerAtom;
1741 }
1742 break;
1744 break; // TODO
1746 break; // TODO
1747 default:;
1748 }
1749 }
1750
1751 if (esDescParentAtom) {
1752 // parse AVC configuration
1753 if (auto *const avcConfigAtom = esDescParentAtom->childById(Mp4AtomIds::AvcConfiguration, diag)) {
1754 m_istream->seekg(static_cast<streamoff>(avcConfigAtom->dataOffset()));
1755 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1756 try {
1757 m_avcConfig->parse(reader, avcConfigAtom->dataSize(), diag);
1758 addInfo(*m_avcConfig, *this);
1759 } catch (const TruncatedDataException &) {
1760 diag.emplace_back(DiagLevel::Critical, "AVC configuration is truncated.", context);
1761 } catch (const Failure &) {
1762 diag.emplace_back(DiagLevel::Critical, "AVC configuration is invalid.", context);
1763 }
1764 }
1765
1766 // parse AV1 configuration
1767 if (auto *const av1ConfigAtom = esDescParentAtom->childById(Mp4AtomIds::Av1Configuration, diag)) {
1768 m_istream->seekg(static_cast<streamoff>(av1ConfigAtom->dataOffset()));
1769 m_av1Config = make_unique<TagParser::Av1Configuration>();
1770 try {
1771 m_av1Config->parse(reader, av1ConfigAtom->dataSize(), diag);
1772 addInfo(*m_av1Config, *this);
1773 } catch (const NotImplementedException &) {
1774 diag.emplace_back(DiagLevel::Information, "Parsing AV1 configuration is not supported yet.", context);
1775 } catch (const TruncatedDataException &) {
1776 diag.emplace_back(DiagLevel::Critical, "AV1 configuration is truncated.", context);
1777 } catch (const Failure &) {
1778 diag.emplace_back(DiagLevel::Critical, "AV1 configuration is invalid.", context);
1779 }
1780 }
1781
1782 // parse MPEG-4 elementary stream descriptor
1783 auto *esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor, diag);
1784 if (!esDescAtom) {
1785 esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor2, diag);
1786 }
1787 if (esDescAtom) {
1788 try {
1789 if ((m_esInfo = parseMpeg4ElementaryStreamInfo(m_reader, esDescAtom, diag))) {
1791 m_bitrate = static_cast<double>(m_esInfo->averageBitrate) / 1000;
1792 m_maxBitrate = static_cast<double>(m_esInfo->maxBitrate) / 1000;
1793 if (m_esInfo->audioSpecificConfig) {
1794 // check the audio specific config for useful information
1795 m_format += Mpeg4AudioObjectIds::idToMediaFormat(m_esInfo->audioSpecificConfig->audioObjectType,
1796 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1797 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1798 m_samplingFrequency = m_esInfo->audioSpecificConfig->sampleFrequency;
1799 } else if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1800 m_samplingFrequency = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->sampleFrequencyIndex];
1801 } else {
1802 diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid sample frequency index.", context);
1803 }
1804 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1805 m_extensionSamplingFrequency = m_esInfo->audioSpecificConfig->extensionSampleFrequency;
1806 } else if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1808 = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex];
1809 } else {
1810 diag.emplace_back(
1811 DiagLevel::Warning, "Audio specific config has invalid extension sample frequency index.", context);
1812 }
1813 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1814 m_extensionChannelConfig = m_esInfo->audioSpecificConfig->extensionChannelConfiguration;
1815 }
1816 if (m_esInfo->videoSpecificConfig) {
1817 // check the video specific config for useful information
1818 if (m_format.general == GeneralMediaFormat::Mpeg4Video && m_esInfo->videoSpecificConfig->profile) {
1819 m_format.sub = m_esInfo->videoSpecificConfig->profile;
1820 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1821 m_formatId += " / ";
1822 m_formatId += m_esInfo->videoSpecificConfig->userData;
1823 }
1824 }
1825 }
1826 // check the stream data for missing information
1827 switch (m_format.general) {
1830 MpegAudioFrame frame;
1831 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
1832 m_istream->seekg(static_cast<streamoff>(m_chunkOffsetSize == 8 ? reader.readUInt64BE() : reader.readUInt32BE()));
1833 frame.parseHeader(reader, diag);
1834 MpegAudioFrameStream::addInfo(frame, *this);
1835 break;
1836 }
1837 default:;
1838 }
1839 }
1840 } catch (const Failure &) {
1841 }
1842 }
1843 }
1844 } catch (const Failure &) {
1845 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of \"stsd\"-atom.", context);
1846 }
1847 }
1848
1849 // read stsz atom which holds the sample size table
1850 m_sampleSizes.clear();
1851 m_size = m_sampleCount = 0;
1852 std::uint64_t actualSampleSizeTableSize = m_stszAtom->dataSize();
1853 if (actualSampleSizeTableSize < 12) {
1854 diag.emplace_back(DiagLevel::Critical,
1855 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1856 } else {
1857 actualSampleSizeTableSize -= 12; // subtract size of version and flags
1858 m_istream->seekg(static_cast<streamoff>(m_stszAtom->dataOffset() + 4)); // seek to beg, skip size, name, version and flags
1859 std::uint32_t fieldSize;
1860 std::uint32_t constantSize;
1861 if (m_stszAtom->id() == Mp4AtomIds::CompactSampleSize) {
1862 constantSize = 0;
1863 m_istream->seekg(3, ios_base::cur); // seek reserved bytes
1864 fieldSize = reader.readByte();
1865 m_sampleCount = reader.readUInt32BE();
1866 } else {
1867 constantSize = reader.readUInt32BE();
1868 m_sampleCount = reader.readUInt32BE();
1869 fieldSize = 32;
1870 }
1871 if (constantSize) {
1872 m_sampleSizes.push_back(constantSize);
1873 m_size = constantSize * m_sampleCount;
1874 } else {
1875 auto actualSampleCount = m_sampleCount;
1876 const auto calculatedSampleSizeTableSize
1877 = static_cast<std::uint64_t>(std::ceil((0.125 * fieldSize) * static_cast<double>(m_sampleCount)));
1878 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1879 diag.emplace_back(
1880 DiagLevel::Critical, "The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1881 } else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1882 diag.emplace_back(DiagLevel::Critical, "The stsz atom is truncated. It stores less entries as denoted.", context);
1883 actualSampleCount = static_cast<std::uint64_t>(floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize)));
1884 }
1885 m_sampleSizes.reserve(actualSampleCount);
1886 std::uint32_t i = 1;
1887 switch (fieldSize) {
1888 case 4:
1889 for (; i <= actualSampleCount; i += 2) {
1890 std::uint8_t val = reader.readByte();
1891 m_sampleSizes.push_back(val >> 4);
1892 m_sampleSizes.push_back(val & 0xF0);
1893 m_size += (val >> 4) + (val & 0xF0);
1894 }
1895 if (i <= actualSampleCount + 1) {
1896 m_sampleSizes.push_back(reader.readByte() >> 4);
1897 m_size += m_sampleSizes.back();
1898 }
1899 break;
1900 case 8:
1901 for (; i <= actualSampleCount; ++i) {
1902 m_sampleSizes.push_back(reader.readByte());
1903 m_size += m_sampleSizes.back();
1904 }
1905 break;
1906 case 16:
1907 for (; i <= actualSampleCount; ++i) {
1908 m_sampleSizes.push_back(reader.readUInt16BE());
1909 m_size += m_sampleSizes.back();
1910 }
1911 break;
1912 case 32:
1913 for (; i <= actualSampleCount; ++i) {
1914 m_sampleSizes.push_back(reader.readUInt32BE());
1915 m_size += m_sampleSizes.back();
1916 }
1917 break;
1918 default:
1919 diag.emplace_back(DiagLevel::Critical,
1920 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1921 context);
1922 }
1923 }
1924 }
1925
1926 // no sample sizes found, search for trun atoms
1927 std::uint64_t totalDuration = 0;
1928 for (Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingByIdIncludingThis(MovieFragment, diag); moofAtom;
1929 moofAtom = moofAtom->siblingById(MovieFragment, diag)) {
1930 moofAtom->parse(diag);
1931 for (Mp4Atom *trafAtom = moofAtom->childById(TrackFragment, diag); trafAtom; trafAtom = trafAtom->siblingById(TrackFragment, diag)) {
1932 trafAtom->parse(diag);
1933 for (Mp4Atom *tfhdAtom = trafAtom->childById(TrackFragmentHeader, diag); tfhdAtom;
1934 tfhdAtom = tfhdAtom->siblingById(TrackFragmentHeader, diag)) {
1935 tfhdAtom->parse(diag);
1936 std::uint32_t calculatedDataSize = 0;
1937 if (tfhdAtom->dataSize() < calculatedDataSize) {
1938 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated.", context);
1939 } else {
1940 m_istream->seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
1941 std::uint32_t tfhdFlags = reader.readUInt24BE();
1942 if (m_id == reader.readUInt32BE()) { // check track ID
1943 if (tfhdFlags & 0x000001) { // base-data-offset present
1944 calculatedDataSize += 8;
1945 }
1946 if (tfhdFlags & 0x000002) { // sample-description-index present
1947 calculatedDataSize += 4;
1948 }
1949 if (tfhdFlags & 0x000008) { // default-sample-duration present
1950 calculatedDataSize += 4;
1951 }
1952 if (tfhdFlags & 0x000010) { // default-sample-size present
1953 calculatedDataSize += 4;
1954 }
1955 if (tfhdFlags & 0x000020) { // default-sample-flags present
1956 calculatedDataSize += 4;
1957 }
1958 //uint64 baseDataOffset = moofAtom->startOffset();
1959 //uint32 defaultSampleDescriptionIndex = 0;
1960 std::uint32_t defaultSampleDuration = 0;
1961 std::uint32_t defaultSampleSize = 0;
1962 //uint32 defaultSampleFlags = 0;
1963 if (tfhdAtom->dataSize() < calculatedDataSize) {
1964 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
1965 } else {
1966 if (tfhdFlags & 0x000001) { // base-data-offset present
1967 //baseDataOffset = reader.readUInt64();
1968 m_istream->seekg(8, ios_base::cur);
1969 }
1970 if (tfhdFlags & 0x000002) { // sample-description-index present
1971 //defaultSampleDescriptionIndex = reader.readUInt32();
1972 m_istream->seekg(4, ios_base::cur);
1973 }
1974 if (tfhdFlags & 0x000008) { // default-sample-duration present
1975 defaultSampleDuration = reader.readUInt32BE();
1976 //m_istream->seekg(4, ios_base::cur);
1977 }
1978 if (tfhdFlags & 0x000010) { // default-sample-size present
1979 defaultSampleSize = reader.readUInt32BE();
1980 }
1981 if (tfhdFlags & 0x000020) { // default-sample-flags present
1982 //defaultSampleFlags = reader.readUInt32BE();
1983 m_istream->seekg(4, ios_base::cur);
1984 }
1985 }
1986 for (Mp4Atom *trunAtom = trafAtom->childById(TrackFragmentRun, diag); trunAtom;
1987 trunAtom = trunAtom->siblingById(TrackFragmentRun, diag)) {
1988 std::uint32_t trunCalculatedDataSize = 8;
1989 if (trunAtom->dataSize() < trunCalculatedDataSize) {
1990 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated.", context);
1991 } else {
1992 m_istream->seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
1993 std::uint32_t trunFlags = reader.readUInt24BE();
1994 std::uint32_t sampleCount = reader.readUInt32BE();
1996 if (trunFlags & 0x000001) { // data offset present
1997 trunCalculatedDataSize += 4;
1998 }
1999 if (trunFlags & 0x000004) { // first-sample-flags present
2000 trunCalculatedDataSize += 4;
2001 }
2002 std::uint32_t entrySize = 0;
2003 if (trunFlags & 0x000100) { // sample-duration present
2004 entrySize += 4;
2005 }
2006 if (trunFlags & 0x000200) { // sample-size present
2007 entrySize += 4;
2008 }
2009 if (trunFlags & 0x000400) { // sample-flags present
2010 entrySize += 4;
2011 }
2012 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
2013 entrySize += 4;
2014 }
2015 trunCalculatedDataSize += entrySize * sampleCount;
2016 if (trunAtom->dataSize() < trunCalculatedDataSize) {
2017 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated (presence of fields denoted).", context);
2018 } else {
2019 if (trunFlags & 0x000001) { // data offset present
2020 m_istream->seekg(4, ios_base::cur);
2021 //int32 dataOffset = reader.readInt32();
2022 }
2023 if (trunFlags & 0x000004) { // first-sample-flags present
2024 m_istream->seekg(4, ios_base::cur);
2025 }
2026 for (std::uint32_t i = 0; i < sampleCount; ++i) {
2027 if (trunFlags & 0x000100) { // sample-duration present
2028 totalDuration += reader.readUInt32BE();
2029 } else {
2030 totalDuration += defaultSampleDuration;
2031 }
2032 if (trunFlags & 0x000200) { // sample-size present
2033 m_sampleSizes.push_back(reader.readUInt32BE());
2034 m_size += m_sampleSizes.back();
2035 } else {
2036 m_size += defaultSampleSize;
2037 }
2038 if (trunFlags & 0x000400) { // sample-flags present
2039 m_istream->seekg(4, ios_base::cur);
2040 }
2041 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
2042 m_istream->seekg(4, ios_base::cur);
2043 }
2044 }
2045 }
2046 }
2047 }
2048 if (m_sampleSizes.empty() && defaultSampleSize) {
2049 m_sampleSizes.push_back(defaultSampleSize);
2050 }
2051 }
2052 }
2053 }
2054 }
2055 }
2056
2057 // set duration from "trun-information" if the duration has not been determined yet
2058 if (m_duration.isNull() && totalDuration) {
2059 std::uint32_t timeScale = m_timeScale;
2060 if (!timeScale) {
2061 timeScale = trakAtom().container().timeScale();
2062 }
2063 if (timeScale) {
2064 m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(timeScale));
2065 }
2066 }
2067
2068 // calculate average bitrate
2069 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2070 m_bitrate = (static_cast<double>(m_size) * 0.0078125) / m_duration.totalSeconds();
2071 }
2072
2073 // read stsc atom (only number of entries)
2074 m_istream->seekg(static_cast<streamoff>(m_stscAtom->dataOffset() + 4));
2075 m_sampleToChunkEntryCount = reader.readUInt32BE();
2076}
2077
2078} // 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:815
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:558
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:459
static void addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
Adds the information from the specified avcConfig to the specified track.
Definition: mp4track.cpp:1017
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:508
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:891
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
This method is internally called to parse header information.
Definition: mp4track.cpp:1479
void makeSampleTable(Diagnostics &diag)
Makes the sample table (stbl atom) for the track.
Definition: mp4track.cpp:1419
std::uint64_t requiredSize(Diagnostics &diag) const
Returns the number of bytes written when calling makeTrack().
Definition: mp4track.cpp:1087
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:1267
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:1169
void bufferTrackAtoms(Diagnostics &diag)
Buffers all atoms required by the makeTrack() method.
Definition: mp4track.cpp:1064
void makeMediaInfo(Diagnostics &diag)
Makes a media information (minf atom) for the track.
Definition: mp4track.cpp:1364
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:1140
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:650
void updateChunkOffset(std::uint32_t chunkIndex, std::uint64_t offset)
Updates a particular chunk offset.
Definition: mp4track.cpp:996
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