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