Tag Parser 12.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
mp4track.cpp
Go to the documentation of this file.
1#define CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
2
3#include "./mp4track.h"
4#include "./mp4atom.h"
5#include "./mp4container.h"
6#include "./mp4ids.h"
7#include "./mpeg4descriptor.h"
8
9#include "../av1/av1configuration.h"
10
11#include "../avc/avcconfiguration.h"
12
13#include "../mpegaudio/mpegaudioframe.h"
14#include "../mpegaudio/mpegaudioframestream.h"
15
16#include "../exceptions.h"
17#include "../mediafileinfo.h"
18#include "../mediaformat.h"
19
20#include <c++utilities/conversion/stringbuilder.h>
21#include <c++utilities/io/binaryreader.h>
22#include <c++utilities/io/binarywriter.h>
23#include <c++utilities/io/bitreader.h>
24
25#include <cmath>
26#include <locale>
27
28using namespace std;
29using namespace CppUtilities;
30
31namespace TagParser {
32
36struct Mp4Timings {
39 std::uint64_t tkhdDuration, mdhdDuration = 0;
40 constexpr std::uint8_t requiredTkhdVersion() const;
41 constexpr std::uint8_t requiredMdhdVersion() const;
42};
43
51 friend class Mp4Track;
52
53private:
55 std::uint64_t requiredSize = 100;
57 bool canUseExisting = false;
59 bool truncated = false;
61 std::uint8_t version = 0;
63 std::uint8_t writeVersion = 0;
65 bool versionUnknown = false;
67 Mp4Timings timings;
69 std::uint8_t timingsVersion = 0;
71 std::uint8_t additionalDataOffset = 0;
73 bool discardBuffer = false;
74};
75
76constexpr std::uint8_t Mp4Timings::requiredTkhdVersion() const
77{
78 return (tkhdCreationTime > std::numeric_limits<std::uint32_t>::max() || tkhdModificationTime > std::numeric_limits<std::uint32_t>::max()
79 || tkhdDuration > std::numeric_limits<std::uint32_t>::max())
80 ? 1
81 : 0;
82}
83
84constexpr std::uint8_t Mp4Timings::requiredMdhdVersion() const
85{
86 return (mdhdCreationTime > std::numeric_limits<std::uint32_t>::max() || mdhdModificationTime > std::numeric_limits<std::uint32_t>::max()
87 || mdhdDuration > std::numeric_limits<std::uint32_t>::max())
88 ? 1
89 : 0;
90}
91
99 : audioObjectType(0)
100 , sampleFrequencyIndex(0xF)
101 , sampleFrequency(0)
102 , channelConfiguration(0)
103 , extensionAudioObjectType(0)
104 , sbrPresent(false)
105 , psPresent(false)
106 , extensionSampleFrequencyIndex(0xF)
107 , extensionSampleFrequency(0)
108 , extensionChannelConfiguration(0)
109 , frameLengthFlag(false)
110 , dependsOnCoreCoder(false)
111 , coreCoderDelay(0)
112 , extensionFlag(0)
113 , layerNr(0)
114 , numOfSubFrame(0)
115 , layerLength(0)
116 , resilienceFlags(0)
117 , epConfig(0)
118{
119}
120
133
151 : AbstractTrack(trakAtom.stream(), trakAtom.startOffset())
152 , m_trakAtom(&trakAtom)
153 , m_tkhdAtom(nullptr)
154 , m_mdiaAtom(nullptr)
155 , m_mdhdAtom(nullptr)
156 , m_hdlrAtom(nullptr)
157 , m_minfAtom(nullptr)
158 , m_stblAtom(nullptr)
159 , m_stsdAtom(nullptr)
160 , m_stscAtom(nullptr)
161 , m_stcoAtom(nullptr)
162 , m_stszAtom(nullptr)
163 , m_rawMediaType(0)
164 , m_framesPerSample(1)
165 , m_chunkOffsetSize(4)
166 , m_chunkCount(0)
167 , m_sampleToChunkEntryCount(0)
168 , m_rawTkhdCreationTime(0)
169 , m_rawMdhdCreationTime(0)
170 , m_rawTkhdModificationTime(0)
171 , m_rawMdhdModificationTime(0)
172 , m_rawTkhdDuration(0)
173 , m_rawMdhdDuration(0)
174{
175}
176
183
185{
186 return TrackType::Mp4Track;
187}
188
199std::vector<std::uint64_t> Mp4Track::readChunkOffsets(bool parseFragments, Diagnostics &diag)
200{
201 static const string context("reading chunk offset table of MP4 track");
202 if (!isHeaderValid() || !m_istream) {
203 diag.emplace_back(DiagLevel::Critical, "Track has not been parsed.", context);
204 throw InvalidDataException();
205 }
206 vector<std::uint64_t> offsets;
207 if (m_stcoAtom) {
208 // verify integrity of the chunk offset table
209 std::uint64_t actualTableSize = m_stcoAtom->dataSize();
210 if (actualTableSize < (8 + chunkOffsetSize())) {
211 diag.emplace_back(DiagLevel::Critical, "The stco atom is truncated. There are no chunk offsets present.", context);
212 throw InvalidDataException();
213 } else {
214 actualTableSize -= 8;
215 }
216 std::uint32_t actualChunkCount = chunkCount();
217 std::uint64_t calculatedTableSize = chunkCount() * chunkOffsetSize();
218 if (calculatedTableSize < actualTableSize) {
219 diag.emplace_back(
220 DiagLevel::Critical, "The stco atom stores more chunk offsets as denoted. The additional chunk offsets will be ignored.", context);
221 } else if (calculatedTableSize > actualTableSize) {
222 diag.emplace_back(DiagLevel::Critical, "The stco atom is truncated. It stores less chunk offsets as denoted.", context);
223 actualChunkCount = static_cast<std::uint32_t>(floor(static_cast<double>(actualTableSize) / static_cast<double>(chunkOffsetSize())));
224 }
225 // read the table
226 offsets.reserve(actualChunkCount);
227 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
228 switch (chunkOffsetSize()) {
229 case 4:
230 for (std::uint32_t i = 0; i < actualChunkCount; ++i) {
231 offsets.push_back(reader().readUInt32BE());
232 }
233 break;
234 case 8:
235 for (std::uint32_t i = 0; i < actualChunkCount; ++i) {
236 offsets.push_back(reader().readUInt64BE());
237 }
238 break;
239 default:
240 diag.emplace_back(DiagLevel::Critical, "The determined chunk offset size is invalid.", context);
241 throw InvalidDataException();
242 }
243 }
244 // read sample offsets of fragments
245 if (parseFragments) {
246 //std::uint64_t totalDuration = 0;
247 for (Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingByIdIncludingThis(Mp4AtomIds::MovieFragment, diag); moofAtom;
248 moofAtom = moofAtom->siblingById(Mp4AtomIds::MovieFragment, diag)) {
249 moofAtom->parse(diag);
250 for (Mp4Atom *trafAtom = moofAtom->childById(Mp4AtomIds::TrackFragment, diag); trafAtom;
251 trafAtom = trafAtom->siblingById(Mp4AtomIds::TrackFragment, diag)) {
252 trafAtom->parse(diag);
253 for (Mp4Atom *tfhdAtom = trafAtom->childById(Mp4AtomIds::TrackFragmentHeader, diag); tfhdAtom;
254 tfhdAtom = tfhdAtom->siblingById(Mp4AtomIds::TrackFragmentHeader, diag)) {
255 tfhdAtom->parse(diag);
256 std::uint32_t calculatedDataSize = 0;
257 if (tfhdAtom->dataSize() < calculatedDataSize) {
258 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated.", context);
259 } else {
260 inputStream().seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
261 const std::uint32_t flags = reader().readUInt24BE();
262 if (m_id == reader().readUInt32BE()) { // check track ID
263 if (flags & 0x000001) { // base-data-offset present
264 calculatedDataSize += 8;
265 }
266 if (flags & 0x000002) { // sample-description-index present
267 calculatedDataSize += 4;
268 }
269 if (flags & 0x000008) { // default-sample-duration present
270 calculatedDataSize += 4;
271 }
272 if (flags & 0x000010) { // default-sample-size present
273 calculatedDataSize += 4;
274 }
275 if (flags & 0x000020) { // default-sample-flags present
276 calculatedDataSize += 4;
277 }
278 // some variables are currently skipped because they are currently not interesting
279 //std::uint64_t baseDataOffset = moofAtom->startOffset();
280 //std::uint32_t defaultSampleDescriptionIndex = 0;
281 //std::uint32_t defaultSampleDuration = 0;
282 std::uint32_t defaultSampleSize = 0;
283 //std::uint32_t defaultSampleFlags = 0;
284 if (tfhdAtom->dataSize() < calculatedDataSize) {
285 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
286 } else {
287 if (flags & 0x000001) { // base-data-offset present
288 //baseDataOffset = reader.readUInt64();
289 inputStream().seekg(8, ios_base::cur);
290 }
291 if (flags & 0x000002) { // sample-description-index present
292 //defaultSampleDescriptionIndex = reader.readUInt32();
293 inputStream().seekg(4, ios_base::cur);
294 }
295 if (flags & 0x000008) { // default-sample-duration present
296 //defaultSampleDuration = reader().readUInt32BE();
297 inputStream().seekg(4, ios_base::cur);
298 }
299 if (flags & 0x000010) { // default-sample-size present
300 defaultSampleSize = reader().readUInt32BE();
301 }
302 if (flags & 0x000020) { // default-sample-flags present
303 //defaultSampleFlags = reader().readUInt32BE();
304 inputStream().seekg(4, ios_base::cur);
305 }
306 }
307 for (Mp4Atom *trunAtom = trafAtom->childById(Mp4AtomIds::TrackFragmentRun, diag); trunAtom;
308 trunAtom = trunAtom->siblingById(Mp4AtomIds::TrackFragmentRun, diag)) {
309 std::uint32_t trunCalculatedDataSize = 8;
310 if (trunAtom->dataSize() < trunCalculatedDataSize) {
311 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated.", context);
312 } else {
313 inputStream().seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
314 std::uint32_t trunFlags = reader().readUInt24BE();
315 std::uint32_t sampleCount = reader().readUInt32BE();
317 if (trunFlags & 0x000001) { // data offset present
318 trunCalculatedDataSize += 4;
319 }
320 if (trunFlags & 0x000004) { // first-sample-flags present
321 trunCalculatedDataSize += 4;
322 }
323 std::uint32_t entrySize = 0;
324 if (trunFlags & 0x000100) { // sample-duration present
325 entrySize += 4;
326 }
327 if (trunFlags & 0x000200) { // sample-size present
328 entrySize += 4;
329 }
330 if (trunFlags & 0x000400) { // sample-flags present
331 entrySize += 4;
332 }
333 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
334 entrySize += 4;
335 }
336 trunCalculatedDataSize += entrySize * sampleCount;
337 if (trunAtom->dataSize() < trunCalculatedDataSize) {
338 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated (presence of fields denoted).", context);
339 } else {
340 if (trunFlags & 0x000001) { // data offset present
341 inputStream().seekg(4, ios_base::cur);
342 //int32 dataOffset = reader().readInt32BE();
343 }
344 if (trunFlags & 0x000004) { // first-sample-flags present
345 inputStream().seekg(4, ios_base::cur);
346 }
347 for (std::uint32_t i = 0; i < sampleCount; ++i) {
348 if (trunFlags & 0x000100) { // sample-duration present
349 //totalDuration += reader().readUInt32BE();
350 inputStream().seekg(4, ios_base::cur);
351 } else {
352 //totalDuration += defaultSampleDuration;
353 }
354 if (trunFlags & 0x000200) { // sample-size present
355 m_sampleSizes.push_back(reader().readUInt32BE());
356 m_size += m_sampleSizes.back();
357 } else {
358 m_size += defaultSampleSize;
359 }
360 if (trunFlags & 0x000400) { // sample-flags present
361 inputStream().seekg(4, ios_base::cur);
362 }
363 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
364 inputStream().seekg(4, ios_base::cur);
365 }
366 }
367 }
368 }
369 }
370 if (m_sampleSizes.empty() && defaultSampleSize) {
371 m_sampleSizes.push_back(defaultSampleSize);
372 }
373 }
374 }
375 }
376 }
377 }
378 }
379 return offsets;
380}
381
386std::uint64_t Mp4Track::accumulateSampleSizes(size_t &sampleIndex, size_t count, Diagnostics &diag)
387{
388 if (sampleIndex + count <= m_sampleSizes.size()) {
389 std::uint64_t sum = 0;
390 for (size_t end = sampleIndex + count; sampleIndex < end; ++sampleIndex) {
391 sum += m_sampleSizes[sampleIndex];
392 }
393 return sum;
394 } else if (m_sampleSizes.size() == 1) {
395 sampleIndex += count;
396 return static_cast<std::uint64_t>(m_sampleSizes.front()) * count;
397 } else {
398 diag.emplace_back(DiagLevel::Critical, "There are not as many sample size entries as samples.", "reading chunk sizes of MP4 track");
399 throw InvalidDataException();
400 }
401}
402
411void Mp4Track::addChunkSizeEntries(
412 std::vector<std::uint64_t> &chunkSizeTable, size_t count, size_t &sampleIndex, std::uint32_t sampleCount, Diagnostics &diag)
413{
414 for (size_t i = 0; i < count; ++i) {
415 chunkSizeTable.push_back(accumulateSampleSizes(sampleIndex, sampleCount, diag));
416 }
417}
418
423const TrackHeaderInfo &Mp4Track::verifyPresentTrackHeader() const
424{
425 if (m_trackHeaderInfo) {
426 return *m_trackHeaderInfo;
427 }
428
429 // return the default TrackHeaderInfo in case there is no track header prsent
430 auto &info = *(m_trackHeaderInfo = std::make_unique<TrackHeaderInfo>());
431 if (!m_tkhdAtom) {
432 return info;
433 }
434
435 // ensure the tkhd atom is buffered but mark the buffer to be discarded again if it has not been present
436 info.discardBuffer = m_tkhdAtom->buffer() == nullptr;
437 if (info.discardBuffer) {
438 m_tkhdAtom->makeBuffer();
439 }
440
441 // check the version of the existing tkhd atom to determine where additional data starts
442 switch (info.version = static_cast<std::uint8_t>(m_tkhdAtom->buffer()[m_tkhdAtom->headerSize()])) {
443 case 0:
444 info.additionalDataOffset = 32;
445 break;
446 case 1:
447 info.additionalDataOffset = 44;
448 break;
449 default:
450 info.additionalDataOffset = 44;
451 info.versionUnknown = true;
452 }
453
454 // check whether the existing tkhd atom is not truncated
455 if (info.additionalDataOffset + 48u <= m_tkhdAtom->dataSize()) {
456 info.canUseExisting = true;
457 } else {
458 info.truncated = true;
459 info.canUseExisting = info.additionalDataOffset < m_tkhdAtom->dataSize();
460 if (!info.canUseExisting && info.discardBuffer) {
461 m_tkhdAtom->discardBuffer();
462 }
463 }
464
465 // determine required size
466 info.requiredSize = m_tkhdAtom->dataSize() + 8;
467 info.timings = computeTimings();
468 info.timingsVersion = info.timings.requiredTkhdVersion();
469 if (info.version == 0) {
470 info.writeVersion = info.timingsVersion;
471 // add 12 byte to size if update from version 0 to version 1 is required (which needs 12 byte more)
472 if (info.writeVersion != 0) {
473 info.requiredSize += 12;
474 }
475 } else {
476 info.writeVersion = info.version;
477 }
478 // -> add 8 byte to the size because it must be denoted using a 64-bit integer
479 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
480 info.requiredSize += 8;
481 }
482 return info;
483}
484
488Mp4Timings Mp4Track::computeTimings() const
489{
490 auto timings = Mp4Timings();
491 if (m_trakAtom && (m_trakAtom->container().fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::PreserveRawTimingValues)) {
492 timings.tkhdCreationTime = m_rawTkhdCreationTime;
493 timings.tkhdModificationTime = m_rawTkhdModificationTime;
494 timings.tkhdDuration = m_rawTkhdDuration;
495 timings.mdhdCreationTime = m_rawMdhdCreationTime;
496 timings.mdhdModificationTime = m_rawMdhdModificationTime;
497 timings.mdhdDuration = m_rawMdhdDuration;
498 } else {
499 timings.tkhdCreationTime = timings.mdhdCreationTime = static_cast<std::uint64_t>((m_creationTime - Mp4Container::epoch).totalSeconds());
500 timings.tkhdModificationTime = timings.mdhdModificationTime
501 = static_cast<std::uint64_t>((m_modificationTime - Mp4Container::epoch).totalSeconds());
502 timings.tkhdDuration = timings.mdhdDuration = static_cast<std::uint64_t>(m_duration.totalTicks() * m_timeScale / TimeSpan::ticksPerSecond);
503 }
504 return timings;
505}
506
514vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> Mp4Track::readSampleToChunkTable(Diagnostics &diag)
515{
516 static const string context("reading sample to chunk table of MP4 track");
517 if (!isHeaderValid() || !m_istream || !m_stscAtom) {
518 diag.emplace_back(DiagLevel::Critical, "Track has not been parsed or is invalid.", context);
519 throw InvalidDataException();
520 }
521 // verify integrity of the sample to chunk table
522 std::uint64_t actualTableSize = m_stscAtom->dataSize();
523 if (actualTableSize < 20) {
524 diag.emplace_back(DiagLevel::Critical, "The stsc atom is truncated. There are no \"sample to chunk\" entries present.", context);
525 throw InvalidDataException();
526 } else {
527 actualTableSize -= 8;
528 }
529 std::uint64_t actualSampleToChunkEntryCount = sampleToChunkEntryCount();
530 std::uint64_t calculatedTableSize = actualSampleToChunkEntryCount * 12;
531 if (calculatedTableSize < actualTableSize) {
532 diag.emplace_back(DiagLevel::Critical, "The stsc atom stores more entries as denoted. The additional entries will be ignored.", context);
533 } else if (calculatedTableSize > actualTableSize) {
534 diag.emplace_back(DiagLevel::Critical, "The stsc atom is truncated. It stores less entries as denoted.", context);
535 actualSampleToChunkEntryCount = actualTableSize / 12;
536 }
537 // prepare reading
538 vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> sampleToChunkTable;
539 sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
540 m_istream->seekg(static_cast<streamoff>(m_stscAtom->dataOffset() + 8));
541 for (std::uint32_t i = 0; i < actualSampleToChunkEntryCount; ++i) {
542 // read entry
543 std::uint32_t firstChunk = reader().readUInt32BE();
544 std::uint32_t samplesPerChunk = reader().readUInt32BE();
545 std::uint32_t sampleDescriptionIndex = reader().readUInt32BE();
546 sampleToChunkTable.emplace_back(firstChunk, samplesPerChunk, sampleDescriptionIndex);
547 }
548 return sampleToChunkTable;
549}
550
563vector<std::uint64_t> Mp4Track::readChunkSizes(Diagnostics &diag)
564{
565 static const string context("reading chunk sizes of MP4 track");
566 if (!isHeaderValid() || !m_istream || !m_stcoAtom) {
567 diag.emplace_back(DiagLevel::Critical, "Track has not been parsed or is invalid.", context);
568 throw InvalidDataException();
569 }
570 // read sample to chunk table
571 const auto sampleToChunkTable = readSampleToChunkTable(diag);
572 // accumulate chunk sizes from the table
573 vector<std::uint64_t> chunkSizes;
574 if (!sampleToChunkTable.empty()) {
575 // prepare reading
576 auto tableIterator = sampleToChunkTable.cbegin();
577 chunkSizes.reserve(m_chunkCount);
578 // read first entry
579 size_t sampleIndex = 0;
580 std::uint32_t previousChunkIndex = get<0>(*tableIterator); // the first chunk has the index 1 and not zero!
581 if (previousChunkIndex != 1) {
582 diag.emplace_back(DiagLevel::Critical, "The first chunk of the first \"sample to chunk\" entry must be 1.", context);
583 previousChunkIndex = 1; // try to read the entry anyway
584 }
585 std::uint32_t samplesPerChunk = get<1>(*tableIterator);
586 // read the following entries
587 ++tableIterator;
588 for (const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
589 std::uint32_t firstChunkIndex = get<0>(*tableIterator);
590 if (firstChunkIndex > previousChunkIndex && firstChunkIndex <= m_chunkCount) {
591 addChunkSizeEntries(chunkSizes, firstChunkIndex - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
592 } else {
593 diag.emplace_back(DiagLevel::Critical,
594 "The first chunk index of a \"sample to chunk\" entry must be greater than the first chunk of the previous entry and not "
595 "greater than the chunk count.",
596 context);
597 throw InvalidDataException();
598 }
599 previousChunkIndex = firstChunkIndex;
600 samplesPerChunk = get<1>(*tableIterator);
601 }
602 if (m_chunkCount >= previousChunkIndex) {
603 addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
604 }
605 }
606 return chunkSizes;
607}
608
613std::unique_ptr<Mpeg4ElementaryStreamInfo> Mp4Track::parseMpeg4ElementaryStreamInfo(
614 CppUtilities::BinaryReader &reader, Mp4Atom *esDescAtom, Diagnostics &diag)
615{
616 static const string context("parsing MPEG-4 elementary stream descriptor");
617 using namespace Mpeg4ElementaryStreamObjectIds;
618 unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
619 if (esDescAtom->dataSize() >= 12) {
620 reader.stream()->seekg(static_cast<streamoff>(esDescAtom->dataOffset()));
621 // read version/flags
622 if (reader.readUInt32BE() != 0) {
623 diag.emplace_back(DiagLevel::Warning, "Unknown version/flags.", context);
624 }
625 // read extended descriptor
626 Mpeg4Descriptor esDesc(esDescAtom->container(), static_cast<std::uint64_t>(reader.stream()->tellg()), esDescAtom->dataSize() - 4);
627 try {
628 esDesc.parse(diag);
629 // check ID
631 diag.emplace_back(DiagLevel::Critical, "Invalid descriptor found.", context);
632 throw Failure();
633 }
634 // read stream info
635 reader.stream()->seekg(static_cast<streamoff>(esDesc.dataOffset()));
636 esInfo = make_unique<Mpeg4ElementaryStreamInfo>();
637 esInfo->id = reader.readUInt16BE();
638 esInfo->esDescFlags = reader.readByte();
639 if (esInfo->dependencyFlag()) {
640 esInfo->dependsOnId = reader.readUInt16BE();
641 }
642 if (esInfo->urlFlag()) {
643 esInfo->url = reader.readString(reader.readByte());
644 }
645 if (esInfo->ocrFlag()) {
646 esInfo->ocrId = reader.readUInt16BE();
647 }
648 for (Mpeg4Descriptor *esDescChild
649 = esDesc.denoteFirstChild(static_cast<std::uint32_t>(static_cast<std::uint64_t>(reader.stream()->tellg()) - esDesc.startOffset()));
650 esDescChild; esDescChild = esDescChild->nextSibling()) {
651 esDescChild->parse(diag);
652 switch (esDescChild->id()) {
654 // read decoder config descriptor
655 reader.stream()->seekg(static_cast<streamoff>(esDescChild->dataOffset()));
656 esInfo->objectTypeId = reader.readByte();
657 esInfo->decCfgDescFlags = reader.readByte();
658 esInfo->bufferSize = reader.readUInt24BE();
659 esInfo->maxBitrate = reader.readUInt32BE();
660 esInfo->averageBitrate = reader.readUInt32BE();
661 for (Mpeg4Descriptor *decCfgDescChild = esDescChild->denoteFirstChild(esDescChild->headerSize() + 13); decCfgDescChild;
662 decCfgDescChild = decCfgDescChild->nextSibling()) {
663 decCfgDescChild->parse(diag);
664 switch (decCfgDescChild->id()) {
666 // read decoder specific info
667 switch (esInfo->objectTypeId) {
668 case Aac:
669 case Mpeg2AacMainProfile:
670 case Mpeg2AacLowComplexityProfile:
671 case Mpeg2AacScaleableSamplingRateProfile:
672 case Mpeg2Audio:
673 case Mpeg1Audio:
674 esInfo->audioSpecificConfig
675 = parseAudioSpecificConfig(*reader.stream(), decCfgDescChild->dataOffset(), decCfgDescChild->dataSize(), diag);
676 break;
677 case Mpeg4Visual:
678 esInfo->videoSpecificConfig
679 = parseVideoSpecificConfig(reader, decCfgDescChild->dataOffset(), decCfgDescChild->dataSize(), diag);
680 break;
681 default:; // TODO: cover more object types
682 }
683 break;
684 }
685 }
686 break;
688 // uninteresting
689 break;
690 }
691 }
692 } catch (const Failure &) {
693 diag.emplace_back(DiagLevel::Critical, "The MPEG-4 descriptor element structure is invalid.", context);
694 }
695 } else {
696 diag.emplace_back(DiagLevel::Warning, "Elementary stream descriptor atom (esds) is truncated.", context);
697 }
698 return esInfo;
699}
700
705unique_ptr<Mpeg4AudioSpecificConfig> Mp4Track::parseAudioSpecificConfig(
706 istream &stream, std::uint64_t startOffset, std::uint64_t size, Diagnostics &diag)
707{
708 static const string context("parsing MPEG-4 audio specific config from elementary stream descriptor");
709 using namespace Mpeg4AudioObjectIds;
710 // read config into buffer and construct BitReader for bitwise reading
711 stream.seekg(static_cast<streamoff>(startOffset));
712 auto buff = make_unique<char[]>(size);
713 stream.read(buff.get(), static_cast<streamoff>(size));
714 BitReader bitReader(buff.get(), size);
715 auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
716 try {
717 // read audio object type
718 auto getAudioObjectType = [&bitReader] {
719 std::uint8_t objType = bitReader.readBits<std::uint8_t>(5);
720 if (objType == 31) {
721 objType = 32 + bitReader.readBits<std::uint8_t>(6);
722 }
723 return objType;
724 };
725 audioCfg->audioObjectType = getAudioObjectType();
726 // read sampling frequency
727 if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
728 audioCfg->sampleFrequency = bitReader.readBits<std::uint32_t>(24);
729 }
730 // read channel config
731 audioCfg->channelConfiguration = bitReader.readBits<std::uint8_t>(4);
732 // read extension header
733 switch (audioCfg->audioObjectType) {
734 case Sbr:
735 case Ps:
736 audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
737 audioCfg->sbrPresent = true;
738 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
739 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
740 }
741 if ((audioCfg->audioObjectType = getAudioObjectType()) == ErBsac) {
742 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
743 }
744 break;
745 }
746 switch (audioCfg->extensionAudioObjectType) {
747 case Ps:
748 audioCfg->psPresent = true;
749 audioCfg->extensionChannelConfiguration = Mpeg4ChannelConfigs::FrontLeftFrontRight;
750 break;
751 }
752 // read GA specific config
753 switch (audioCfg->audioObjectType) {
754 case AacMain:
755 case AacLc:
756 case AacLtp:
757 case AacScalable:
758 case TwinVq:
759 case ErAacLc:
760 case ErAacLtp:
761 case ErAacScalable:
762 case ErTwinVq:
763 case ErBsac:
764 case ErAacLd:
765 audioCfg->frameLengthFlag = bitReader.readBits<std::uint8_t>(1);
766 if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
767 audioCfg->coreCoderDelay = bitReader.readBits<std::uint8_t>(14);
768 }
769 audioCfg->extensionFlag = bitReader.readBit();
770 if (audioCfg->channelConfiguration == 0) {
771 throw NotImplementedException(); // TODO: parse program_config_element
772 }
773 switch (audioCfg->audioObjectType) {
774 case AacScalable:
775 case ErAacScalable:
776 audioCfg->layerNr = bitReader.readBits<std::uint8_t>(3);
777 break;
778 default:;
779 }
780 if (audioCfg->extensionFlag == 1) {
781 switch (audioCfg->audioObjectType) {
782 case ErBsac:
783 audioCfg->numOfSubFrame = bitReader.readBits<std::uint8_t>(5);
784 audioCfg->layerLength = bitReader.readBits<std::uint16_t>(11);
785 break;
786 case ErAacLc:
787 case ErAacLtp:
788 case ErAacScalable:
789 case ErAacLd:
790 audioCfg->resilienceFlags = bitReader.readBits<std::uint8_t>(3);
791 break;
792 default:;
793 }
794 if (bitReader.readBit() == 1) { // extension flag 3
795 throw NotImplementedException(); // TODO
796 }
797 }
798 break;
799 default:
800 throw NotImplementedException(); // TODO: cover remaining object types
801 }
802 // read error specific config
803 switch (audioCfg->audioObjectType) {
804 case ErAacLc:
805 case ErAacLtp:
806 case ErAacScalable:
807 case ErTwinVq:
808 case ErBsac:
809 case ErAacLd:
810 case ErCelp:
811 case ErHvxc:
812 case ErHiln:
813 case ErParametric:
814 case ErAacEld:
815 switch (audioCfg->epConfig = bitReader.readBits<std::uint8_t>(2)) {
816 case 2:
817 break;
818 case 3:
819 bitReader.skipBits(1);
820 break;
821 default:
822 throw NotImplementedException(); // TODO
823 }
824 break;
825 }
826 if (audioCfg->extensionAudioObjectType != Sbr && audioCfg->extensionAudioObjectType != Ps && bitReader.bitsAvailable() >= 16) {
827 std::uint16_t syncExtensionType = bitReader.readBits<std::uint16_t>(11);
828 if (syncExtensionType == 0x2B7) {
829 if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) == Sbr) {
830 if ((audioCfg->sbrPresent = bitReader.readBit())) {
831 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
832 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
833 }
834 if (bitReader.bitsAvailable() >= 12) {
835 if ((syncExtensionType = bitReader.readBits<std::uint16_t>(11)) == 0x548) {
836 audioCfg->psPresent = bitReader.readBits<std::uint8_t>(1);
837 }
838 }
839 }
840 } else if (audioCfg->extensionAudioObjectType == ErBsac) {
841 if ((audioCfg->sbrPresent = bitReader.readBit())) {
842 if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
843 audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
844 }
845 }
846 audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
847 }
848 } else if (syncExtensionType == 0x548) {
849 audioCfg->psPresent = bitReader.readBit();
850 }
851 }
852 } catch (const NotImplementedException &) {
853 diag.emplace_back(DiagLevel::Information, "Not implemented for the format of audio track.", context);
854 } catch (const std::ios_base::failure &) {
855 if (stream.fail()) {
856 // IO error caused by input stream
857 throw;
858 } else {
859 // IO error caused by bitReader
860 diag.emplace_back(DiagLevel::Critical, "Audio specific configuration is truncated.", context);
861 }
862 }
863 return audioCfg;
864}
865
870std::unique_ptr<Mpeg4VideoSpecificConfig> Mp4Track::parseVideoSpecificConfig(
871 BinaryReader &reader, std::uint64_t startOffset, std::uint64_t size, Diagnostics &diag)
872{
873 static const string context("parsing MPEG-4 video specific config from elementary stream descriptor");
874 using namespace Mpeg4AudioObjectIds;
875 auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
876 // seek to start
877 reader.stream()->seekg(static_cast<streamoff>(startOffset));
878 if (size > 3 && (reader.readUInt24BE() == 1)) {
879 size -= 3;
880 std::uint32_t buff1;
881 while (size) {
882 --size;
883 switch (reader.readByte()) { // read start code
885 if (size) {
886 videoCfg->profile = reader.readByte();
887 --size;
888 }
889 break;
891
892 break;
894 buff1 = 0;
895 while (size >= 3) {
896 if ((buff1 = reader.readUInt24BE()) != 1) {
897 reader.stream()->seekg(-2, ios_base::cur);
898 videoCfg->userData.push_back(static_cast<char>(buff1 >> 16));
899 --size;
900 } else {
901 size -= 3;
902 break;
903 }
904 }
905 if (buff1 != 1 && size > 0) {
906 videoCfg->userData += reader.readString(size);
907 size = 0;
908 }
909 break;
910 default:;
911 }
912 // skip remaining values to get the start of the next video object
913 while (size >= 3) {
914 if (reader.readUInt24BE() != 1) {
915 reader.stream()->seekg(-2, ios_base::cur);
916 --size;
917 } else {
918 size -= 3;
919 break;
920 }
921 }
922 }
923 } else {
924 diag.emplace_back(DiagLevel::Critical, "\"Visual Object Sequence Header\" not found.", context);
925 }
926 return videoCfg;
927}
928
946void Mp4Track::updateChunkOffsets(const vector<std::int64_t> &oldMdatOffsets, const vector<std::int64_t> &newMdatOffsets)
947{
948 if (!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
949 throw InvalidDataException();
950 }
951 if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
952 throw InvalidDataException();
953 }
954 static const unsigned int stcoDataBegin = 8;
955 std::uint64_t startPos = m_stcoAtom->dataOffset() + stcoDataBegin;
956 std::uint64_t endPos = startPos + m_stcoAtom->dataSize() - stcoDataBegin;
957 m_istream->seekg(static_cast<streamoff>(startPos));
958 m_ostream->seekp(static_cast<streamoff>(startPos));
959 vector<std::int64_t>::size_type i;
960 vector<std::int64_t>::size_type size;
961 auto currentPos = static_cast<std::uint64_t>(m_istream->tellg());
962 switch (m_stcoAtom->id()) {
964 std::uint32_t off;
965 while ((currentPos + 4) <= endPos) {
966 off = m_reader.readUInt32BE();
967 for (i = 0, size = oldMdatOffsets.size(); i < size; ++i) {
968 if (off > static_cast<std::uint64_t>(oldMdatOffsets[i])) {
969 off += static_cast<std::uint32_t>(newMdatOffsets[i] - oldMdatOffsets[i]);
970 break;
971 }
972 }
973 m_ostream->seekp(static_cast<streamoff>(currentPos));
974 m_writer.writeUInt32BE(off);
975 currentPos += static_cast<std::uint64_t>(m_istream->gcount());
976 }
977 break;
978 }
980 std::uint64_t off;
981 while ((currentPos + 8) <= endPos) {
982 off = m_reader.readUInt64BE();
983 for (i = 0, size = oldMdatOffsets.size(); i < size; ++i) {
984 if (off > static_cast<std::uint64_t>(oldMdatOffsets[i])) {
985 off += static_cast<std::uint64_t>(newMdatOffsets[i] - oldMdatOffsets[i]);
986 break;
987 }
988 }
989 m_ostream->seekp(static_cast<streamoff>(currentPos));
990 m_writer.writeUInt64BE(off);
991 currentPos += static_cast<std::uint64_t>(m_istream->gcount());
992 }
993 break;
994 }
995 default:
996 throw InvalidDataException();
997 }
998}
999
1013void Mp4Track::updateChunkOffsets(const std::vector<std::uint64_t> &chunkOffsets)
1014{
1015 if (!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
1016 throw InvalidDataException();
1017 }
1018 if (chunkOffsets.size() != chunkCount()) {
1019 throw InvalidDataException();
1020 }
1021 m_ostream->seekp(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
1022 switch (m_stcoAtom->id()) {
1024 for (auto offset : chunkOffsets) {
1025 m_writer.writeUInt32BE(static_cast<std::uint32_t>(offset));
1026 }
1027 break;
1029 for (auto offset : chunkOffsets) {
1030 m_writer.writeUInt64BE(offset);
1031 }
1032 break;
1033 default:
1034 throw InvalidDataException();
1035 }
1036}
1037
1051void Mp4Track::updateChunkOffset(std::uint32_t chunkIndex, std::uint64_t offset)
1052{
1053 if (!isHeaderValid() || !m_istream || !m_stcoAtom || chunkIndex >= m_chunkCount) {
1054 throw InvalidDataException();
1055 }
1056 m_ostream->seekp(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8 + chunkOffsetSize() * chunkIndex));
1057 switch (chunkOffsetSize()) {
1058 case 4:
1059 writer().writeUInt32BE(static_cast<std::uint32_t>(offset));
1060 break;
1061 case 8:
1062 writer().writeUInt64BE(offset);
1063 break;
1064 default:
1065 throw InvalidDataException();
1066 }
1067}
1068
1073{
1074 if (!avcConfig.spsInfos.empty()) {
1075 const SpsInfo &spsInfo = avcConfig.spsInfos.back();
1076 track.m_format.sub = spsInfo.profileIndication;
1077 track.m_version = static_cast<double>(spsInfo.levelIndication) / 10;
1078 track.m_cropping = spsInfo.cropping;
1079 track.m_pixelSize = spsInfo.pictureSize;
1080 switch (spsInfo.chromaFormatIndication) {
1081 case 0:
1082 track.m_chromaFormat = "monochrome";
1083 break;
1084 case 1:
1085 track.m_chromaFormat = "YUV 4:2:0";
1086 break;
1087 case 2:
1088 track.m_chromaFormat = "YUV 4:2:2";
1089 break;
1090 case 3:
1091 track.m_chromaFormat = "YUV 4:4:4";
1092 break;
1093 default:;
1094 }
1095 track.m_pixelAspectRatio = spsInfo.pixelAspectRatio;
1096 } else {
1097 track.m_format.sub = avcConfig.profileIndication;
1098 track.m_version = static_cast<double>(avcConfig.levelIndication) / 10;
1099 }
1100}
1101
1107{
1108 CPP_UTILITIES_UNUSED(av1Config)
1109 CPP_UTILITIES_UNUSED(track)
1111}
1112
1120{
1121 CPP_UTILITIES_UNUSED(diag)
1122
1123 if (m_tkhdAtom) {
1124 m_tkhdAtom->makeBuffer();
1125 }
1126 for (Mp4Atom *trakChild = m_trakAtom->firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1127 if (trakChild->id() == Mp4AtomIds::Media) {
1128 continue;
1129 }
1130 trakChild->makeBuffer();
1131 }
1132 if (m_minfAtom) {
1133 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1134 childAtom->makeBuffer();
1135 }
1136 }
1137}
1138
1142std::uint64_t Mp4Track::requiredSize(Diagnostics &diag) const
1143{
1144 CPP_UTILITIES_UNUSED(diag)
1145
1146 const auto &info = verifyPresentTrackHeader();
1147 // add size of
1148 // ... trak header
1149 std::uint64_t size = 8;
1150 // ... tkhd atom
1151 size += info.requiredSize;
1152 // ... children beside tkhd and mdia
1153 for (Mp4Atom *trakChild = m_trakAtom->firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1154 if (trakChild->id() == Mp4AtomIds::Media || trakChild->id() == Mp4AtomIds::TrackHeader) {
1155 continue;
1156 }
1157 size += trakChild->totalSize();
1158 }
1159 // ... mdhd total size
1160 if (info.timingsVersion == 0) {
1161 // write version 0 where timing fields are 32-bit
1162 size += 32;
1163 } else {
1164 // write version 1 where timing fields are 64-bit
1165 size += 44;
1166 }
1167 // ... mdia header + hdlr total size + minf header
1168 size += 8 + (33 + m_name.size()) + 8;
1169 // ... minf children
1170 bool dinfAtomWritten = false;
1171 if (m_minfAtom) {
1172 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1173 if (childAtom->id() == Mp4AtomIds::DataInformation) {
1174 dinfAtomWritten = true;
1175 }
1176 size += childAtom->totalSize();
1177 }
1178 }
1179 if (!dinfAtomWritten) {
1180 // take 36 bytes for a self-made dinf atom into account if the file lacks one
1181 size += 36;
1182 }
1183 return size;
1184}
1185
1195{
1196 // write header
1197 ostream::pos_type trakStartOffset = outputStream().tellp();
1198 m_writer.writeUInt32BE(0); // write size later
1199 m_writer.writeUInt32BE(Mp4AtomIds::Track);
1200
1201 // write tkhd atom
1202 makeTrackHeader(diag);
1203
1204 // write children of trak atom except mdia
1205 for (Mp4Atom *trakChild = trakAtom().firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
1206 if (trakChild->id() == Mp4AtomIds::Media || trakChild->id() == Mp4AtomIds::TrackHeader) {
1207 continue;
1208 }
1209 trakChild->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1210 }
1211
1212 // write mdia atom
1213 makeMedia(diag);
1214
1215 // write size (of trak atom)
1216 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), trakStartOffset, diag);
1217}
1218
1224{
1225 // verify the existing track header to make the new one based on it (if possible)
1226 const auto &info = verifyPresentTrackHeader();
1227
1228 // add notifications in case the present track header could not be parsed
1229 if (info.versionUnknown) {
1230 diag.emplace_back(DiagLevel::Critical,
1231 argsToString("The version of the present \"tkhd\"-atom (", info.version, ") is unknown. Assuming version 1."),
1232 argsToString("making \"tkhd\"-atom of track ", m_id));
1233 }
1234 if (info.truncated) {
1235 diag.emplace_back(
1236 DiagLevel::Critical, argsToString("The present \"tkhd\"-atom is truncated."), argsToString("making \"tkhd\"-atom of track ", m_id));
1237 }
1238
1239 // make size and element ID
1240 if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
1241 writer().writeUInt32BE(1);
1242 writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
1243 writer().writeUInt64BE(info.requiredSize);
1244 } else {
1245 writer().writeUInt32BE(static_cast<std::uint32_t>(info.requiredSize));
1246 writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
1247 }
1248
1249 // make version and flags
1250 writer().writeByte(info.writeVersion);
1251 std::uint32_t flags = 0;
1252 if (isEnabled()) {
1253 flags |= 0x000001;
1254 }
1256 flags |= 0x000002;
1257 }
1259 flags |= 0x000004;
1260 }
1261 writer().writeUInt24BE(flags);
1262
1263 // make creation and modification time
1264 if (info.writeVersion != 0) {
1265 writer().writeUInt64BE(info.timings.tkhdCreationTime);
1266 writer().writeUInt64BE(info.timings.tkhdModificationTime);
1267 } else {
1268 writer().writeUInt32BE(static_cast<std::uint32_t>(info.timings.tkhdCreationTime));
1269 writer().writeUInt32BE(static_cast<std::uint32_t>(info.timings.tkhdModificationTime));
1270 }
1271
1272 // make track ID and duration
1273 writer().writeUInt32BE(static_cast<std::uint32_t>(m_id));
1274 writer().writeUInt32BE(0); // reserved
1275 if (info.writeVersion != 0) {
1276 writer().writeUInt64BE(info.timings.tkhdDuration);
1277 } else {
1278 writer().writeUInt32BE(static_cast<std::uint32_t>(info.timings.tkhdDuration));
1279 }
1280 writer().writeUInt32BE(0); // reserved
1281 writer().writeUInt32BE(0); // reserved
1282
1283 // make further values, either from existing tkhd atom or just some defaults
1284 if (info.canUseExisting) {
1285 // write all bytes after the previously determined additionalDataOffset
1286 m_ostream->write(m_tkhdAtom->buffer().get() + m_tkhdAtom->headerSize() + info.additionalDataOffset,
1287 static_cast<streamoff>(m_tkhdAtom->dataSize() - info.additionalDataOffset));
1288 // discard the buffer again if it wasn't present before
1289 if (info.discardBuffer) {
1290 m_tkhdAtom->discardBuffer();
1291 }
1292 } else {
1293 // write default values
1294 diag.emplace_back(DiagLevel::Warning, "Writing some default values because the existing tkhd atom is truncated.", "making tkhd atom");
1295 writer().writeInt16BE(0); // layer
1296 writer().writeInt16BE(0); // alternate group
1297 writer().writeFixed8BE(1.0); // volume (fixed 8.8 - 2 byte)
1298 writer().writeUInt16BE(0); // reserved
1299 for (const std::int32_t value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) { // unity matrix
1300 writer().writeInt32BE(value);
1301 }
1302 writer().writeFixed16BE(1.0); // width
1303 writer().writeFixed16BE(1.0); // height
1304 }
1305}
1306
1312{
1313 ostream::pos_type mdiaStartOffset = outputStream().tellp();
1314 writer().writeUInt32BE(0); // write size later
1315 writer().writeUInt32BE(Mp4AtomIds::Media);
1316 // write mdhd atom
1317 const auto &info = verifyPresentTrackHeader();
1318 const auto &timings = info.timings;
1319 const auto timingsVersion = timings.requiredMdhdVersion();
1320 writer().writeUInt32BE(timingsVersion != 0 ? 44 : 32); // size
1321 writer().writeUInt32BE(Mp4AtomIds::MediaHeader);
1322 writer().writeByte(timingsVersion); // version
1323 writer().writeUInt24BE(0); // flags
1324 if (timingsVersion != 0) {
1325 writer().writeUInt64BE(timings.mdhdCreationTime);
1326 writer().writeUInt64BE(timings.mdhdModificationTime);
1327 } else {
1328 writer().writeUInt32BE(static_cast<std::uint32_t>(timings.mdhdCreationTime));
1329 writer().writeUInt32BE(static_cast<std::uint32_t>(timings.mdhdModificationTime));
1330 }
1331 writer().writeUInt32BE(m_timeScale);
1332 if (timingsVersion != 0) {
1333 writer().writeUInt64BE(timings.mdhdDuration);
1334 } else {
1335 writer().writeUInt32BE(static_cast<std::uint32_t>(timings.mdhdDuration));
1336 }
1337 // convert and write language
1338 // note: Not using m_locale.abbreviatedName() here to preserve "und" (explicitly undefined).
1339 const auto *language = static_cast<const std::string *>(&LocaleDetail::getEmpty());
1340 for (const auto &detail : m_locale) {
1341 if (!detail.empty() && (detail.format == LocaleFormat::ISO_639_2_T || detail.format == LocaleFormat::Unknown)) {
1342 language = &detail;
1343 break;
1344 }
1345 }
1346 auto codedLanguage = static_cast<std::uint16_t>(0u);
1347 for (auto charIndex = static_cast<std::size_t>(0); charIndex != 3; ++charIndex) {
1348 const char langChar = charIndex < language->size() ? (*language)[charIndex] : 0;
1349 if (langChar >= 'a' && langChar <= 'z') {
1350 codedLanguage |= static_cast<std::uint16_t>((langChar - 0x60) << (0xA - charIndex * 0x5));
1351 continue;
1352 }
1353
1354 // handle invalid characters
1355 if (language->empty()) {
1356 // preserve null value (empty language field) which is not the same as "und" (explicitly undefined)
1357 codedLanguage = 0;
1358 break;
1359 }
1360 diag.emplace_back(DiagLevel::Warning, "Assigned language \"" % *language + "\" is of an invalid format. Setting language to undefined.",
1361 "making mdhd atom");
1362 codedLanguage = 0x55C4; // und(efined)
1363 break;
1364 }
1365 if (language->size() > 3) {
1366 diag.emplace_back(
1367 DiagLevel::Warning, "Assigned language \"" % *language + "\" is longer than 3 byte and hence will be truncated.", "making mdhd atom");
1368 }
1369 writer().writeUInt16BE(codedLanguage);
1370 writer().writeUInt16BE(0); // pre defined
1371 // write hdlr atom
1372 writer().writeUInt32BE(33 + static_cast<std::uint32_t>(m_name.size())); // size
1373 writer().writeUInt32BE(Mp4AtomIds::HandlerReference);
1374 writer().writeUInt64BE(0); // version, flags, pre defined
1375 switch (m_mediaType) {
1376 case MediaType::Video:
1377 outputStream().write("vide", 4);
1378 break;
1379 case MediaType::Audio:
1380 outputStream().write("soun", 4);
1381 break;
1382 case MediaType::Hint:
1383 outputStream().write("hint", 4);
1384 break;
1385 case MediaType::Text:
1386 outputStream().write("text", 4);
1387 break;
1388 case MediaType::Meta:
1389 outputStream().write("meta", 4);
1390 break;
1391 default:
1393 diag.emplace_back(DiagLevel::Critical, "Media type is invalid; keeping media type as-is.", "making hdlr atom");
1394 }
1395 writer().writeUInt32BE(m_rawMediaType);
1396 break;
1397 }
1398 for (int i = 0; i < 3; ++i)
1399 writer().writeUInt32BE(0); // reserved
1400 writer().writeTerminatedString(m_name);
1401 // write minf atom
1402 makeMediaInfo(diag);
1403 // write size (of mdia atom)
1404 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), mdiaStartOffset, diag);
1405}
1406
1412{
1413 ostream::pos_type minfStartOffset = outputStream().tellp();
1414 writer().writeUInt32BE(0); // write size later
1415 writer().writeUInt32BE(Mp4AtomIds::MediaInformation);
1416 bool dinfAtomWritten = false;
1417 if (m_minfAtom) {
1418 // copy existing atoms except sample table which is handled separately
1419 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1420 if (childAtom->id() == Mp4AtomIds::SampleTable) {
1421 continue;
1422 }
1423 if (childAtom->id() == Mp4AtomIds::DataInformation) {
1424 dinfAtomWritten = true;
1425 }
1426 childAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1427 }
1428 }
1429 // write dinf atom if not written yet
1430 if (!dinfAtomWritten) {
1431 writer().writeUInt32BE(36); // size
1432 writer().writeUInt32BE(Mp4AtomIds::DataInformation);
1433 // write dref atom
1434 writer().writeUInt32BE(28); // size
1435 writer().writeUInt32BE(Mp4AtomIds::DataReference);
1436 writer().writeUInt32BE(0); // version and flags
1437 writer().writeUInt32BE(1); // entry count
1438 // write url atom
1439 writer().writeUInt32BE(12); // size
1440 writer().writeUInt32BE(Mp4AtomIds::DataEntryUrl);
1441 writer().writeByte(0); // version
1442 writer().writeUInt24BE(0x000001); // flags (media data is in the same file as the movie box)
1443 }
1444 // write stbl atom
1445 // -> just copy existing stbl atom because makeSampleTable() is not fully implemented (yet)
1446 bool stblAtomWritten = false;
1447 if (m_minfAtom) {
1448 if (Mp4Atom *const stblAtom = m_minfAtom->childById(Mp4AtomIds::SampleTable, diag)) {
1449 stblAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1450 stblAtomWritten = true;
1451 }
1452 }
1453 if (!stblAtomWritten) {
1454 diag.emplace_back(DiagLevel::Critical,
1455 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.", "making stbl atom");
1456 }
1457 // write size (of minf atom)
1458 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset, diag);
1459}
1460
1467{
1468 // ostream::pos_type stblStartOffset = outputStream().tellp(); (enable when function is fully implemented)
1469
1470 writer().writeUInt32BE(0); // write size later
1471 writer().writeUInt32BE(Mp4AtomIds::SampleTable);
1472 Mp4Atom *const stblAtom = m_minfAtom ? m_minfAtom->childById(Mp4AtomIds::SampleTable, diag) : nullptr;
1473 // write stsd atom
1474 if (m_stsdAtom) {
1475 // copy existing stsd atom
1476 m_stsdAtom->copyEntirely(outputStream(), diag, nullptr);
1477 } else {
1478 diag.emplace_back(DiagLevel::Critical, "Unable to make stsd atom from scratch.", "making stsd atom");
1480 }
1481 // write stts and ctts atoms
1482 Mp4Atom *const sttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::DecodingTimeToSample, diag) : nullptr;
1483 if (sttsAtom) {
1484 // copy existing stts atom
1485 sttsAtom->copyEntirely(outputStream(), diag, nullptr);
1486 } else {
1487 diag.emplace_back(DiagLevel::Critical, "Unable to make stts atom from scratch.", "making stts atom");
1489 }
1490 Mp4Atom *const cttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::CompositionTimeToSample, diag) : nullptr;
1491 if (cttsAtom) {
1492 // copy existing ctts atom
1493 cttsAtom->copyEntirely(outputStream(), diag, nullptr);
1494 }
1495 // write stsc atom (sample-to-chunk table)
1497
1498 // write stsz atom (sample sizes)
1499
1500 // write stz2 atom (compact sample sizes)
1501
1502 // write stco/co64 atom (chunk offset table)
1503
1504 // write stss atom (sync sample table)
1505
1506 // write stsh atom (shadow sync sample table)
1507
1508 // write padb atom (sample padding bits)
1509
1510 // write stdp atom (sample degradation priority)
1511
1512 // write sdtp atom (independent and disposable samples)
1513
1514 // write sbgp atom (sample group description)
1515
1516 // write sbgp atom (sample-to-group)
1517
1518 // write sgpd atom (sample group description)
1519
1520 // write subs atom (sub-sample information)
1521
1522 // write size of stbl atom (enable when function is fully implemented)
1523 // Mp4Atom::seekBackAndWriteAtomSize(outputStream(), stblStartOffset, diag);
1524}
1525
1527{
1528 CPP_UTILITIES_UNUSED(progress)
1529
1530 static const string context("parsing MP4 track");
1531 using namespace Mp4AtomIds;
1532 if (!m_trakAtom) {
1533 diag.emplace_back(DiagLevel::Critical, "\"trak\"-atom is null.", context);
1534 throw InvalidDataException();
1535 }
1536
1537 // get atoms
1538 try {
1539 if (!(m_tkhdAtom = m_trakAtom->childById(TrackHeader, diag))) {
1540 diag.emplace_back(DiagLevel::Critical, "No \"tkhd\"-atom found.", context);
1541 throw InvalidDataException();
1542 }
1543 if (!(m_mdiaAtom = m_trakAtom->childById(Media, diag))) {
1544 diag.emplace_back(DiagLevel::Critical, "No \"mdia\"-atom found.", context);
1545 throw InvalidDataException();
1546 }
1547 if (!(m_mdhdAtom = m_mdiaAtom->childById(MediaHeader, diag))) {
1548 diag.emplace_back(DiagLevel::Critical, "No \"mdhd\"-atom found.", context);
1549 throw InvalidDataException();
1550 }
1551 if (!(m_hdlrAtom = m_mdiaAtom->childById(HandlerReference, diag))) {
1552 diag.emplace_back(DiagLevel::Critical, "No \"hdlr\"-atom found.", context);
1553 throw InvalidDataException();
1554 }
1555 if (!(m_minfAtom = m_mdiaAtom->childById(MediaInformation, diag))) {
1556 diag.emplace_back(DiagLevel::Critical, "No \"minf\"-atom found.", context);
1557 throw InvalidDataException();
1558 }
1559 if (!(m_stblAtom = m_minfAtom->childById(SampleTable, diag))) {
1560 diag.emplace_back(DiagLevel::Critical, "No \"stbl\"-atom found.", context);
1561 throw InvalidDataException();
1562 }
1563 if (!(m_stsdAtom = m_stblAtom->childById(SampleDescription, diag))) {
1564 diag.emplace_back(DiagLevel::Critical, "No \"stsd\"-atom found.", context);
1565 throw InvalidDataException();
1566 }
1567 if (!(m_stcoAtom = m_stblAtom->childById(ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(ChunkOffset64, diag))) {
1568 diag.emplace_back(DiagLevel::Critical, "No \"stco\"/\"co64\"-atom found.", context);
1569 throw InvalidDataException();
1570 }
1571 if (!(m_stscAtom = m_stblAtom->childById(SampleToChunk, diag))) {
1572 diag.emplace_back(DiagLevel::Critical, "No \"stsc\"-atom found.", context);
1573 throw InvalidDataException();
1574 }
1575 if (!(m_stszAtom = m_stblAtom->childById(SampleSize, diag)) && !(m_stszAtom = m_stblAtom->childById(CompactSampleSize, diag))) {
1576 diag.emplace_back(DiagLevel::Critical, "No \"stsz\"/\"stz2\"-atom found.", context);
1577 throw InvalidDataException();
1578 }
1579 } catch (const Failure &) {
1580 diag.emplace_back(DiagLevel::Critical, "Unable to parse relevant atoms.", context);
1581 throw InvalidDataException();
1582 }
1583
1584 BinaryReader &reader = m_trakAtom->reader();
1585
1586 // read tkhd atom
1587 m_istream->seekg(static_cast<streamoff>(m_tkhdAtom->startOffset() + 8)); // seek to beg, skip size and name
1588 auto atomVersion = reader.readByte(); // read version
1589 const auto flags = reader.readUInt24BE();
1590 modFlagEnum(m_flags, TrackFlags::Enabled, flags & 0x000001);
1591 modFlagEnum(m_flags, TrackFlags::UsedInPresentation, flags & 0x000002);
1592 modFlagEnum(m_flags, TrackFlags::UsedWhenPreviewing, flags & 0x000004);
1593 switch (atomVersion) {
1594 case 0:
1595 m_rawTkhdCreationTime = reader.readUInt32BE();
1596 m_rawTkhdModificationTime = reader.readUInt32BE();
1597 m_id = reader.readUInt32BE();
1598 m_istream->seekg(4, std::ios_base::cur);
1599 m_rawTkhdDuration = reader.readUInt32BE();
1600 break;
1601 case 1:
1602 m_rawTkhdCreationTime = reader.readUInt64BE();
1603 m_rawTkhdModificationTime = reader.readUInt64BE();
1604 m_id = reader.readUInt32BE();
1605 m_istream->seekg(4, std::ios_base::cur);
1606 m_rawTkhdDuration = reader.readUInt64BE();
1607 break;
1608 default:
1609 diag.emplace_back(DiagLevel::Critical,
1610 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1611 context);
1612 m_rawTkhdCreationTime = m_rawTkhdModificationTime = m_rawTkhdDuration = 0;
1615 m_id = 0;
1616 }
1617
1618 // read mdhd atom
1619 m_istream->seekg(static_cast<streamoff>(m_mdhdAtom->dataOffset())); // seek to beg, skip size and name
1620 atomVersion = reader.readByte(); // read version
1621 m_istream->seekg(3, ios_base::cur); // skip flags
1622 switch (atomVersion) {
1623 case 0:
1624 m_rawMdhdCreationTime = reader.readUInt32BE();
1625 m_rawMdhdModificationTime = reader.readUInt32BE();
1626 m_timeScale = reader.readUInt32BE();
1627 m_rawMdhdDuration = reader.readUInt32BE();
1628 break;
1629 case 1:
1630 m_rawMdhdCreationTime = reader.readUInt64BE();
1631 m_rawMdhdModificationTime = reader.readUInt64BE();
1632 m_timeScale = reader.readUInt32BE();
1633 m_rawMdhdDuration = reader.readUInt64BE();
1634 break;
1635 default:
1636 diag.emplace_back(DiagLevel::Warning,
1637 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be "
1638 "determined.",
1639 context);
1640 m_rawMdhdCreationTime = m_rawMdhdModificationTime = m_rawMdhdDuration = 0;
1641 m_timeScale = 0;
1642 m_duration = TimeSpan();
1643 }
1644 m_creationTime = Mp4Container::epoch + TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(m_rawMdhdCreationTime));
1645 m_modificationTime = Mp4Container::epoch + TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(m_rawMdhdModificationTime));
1646 m_duration = TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(m_rawMdhdDuration)) / static_cast<TimeSpan::TickType>(m_timeScale);
1647
1648 std::uint16_t tmp = reader.readUInt16BE();
1649 if (tmp) {
1650 const char buff[] = {
1651 static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1652 static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1653 static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1654 };
1655 m_locale.emplace_back(std::string(buff, 3), LocaleFormat::ISO_639_2_T);
1656 } else {
1657 m_locale.clear();
1658 }
1659
1660 // read hdlr atom
1661 // -> seek to begin skipping size, name, version, flags and reserved bytes
1662 m_istream->seekg(static_cast<streamoff>(m_hdlrAtom->dataOffset() + 8));
1663 // -> track type
1664 switch (m_rawMediaType = reader.readUInt32BE()) {
1665 case 0x76696465:
1667 break;
1668 case 0x736F756E:
1670 break;
1671 case 0x68696E74:
1673 break;
1674 case 0x6D657461:
1676 break;
1677 case 0x74657874:
1679 break;
1680 default:
1682 }
1683 // -> name
1684 m_istream->seekg(12, ios_base::cur); // skip reserved bytes
1685 if (static_cast<std::uint64_t>(tmp = static_cast<std::uint8_t>(m_istream->peek())) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1686 // assume size prefixed string (seems to appear in QuickTime files)
1687 m_istream->seekg(1, ios_base::cur);
1688 m_name = reader.readString(tmp);
1689 } else {
1690 // assume null terminated string (appears in MP4 files)
1691 m_name = reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1692 }
1693
1694 // read stco atom (only chunk count)
1695 m_chunkOffsetSize = (m_stcoAtom->id() == Mp4AtomIds::ChunkOffset64) ? 8 : 4;
1696 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 4));
1697 m_chunkCount = reader.readUInt32BE();
1698
1699 // read stsd atom
1700 m_istream->seekg(static_cast<streamoff>(m_stsdAtom->dataOffset() + 4)); // seek to beg, skip size, name, version and flags
1701 const auto entryCount = reader.readUInt32BE();
1702 Mp4Atom *esDescParentAtom = nullptr;
1703 if (entryCount) {
1704 try {
1705 for (Mp4Atom *codecConfigContainerAtom = m_stsdAtom->firstChild(); codecConfigContainerAtom;
1706 codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1707 codecConfigContainerAtom->parse(diag);
1708
1709 // parse FOURCC
1710 m_formatId = interpretIntegerAsString<std::uint32_t>(codecConfigContainerAtom->id());
1711 m_format = FourccIds::fourccToMediaFormat(codecConfigContainerAtom->id());
1712
1713 // parse codecConfigContainerAtom
1714 m_istream->seekg(static_cast<streamoff>(codecConfigContainerAtom->dataOffset()));
1715 switch (codecConfigContainerAtom->id()) {
1718 case FourccIds::Amr:
1719 case FourccIds::Drms:
1720 case FourccIds::Alac:
1722 case FourccIds::Ac3:
1723 case FourccIds::EAc3:
1725 case FourccIds::Dts:
1726 case FourccIds::DtsH:
1727 case FourccIds::DtsE:
1728 case FourccIds::Flac:
1729 case FourccIds::Opus:
1730 m_istream->seekg(6 + 2, ios_base::cur); // skip reserved bytes, data reference index
1731 tmp = reader.readUInt16BE(); // read sound version
1732 m_istream->seekg(6, ios_base::cur);
1733 m_channelCount = reader.readUInt16BE();
1734 m_bitsPerSample = reader.readUInt16BE();
1735 m_istream->seekg(4, ios_base::cur); // skip reserved bytes (again)
1736 if (!m_samplingFrequency) {
1737 m_samplingFrequency = reader.readUInt32BE() >> 16;
1738 if (codecConfigContainerAtom->id() != FourccIds::DolbyMpl) {
1739 m_samplingFrequency >>= 16;
1740 }
1741 } else {
1742 m_istream->seekg(4, ios_base::cur);
1743 }
1744 if (codecConfigContainerAtom->id() != FourccIds::WindowsMediaAudio) {
1745 switch (tmp) {
1746 case 1:
1747 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1748 break;
1749 case 2:
1750 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1751 break;
1752 default:
1753 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1754 }
1755 if (!esDescParentAtom) {
1756 esDescParentAtom = codecConfigContainerAtom;
1757 }
1758 }
1759 break;
1763 case FourccIds::Avc1:
1764 case FourccIds::Avc2:
1765 case FourccIds::Avc3:
1766 case FourccIds::Avc4:
1767 case FourccIds::Drmi:
1768 case FourccIds::Hevc1:
1769 case FourccIds::Hevc2:
1770 case FourccIds::Av1_IVF:
1772 case FourccIds::Vp9_2:
1773 m_istream->seekg(6 + 2 + 16, ios_base::cur); // skip reserved bytes, data reference index, and reserved bytes (again)
1774 m_pixelSize.setWidth(reader.readUInt16BE());
1775 m_pixelSize.setHeight(reader.readUInt16BE());
1776 m_resolution.setWidth(static_cast<std::uint32_t>(reader.readFixed16BE()));
1777 m_resolution.setHeight(static_cast<std::uint32_t>(reader.readFixed16BE()));
1778 m_istream->seekg(4, ios_base::cur); // skip reserved bytes
1779 m_framesPerSample = reader.readUInt16BE();
1780 tmp = reader.readByte();
1781 m_compressorName = reader.readString(31);
1782 if (tmp == 0) {
1783 m_compressorName.clear();
1784 } else if (tmp < 32) {
1785 m_compressorName.resize(tmp);
1786 }
1787 m_depth = reader.readUInt16BE(); // 24: color without alpha
1788 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1789 if (!esDescParentAtom) {
1790 esDescParentAtom = codecConfigContainerAtom;
1791 }
1792 break;
1794 // skip reserved bytes and data reference index
1795 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1796 if (!esDescParentAtom) {
1797 esDescParentAtom = codecConfigContainerAtom;
1798 }
1799 break;
1801 break; // TODO
1803 break; // TODO
1804 default:;
1805 }
1806 }
1807
1808 if (esDescParentAtom) {
1809 // parse AVC configuration
1810 if (auto *const avcConfigAtom = esDescParentAtom->childById(Mp4AtomIds::AvcConfiguration, diag)) {
1811 m_istream->seekg(static_cast<streamoff>(avcConfigAtom->dataOffset()));
1812 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1813 try {
1814 m_avcConfig->parse(reader, avcConfigAtom->dataSize(), diag);
1815 addInfo(*m_avcConfig, *this);
1816 } catch (const TruncatedDataException &) {
1817 diag.emplace_back(DiagLevel::Critical, "AVC configuration is truncated.", context);
1818 } catch (const Failure &) {
1819 diag.emplace_back(DiagLevel::Critical, "AVC configuration is invalid.", context);
1820 }
1821 }
1822
1823 // parse AV1 configuration
1824 if (auto *const av1ConfigAtom = esDescParentAtom->childById(Mp4AtomIds::Av1Configuration, diag)) {
1825 m_istream->seekg(static_cast<streamoff>(av1ConfigAtom->dataOffset()));
1826 m_av1Config = make_unique<TagParser::Av1Configuration>();
1827 try {
1828 m_av1Config->parse(reader, av1ConfigAtom->dataSize(), diag);
1829 addInfo(*m_av1Config, *this);
1830 } catch (const NotImplementedException &) {
1831 diag.emplace_back(DiagLevel::Information, "Parsing AV1 configuration is not supported yet.", context);
1832 } catch (const TruncatedDataException &) {
1833 diag.emplace_back(DiagLevel::Critical, "AV1 configuration is truncated.", context);
1834 } catch (const Failure &) {
1835 diag.emplace_back(DiagLevel::Critical, "AV1 configuration is invalid.", context);
1836 }
1837 }
1838
1839 // parse MPEG-4 elementary stream descriptor
1840 auto *esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor, diag);
1841 if (!esDescAtom) {
1842 esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor2, diag);
1843 }
1844 if (esDescAtom) {
1845 try {
1846 if ((m_esInfo = parseMpeg4ElementaryStreamInfo(m_reader, esDescAtom, diag))) {
1848 m_bitrate = static_cast<double>(m_esInfo->averageBitrate) / 1000;
1849 m_maxBitrate = static_cast<double>(m_esInfo->maxBitrate) / 1000;
1850 if (m_esInfo->audioSpecificConfig) {
1851 // check the audio specific config for useful information
1852 m_format += Mpeg4AudioObjectIds::idToMediaFormat(m_esInfo->audioSpecificConfig->audioObjectType,
1853 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1854 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1855 m_samplingFrequency = m_esInfo->audioSpecificConfig->sampleFrequency;
1856 } else if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1857 m_samplingFrequency = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->sampleFrequencyIndex];
1858 } else {
1859 diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid sample frequency index.", context);
1860 }
1861 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1862 m_extensionSamplingFrequency = m_esInfo->audioSpecificConfig->extensionSampleFrequency;
1863 } else if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1865 = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex];
1866 } else {
1867 diag.emplace_back(
1868 DiagLevel::Warning, "Audio specific config has invalid extension sample frequency index.", context);
1869 }
1870 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1871 m_extensionChannelConfig = m_esInfo->audioSpecificConfig->extensionChannelConfiguration;
1872 }
1873 if (m_esInfo->videoSpecificConfig) {
1874 // check the video specific config for useful information
1875 if (m_format.general == GeneralMediaFormat::Mpeg4Video && m_esInfo->videoSpecificConfig->profile) {
1876 m_format.sub = m_esInfo->videoSpecificConfig->profile;
1877 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1878 m_formatId += " / ";
1879 m_formatId += m_esInfo->videoSpecificConfig->userData;
1880 }
1881 }
1882 }
1883 // check the stream data for missing information
1884 switch (m_format.general) {
1887 MpegAudioFrame frame;
1888 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
1889 m_istream->seekg(static_cast<streamoff>(m_chunkOffsetSize == 8 ? reader.readUInt64BE() : reader.readUInt32BE()));
1890 frame.parseHeader(reader, diag);
1891 MpegAudioFrameStream::addInfo(frame, *this);
1892 break;
1893 }
1894 default:;
1895 }
1896 }
1897 } catch (const Failure &) {
1898 }
1899 }
1900 }
1901 } catch (const Failure &) {
1902 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of \"stsd\"-atom.", context);
1903 }
1904 }
1905
1906 // read stsz atom which holds the sample size table
1907 m_sampleSizes.clear();
1908 m_size = m_sampleCount = 0;
1909 std::uint64_t actualSampleSizeTableSize = m_stszAtom->dataSize();
1910 if (actualSampleSizeTableSize < 12) {
1911 diag.emplace_back(DiagLevel::Critical,
1912 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1913 } else {
1914 actualSampleSizeTableSize -= 12; // subtract size of version and flags
1915 m_istream->seekg(static_cast<streamoff>(m_stszAtom->dataOffset() + 4)); // seek to beg, skip size, name, version and flags
1916 std::uint32_t fieldSize;
1917 std::uint32_t constantSize;
1918 if (m_stszAtom->id() == Mp4AtomIds::CompactSampleSize) {
1919 constantSize = 0;
1920 m_istream->seekg(3, ios_base::cur); // seek reserved bytes
1921 fieldSize = reader.readByte();
1922 m_sampleCount = reader.readUInt32BE();
1923 } else {
1924 constantSize = reader.readUInt32BE();
1925 m_sampleCount = reader.readUInt32BE();
1926 fieldSize = 32;
1927 }
1928 if (constantSize) {
1929 m_sampleSizes.push_back(constantSize);
1930 m_size = constantSize * m_sampleCount;
1931 } else {
1932 auto actualSampleCount = m_sampleCount;
1933 const auto calculatedSampleSizeTableSize
1934 = static_cast<std::uint64_t>(std::ceil((0.125 * fieldSize) * static_cast<double>(m_sampleCount)));
1935 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1936 diag.emplace_back(
1937 DiagLevel::Critical, "The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1938 } else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1939 diag.emplace_back(DiagLevel::Critical, "The stsz atom is truncated. It stores less entries as denoted.", context);
1940 actualSampleCount = static_cast<std::uint64_t>(floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize)));
1941 }
1942 m_sampleSizes.reserve(actualSampleCount);
1943 std::uint32_t i = 1;
1944 switch (fieldSize) {
1945 case 4:
1946 for (; i <= actualSampleCount; i += 2) {
1947 std::uint8_t val = reader.readByte();
1948 m_sampleSizes.push_back(val >> 4);
1949 m_sampleSizes.push_back(val & 0xF0);
1950 m_size += (val >> 4) + (val & 0xF0);
1951 }
1952 if (i <= actualSampleCount + 1) {
1953 m_sampleSizes.push_back(reader.readByte() >> 4);
1954 m_size += m_sampleSizes.back();
1955 }
1956 break;
1957 case 8:
1958 for (; i <= actualSampleCount; ++i) {
1959 m_sampleSizes.push_back(reader.readByte());
1960 m_size += m_sampleSizes.back();
1961 }
1962 break;
1963 case 16:
1964 for (; i <= actualSampleCount; ++i) {
1965 m_sampleSizes.push_back(reader.readUInt16BE());
1966 m_size += m_sampleSizes.back();
1967 }
1968 break;
1969 case 32:
1970 for (; i <= actualSampleCount; ++i) {
1971 m_sampleSizes.push_back(reader.readUInt32BE());
1972 m_size += m_sampleSizes.back();
1973 }
1974 break;
1975 default:
1976 diag.emplace_back(DiagLevel::Critical,
1977 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1978 context);
1979 }
1980 }
1981 }
1982
1983 // no sample sizes found, search for trun atoms
1984 std::uint64_t totalDuration = 0;
1985 for (Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingByIdIncludingThis(MovieFragment, diag); moofAtom;
1986 moofAtom = moofAtom->siblingById(MovieFragment, diag)) {
1987 moofAtom->parse(diag);
1988 for (Mp4Atom *trafAtom = moofAtom->childById(TrackFragment, diag); trafAtom; trafAtom = trafAtom->siblingById(TrackFragment, diag)) {
1989 trafAtom->parse(diag);
1990 for (Mp4Atom *tfhdAtom = trafAtom->childById(TrackFragmentHeader, diag); tfhdAtom;
1991 tfhdAtom = tfhdAtom->siblingById(TrackFragmentHeader, diag)) {
1992 tfhdAtom->parse(diag);
1993 std::uint32_t calculatedDataSize = 0;
1994 if (tfhdAtom->dataSize() < calculatedDataSize) {
1995 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated.", context);
1996 } else {
1997 m_istream->seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
1998 std::uint32_t tfhdFlags = reader.readUInt24BE();
1999 if (m_id == reader.readUInt32BE()) { // check track ID
2000 if (tfhdFlags & 0x000001) { // base-data-offset present
2001 calculatedDataSize += 8;
2002 }
2003 if (tfhdFlags & 0x000002) { // sample-description-index present
2004 calculatedDataSize += 4;
2005 }
2006 if (tfhdFlags & 0x000008) { // default-sample-duration present
2007 calculatedDataSize += 4;
2008 }
2009 if (tfhdFlags & 0x000010) { // default-sample-size present
2010 calculatedDataSize += 4;
2011 }
2012 if (tfhdFlags & 0x000020) { // default-sample-flags present
2013 calculatedDataSize += 4;
2014 }
2015 //uint64 baseDataOffset = moofAtom->startOffset();
2016 //uint32 defaultSampleDescriptionIndex = 0;
2017 std::uint32_t defaultSampleDuration = 0;
2018 std::uint32_t defaultSampleSize = 0;
2019 //uint32 defaultSampleFlags = 0;
2020 if (tfhdAtom->dataSize() < calculatedDataSize) {
2021 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
2022 } else {
2023 if (tfhdFlags & 0x000001) { // base-data-offset present
2024 //baseDataOffset = reader.readUInt64();
2025 m_istream->seekg(8, ios_base::cur);
2026 }
2027 if (tfhdFlags & 0x000002) { // sample-description-index present
2028 //defaultSampleDescriptionIndex = reader.readUInt32();
2029 m_istream->seekg(4, ios_base::cur);
2030 }
2031 if (tfhdFlags & 0x000008) { // default-sample-duration present
2032 defaultSampleDuration = reader.readUInt32BE();
2033 //m_istream->seekg(4, ios_base::cur);
2034 }
2035 if (tfhdFlags & 0x000010) { // default-sample-size present
2036 defaultSampleSize = reader.readUInt32BE();
2037 }
2038 if (tfhdFlags & 0x000020) { // default-sample-flags present
2039 //defaultSampleFlags = reader.readUInt32BE();
2040 m_istream->seekg(4, ios_base::cur);
2041 }
2042 }
2043 for (Mp4Atom *trunAtom = trafAtom->childById(TrackFragmentRun, diag); trunAtom;
2044 trunAtom = trunAtom->siblingById(TrackFragmentRun, diag)) {
2045 std::uint32_t trunCalculatedDataSize = 8;
2046 if (trunAtom->dataSize() < trunCalculatedDataSize) {
2047 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated.", context);
2048 } else {
2049 m_istream->seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
2050 std::uint32_t trunFlags = reader.readUInt24BE();
2051 std::uint32_t sampleCount = reader.readUInt32BE();
2053 if (trunFlags & 0x000001) { // data offset present
2054 trunCalculatedDataSize += 4;
2055 }
2056 if (trunFlags & 0x000004) { // first-sample-flags present
2057 trunCalculatedDataSize += 4;
2058 }
2059 std::uint32_t entrySize = 0;
2060 if (trunFlags & 0x000100) { // sample-duration present
2061 entrySize += 4;
2062 }
2063 if (trunFlags & 0x000200) { // sample-size present
2064 entrySize += 4;
2065 }
2066 if (trunFlags & 0x000400) { // sample-flags present
2067 entrySize += 4;
2068 }
2069 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
2070 entrySize += 4;
2071 }
2072 trunCalculatedDataSize += entrySize * sampleCount;
2073 if (trunAtom->dataSize() < trunCalculatedDataSize) {
2074 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated (presence of fields denoted).", context);
2075 } else {
2076 if (trunFlags & 0x000001) { // data offset present
2077 m_istream->seekg(4, ios_base::cur);
2078 //int32 dataOffset = reader.readInt32();
2079 }
2080 if (trunFlags & 0x000004) { // first-sample-flags present
2081 m_istream->seekg(4, ios_base::cur);
2082 }
2083 for (std::uint32_t i = 0; i < sampleCount; ++i) {
2084 if (trunFlags & 0x000100) { // sample-duration present
2085 totalDuration += reader.readUInt32BE();
2086 } else {
2087 totalDuration += defaultSampleDuration;
2088 }
2089 if (trunFlags & 0x000200) { // sample-size present
2090 m_sampleSizes.push_back(reader.readUInt32BE());
2091 m_size += m_sampleSizes.back();
2092 } else {
2093 m_size += defaultSampleSize;
2094 }
2095 if (trunFlags & 0x000400) { // sample-flags present
2096 m_istream->seekg(4, ios_base::cur);
2097 }
2098 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
2099 m_istream->seekg(4, ios_base::cur);
2100 }
2101 }
2102 }
2103 }
2104 }
2105 if (m_sampleSizes.empty() && defaultSampleSize) {
2106 m_sampleSizes.push_back(defaultSampleSize);
2107 }
2108 }
2109 }
2110 }
2111 }
2112 }
2113
2114 // set duration from "trun-information" if the duration has not been determined yet
2115 if (m_duration.isNull() && totalDuration) {
2116 std::uint32_t timeScale = m_timeScale;
2117 if (!timeScale) {
2118 timeScale = trakAtom().container().timeScale();
2119 }
2120 if (timeScale) {
2121 m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(timeScale));
2122 }
2123 }
2124
2125 // calculate average bitrate
2126 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2127 m_bitrate = (static_cast<double>(m_size) * 0.0078125) / m_duration.totalSeconds();
2128 }
2129
2130 // read stsc atom (only number of entries)
2131 m_istream->seekg(static_cast<streamoff>(m_stscAtom->dataOffset() + 4));
2132 m_sampleToChunkEntryCount = reader.readUInt32BE();
2133}
2134
2135} // 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...
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.
std::uint8_t m_extensionChannelConfig
std::string_view m_chromaFormat
std::uint64_t startOffset() const
Returns the start offset of the track in the associated stream.
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::uint64_t sampleCount() const
Returns the number of samples/frames if known; otherwise returns 0.
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.
std::uint32_t m_extensionSamplingFrequency
CppUtilities::DateTime m_creationTime
std::uint32_t m_samplingFrequency
CppUtilities::BinaryWriter & writer()
Returns a binary writer for the associated stream.
The Diagnostics class is a container for DiagMessage.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
void copyEntirely(TargetStream &targetStream, Diagnostics &diag, AbortableProgressFeedback *progress)
Writes the entire element including all children to the specified targetStream.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void discardBuffer()
Discards buffered data.
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
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
static const CppUtilities::DateTime epoch
Dates within MP4 tracks are expressed as the number of seconds since this date.
Implementation of TagParser::AbstractTrack for the MP4 container.
Definition mp4track.h:119
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:870
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:613
std::uint32_t chunkCount() const
Returns the number of chunks denoted by the stco atom.
Definition mp4track.h:238
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:514
static void addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
Adds the information from the specified avcConfig to the specified track.
std::vector< std::uint64_t > readChunkSizes(TagParser::Diagnostics &diag)
Reads the chunk sizes from the stsz (sample sizes) and stsc (samples per chunk) atom.
Definition mp4track.cpp:563
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:946
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
This method is internally called to parse header information.
void makeSampleTable(Diagnostics &diag)
Makes the sample table (stbl atom) for the track.
std::uint64_t requiredSize(Diagnostics &diag) const
Returns the number of bytes written when calling makeTrack().
TrackType type() const override
Returns the type of the track if known; otherwise returns TrackType::Unspecified.
Definition mp4track.cpp:184
std::uint32_t sampleToChunkEntryCount() const
Returns the number of "sample to chunk" entries within the stsc atom.
Definition mp4track.h:246
void makeMedia(Diagnostics &diag)
Makes the media information (mdia atom) for the track.
std::vector< std::uint64_t > readChunkOffsets(bool parseFragments, Diagnostics &diag)
Reads the chunk offsets from the stco atom and fragments if parseFragments is true.
Definition mp4track.cpp:199
unsigned int chunkOffsetSize() const
Returns the size of a single chunk offset denotation within the stco atom.
Definition mp4track.h:230
~Mp4Track() override
Destroys the track.
Definition mp4track.cpp:180
void makeTrackHeader(Diagnostics &diag)
Makes the track header (tkhd atom) for the track.
void bufferTrackAtoms(Diagnostics &diag)
Buffers all atoms required by the makeTrack() method.
void makeMediaInfo(Diagnostics &diag)
Makes a media information (minf atom) for the track.
Mp4Atom & trakAtom()
Returns the trak atom for the current instance.
Definition mp4track.h:208
void makeTrack(Diagnostics &diag)
Makes the track entry ("trak"-atom) for the track.
static std::unique_ptr< Mpeg4AudioSpecificConfig > parseAudioSpecificConfig(std::istream &stream, std::uint64_t startOffset, std::uint64_t size, Diagnostics &diag)
Parses the audio specific configuration for the track.
Definition mp4track.cpp:705
void updateChunkOffset(std::uint32_t chunkIndex, std::uint64_t offset)
Updates a particular chunk offset.
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:48
TAG_PARSER_EXPORT MediaFormat idToMediaFormat(std::uint8_t mpeg4AudioObjectId, bool sbrPresent=false, bool psPresent=false)
Definition mp4ids.cpp:368
TAG_PARSER_EXPORT MediaFormat streamObjectTypeFormat(std::uint8_t streamObjectTypeId)
Returns the TagParser::MediaFormat denoted by the specified MPEG-4 stream ID.
Definition mp4ids.cpp:216
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
std::uint32_t mpeg4SamplingFrequencyTable[13]
Definition mp4ids.cpp:424
TrackType
The TrackType enum specifies the underlying file type of a track and the concrete class of the track ...
The Av1Configuration struct provides a parser for AV1 configuration found in ISOBMFF files.
The AvcConfiguration struct provides a parser for AVC configuration.
std::vector< SpsInfo > spsInfos
static const LocaleDetail & getEmpty()
Returns an empty LocaleDetail.
The Mp4Timings struct holds timing values found in multiple MP4 atoms.
Definition mp4track.cpp:36
std::uint64_t mdhdModificationTime
Definition mp4track.cpp:38
constexpr std::uint8_t requiredTkhdVersion() const
Definition mp4track.cpp:76
std::uint64_t tkhdModificationTime
Definition mp4track.cpp:38
std::uint64_t tkhdCreationTime
Definition mp4track.cpp:37
std::uint64_t tkhdDuration
Definition mp4track.cpp:39
std::uint64_t mdhdDuration
Definition mp4track.cpp:39
std::uint64_t mdhdCreationTime
Definition mp4track.cpp:37
constexpr std::uint8_t requiredMdhdVersion() const
Definition mp4track.cpp:84
The SpsInfo struct holds the sequence parameter set.
Definition avcinfo.h:71
AspectRatio pixelAspectRatio
Definition avcinfo.h:87
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:50