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