Tag Parser 11.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#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
130 : profile(0)
131{
132}
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
181{
182}
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:
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
1339 std::uint16_t codedLanguage = 0;
1340 for (size_t charIndex = 0; charIndex != 3; ++charIndex) {
1341 const char langChar = charIndex < language.size() ? language[charIndex] : 0;
1342 if (langChar >= 'a' && langChar <= 'z') {
1343 codedLanguage |= static_cast<std::uint16_t>((langChar - 0x60) << (0xA - charIndex * 0x5));
1344 continue;
1345 }
1346
1347 // handle invalid characters
1348 if (language.empty()) {
1349 // preserve empty language field
1350 codedLanguage = 0;
1351 break;
1352 }
1353 diag.emplace_back(
1354 DiagLevel::Warning, "Assigned language \"" % language + "\" is of an invalid format. Setting language to undefined.", "making mdhd atom");
1355 codedLanguage = 0x55C4; // und(efined)
1356 break;
1357 }
1358 if (language.size() > 3) {
1359 diag.emplace_back(
1360 DiagLevel::Warning, "Assigned language \"" % language + "\" is longer than 3 byte and hence will be truncated.", "making mdhd atom");
1361 }
1362 writer().writeUInt16BE(codedLanguage);
1363 writer().writeUInt16BE(0); // pre defined
1364 // write hdlr atom
1365 writer().writeUInt32BE(33 + static_cast<std::uint32_t>(m_name.size())); // size
1366 writer().writeUInt32BE(Mp4AtomIds::HandlerReference);
1367 writer().writeUInt64BE(0); // version, flags, pre defined
1368 switch (m_mediaType) {
1369 case MediaType::Video:
1370 outputStream().write("vide", 4);
1371 break;
1372 case MediaType::Audio:
1373 outputStream().write("soun", 4);
1374 break;
1375 case MediaType::Hint:
1376 outputStream().write("hint", 4);
1377 break;
1378 case MediaType::Text:
1379 outputStream().write("text", 4);
1380 break;
1381 case MediaType::Meta:
1382 outputStream().write("meta", 4);
1383 break;
1384 default:
1386 diag.emplace_back(DiagLevel::Critical, "Media type is invalid; keeping media type as-is.", "making hdlr atom");
1387 }
1388 writer().writeUInt32BE(m_rawMediaType);
1389 break;
1390 }
1391 for (int i = 0; i < 3; ++i)
1392 writer().writeUInt32BE(0); // reserved
1393 writer().writeTerminatedString(m_name);
1394 // write minf atom
1395 makeMediaInfo(diag);
1396 // write size (of mdia atom)
1397 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), mdiaStartOffset, diag);
1398}
1399
1405{
1406 ostream::pos_type minfStartOffset = outputStream().tellp();
1407 writer().writeUInt32BE(0); // write size later
1408 writer().writeUInt32BE(Mp4AtomIds::MediaInformation);
1409 bool dinfAtomWritten = false;
1410 if (m_minfAtom) {
1411 // copy existing atoms except sample table which is handled separately
1412 for (Mp4Atom *childAtom = m_minfAtom->firstChild(); childAtom; childAtom = childAtom->nextSibling()) {
1413 if (childAtom->id() == Mp4AtomIds::SampleTable) {
1414 continue;
1415 }
1416 if (childAtom->id() == Mp4AtomIds::DataInformation) {
1417 dinfAtomWritten = true;
1418 }
1419 childAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1420 }
1421 }
1422 // write dinf atom if not written yet
1423 if (!dinfAtomWritten) {
1424 writer().writeUInt32BE(36); // size
1425 writer().writeUInt32BE(Mp4AtomIds::DataInformation);
1426 // write dref atom
1427 writer().writeUInt32BE(28); // size
1428 writer().writeUInt32BE(Mp4AtomIds::DataReference);
1429 writer().writeUInt32BE(0); // version and flags
1430 writer().writeUInt32BE(1); // entry count
1431 // write url atom
1432 writer().writeUInt32BE(12); // size
1433 writer().writeUInt32BE(Mp4AtomIds::DataEntryUrl);
1434 writer().writeByte(0); // version
1435 writer().writeUInt24BE(0x000001); // flags (media data is in the same file as the movie box)
1436 }
1437 // write stbl atom
1438 // -> just copy existing stbl atom because makeSampleTable() is not fully implemented (yet)
1439 bool stblAtomWritten = false;
1440 if (m_minfAtom) {
1441 if (Mp4Atom *const stblAtom = m_minfAtom->childById(Mp4AtomIds::SampleTable, diag)) {
1442 stblAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1443 stblAtomWritten = true;
1444 }
1445 }
1446 if (!stblAtomWritten) {
1447 diag.emplace_back(DiagLevel::Critical,
1448 "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.", "making stbl atom");
1449 }
1450 // write size (of minf atom)
1451 Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset, diag);
1452}
1453
1460{
1461 // ostream::pos_type stblStartOffset = outputStream().tellp(); (enable when function is fully implemented)
1462
1463 writer().writeUInt32BE(0); // write size later
1464 writer().writeUInt32BE(Mp4AtomIds::SampleTable);
1465 Mp4Atom *const stblAtom = m_minfAtom ? m_minfAtom->childById(Mp4AtomIds::SampleTable, diag) : nullptr;
1466 // write stsd atom
1467 if (m_stsdAtom) {
1468 // copy existing stsd atom
1469 m_stsdAtom->copyEntirely(outputStream(), diag, nullptr);
1470 } else {
1471 diag.emplace_back(DiagLevel::Critical, "Unable to make stsd atom from scratch.", "making stsd atom");
1473 }
1474 // write stts and ctts atoms
1475 Mp4Atom *const sttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::DecodingTimeToSample, diag) : nullptr;
1476 if (sttsAtom) {
1477 // copy existing stts atom
1478 sttsAtom->copyEntirely(outputStream(), diag, nullptr);
1479 } else {
1480 diag.emplace_back(DiagLevel::Critical, "Unable to make stts atom from scratch.", "making stts atom");
1482 }
1483 Mp4Atom *const cttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::CompositionTimeToSample, diag) : nullptr;
1484 if (cttsAtom) {
1485 // copy existing ctts atom
1486 cttsAtom->copyEntirely(outputStream(), diag, nullptr);
1487 }
1488 // write stsc atom (sample-to-chunk table)
1490
1491 // write stsz atom (sample sizes)
1492
1493 // write stz2 atom (compact sample sizes)
1494
1495 // write stco/co64 atom (chunk offset table)
1496
1497 // write stss atom (sync sample table)
1498
1499 // write stsh atom (shadow sync sample table)
1500
1501 // write padb atom (sample padding bits)
1502
1503 // write stdp atom (sample degradation priority)
1504
1505 // write sdtp atom (independent and disposable samples)
1506
1507 // write sbgp atom (sample group description)
1508
1509 // write sbgp atom (sample-to-group)
1510
1511 // write sgpd atom (sample group description)
1512
1513 // write subs atom (sub-sample information)
1514
1515 // write size of stbl atom (enable when function is fully implemented)
1516 // Mp4Atom::seekBackAndWriteAtomSize(outputStream(), stblStartOffset, diag);
1517}
1518
1520{
1521 CPP_UTILITIES_UNUSED(progress)
1522
1523 static const string context("parsing MP4 track");
1524 using namespace Mp4AtomIds;
1525 if (!m_trakAtom) {
1526 diag.emplace_back(DiagLevel::Critical, "\"trak\"-atom is null.", context);
1527 throw InvalidDataException();
1528 }
1529
1530 // get atoms
1531 try {
1532 if (!(m_tkhdAtom = m_trakAtom->childById(TrackHeader, diag))) {
1533 diag.emplace_back(DiagLevel::Critical, "No \"tkhd\"-atom found.", context);
1534 throw InvalidDataException();
1535 }
1536 if (!(m_mdiaAtom = m_trakAtom->childById(Media, diag))) {
1537 diag.emplace_back(DiagLevel::Critical, "No \"mdia\"-atom found.", context);
1538 throw InvalidDataException();
1539 }
1540 if (!(m_mdhdAtom = m_mdiaAtom->childById(MediaHeader, diag))) {
1541 diag.emplace_back(DiagLevel::Critical, "No \"mdhd\"-atom found.", context);
1542 throw InvalidDataException();
1543 }
1544 if (!(m_hdlrAtom = m_mdiaAtom->childById(HandlerReference, diag))) {
1545 diag.emplace_back(DiagLevel::Critical, "No \"hdlr\"-atom found.", context);
1546 throw InvalidDataException();
1547 }
1548 if (!(m_minfAtom = m_mdiaAtom->childById(MediaInformation, diag))) {
1549 diag.emplace_back(DiagLevel::Critical, "No \"minf\"-atom found.", context);
1550 throw InvalidDataException();
1551 }
1552 if (!(m_stblAtom = m_minfAtom->childById(SampleTable, diag))) {
1553 diag.emplace_back(DiagLevel::Critical, "No \"stbl\"-atom found.", context);
1554 throw InvalidDataException();
1555 }
1556 if (!(m_stsdAtom = m_stblAtom->childById(SampleDescription, diag))) {
1557 diag.emplace_back(DiagLevel::Critical, "No \"stsd\"-atom found.", context);
1558 throw InvalidDataException();
1559 }
1560 if (!(m_stcoAtom = m_stblAtom->childById(ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(ChunkOffset64, diag))) {
1561 diag.emplace_back(DiagLevel::Critical, "No \"stco\"/\"co64\"-atom found.", context);
1562 throw InvalidDataException();
1563 }
1564 if (!(m_stscAtom = m_stblAtom->childById(SampleToChunk, diag))) {
1565 diag.emplace_back(DiagLevel::Critical, "No \"stsc\"-atom found.", context);
1566 throw InvalidDataException();
1567 }
1568 if (!(m_stszAtom = m_stblAtom->childById(SampleSize, diag)) && !(m_stszAtom = m_stblAtom->childById(CompactSampleSize, diag))) {
1569 diag.emplace_back(DiagLevel::Critical, "No \"stsz\"/\"stz2\"-atom found.", context);
1570 throw InvalidDataException();
1571 }
1572 } catch (const Failure &) {
1573 diag.emplace_back(DiagLevel::Critical, "Unable to parse relevant atoms.", context);
1574 throw InvalidDataException();
1575 }
1576
1577 BinaryReader &reader = m_trakAtom->reader();
1578
1579 // read tkhd atom
1580 m_istream->seekg(static_cast<streamoff>(m_tkhdAtom->startOffset() + 8)); // seek to beg, skip size and name
1581 auto atomVersion = reader.readByte(); // read version
1582 const auto flags = reader.readUInt24BE();
1583 modFlagEnum(m_flags, TrackFlags::Enabled, flags & 0x000001);
1584 modFlagEnum(m_flags, TrackFlags::UsedInPresentation, flags & 0x000002);
1585 modFlagEnum(m_flags, TrackFlags::UsedWhenPreviewing, flags & 0x000004);
1586 switch (atomVersion) {
1587 case 0:
1588 m_rawTkhdCreationTime = reader.readUInt32BE();
1589 m_rawTkhdModificationTime = reader.readUInt32BE();
1590 m_id = reader.readUInt32BE();
1591 m_rawTkhdDuration = reader.readUInt32BE();
1592 break;
1593 case 1:
1594 m_rawTkhdCreationTime = reader.readUInt64BE();
1595 m_rawTkhdModificationTime = reader.readUInt64BE();
1596 m_id = reader.readUInt32BE();
1597 m_rawTkhdDuration = reader.readUInt64BE();
1598 break;
1599 default:
1600 diag.emplace_back(DiagLevel::Critical,
1601 "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1602 context);
1603 m_rawTkhdCreationTime = m_rawTkhdModificationTime = m_rawTkhdDuration = 0;
1606 m_id = 0;
1607 }
1608
1609 // read mdhd atom
1610 m_istream->seekg(static_cast<streamoff>(m_mdhdAtom->dataOffset())); // seek to beg, skip size and name
1611 atomVersion = reader.readByte(); // read version
1612 m_istream->seekg(3, ios_base::cur); // skip flags
1613 switch (atomVersion) {
1614 case 0:
1615 m_rawMdhdCreationTime = reader.readUInt32BE();
1616 m_rawMdhdModificationTime = reader.readUInt32BE();
1617 m_timeScale = reader.readUInt32BE();
1618 m_rawMdhdDuration = reader.readUInt32BE();
1619 break;
1620 case 1:
1621 m_rawMdhdCreationTime = reader.readUInt64BE();
1622 m_rawMdhdModificationTime = reader.readUInt64BE();
1623 m_timeScale = reader.readUInt32BE();
1624 m_rawMdhdDuration = reader.readUInt64BE();
1625 break;
1626 default:
1627 diag.emplace_back(DiagLevel::Warning,
1628 "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be "
1629 "determined.",
1630 context);
1631 m_rawMdhdCreationTime = m_rawMdhdModificationTime = m_rawMdhdDuration = 0;
1632 m_timeScale = 0;
1633 m_duration = TimeSpan();
1634 }
1635 m_creationTime = Mp4Container::epoch + TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(m_rawMdhdCreationTime));
1636 m_modificationTime = Mp4Container::epoch + TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(m_rawMdhdModificationTime));
1637 m_duration = TimeSpan::fromSeconds(static_cast<TimeSpan::TickType>(m_rawMdhdDuration)) / static_cast<TimeSpan::TickType>(m_timeScale);
1638
1639 std::uint16_t tmp = reader.readUInt16BE();
1640 if (tmp) {
1641 const char buff[] = {
1642 static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1643 static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1644 static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1645 };
1646 m_locale.emplace_back(std::string(buff, 3), LocaleFormat::ISO_639_2_T);
1647 } else {
1648 m_locale.clear();
1649 }
1650
1651 // read hdlr atom
1652 // -> seek to begin skipping size, name, version, flags and reserved bytes
1653 m_istream->seekg(static_cast<streamoff>(m_hdlrAtom->dataOffset() + 8));
1654 // -> track type
1655 switch (m_rawMediaType = reader.readUInt32BE()) {
1656 case 0x76696465:
1658 break;
1659 case 0x736F756E:
1661 break;
1662 case 0x68696E74:
1664 break;
1665 case 0x6D657461:
1667 break;
1668 case 0x74657874:
1670 break;
1671 default:
1673 }
1674 // -> name
1675 m_istream->seekg(12, ios_base::cur); // skip reserved bytes
1676 if (static_cast<std::uint64_t>(tmp = static_cast<std::uint8_t>(m_istream->peek())) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1677 // assume size prefixed string (seems to appear in QuickTime files)
1678 m_istream->seekg(1, ios_base::cur);
1679 m_name = reader.readString(tmp);
1680 } else {
1681 // assume null terminated string (appears in MP4 files)
1682 m_name = reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1683 }
1684
1685 // read stco atom (only chunk count)
1686 m_chunkOffsetSize = (m_stcoAtom->id() == Mp4AtomIds::ChunkOffset64) ? 8 : 4;
1687 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 4));
1688 m_chunkCount = reader.readUInt32BE();
1689
1690 // read stsd atom
1691 m_istream->seekg(static_cast<streamoff>(m_stsdAtom->dataOffset() + 4)); // seek to beg, skip size, name, version and flags
1692 const auto entryCount = reader.readUInt32BE();
1693 Mp4Atom *esDescParentAtom = nullptr;
1694 if (entryCount) {
1695 try {
1696 for (Mp4Atom *codecConfigContainerAtom = m_stsdAtom->firstChild(); codecConfigContainerAtom;
1697 codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1698 codecConfigContainerAtom->parse(diag);
1699
1700 // parse FOURCC
1701 m_formatId = interpretIntegerAsString<std::uint32_t>(codecConfigContainerAtom->id());
1702 m_format = FourccIds::fourccToMediaFormat(codecConfigContainerAtom->id());
1703
1704 // parse codecConfigContainerAtom
1705 m_istream->seekg(static_cast<streamoff>(codecConfigContainerAtom->dataOffset()));
1706 switch (codecConfigContainerAtom->id()) {
1709 case FourccIds::Amr:
1710 case FourccIds::Drms:
1711 case FourccIds::Alac:
1713 case FourccIds::Ac3:
1714 case FourccIds::EAc3:
1716 case FourccIds::Dts:
1717 case FourccIds::DtsH:
1718 case FourccIds::DtsE:
1719 case FourccIds::Flac:
1720 case FourccIds::Opus:
1721 m_istream->seekg(6 + 2, ios_base::cur); // skip reserved bytes, data reference index
1722 tmp = reader.readUInt16BE(); // read sound version
1723 m_istream->seekg(6, ios_base::cur);
1724 m_channelCount = reader.readUInt16BE();
1725 m_bitsPerSample = reader.readUInt16BE();
1726 m_istream->seekg(4, ios_base::cur); // skip reserved bytes (again)
1727 if (!m_samplingFrequency) {
1728 m_samplingFrequency = reader.readUInt32BE() >> 16;
1729 if (codecConfigContainerAtom->id() != FourccIds::DolbyMpl) {
1730 m_samplingFrequency >>= 16;
1731 }
1732 } else {
1733 m_istream->seekg(4, ios_base::cur);
1734 }
1735 if (codecConfigContainerAtom->id() != FourccIds::WindowsMediaAudio) {
1736 switch (tmp) {
1737 case 1:
1738 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1739 break;
1740 case 2:
1741 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1742 break;
1743 default:
1744 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1745 }
1746 if (!esDescParentAtom) {
1747 esDescParentAtom = codecConfigContainerAtom;
1748 }
1749 }
1750 break;
1754 case FourccIds::Avc1:
1755 case FourccIds::Avc2:
1756 case FourccIds::Avc3:
1757 case FourccIds::Avc4:
1758 case FourccIds::Drmi:
1759 case FourccIds::Hevc1:
1760 case FourccIds::Hevc2:
1761 case FourccIds::Av1_IVF:
1763 case FourccIds::Vp9_2:
1764 m_istream->seekg(6 + 2 + 16, ios_base::cur); // skip reserved bytes, data reference index, and reserved bytes (again)
1765 m_pixelSize.setWidth(reader.readUInt16BE());
1766 m_pixelSize.setHeight(reader.readUInt16BE());
1767 m_resolution.setWidth(static_cast<std::uint32_t>(reader.readFixed16BE()));
1768 m_resolution.setHeight(static_cast<std::uint32_t>(reader.readFixed16BE()));
1769 m_istream->seekg(4, ios_base::cur); // skip reserved bytes
1770 m_framesPerSample = reader.readUInt16BE();
1771 tmp = reader.readByte();
1772 m_compressorName = reader.readString(31);
1773 if (tmp == 0) {
1774 m_compressorName.clear();
1775 } else if (tmp < 32) {
1776 m_compressorName.resize(tmp);
1777 }
1778 m_depth = reader.readUInt16BE(); // 24: color without alpha
1779 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1780 if (!esDescParentAtom) {
1781 esDescParentAtom = codecConfigContainerAtom;
1782 }
1783 break;
1785 // skip reserved bytes and data reference index
1786 codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1787 if (!esDescParentAtom) {
1788 esDescParentAtom = codecConfigContainerAtom;
1789 }
1790 break;
1792 break; // TODO
1794 break; // TODO
1795 default:;
1796 }
1797 }
1798
1799 if (esDescParentAtom) {
1800 // parse AVC configuration
1801 if (auto *const avcConfigAtom = esDescParentAtom->childById(Mp4AtomIds::AvcConfiguration, diag)) {
1802 m_istream->seekg(static_cast<streamoff>(avcConfigAtom->dataOffset()));
1803 m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1804 try {
1805 m_avcConfig->parse(reader, avcConfigAtom->dataSize(), diag);
1806 addInfo(*m_avcConfig, *this);
1807 } catch (const TruncatedDataException &) {
1808 diag.emplace_back(DiagLevel::Critical, "AVC configuration is truncated.", context);
1809 } catch (const Failure &) {
1810 diag.emplace_back(DiagLevel::Critical, "AVC configuration is invalid.", context);
1811 }
1812 }
1813
1814 // parse AV1 configuration
1815 if (auto *const av1ConfigAtom = esDescParentAtom->childById(Mp4AtomIds::Av1Configuration, diag)) {
1816 m_istream->seekg(static_cast<streamoff>(av1ConfigAtom->dataOffset()));
1817 m_av1Config = make_unique<TagParser::Av1Configuration>();
1818 try {
1819 m_av1Config->parse(reader, av1ConfigAtom->dataSize(), diag);
1820 addInfo(*m_av1Config, *this);
1821 } catch (const NotImplementedException &) {
1822 diag.emplace_back(DiagLevel::Information, "Parsing AV1 configuration is not supported yet.", context);
1823 } catch (const TruncatedDataException &) {
1824 diag.emplace_back(DiagLevel::Critical, "AV1 configuration is truncated.", context);
1825 } catch (const Failure &) {
1826 diag.emplace_back(DiagLevel::Critical, "AV1 configuration is invalid.", context);
1827 }
1828 }
1829
1830 // parse MPEG-4 elementary stream descriptor
1831 auto *esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor, diag);
1832 if (!esDescAtom) {
1833 esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor2, diag);
1834 }
1835 if (esDescAtom) {
1836 try {
1837 if ((m_esInfo = parseMpeg4ElementaryStreamInfo(m_reader, esDescAtom, diag))) {
1839 m_bitrate = static_cast<double>(m_esInfo->averageBitrate) / 1000;
1840 m_maxBitrate = static_cast<double>(m_esInfo->maxBitrate) / 1000;
1841 if (m_esInfo->audioSpecificConfig) {
1842 // check the audio specific config for useful information
1843 m_format += Mpeg4AudioObjectIds::idToMediaFormat(m_esInfo->audioSpecificConfig->audioObjectType,
1844 m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1845 if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1846 m_samplingFrequency = m_esInfo->audioSpecificConfig->sampleFrequency;
1847 } else if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1848 m_samplingFrequency = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->sampleFrequencyIndex];
1849 } else {
1850 diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid sample frequency index.", context);
1851 }
1852 if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1853 m_extensionSamplingFrequency = m_esInfo->audioSpecificConfig->extensionSampleFrequency;
1854 } else if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1856 = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex];
1857 } else {
1858 diag.emplace_back(
1859 DiagLevel::Warning, "Audio specific config has invalid extension sample frequency index.", context);
1860 }
1861 m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1862 m_extensionChannelConfig = m_esInfo->audioSpecificConfig->extensionChannelConfiguration;
1863 }
1864 if (m_esInfo->videoSpecificConfig) {
1865 // check the video specific config for useful information
1866 if (m_format.general == GeneralMediaFormat::Mpeg4Video && m_esInfo->videoSpecificConfig->profile) {
1867 m_format.sub = m_esInfo->videoSpecificConfig->profile;
1868 if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1869 m_formatId += " / ";
1870 m_formatId += m_esInfo->videoSpecificConfig->userData;
1871 }
1872 }
1873 }
1874 // check the stream data for missing information
1875 switch (m_format.general) {
1878 MpegAudioFrame frame;
1879 m_istream->seekg(static_cast<streamoff>(m_stcoAtom->dataOffset() + 8));
1880 m_istream->seekg(static_cast<streamoff>(m_chunkOffsetSize == 8 ? reader.readUInt64BE() : reader.readUInt32BE()));
1881 frame.parseHeader(reader, diag);
1882 MpegAudioFrameStream::addInfo(frame, *this);
1883 break;
1884 }
1885 default:;
1886 }
1887 }
1888 } catch (const Failure &) {
1889 }
1890 }
1891 }
1892 } catch (const Failure &) {
1893 diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of \"stsd\"-atom.", context);
1894 }
1895 }
1896
1897 // read stsz atom which holds the sample size table
1898 m_sampleSizes.clear();
1899 m_size = m_sampleCount = 0;
1900 std::uint64_t actualSampleSizeTableSize = m_stszAtom->dataSize();
1901 if (actualSampleSizeTableSize < 12) {
1902 diag.emplace_back(DiagLevel::Critical,
1903 "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1904 } else {
1905 actualSampleSizeTableSize -= 12; // subtract size of version and flags
1906 m_istream->seekg(static_cast<streamoff>(m_stszAtom->dataOffset() + 4)); // seek to beg, skip size, name, version and flags
1907 std::uint32_t fieldSize;
1908 std::uint32_t constantSize;
1909 if (m_stszAtom->id() == Mp4AtomIds::CompactSampleSize) {
1910 constantSize = 0;
1911 m_istream->seekg(3, ios_base::cur); // seek reserved bytes
1912 fieldSize = reader.readByte();
1913 m_sampleCount = reader.readUInt32BE();
1914 } else {
1915 constantSize = reader.readUInt32BE();
1916 m_sampleCount = reader.readUInt32BE();
1917 fieldSize = 32;
1918 }
1919 if (constantSize) {
1920 m_sampleSizes.push_back(constantSize);
1921 m_size = constantSize * m_sampleCount;
1922 } else {
1923 auto actualSampleCount = m_sampleCount;
1924 const auto calculatedSampleSizeTableSize
1925 = static_cast<std::uint64_t>(std::ceil((0.125 * fieldSize) * static_cast<double>(m_sampleCount)));
1926 if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1927 diag.emplace_back(
1928 DiagLevel::Critical, "The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1929 } else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1930 diag.emplace_back(DiagLevel::Critical, "The stsz atom is truncated. It stores less entries as denoted.", context);
1931 actualSampleCount = static_cast<std::uint64_t>(floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize)));
1932 }
1933 m_sampleSizes.reserve(actualSampleCount);
1934 std::uint32_t i = 1;
1935 switch (fieldSize) {
1936 case 4:
1937 for (; i <= actualSampleCount; i += 2) {
1938 std::uint8_t val = reader.readByte();
1939 m_sampleSizes.push_back(val >> 4);
1940 m_sampleSizes.push_back(val & 0xF0);
1941 m_size += (val >> 4) + (val & 0xF0);
1942 }
1943 if (i <= actualSampleCount + 1) {
1944 m_sampleSizes.push_back(reader.readByte() >> 4);
1945 m_size += m_sampleSizes.back();
1946 }
1947 break;
1948 case 8:
1949 for (; i <= actualSampleCount; ++i) {
1950 m_sampleSizes.push_back(reader.readByte());
1951 m_size += m_sampleSizes.back();
1952 }
1953 break;
1954 case 16:
1955 for (; i <= actualSampleCount; ++i) {
1956 m_sampleSizes.push_back(reader.readUInt16BE());
1957 m_size += m_sampleSizes.back();
1958 }
1959 break;
1960 case 32:
1961 for (; i <= actualSampleCount; ++i) {
1962 m_sampleSizes.push_back(reader.readUInt32BE());
1963 m_size += m_sampleSizes.back();
1964 }
1965 break;
1966 default:
1967 diag.emplace_back(DiagLevel::Critical,
1968 "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1969 context);
1970 }
1971 }
1972 }
1973
1974 // no sample sizes found, search for trun atoms
1975 std::uint64_t totalDuration = 0;
1976 for (Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingByIdIncludingThis(MovieFragment, diag); moofAtom;
1977 moofAtom = moofAtom->siblingById(MovieFragment, diag)) {
1978 moofAtom->parse(diag);
1979 for (Mp4Atom *trafAtom = moofAtom->childById(TrackFragment, diag); trafAtom; trafAtom = trafAtom->siblingById(TrackFragment, diag)) {
1980 trafAtom->parse(diag);
1981 for (Mp4Atom *tfhdAtom = trafAtom->childById(TrackFragmentHeader, diag); tfhdAtom;
1982 tfhdAtom = tfhdAtom->siblingById(TrackFragmentHeader, diag)) {
1983 tfhdAtom->parse(diag);
1984 std::uint32_t calculatedDataSize = 0;
1985 if (tfhdAtom->dataSize() < calculatedDataSize) {
1986 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated.", context);
1987 } else {
1988 m_istream->seekg(static_cast<streamoff>(tfhdAtom->dataOffset() + 1));
1989 std::uint32_t tfhdFlags = reader.readUInt24BE();
1990 if (m_id == reader.readUInt32BE()) { // check track ID
1991 if (tfhdFlags & 0x000001) { // base-data-offset present
1992 calculatedDataSize += 8;
1993 }
1994 if (tfhdFlags & 0x000002) { // sample-description-index present
1995 calculatedDataSize += 4;
1996 }
1997 if (tfhdFlags & 0x000008) { // default-sample-duration present
1998 calculatedDataSize += 4;
1999 }
2000 if (tfhdFlags & 0x000010) { // default-sample-size present
2001 calculatedDataSize += 4;
2002 }
2003 if (tfhdFlags & 0x000020) { // default-sample-flags present
2004 calculatedDataSize += 4;
2005 }
2006 //uint64 baseDataOffset = moofAtom->startOffset();
2007 //uint32 defaultSampleDescriptionIndex = 0;
2008 std::uint32_t defaultSampleDuration = 0;
2009 std::uint32_t defaultSampleSize = 0;
2010 //uint32 defaultSampleFlags = 0;
2011 if (tfhdAtom->dataSize() < calculatedDataSize) {
2012 diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
2013 } else {
2014 if (tfhdFlags & 0x000001) { // base-data-offset present
2015 //baseDataOffset = reader.readUInt64();
2016 m_istream->seekg(8, ios_base::cur);
2017 }
2018 if (tfhdFlags & 0x000002) { // sample-description-index present
2019 //defaultSampleDescriptionIndex = reader.readUInt32();
2020 m_istream->seekg(4, ios_base::cur);
2021 }
2022 if (tfhdFlags & 0x000008) { // default-sample-duration present
2023 defaultSampleDuration = reader.readUInt32BE();
2024 //m_istream->seekg(4, ios_base::cur);
2025 }
2026 if (tfhdFlags & 0x000010) { // default-sample-size present
2027 defaultSampleSize = reader.readUInt32BE();
2028 }
2029 if (tfhdFlags & 0x000020) { // default-sample-flags present
2030 //defaultSampleFlags = reader.readUInt32BE();
2031 m_istream->seekg(4, ios_base::cur);
2032 }
2033 }
2034 for (Mp4Atom *trunAtom = trafAtom->childById(TrackFragmentRun, diag); trunAtom;
2035 trunAtom = trunAtom->siblingById(TrackFragmentRun, diag)) {
2036 std::uint32_t trunCalculatedDataSize = 8;
2037 if (trunAtom->dataSize() < trunCalculatedDataSize) {
2038 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated.", context);
2039 } else {
2040 m_istream->seekg(static_cast<streamoff>(trunAtom->dataOffset() + 1));
2041 std::uint32_t trunFlags = reader.readUInt24BE();
2042 std::uint32_t sampleCount = reader.readUInt32BE();
2044 if (trunFlags & 0x000001) { // data offset present
2045 trunCalculatedDataSize += 4;
2046 }
2047 if (trunFlags & 0x000004) { // first-sample-flags present
2048 trunCalculatedDataSize += 4;
2049 }
2050 std::uint32_t entrySize = 0;
2051 if (trunFlags & 0x000100) { // sample-duration present
2052 entrySize += 4;
2053 }
2054 if (trunFlags & 0x000200) { // sample-size present
2055 entrySize += 4;
2056 }
2057 if (trunFlags & 0x000400) { // sample-flags present
2058 entrySize += 4;
2059 }
2060 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
2061 entrySize += 4;
2062 }
2063 trunCalculatedDataSize += entrySize * sampleCount;
2064 if (trunAtom->dataSize() < trunCalculatedDataSize) {
2065 diag.emplace_back(DiagLevel::Critical, "trun atom is truncated (presence of fields denoted).", context);
2066 } else {
2067 if (trunFlags & 0x000001) { // data offset present
2068 m_istream->seekg(4, ios_base::cur);
2069 //int32 dataOffset = reader.readInt32();
2070 }
2071 if (trunFlags & 0x000004) { // first-sample-flags present
2072 m_istream->seekg(4, ios_base::cur);
2073 }
2074 for (std::uint32_t i = 0; i < sampleCount; ++i) {
2075 if (trunFlags & 0x000100) { // sample-duration present
2076 totalDuration += reader.readUInt32BE();
2077 } else {
2078 totalDuration += defaultSampleDuration;
2079 }
2080 if (trunFlags & 0x000200) { // sample-size present
2081 m_sampleSizes.push_back(reader.readUInt32BE());
2082 m_size += m_sampleSizes.back();
2083 } else {
2084 m_size += defaultSampleSize;
2085 }
2086 if (trunFlags & 0x000400) { // sample-flags present
2087 m_istream->seekg(4, ios_base::cur);
2088 }
2089 if (trunFlags & 0x000800) { // sample-composition-time-offsets present
2090 m_istream->seekg(4, ios_base::cur);
2091 }
2092 }
2093 }
2094 }
2095 }
2096 if (m_sampleSizes.empty() && defaultSampleSize) {
2097 m_sampleSizes.push_back(defaultSampleSize);
2098 }
2099 }
2100 }
2101 }
2102 }
2103 }
2104
2105 // set duration from "trun-information" if the duration has not been determined yet
2106 if (m_duration.isNull() && totalDuration) {
2107 std::uint32_t timeScale = m_timeScale;
2108 if (!timeScale) {
2109 timeScale = trakAtom().container().timeScale();
2110 }
2111 if (timeScale) {
2112 m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(timeScale));
2113 }
2114 }
2115
2116 // calculate average bitrate
2117 if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2118 m_bitrate = (static_cast<double>(m_size) * 0.0078125) / m_duration.totalSeconds();
2119 }
2120
2121 // read stsc atom (only number of entries)
2122 m_istream->seekg(static_cast<streamoff>(m_stscAtom->dataOffset() + 4));
2123 m_sampleToChunkEntryCount = reader.readUInt32BE();
2124}
2125
2126} // 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::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.
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.
AspectRatio m_pixelAspectRatio
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.
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
static const CppUtilities::DateTime epoch
Dates within MP4 tracks are expressed as the number of seconds since this date.
Definition: mp4container.h:29
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.
Definition: mp4track.cpp:1072
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.
Definition: mp4track.cpp:1519
void makeSampleTable(Diagnostics &diag)
Makes the sample table (stbl atom) for the track.
Definition: mp4track.cpp:1459
std::uint64_t requiredSize(Diagnostics &diag) const
Returns the number of bytes written when calling makeTrack().
Definition: mp4track.cpp:1142
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.
Definition: mp4track.cpp:1311
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.
Definition: mp4track.cpp:1223
void bufferTrackAtoms(Diagnostics &diag)
Buffers all atoms required by the makeTrack() method.
Definition: mp4track.cpp:1119
void makeMediaInfo(Diagnostics &diag)
Makes a media information (minf atom) for the track.
Definition: mp4track.cpp:1404
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.
Definition: mp4track.cpp:1194
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.
Definition: mp4track.cpp:1051
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
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: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
constexpr TAG_PARSER_EXPORT std::string_view version()
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 ...
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 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
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:50