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