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