Tag Parser  7.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 "../avc/avcconfiguration.h"
8 
9 #include "../mpegaudio/mpegaudioframe.h"
10 #include "../mpegaudio/mpegaudioframestream.h"
11 
12 #include "../exceptions.h"
13 #include "../mediaformat.h"
14 
15 #include <c++utilities/conversion/stringbuilder.h>
16 #include <c++utilities/io/binaryreader.h>
17 #include <c++utilities/io/binarywriter.h>
18 #include <c++utilities/io/bitreader.h>
19 #include <c++utilities/io/catchiofailure.h>
20 
21 #include <cmath>
22 #include <locale>
23 
24 using namespace std;
25 using namespace IoUtilities;
26 using namespace ConversionUtilities;
27 using namespace ChronoUtilities;
28 
29 namespace TagParser {
30 
38  friend class Mp4Track;
39 
40 private:
42 
44  uint64 requiredSize;
46  bool canUseExisting;
48  bool truncated;
50  byte version;
52  bool versionUnknown;
54  byte additionalDataOffset;
56  bool discardBuffer;
57 };
58 
59 inline TrackHeaderInfo::TrackHeaderInfo()
60  : requiredSize(100)
61  , canUseExisting(false)
62  , truncated(false)
63  , version(0)
64  , versionUnknown(false)
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  ,
144  //m_codecConfigAtom(nullptr),
145  //m_esDescAtom(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 = floor(static_cast<double>(actualTableSize) / static_cast<double>(chunkOffsetSize()));
200  }
201  // read the table
202  offsets.reserve(actualChunkCount);
203  m_istream->seekg(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(tfhdAtom->dataOffset() + 1);
237  const uint32 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(trunAtom->dataOffset() + 1);
290  uint32 flags = reader().readUInt24BE();
291  uint32 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  if (info.version == 0) {
439  // add 12 byte to size if the existing version is 0 because we always write version 1 which takes 12 byte more space
440  info.requiredSize += 12;
441  }
442  if (info.requiredSize > numeric_limits<uint32>::max()) {
443  // add 8 byte to the size because it must be denoted using a 64-bit integer
444  info.requiredSize += 8;
445  }
446  return info;
447 }
448 
456 vector<tuple<uint32, uint32, uint32>> Mp4Track::readSampleToChunkTable(Diagnostics &diag)
457 {
458  static const string context("reading sample to chunk table of MP4 track");
459  if (!isHeaderValid() || !m_istream || !m_stscAtom) {
460  diag.emplace_back(DiagLevel::Critical, "Track has not been parsed or is invalid.", context);
461  throw InvalidDataException();
462  }
463  // verify integrity of the sample to chunk table
464  uint64 actualTableSize = m_stscAtom->dataSize();
465  if (actualTableSize < 20) {
466  diag.emplace_back(DiagLevel::Critical, "The stsc atom is truncated. There are no \"sample to chunk\" entries present.", context);
467  throw InvalidDataException();
468  } else {
469  actualTableSize -= 8;
470  }
471  uint32 actualSampleToChunkEntryCount = sampleToChunkEntryCount();
472  uint64 calculatedTableSize = actualSampleToChunkEntryCount * 12;
473  if (calculatedTableSize < actualTableSize) {
474  diag.emplace_back(DiagLevel::Critical, "The stsc atom stores more entries as denoted. The additional entries will be ignored.", context);
475  } else if (calculatedTableSize > actualTableSize) {
476  diag.emplace_back(DiagLevel::Critical, "The stsc atom is truncated. It stores less entries as denoted.", context);
477  // FIXME: floor and cast actually required?
478  actualSampleToChunkEntryCount = floor(static_cast<double>(actualTableSize) / 12.0);
479  }
480  // prepare reading
481  vector<tuple<uint32, uint32, uint32>> sampleToChunkTable;
482  sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
483  m_istream->seekg(m_stscAtom->dataOffset() + 8);
484  for (uint32 i = 0; i < actualSampleToChunkEntryCount; ++i) {
485  // read entry
486  uint32 firstChunk = reader().readUInt32BE();
487  uint32 samplesPerChunk = reader().readUInt32BE();
488  uint32 sampleDescriptionIndex = reader().readUInt32BE();
489  sampleToChunkTable.emplace_back(firstChunk, samplesPerChunk, sampleDescriptionIndex);
490  }
491  return sampleToChunkTable;
492 }
493 
507 {
508  static const string context("reading chunk sizes of MP4 track");
509  if (!isHeaderValid() || !m_istream || !m_stcoAtom) {
510  diag.emplace_back(DiagLevel::Critical, "Track has not been parsed or is invalid.", context);
511  throw InvalidDataException();
512  }
513  // read sample to chunk table
514  const auto sampleToChunkTable = readSampleToChunkTable(diag);
515  // accumulate chunk sizes from the table
516  vector<uint64> chunkSizes;
517  if (!sampleToChunkTable.empty()) {
518  // prepare reading
519  auto tableIterator = sampleToChunkTable.cbegin();
520  chunkSizes.reserve(m_chunkCount);
521  // read first entry
522  size_t sampleIndex = 0;
523  uint32 previousChunkIndex = get<0>(*tableIterator); // the first chunk has the index 1 and not zero!
524  if (previousChunkIndex != 1) {
525  diag.emplace_back(DiagLevel::Critical, "The first chunk of the first \"sample to chunk\" entry must be 1.", context);
526  previousChunkIndex = 1; // try to read the entry anyway
527  }
528  uint32 samplesPerChunk = get<1>(*tableIterator);
529  // read the following entries
530  ++tableIterator;
531  for (const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
532  uint32 firstChunkIndex = get<0>(*tableIterator);
533  if (firstChunkIndex > previousChunkIndex && firstChunkIndex <= m_chunkCount) {
534  addChunkSizeEntries(chunkSizes, firstChunkIndex - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
535  } else {
536  diag.emplace_back(DiagLevel::Critical,
537  "The first chunk index of a \"sample to chunk\" entry must be greather than the first chunk of the previous entry and not "
538  "greather than the chunk count.",
539  context);
540  throw InvalidDataException();
541  }
542  previousChunkIndex = firstChunkIndex;
543  samplesPerChunk = get<1>(*tableIterator);
544  }
545  if (m_chunkCount >= previousChunkIndex) {
546  addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk, diag);
547  }
548  }
549  return chunkSizes;
550 }
551 
558 std::unique_ptr<Mpeg4ElementaryStreamInfo> Mp4Track::parseMpeg4ElementaryStreamInfo(
559  IoUtilities::BinaryReader &reader, Mp4Atom *esDescAtom, Diagnostics &diag)
560 {
561  static const string context("parsing MPEG-4 elementary stream descriptor");
562  using namespace Mpeg4ElementaryStreamObjectIds;
563  unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
564  if (esDescAtom->dataSize() >= 12) {
565  reader.stream()->seekg(esDescAtom->dataOffset());
566  // read version/flags
567  if (reader.readUInt32BE() != 0) {
568  diag.emplace_back(DiagLevel::Warning, "Unknown version/flags.", context);
569  }
570  // read extended descriptor
571  Mpeg4Descriptor esDesc(esDescAtom->container(), reader.stream()->tellg(), esDescAtom->dataSize() - 4);
572  try {
573  esDesc.parse(diag);
574  // check ID
575  if (esDesc.id() != Mpeg4DescriptorIds::ElementaryStreamDescr) {
576  diag.emplace_back(DiagLevel::Critical, "Invalid descriptor found.", context);
577  throw Failure();
578  }
579  // read stream info
580  reader.stream()->seekg(esDesc.dataOffset());
581  esInfo = make_unique<Mpeg4ElementaryStreamInfo>();
582  esInfo->id = reader.readUInt16BE();
583  esInfo->esDescFlags = reader.readByte();
584  if (esInfo->dependencyFlag()) {
585  esInfo->dependsOnId = reader.readUInt16BE();
586  }
587  if (esInfo->urlFlag()) {
588  esInfo->url = reader.readString(reader.readByte());
589  }
590  if (esInfo->ocrFlag()) {
591  esInfo->ocrId = reader.readUInt16BE();
592  }
593  for (Mpeg4Descriptor *esDescChild = esDesc.denoteFirstChild(static_cast<uint64>(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(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 
651 unique_ptr<Mpeg4AudioSpecificConfig> Mp4Track::parseAudioSpecificConfig(istream &stream, uint64 startOffset, uint64 size, Diagnostics &diag)
652 {
653  static const string context("parsing MPEG-4 audio specific config from elementary stream descriptor");
654  using namespace Mpeg4AudioObjectIds;
655  // read config into buffer and construct BitReader for bitwise reading
656  stream.seekg(startOffset);
657  auto buff = make_unique<char[]>(size);
658  stream.read(buff.get(), size);
659  BitReader bitReader(buff.get(), size);
660  auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
661  try {
662  // read audio object type
663  auto getAudioObjectType = [&bitReader] {
664  byte objType = bitReader.readBits<byte>(5);
665  if (objType == 31) {
666  objType = 32 + bitReader.readBits<byte>(6);
667  }
668  return objType;
669  };
670  audioCfg->audioObjectType = getAudioObjectType();
671  // read sampling frequency
672  if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
673  audioCfg->sampleFrequency = bitReader.readBits<uint32>(24);
674  }
675  // read channel config
676  audioCfg->channelConfiguration = bitReader.readBits<byte>(4);
677  // read extension header
678  switch (audioCfg->audioObjectType) {
679  case Sbr:
680  case Ps:
681  audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
682  audioCfg->sbrPresent = true;
683  if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
684  audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
685  }
686  if ((audioCfg->audioObjectType = getAudioObjectType()) == ErBsac) {
687  audioCfg->extensionChannelConfiguration = bitReader.readBits<byte>(4);
688  }
689  break;
690  }
691  switch (audioCfg->extensionAudioObjectType) {
692  case Ps:
693  audioCfg->psPresent = true;
694  audioCfg->extensionChannelConfiguration = Mpeg4ChannelConfigs::FrontLeftFrontRight;
695  break;
696  }
697  // read GA specific config
698  switch (audioCfg->audioObjectType) {
699  case AacMain:
700  case AacLc:
701  case AacLtp:
702  case AacScalable:
703  case TwinVq:
704  case ErAacLc:
705  case ErAacLtp:
706  case ErAacScalable:
707  case ErTwinVq:
708  case ErBsac:
709  case ErAacLd:
710  audioCfg->frameLengthFlag = bitReader.readBits<byte>(1);
711  if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
712  audioCfg->coreCoderDelay = bitReader.readBits<byte>(14);
713  }
714  audioCfg->extensionFlag = bitReader.readBit();
715  if (audioCfg->channelConfiguration == 0) {
716  throw NotImplementedException(); // TODO: parse program_config_element
717  }
718  switch (audioCfg->audioObjectType) {
719  case AacScalable:
720  case ErAacScalable:
721  audioCfg->layerNr = bitReader.readBits<byte>(3);
722  break;
723  default:;
724  }
725  if (audioCfg->extensionFlag == 1) {
726  switch (audioCfg->audioObjectType) {
727  case ErBsac:
728  audioCfg->numOfSubFrame = bitReader.readBits<byte>(5);
729  audioCfg->layerLength = bitReader.readBits<uint16>(11);
730  break;
731  case ErAacLc:
732  case ErAacLtp:
733  case ErAacScalable:
734  case ErAacLd:
735  audioCfg->resilienceFlags = bitReader.readBits<byte>(3);
736  break;
737  default:;
738  }
739  if (bitReader.readBit() == 1) { // extension flag 3
740  throw NotImplementedException(); // TODO
741  }
742  }
743  break;
744  default:
745  throw NotImplementedException(); // TODO: cover remaining object types
746  }
747  // read error specific config
748  switch (audioCfg->audioObjectType) {
749  case ErAacLc:
750  case ErAacLtp:
751  case ErAacScalable:
752  case ErTwinVq:
753  case ErBsac:
754  case ErAacLd:
755  case ErCelp:
756  case ErHvxc:
757  case ErHiln:
758  case ErParametric:
759  case ErAacEld:
760  switch (audioCfg->epConfig = bitReader.readBits<byte>(2)) {
761  case 2:
762  break;
763  case 3:
764  bitReader.skipBits(1);
765  break;
766  default:
767  throw NotImplementedException(); // TODO
768  }
769  break;
770  }
771  if (audioCfg->extensionAudioObjectType != Sbr && audioCfg->extensionAudioObjectType != Ps && bitReader.bitsAvailable() >= 16) {
772  uint16 syncExtensionType = bitReader.readBits<uint16>(11);
773  if (syncExtensionType == 0x2B7) {
774  if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) == Sbr) {
775  if ((audioCfg->sbrPresent = bitReader.readBit())) {
776  if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
777  audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
778  }
779  if (bitReader.bitsAvailable() >= 12) {
780  if ((syncExtensionType = bitReader.readBits<uint16>(11)) == 0x548) {
781  audioCfg->psPresent = bitReader.readBits<byte>(1);
782  }
783  }
784  }
785  } else if (audioCfg->extensionAudioObjectType == ErBsac) {
786  if ((audioCfg->sbrPresent = bitReader.readBit())) {
787  if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
788  audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
789  }
790  }
791  audioCfg->extensionChannelConfiguration = bitReader.readBits<byte>(4);
792  }
793  } else if (syncExtensionType == 0x548) {
794  audioCfg->psPresent = bitReader.readBit();
795  }
796  }
797  } catch (const NotImplementedException &) {
798  diag.emplace_back(DiagLevel::Information, "Not implemented for the format of audio track.", context);
799  } catch (...) {
800  const char *what = catchIoFailure();
801  if (stream.fail()) {
802  // IO error caused by input stream
803  throwIoFailure(what);
804  } else {
805  // IO error caused by bitReader
806  diag.emplace_back(DiagLevel::Critical, "Audio specific configuration is truncated.", context);
807  }
808  }
809  return audioCfg;
810 }
811 
818 std::unique_ptr<Mpeg4VideoSpecificConfig> Mp4Track::parseVideoSpecificConfig(BinaryReader &reader, uint64 startOffset, uint64 size, Diagnostics &diag)
819 {
820  static const string context("parsing MPEG-4 video specific config from elementary stream descriptor");
821  using namespace Mpeg4AudioObjectIds;
822  auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
823  // seek to start
824  reader.stream()->seekg(startOffset);
825  if (size > 3 && (reader.readUInt24BE() == 1)) {
826  size -= 3;
827  uint32 buff1;
828  while (size) {
829  --size;
830  switch (reader.readByte()) { // read start code
832  if (size) {
833  videoCfg->profile = reader.readByte();
834  --size;
835  }
836  break;
838 
839  break;
841  buff1 = 0;
842  while (size >= 3) {
843  if ((buff1 = reader.readUInt24BE()) != 1) {
844  reader.stream()->seekg(-2, ios_base::cur);
845  videoCfg->userData.push_back(buff1 >> 16);
846  --size;
847  } else {
848  size -= 3;
849  break;
850  }
851  }
852  if (buff1 != 1 && size > 0) {
853  videoCfg->userData += reader.readString(size);
854  size = 0;
855  }
856  break;
857  default:;
858  }
859  // skip remainging values to get the start of the next video object
860  while (size >= 3) {
861  if (reader.readUInt24BE() != 1) {
862  reader.stream()->seekg(-2, ios_base::cur);
863  --size;
864  } else {
865  size -= 3;
866  break;
867  }
868  }
869  }
870  } else {
871  diag.emplace_back(DiagLevel::Critical, "\"Visual Object Sequence Header\" not found.", context);
872  }
873  return videoCfg;
874 }
875 
893 void Mp4Track::updateChunkOffsets(const vector<int64> &oldMdatOffsets, const vector<int64> &newMdatOffsets)
894 {
895  if (!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
896  throw InvalidDataException();
897  }
898  if (oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
899  throw InvalidDataException();
900  }
901  static const unsigned int stcoDataBegin = 8;
902  uint64 startPos = m_stcoAtom->dataOffset() + stcoDataBegin;
903  uint64 endPos = startPos + m_stcoAtom->dataSize() - stcoDataBegin;
904  m_istream->seekg(startPos);
905  m_ostream->seekp(startPos);
906  vector<int64>::size_type i;
907  vector<int64>::size_type size;
908  uint64 currentPos = m_istream->tellg();
909  switch (m_stcoAtom->id()) {
911  uint32 off;
912  while ((currentPos + 4) <= endPos) {
913  off = m_reader.readUInt32BE();
914  for (i = 0, size = oldMdatOffsets.size(); i < size; ++i) {
915  if (off > static_cast<uint64>(oldMdatOffsets[i])) {
916  off += (newMdatOffsets[i] - oldMdatOffsets[i]);
917  break;
918  }
919  }
920  m_ostream->seekp(currentPos);
921  m_writer.writeUInt32BE(off);
922  currentPos += m_istream->gcount();
923  }
924  break;
925  }
927  uint64 off;
928  while ((currentPos + 8) <= endPos) {
929  off = m_reader.readUInt64BE();
930  for (i = 0, size = oldMdatOffsets.size(); i < size; ++i) {
931  if (off > static_cast<uint64>(oldMdatOffsets[i])) {
932  off += (newMdatOffsets[i] - oldMdatOffsets[i]);
933  break;
934  }
935  }
936  m_ostream->seekp(currentPos);
937  m_writer.writeUInt64BE(off);
938  currentPos += m_istream->gcount();
939  }
940  break;
941  }
942  default:
943  throw InvalidDataException();
944  }
945 }
946 
959 void Mp4Track::updateChunkOffsets(const std::vector<uint64> &chunkOffsets)
960 {
961  if (!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
962  throw InvalidDataException();
963  }
964  if (chunkOffsets.size() != chunkCount()) {
965  throw InvalidDataException();
966  }
967  m_ostream->seekp(m_stcoAtom->dataOffset() + 8);
968  switch (m_stcoAtom->id()) {
970  for (auto offset : chunkOffsets) {
971  m_writer.writeUInt32BE(offset);
972  }
973  break;
975  for (auto offset : chunkOffsets) {
976  m_writer.writeUInt64BE(offset);
977  }
978  default:
979  throw InvalidDataException();
980  }
981 }
982 
995 void Mp4Track::updateChunkOffset(uint32 chunkIndex, uint64 offset)
996 {
997  if (!isHeaderValid() || !m_istream || !m_stcoAtom || chunkIndex >= m_chunkCount) {
998  throw InvalidDataException();
999  }
1000  m_ostream->seekp(m_stcoAtom->dataOffset() + 8 + chunkOffsetSize() * chunkIndex);
1001  switch (chunkOffsetSize()) {
1002  case 4:
1003  writer().writeUInt32BE(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 
1053 {
1054  if (m_tkhdAtom) {
1055  m_tkhdAtom->makeBuffer();
1056  }
1057  if (Mp4Atom *trefAtom = m_trakAtom->childById(Mp4AtomIds::TrackReference, diag)) {
1058  trefAtom->makeBuffer();
1059  }
1060  if (Mp4Atom *edtsAtom = m_trakAtom->childById(Mp4AtomIds::Edit, diag)) {
1061  edtsAtom->makeBuffer();
1062  }
1063  if (m_minfAtom) {
1064  if (Mp4Atom *vmhdAtom = m_minfAtom->childById(Mp4AtomIds::VideoMediaHeader, diag)) {
1065  vmhdAtom->makeBuffer();
1066  }
1067  if (Mp4Atom *smhdAtom = m_minfAtom->childById(Mp4AtomIds::SoundMediaHeader, diag)) {
1068  smhdAtom->makeBuffer();
1069  }
1070  if (Mp4Atom *hmhdAtom = m_minfAtom->childById(Mp4AtomIds::HintMediaHeader, diag)) {
1071  hmhdAtom->makeBuffer();
1072  }
1073  if (Mp4Atom *nmhdAtom = m_minfAtom->childById(Mp4AtomIds::NullMediaHeaderBox, diag)) {
1074  nmhdAtom->makeBuffer();
1075  }
1076  if (Mp4Atom *dinfAtom = m_minfAtom->childById(Mp4AtomIds::DataInformation, diag)) {
1077  dinfAtom->makeBuffer();
1078  }
1079  if (Mp4Atom *stblAtom = m_minfAtom->childById(Mp4AtomIds::SampleTable, diag)) {
1080  stblAtom->makeBuffer();
1081  }
1082  }
1083 }
1084 
1089 {
1090  // add size of
1091  // ... trak header
1092  uint64 size = 8;
1093  // ... tkhd atom (TODO: buffer TrackHeaderInfo in v7)
1094  size += verifyPresentTrackHeader().requiredSize;
1095  // ... tref atom (if one exists)
1096  if (Mp4Atom *trefAtom = m_trakAtom->childById(Mp4AtomIds::TrackReference, diag)) {
1097  size += trefAtom->totalSize();
1098  }
1099  // ... edts atom (if one exists)
1100  if (Mp4Atom *edtsAtom = m_trakAtom->childById(Mp4AtomIds::Edit, diag)) {
1101  size += edtsAtom->totalSize();
1102  }
1103  // ... mdia header + mdhd total size + hdlr total size + minf header
1104  size += 8 + 44 + (33 + m_name.size()) + 8;
1105  // ... minf childs
1106  bool dinfAtomWritten = false;
1107  if (m_minfAtom) {
1108  if (Mp4Atom *vmhdAtom = m_minfAtom->childById(Mp4AtomIds::VideoMediaHeader, diag)) {
1109  size += vmhdAtom->totalSize();
1110  }
1111  if (Mp4Atom *smhdAtom = m_minfAtom->childById(Mp4AtomIds::SoundMediaHeader, diag)) {
1112  size += smhdAtom->totalSize();
1113  }
1114  if (Mp4Atom *hmhdAtom = m_minfAtom->childById(Mp4AtomIds::HintMediaHeader, diag)) {
1115  size += hmhdAtom->totalSize();
1116  }
1117  if (Mp4Atom *nmhdAtom = m_minfAtom->childById(Mp4AtomIds::NullMediaHeaderBox, diag)) {
1118  size += nmhdAtom->totalSize();
1119  }
1120  if (Mp4Atom *dinfAtom = m_minfAtom->childById(Mp4AtomIds::DataInformation, diag)) {
1121  size += dinfAtom->totalSize();
1122  dinfAtomWritten = true;
1123  }
1124  if (Mp4Atom *stblAtom = m_minfAtom->childById(Mp4AtomIds::SampleTable, diag)) {
1125  size += stblAtom->totalSize();
1126  }
1127  }
1128  if (!dinfAtomWritten) {
1129  size += 36;
1130  }
1131  return size;
1132 }
1133 
1143 {
1144  // write header
1145  ostream::pos_type trakStartOffset = outputStream().tellp();
1146  m_writer.writeUInt32BE(0); // write size later
1147  m_writer.writeUInt32BE(Mp4AtomIds::Track);
1148  // write tkhd atom
1149  makeTrackHeader(diag);
1150  // write tref atom (if one exists)
1151  if (Mp4Atom *trefAtom = trakAtom().childById(Mp4AtomIds::TrackReference, diag)) {
1152  trefAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1153  }
1154  // write edts atom (if one exists)
1155  if (Mp4Atom *edtsAtom = trakAtom().childById(Mp4AtomIds::Edit, diag)) {
1156  edtsAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1157  }
1158  // write mdia atom
1159  makeMedia(diag);
1160  // write size (of trak atom)
1161  Mp4Atom::seekBackAndWriteAtomSize(outputStream(), trakStartOffset);
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<uint32>::max()) {
1186  writer().writeUInt32BE(1);
1187  writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
1188  writer().writeUInt64BE(info.requiredSize);
1189  } else {
1190  writer().writeUInt32BE(static_cast<uint32>(info.requiredSize));
1191  writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
1192  }
1193 
1194  // make version and flags
1195  writer().writeByte(1);
1196  uint32 flags = 0;
1197  if (m_enabled) {
1198  flags |= 0x000001;
1199  }
1200  if (m_usedInPresentation) {
1201  flags |= 0x000002;
1202  }
1203  if (m_usedWhenPreviewing) {
1204  flags |= 0x000004;
1205  }
1206  writer().writeUInt24BE(flags);
1207 
1208  // make creation and modification time
1209  writer().writeUInt64BE(static_cast<uint64>((m_creationTime - startDate).totalSeconds()));
1210  writer().writeUInt64BE(static_cast<uint64>((m_modificationTime - startDate).totalSeconds()));
1211 
1212  // make track ID and duration
1213  writer().writeUInt32BE(static_cast<uint32>(m_id));
1214  writer().writeUInt32BE(0); // reserved
1215  writer().writeUInt64BE(static_cast<uint64>(m_duration.totalSeconds() * m_timeScale));
1216  writer().writeUInt32BE(0); // reserved
1217  writer().writeUInt32BE(0); // reserved
1218 
1219  // make further values, either from existing tkhd atom or just some defaults
1220  if (info.canUseExisting) {
1221  // write all bytes after the previously determined additionalDataOffset
1222  m_ostream->write(
1223  m_tkhdAtom->buffer().get() + m_tkhdAtom->headerSize() + info.additionalDataOffset, m_tkhdAtom->dataSize() - info.additionalDataOffset);
1224  // discard the buffer again if it wasn't present before
1225  if (info.discardBuffer) {
1226  m_tkhdAtom->discardBuffer();
1227  }
1228  } else {
1229  // write default values
1230  writer().writeInt16BE(0); // layer
1231  writer().writeInt16BE(0); // alternate group
1232  writer().writeFixed8BE(1.0); // volume (fixed 8.8 - 2 byte)
1233  writer().writeUInt16BE(0); // reserved
1234  for (const int32 value : { 0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000 }) { // unity matrix
1235  writer().writeInt32BE(value);
1236  }
1237  writer().writeFixed16BE(1.0); // width
1238  writer().writeFixed16BE(1.0); // height
1239  }
1240 }
1241 
1247 {
1248  ostream::pos_type mdiaStartOffset = outputStream().tellp();
1249  writer().writeUInt32BE(0); // write size later
1250  writer().writeUInt32BE(Mp4AtomIds::Media);
1251  // write mdhd atom
1252  writer().writeUInt32BE(44); // size
1253  writer().writeUInt32BE(Mp4AtomIds::MediaHeader);
1254  writer().writeByte(1); // version
1255  writer().writeUInt24BE(0); // flags
1256  writer().writeUInt64BE(static_cast<uint64>((m_creationTime - startDate).totalSeconds()));
1257  writer().writeUInt64BE(static_cast<uint64>((m_modificationTime - startDate).totalSeconds()));
1258  writer().writeUInt32BE(m_timeScale);
1259  writer().writeUInt64BE(static_cast<uint64>(m_duration.totalSeconds() * m_timeScale));
1260  // convert and write language
1261  uint16 language = 0;
1262  for (size_t charIndex = 0; charIndex != 3; ++charIndex) {
1263  const char langChar = charIndex < m_language.size() ? m_language[charIndex] : 0;
1264  if (langChar >= 'a' && langChar <= 'z') {
1265  language |= static_cast<uint16>(langChar - 0x60) << (0xA - charIndex * 0x5);
1266  } else { // invalid character
1267  diag.emplace_back(
1268  DiagLevel::Warning, "Assigned language \"" % m_language + "\" is of an invalid format and will be ignored.", "making mdhd atom");
1269  language = 0x55C4; // und
1270  break;
1271  }
1272  }
1273  if (m_language.size() > 3) {
1274  diag.emplace_back(
1275  DiagLevel::Warning, "Assigned language \"" % m_language + "\" is longer than 3 byte and hence will be truncated.", "making mdhd atom");
1276  }
1277  writer().writeUInt16BE(language);
1278  writer().writeUInt16BE(0); // pre defined
1279  // write hdlr atom
1280  writer().writeUInt32BE(33 + m_name.size()); // size
1281  writer().writeUInt32BE(Mp4AtomIds::HandlerReference);
1282  writer().writeUInt64BE(0); // version, flags, pre defined
1283  switch (m_mediaType) {
1284  case MediaType::Video:
1285  outputStream().write("vide", 4);
1286  break;
1287  case MediaType::Audio:
1288  outputStream().write("soun", 4);
1289  break;
1290  case MediaType::Hint:
1291  outputStream().write("hint", 4);
1292  break;
1293  case MediaType::Text:
1294  outputStream().write("meta", 4);
1295  break;
1296  default:
1297  diag.emplace_back(DiagLevel::Critical, "Media type is invalid; The media type video is assumed.", "making hdlr atom");
1298  outputStream().write("vide", 4);
1299  break;
1300  }
1301  for (int i = 0; i < 3; ++i)
1302  writer().writeUInt32BE(0); // reserved
1303  writer().writeTerminatedString(m_name);
1304  // write minf atom
1305  makeMediaInfo(diag);
1306  // write size (of mdia atom)
1307  Mp4Atom::seekBackAndWriteAtomSize(outputStream(), mdiaStartOffset);
1308 }
1309 
1315 {
1316  ostream::pos_type minfStartOffset = outputStream().tellp();
1317  writer().writeUInt32BE(0); // write size later
1318  writer().writeUInt32BE(Mp4AtomIds::MediaInformation);
1319  bool dinfAtomWritten = false;
1320  if (m_minfAtom) {
1321  // copy existing vmhd atom
1322  if (Mp4Atom *vmhdAtom = m_minfAtom->childById(Mp4AtomIds::VideoMediaHeader, diag)) {
1323  vmhdAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1324  }
1325  // copy existing smhd atom
1326  if (Mp4Atom *smhdAtom = m_minfAtom->childById(Mp4AtomIds::SoundMediaHeader, diag)) {
1327  smhdAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1328  }
1329  // copy existing hmhd atom
1330  if (Mp4Atom *hmhdAtom = m_minfAtom->childById(Mp4AtomIds::HintMediaHeader, diag)) {
1331  hmhdAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1332  }
1333  // copy existing nmhd atom
1334  if (Mp4Atom *nmhdAtom = m_minfAtom->childById(Mp4AtomIds::NullMediaHeaderBox, diag)) {
1335  nmhdAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1336  }
1337  // copy existing dinf atom
1338  if (Mp4Atom *dinfAtom = m_minfAtom->childById(Mp4AtomIds::DataInformation, diag)) {
1339  dinfAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1340  dinfAtomWritten = true;
1341  }
1342  }
1343  // write dinf atom if not written yet
1344  if (!dinfAtomWritten) {
1345  writer().writeUInt32BE(36); // size
1346  writer().writeUInt32BE(Mp4AtomIds::DataInformation);
1347  // write dref atom
1348  writer().writeUInt32BE(28); // size
1349  writer().writeUInt32BE(Mp4AtomIds::DataReference);
1350  writer().writeUInt32BE(0); // version and flags
1351  writer().writeUInt32BE(1); // entry count
1352  // write url atom
1353  writer().writeUInt32BE(12); // size
1354  writer().writeUInt32BE(Mp4AtomIds::DataEntryUrl);
1355  writer().writeByte(0); // version
1356  writer().writeUInt24BE(0x000001); // flags (media data is in the same file as the movie box)
1357  }
1358  // write stbl atom
1359  // -> just copy existing stbl atom because makeSampleTable() is not fully implemented (yet)
1360  bool stblAtomWritten = false;
1361  if (m_minfAtom) {
1362  if (Mp4Atom *stblAtom = m_minfAtom->childById(Mp4AtomIds::SampleTable, diag)) {
1363  stblAtom->copyPreferablyFromBuffer(outputStream(), diag, nullptr);
1364  stblAtomWritten = true;
1365  }
1366  }
1367  if (!stblAtomWritten) {
1368  diag.emplace_back(DiagLevel::Critical,
1369  "Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch.", "making stbl atom");
1370  }
1371  // write size (of minf atom)
1372  Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset);
1373 }
1374 
1381 {
1382  ostream::pos_type stblStartOffset = outputStream().tellp();
1383  writer().writeUInt32BE(0); // write size later
1384  writer().writeUInt32BE(Mp4AtomIds::SampleTable);
1385  Mp4Atom *stblAtom = m_minfAtom ? m_minfAtom->childById(Mp4AtomIds::SampleTable, diag) : nullptr;
1386  // write stsd atom
1387  if (m_stsdAtom) {
1388  // copy existing stsd atom
1389  m_stsdAtom->copyEntirely(outputStream(), diag, nullptr);
1390  } else {
1391  diag.emplace_back(DiagLevel::Critical, "Unable to make stsd atom from scratch.", "making stsd atom");
1392  throw NotImplementedException();
1393  }
1394  // write stts and ctts atoms
1395  Mp4Atom *sttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::DecodingTimeToSample, diag) : nullptr;
1396  if (sttsAtom) {
1397  // copy existing stts atom
1398  sttsAtom->copyEntirely(outputStream(), diag, nullptr);
1399  } else {
1400  diag.emplace_back(DiagLevel::Critical, "Unable to make stts atom from scratch.", "making stts atom");
1401  throw NotImplementedException();
1402  }
1403  Mp4Atom *cttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::CompositionTimeToSample, diag) : nullptr;
1404  if (cttsAtom) {
1405  // copy existing ctts atom
1406  cttsAtom->copyEntirely(outputStream(), diag, nullptr);
1407  }
1408  // write stsc atom (sample-to-chunk table)
1409  throw NotImplementedException();
1410 
1411  // write stsz atom (sample sizes)
1412 
1413  // write stz2 atom (compact sample sizes)
1414 
1415  // write stco/co64 atom (chunk offset table)
1416 
1417  // write stss atom (sync sample table)
1418 
1419  // write stsh atom (shadow sync sample table)
1420 
1421  // write padb atom (sample padding bits)
1422 
1423  // write stdp atom (sample degradation priority)
1424 
1425  // write sdtp atom (independent and disposable samples)
1426 
1427  // write sbgp atom (sample group description)
1428 
1429  // write sbgp atom (sample-to-group)
1430 
1431  // write sgpd atom (sample group description)
1432 
1433  // write subs atom (sub-sample information)
1434 
1435  // write size (of stbl atom)
1436  Mp4Atom::seekBackAndWriteAtomSize(outputStream(), stblStartOffset);
1437 }
1438 
1440 {
1441  static const string context("parsing MP4 track");
1442  using namespace Mp4AtomIds;
1443  if (!m_trakAtom) {
1444  diag.emplace_back(DiagLevel::Critical, "\"trak\"-atom is null.", context);
1445  throw InvalidDataException();
1446  }
1447 
1448  // get atoms
1449  try {
1450  if (!(m_tkhdAtom = m_trakAtom->childById(TrackHeader, diag))) {
1451  diag.emplace_back(DiagLevel::Critical, "No \"tkhd\"-atom found.", context);
1452  throw InvalidDataException();
1453  }
1454  if (!(m_mdiaAtom = m_trakAtom->childById(Media, diag))) {
1455  diag.emplace_back(DiagLevel::Critical, "No \"mdia\"-atom found.", context);
1456  throw InvalidDataException();
1457  }
1458  if (!(m_mdhdAtom = m_mdiaAtom->childById(MediaHeader, diag))) {
1459  diag.emplace_back(DiagLevel::Critical, "No \"mdhd\"-atom found.", context);
1460  throw InvalidDataException();
1461  }
1462  if (!(m_hdlrAtom = m_mdiaAtom->childById(HandlerReference, diag))) {
1463  diag.emplace_back(DiagLevel::Critical, "No \"hdlr\"-atom found.", context);
1464  throw InvalidDataException();
1465  }
1466  if (!(m_minfAtom = m_mdiaAtom->childById(MediaInformation, diag))) {
1467  diag.emplace_back(DiagLevel::Critical, "No \"minf\"-atom found.", context);
1468  throw InvalidDataException();
1469  }
1470  if (!(m_stblAtom = m_minfAtom->childById(SampleTable, diag))) {
1471  diag.emplace_back(DiagLevel::Critical, "No \"stbl\"-atom found.", context);
1472  throw InvalidDataException();
1473  }
1474  if (!(m_stsdAtom = m_stblAtom->childById(SampleDescription, diag))) {
1475  diag.emplace_back(DiagLevel::Critical, "No \"stsd\"-atom found.", context);
1476  throw InvalidDataException();
1477  }
1478  if (!(m_stcoAtom = m_stblAtom->childById(ChunkOffset, diag)) && !(m_stcoAtom = m_stblAtom->childById(ChunkOffset64, diag))) {
1479  diag.emplace_back(DiagLevel::Critical, "No \"stco\"/\"co64\"-atom found.", context);
1480  throw InvalidDataException();
1481  }
1482  if (!(m_stscAtom = m_stblAtom->childById(SampleToChunk, diag))) {
1483  diag.emplace_back(DiagLevel::Critical, "No \"stsc\"-atom found.", context);
1484  throw InvalidDataException();
1485  }
1486  if (!(m_stszAtom = m_stblAtom->childById(SampleSize, diag)) && !(m_stszAtom = m_stblAtom->childById(CompactSampleSize, diag))) {
1487  diag.emplace_back(DiagLevel::Critical, "No \"stsz\"/\"stz2\"-atom found.", context);
1488  throw InvalidDataException();
1489  }
1490  } catch (const Failure &) {
1491  diag.emplace_back(DiagLevel::Critical, "Unable to parse relevant atoms.", context);
1492  throw InvalidDataException();
1493  }
1494 
1495  BinaryReader &reader = m_trakAtom->reader();
1496 
1497  // read tkhd atom
1498  m_istream->seekg(m_tkhdAtom->startOffset() + 8); // seek to beg, skip size and name
1499  byte atomVersion = reader.readByte(); // read version
1500  uint32 flags = reader.readUInt24BE();
1501  m_enabled = flags & 0x000001;
1502  m_usedInPresentation = flags & 0x000002;
1503  m_usedWhenPreviewing = flags & 0x000004;
1504  switch (atomVersion) {
1505  case 0:
1506  m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1507  m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1508  m_id = reader.readUInt32BE();
1509  break;
1510  case 1:
1511  m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt64BE());
1512  m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt64BE());
1513  m_id = reader.readUInt32BE();
1514  break;
1515  default:
1516  diag.emplace_back(DiagLevel::Critical,
1517  "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.",
1518  context);
1521  m_id = 0;
1522  }
1523 
1524  // read mdhd atom
1525  m_istream->seekg(m_mdhdAtom->dataOffset()); // seek to beg, skip size and name
1526  atomVersion = reader.readByte(); // read version
1527  m_istream->seekg(3, ios_base::cur); // skip flags
1528  switch (atomVersion) {
1529  case 0:
1530  m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1531  m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1532  m_timeScale = reader.readUInt32BE();
1533  m_duration = TimeSpan::fromSeconds(static_cast<double>(reader.readUInt32BE()) / static_cast<double>(m_timeScale));
1534  break;
1535  case 1:
1536  m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt64BE());
1537  m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt64BE());
1538  m_timeScale = reader.readUInt32BE();
1539  m_duration = TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()) / static_cast<double>(m_timeScale));
1540  break;
1541  default:
1542  diag.emplace_back(DiagLevel::Warning,
1543  "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be "
1544  "determined.",
1545  context);
1546  m_timeScale = 0;
1547  m_duration = TimeSpan();
1548  }
1549  uint16 tmp = reader.readUInt16BE();
1550  if (tmp) {
1551  const char buff[] = {
1552  static_cast<char>(((tmp & 0x7C00) >> 0xA) + 0x60),
1553  static_cast<char>(((tmp & 0x03E0) >> 0x5) + 0x60),
1554  static_cast<char>(((tmp & 0x001F) >> 0x0) + 0x60),
1555  };
1556  m_language = string(buff, 3);
1557  } else {
1558  m_language.clear();
1559  }
1560 
1561  // read hdlr atom
1562  // -> seek to begin skipping size, name, version, flags and reserved bytes
1563  m_istream->seekg(m_hdlrAtom->dataOffset() + 8);
1564  // -> track type
1565  switch (reader.readUInt32BE()) {
1566  case 0x76696465:
1568  break;
1569  case 0x736F756E:
1571  break;
1572  case 0x68696E74:
1574  break;
1575  case 0x6D657461:
1576  case 0x74657874:
1578  break;
1579  default:
1581  }
1582  // -> name
1583  m_istream->seekg(12, ios_base::cur); // skip reserved bytes
1584  if ((tmp = m_istream->peek()) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1585  // assume size prefixed string (seems to appear in QuickTime files)
1586  m_istream->seekg(1, ios_base::cur);
1587  m_name = reader.readString(tmp);
1588  } else {
1589  // assume null terminated string (appears in MP4 files)
1590  m_name = reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1591  }
1592 
1593  // read stco atom (only chunk count)
1594  m_chunkOffsetSize = (m_stcoAtom->id() == Mp4AtomIds::ChunkOffset64) ? 8 : 4;
1595  m_istream->seekg(m_stcoAtom->dataOffset() + 4);
1596  m_chunkCount = reader.readUInt32BE();
1597 
1598  // read stsd atom
1599  m_istream->seekg(m_stsdAtom->dataOffset() + 4); // seek to beg, skip size, name, version and flags
1600  uint32 entryCount = reader.readUInt32BE();
1601  Mp4Atom *esDescParentAtom = nullptr;
1602  if (entryCount) {
1603  try {
1604  for (Mp4Atom *codecConfigContainerAtom = m_stsdAtom->firstChild(); codecConfigContainerAtom;
1605  codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1606  codecConfigContainerAtom->parse(diag);
1607  // parse FOURCC
1608  m_formatId = interpretIntegerAsString<uint32>(codecConfigContainerAtom->id());
1609  m_format = FourccIds::fourccToMediaFormat(codecConfigContainerAtom->id());
1610  // parse codecConfigContainerAtom
1611  m_istream->seekg(codecConfigContainerAtom->dataOffset());
1612  switch (codecConfigContainerAtom->id()) {
1613  case FourccIds::Mpeg4Audio:
1615  case FourccIds::Amr:
1616  case FourccIds::Drms:
1617  case FourccIds::Alac:
1619  case FourccIds::Ac3:
1620  case FourccIds::EAc3:
1621  case FourccIds::DolbyMpl:
1622  case FourccIds::Dts:
1623  case FourccIds::DtsH:
1624  case FourccIds::DtsE:
1625  m_istream->seekg(6 + 2, ios_base::cur); // skip reserved bytes, data reference index
1626  tmp = reader.readUInt16BE(); // read sound version
1627  m_istream->seekg(6, ios_base::cur);
1628  m_channelCount = reader.readUInt16BE();
1629  m_bitsPerSample = reader.readUInt16BE();
1630  m_istream->seekg(4, ios_base::cur); // skip reserved bytes (again)
1631  if (!m_samplingFrequency) {
1632  m_samplingFrequency = reader.readUInt32BE() >> 16;
1633  if (codecConfigContainerAtom->id() != FourccIds::DolbyMpl) {
1634  m_samplingFrequency >>= 16;
1635  }
1636  } else {
1637  m_istream->seekg(4, ios_base::cur);
1638  }
1639  if (codecConfigContainerAtom->id() != FourccIds::WindowsMediaAudio) {
1640  switch (tmp) {
1641  case 1:
1642  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1643  break;
1644  case 2:
1645  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1646  break;
1647  default:
1648  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1649  }
1650  if (!esDescParentAtom) {
1651  esDescParentAtom = codecConfigContainerAtom;
1652  }
1653  }
1654  break;
1655  case FourccIds::Mpeg4Video:
1657  case FourccIds::H2633GPP:
1658  case FourccIds::Avc1:
1659  case FourccIds::Avc2:
1660  case FourccIds::Avc3:
1661  case FourccIds::Avc4:
1662  case FourccIds::Drmi:
1663  case FourccIds::Hevc1:
1664  case FourccIds::Hevc2:
1665  m_istream->seekg(6 + 2 + 16, ios_base::cur); // skip reserved bytes, data reference index, and reserved bytes (again)
1666  m_pixelSize.setWidth(reader.readUInt16BE());
1667  m_pixelSize.setHeight(reader.readUInt16BE());
1668  m_resolution.setWidth(static_cast<uint32>(reader.readFixed16BE()));
1669  m_resolution.setHeight(static_cast<uint32>(reader.readFixed16BE()));
1670  m_istream->seekg(4, ios_base::cur); // skip reserved bytes
1671  m_framesPerSample = reader.readUInt16BE();
1672  tmp = reader.readByte();
1673  m_compressorName = reader.readString(31);
1674  if (tmp == 0) {
1675  m_compressorName.clear();
1676  } else if (tmp < 32) {
1677  m_compressorName.resize(tmp);
1678  }
1679  m_depth = reader.readUInt16BE(); // 24: color without alpha
1680  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1681  if (!esDescParentAtom) {
1682  esDescParentAtom = codecConfigContainerAtom;
1683  }
1684  break;
1686  // skip reserved bytes and data reference index
1687  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1688  if (!esDescParentAtom) {
1689  esDescParentAtom = codecConfigContainerAtom;
1690  }
1691  break;
1693  break; // TODO
1695  break; // TODO
1696  default:;
1697  }
1698  }
1699 
1700  if (esDescParentAtom) {
1701  // parse AVC configuration
1702  if (Mp4Atom *avcConfigAtom = esDescParentAtom->childById(Mp4AtomIds::AvcConfiguration, diag)) {
1703  m_istream->seekg(avcConfigAtom->dataOffset());
1704  m_avcConfig = make_unique<TagParser::AvcConfiguration>();
1705  try {
1706  m_avcConfig->parse(reader, avcConfigAtom->dataSize());
1707  addInfo(*m_avcConfig, *this);
1708  } catch (const TruncatedDataException &) {
1709  diag.emplace_back(DiagLevel::Critical, "AVC configuration is truncated.", context);
1710  } catch (const Failure &) {
1711  diag.emplace_back(DiagLevel::Critical, "AVC configuration is invalid.", context);
1712  }
1713  }
1714 
1715  // parse MPEG-4 elementary stream descriptor
1716  Mp4Atom *esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor, diag);
1717  if (!esDescAtom) {
1718  esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor2, diag);
1719  }
1720  if (esDescAtom) {
1721  try {
1722  if ((m_esInfo = parseMpeg4ElementaryStreamInfo(m_reader, esDescAtom, diag))) {
1724  m_bitrate = static_cast<double>(m_esInfo->averageBitrate) / 1000;
1725  m_maxBitrate = static_cast<double>(m_esInfo->maxBitrate) / 1000;
1726  if (m_esInfo->audioSpecificConfig) {
1727  // check the audio specific config for useful information
1728  m_format += Mpeg4AudioObjectIds::idToMediaFormat(m_esInfo->audioSpecificConfig->audioObjectType,
1729  m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1730  if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1731  m_samplingFrequency = m_esInfo->audioSpecificConfig->sampleFrequency;
1732  } else if (m_esInfo->audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1733  m_samplingFrequency = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->sampleFrequencyIndex];
1734  } else {
1735  diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid sample frequency index.", context);
1736  }
1737  if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1738  m_extensionSamplingFrequency = m_esInfo->audioSpecificConfig->extensionSampleFrequency;
1739  } else if (m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1741  = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex];
1742  } else {
1743  diag.emplace_back(
1744  DiagLevel::Warning, "Audio specific config has invalid extension sample frequency index.", context);
1745  }
1746  m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1747  m_extensionChannelConfig = m_esInfo->audioSpecificConfig->extensionChannelConfiguration;
1748  }
1749  if (m_esInfo->videoSpecificConfig) {
1750  // check the video specific config for useful information
1751  if (m_format.general == GeneralMediaFormat::Mpeg4Video && m_esInfo->videoSpecificConfig->profile) {
1752  m_format.sub = m_esInfo->videoSpecificConfig->profile;
1753  if (!m_esInfo->videoSpecificConfig->userData.empty()) {
1754  m_formatId += " / ";
1755  m_formatId += m_esInfo->videoSpecificConfig->userData;
1756  }
1757  }
1758  }
1759  // check the stream data for missing information
1760  switch (m_format.general) {
1763  MpegAudioFrame frame;
1764  m_istream->seekg(m_stcoAtom->dataOffset() + 8);
1765  m_istream->seekg(m_chunkOffsetSize == 8 ? reader.readUInt64BE() : reader.readUInt32BE());
1766  frame.parseHeader(reader);
1767  MpegAudioFrameStream::addInfo(frame, *this);
1768  break;
1769  }
1770  default:;
1771  }
1772  }
1773  } catch (const Failure &) {
1774  }
1775  }
1776  }
1777  } catch (const Failure &) {
1778  diag.emplace_back(DiagLevel::Critical, "Unable to parse child atoms of \"stsd\"-atom.", context);
1779  }
1780  }
1781 
1782  // read stsz atom which holds the sample size table
1783  m_sampleSizes.clear();
1784  m_size = m_sampleCount = 0;
1785  uint64 actualSampleSizeTableSize = m_stszAtom->dataSize();
1786  if (actualSampleSizeTableSize < 12) {
1787  diag.emplace_back(DiagLevel::Critical,
1788  "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1789  } else {
1790  actualSampleSizeTableSize -= 12; // subtract size of version and flags
1791  m_istream->seekg(m_stszAtom->dataOffset() + 4); // seek to beg, skip size, name, version and flags
1792  uint32 fieldSize;
1793  uint32 constantSize;
1794  if (m_stszAtom->id() == Mp4AtomIds::CompactSampleSize) {
1795  constantSize = 0;
1796  m_istream->seekg(3, ios_base::cur); // seek reserved bytes
1797  fieldSize = reader.readByte();
1798  m_sampleCount = reader.readUInt32BE();
1799  } else {
1800  constantSize = reader.readUInt32BE();
1801  m_sampleCount = reader.readUInt32BE();
1802  fieldSize = 32;
1803  }
1804  if (constantSize) {
1805  m_sampleSizes.push_back(constantSize);
1806  m_size = constantSize * m_sampleCount;
1807  } else {
1808  uint64 actualSampleCount = m_sampleCount;
1809  uint64 calculatedSampleSizeTableSize = ceil((0.125 * fieldSize) * m_sampleCount);
1810  if (calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1811  diag.emplace_back(
1812  DiagLevel::Critical, "The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1813  } else if (calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1814  diag.emplace_back(DiagLevel::Critical, "The stsz atom is truncated. It stores less entries as denoted.", context);
1815  actualSampleCount = floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize));
1816  }
1817  m_sampleSizes.reserve(actualSampleCount);
1818  uint32 i = 1;
1819  switch (fieldSize) {
1820  case 4:
1821  for (; i <= actualSampleCount; i += 2) {
1822  byte val = reader.readByte();
1823  m_sampleSizes.push_back(val >> 4);
1824  m_sampleSizes.push_back(val & 0xF0);
1825  m_size += (val >> 4) + (val & 0xF0);
1826  }
1827  if (i <= actualSampleCount + 1) {
1828  m_sampleSizes.push_back(reader.readByte() >> 4);
1829  m_size += m_sampleSizes.back();
1830  }
1831  break;
1832  case 8:
1833  for (; i <= actualSampleCount; ++i) {
1834  m_sampleSizes.push_back(reader.readByte());
1835  m_size += m_sampleSizes.back();
1836  }
1837  break;
1838  case 16:
1839  for (; i <= actualSampleCount; ++i) {
1840  m_sampleSizes.push_back(reader.readUInt16BE());
1841  m_size += m_sampleSizes.back();
1842  }
1843  break;
1844  case 32:
1845  for (; i <= actualSampleCount; ++i) {
1846  m_sampleSizes.push_back(reader.readUInt32BE());
1847  m_size += m_sampleSizes.back();
1848  }
1849  break;
1850  default:
1851  diag.emplace_back(DiagLevel::Critical,
1852  "The fieldsize used to store the sample sizes is not supported. The sample count and size of the track can not be determined.",
1853  context);
1854  }
1855  }
1856  }
1857 
1858  // no sample sizes found, search for trun atoms
1859  uint64 totalDuration = 0;
1860  for (Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingByIdIncludingThis(MovieFragment, diag); moofAtom;
1861  moofAtom = moofAtom->siblingById(MovieFragment, diag)) {
1862  moofAtom->parse(diag);
1863  for (Mp4Atom *trafAtom = moofAtom->childById(TrackFragment, diag); trafAtom; trafAtom = trafAtom->siblingById(TrackFragment, diag)) {
1864  trafAtom->parse(diag);
1865  for (Mp4Atom *tfhdAtom = trafAtom->childById(TrackFragmentHeader, diag); tfhdAtom;
1866  tfhdAtom = tfhdAtom->siblingById(TrackFragmentHeader, diag)) {
1867  tfhdAtom->parse(diag);
1868  uint32 calculatedDataSize = 0;
1869  if (tfhdAtom->dataSize() < calculatedDataSize) {
1870  diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated.", context);
1871  } else {
1872  m_istream->seekg(tfhdAtom->dataOffset() + 1);
1873  uint32 flags = reader.readUInt24BE();
1874  if (m_id == reader.readUInt32BE()) { // check track ID
1875  if (flags & 0x000001) { // base-data-offset present
1876  calculatedDataSize += 8;
1877  }
1878  if (flags & 0x000002) { // sample-description-index present
1879  calculatedDataSize += 4;
1880  }
1881  if (flags & 0x000008) { // default-sample-duration present
1882  calculatedDataSize += 4;
1883  }
1884  if (flags & 0x000010) { // default-sample-size present
1885  calculatedDataSize += 4;
1886  }
1887  if (flags & 0x000020) { // default-sample-flags present
1888  calculatedDataSize += 4;
1889  }
1890  //uint64 baseDataOffset = moofAtom->startOffset();
1891  //uint32 defaultSampleDescriptionIndex = 0;
1892  uint32 defaultSampleDuration = 0;
1893  uint32 defaultSampleSize = 0;
1894  //uint32 defaultSampleFlags = 0;
1895  if (tfhdAtom->dataSize() < calculatedDataSize) {
1896  diag.emplace_back(DiagLevel::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
1897  } else {
1898  if (flags & 0x000001) { // base-data-offset present
1899  //baseDataOffset = reader.readUInt64();
1900  m_istream->seekg(8, ios_base::cur);
1901  }
1902  if (flags & 0x000002) { // sample-description-index present
1903  //defaultSampleDescriptionIndex = reader.readUInt32();
1904  m_istream->seekg(4, ios_base::cur);
1905  }
1906  if (flags & 0x000008) { // default-sample-duration present
1907  defaultSampleDuration = reader.readUInt32BE();
1908  //m_istream->seekg(4, ios_base::cur);
1909  }
1910  if (flags & 0x000010) { // default-sample-size present
1911  defaultSampleSize = reader.readUInt32BE();
1912  }
1913  if (flags & 0x000020) { // default-sample-flags present
1914  //defaultSampleFlags = reader.readUInt32BE();
1915  m_istream->seekg(4, ios_base::cur);
1916  }
1917  }
1918  for (Mp4Atom *trunAtom = trafAtom->childById(TrackFragmentRun, diag); trunAtom;
1919  trunAtom = trunAtom->siblingById(TrackFragmentRun, diag)) {
1920  uint32 calculatedDataSize = 8;
1921  if (trunAtom->dataSize() < calculatedDataSize) {
1922  diag.emplace_back(DiagLevel::Critical, "trun atom is truncated.", context);
1923  } else {
1924  m_istream->seekg(trunAtom->dataOffset() + 1);
1925  uint32 flags = reader.readUInt24BE();
1926  uint32 sampleCount = reader.readUInt32BE();
1928  if (flags & 0x000001) { // data offset present
1929  calculatedDataSize += 4;
1930  }
1931  if (flags & 0x000004) { // first-sample-flags present
1932  calculatedDataSize += 4;
1933  }
1934  uint32 entrySize = 0;
1935  if (flags & 0x000100) { // sample-duration present
1936  entrySize += 4;
1937  }
1938  if (flags & 0x000200) { // sample-size present
1939  entrySize += 4;
1940  }
1941  if (flags & 0x000400) { // sample-flags present
1942  entrySize += 4;
1943  }
1944  if (flags & 0x000800) { // sample-composition-time-offsets present
1945  entrySize += 4;
1946  }
1947  calculatedDataSize += entrySize * sampleCount;
1948  if (trunAtom->dataSize() < calculatedDataSize) {
1949  diag.emplace_back(DiagLevel::Critical, "trun atom is truncated (presence of fields denoted).", context);
1950  } else {
1951  if (flags & 0x000001) { // data offset present
1952  m_istream->seekg(4, ios_base::cur);
1953  //int32 dataOffset = reader.readInt32();
1954  }
1955  if (flags & 0x000004) { // first-sample-flags present
1956  m_istream->seekg(4, ios_base::cur);
1957  }
1958  for (uint32 i = 0; i < sampleCount; ++i) {
1959  if (flags & 0x000100) { // sample-duration present
1960  totalDuration += reader.readUInt32BE();
1961  } else {
1962  totalDuration += defaultSampleDuration;
1963  }
1964  if (flags & 0x000200) { // sample-size present
1965  m_sampleSizes.push_back(reader.readUInt32BE());
1966  m_size += m_sampleSizes.back();
1967  } else {
1968  m_size += defaultSampleSize;
1969  }
1970  if (flags & 0x000400) { // sample-flags present
1971  m_istream->seekg(4, ios_base::cur);
1972  }
1973  if (flags & 0x000800) { // sample-composition-time-offsets present
1974  m_istream->seekg(4, ios_base::cur);
1975  }
1976  }
1977  }
1978  }
1979  }
1980  if (m_sampleSizes.empty() && defaultSampleSize) {
1981  m_sampleSizes.push_back(defaultSampleSize);
1982  }
1983  }
1984  }
1985  }
1986  }
1987  }
1988 
1989  // set duration from "trun-information" if the duration has not been determined yet
1990  if (m_duration.isNull() && totalDuration) {
1991  uint32 timeScale = m_timeScale;
1992  if (!timeScale) {
1993  timeScale = trakAtom().container().timeScale();
1994  }
1995  if (timeScale) {
1996  m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(timeScale));
1997  }
1998  }
1999 
2000  // caluculate average bitrate
2001  if (m_bitrate < 0.01 && m_bitrate > -0.01) {
2002  m_bitrate = (static_cast<double>(m_size) * 0.0078125) / m_duration.totalSeconds();
2003  }
2004 
2005  // read stsc atom (only number of entries)
2006  m_istream->seekg(m_stscAtom->dataOffset() + 4);
2007  m_sampleToChunkEntryCount = reader.readUInt32BE();
2008 }
2009 
2010 } // 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
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:70
void parseHeader(IoUtilities::BinaryReader &reader)
Parses the header read using the specified reader.
void updateChunkOffsets(const std::vector< int64 > &oldMdatOffsets, const std::vector< int64 > &newMdatOffsets)
Updates the chunk offsets of the track.
Definition: mp4track.cpp:893
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:506
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.
AspectRatio pixelAspectRatio
Definition: avcinfo.h:87
TAG_PARSER_EXPORT const char * version()
uint32 timeScale() const
Returns the time scale if known; otherwise returns 0.
void makeBuffer()
Buffers the element (header and data).
const std::unique_ptr< char[]> & buffer()
Returns buffered data.
byte levelIndication
Definition: avcinfo.h:76
The TrackHeaderInfo struct holds information about the present track header (tkhd atom) and informati...
Definition: mp4track.cpp:37
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:1016
std::ostream & outputStream()
Returns the associated output stream.
DataSizeType dataSize() const
Returns the data size of the element in byte.
STL namespace.
uint32 chunkCount() const
Returns the number of chunks denoted by the stco atom.
Definition: mp4track.h:223
GeneralMediaFormat general
Definition: mediaformat.h:256
void updateChunkOffset(uint32 chunkIndex, uint64 offset)
Updates a particular chunk offset.
Definition: mp4track.cpp:995
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:818
void bufferTrackAtoms(Diagnostics &diag)
Buffers all atoms required by the makeTrack() method.
Definition: mp4track.cpp:1052
unsigned int chunkOffsetSize() const
Returns the size of a single chunk offset denotation within the stco atom.
Definition: mp4track.h:215
void makeTrackHeader(Diagnostics &diag)
Makes the track header (tkhd atom) for the track.
Definition: mp4track.cpp:1168
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:651
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:352
uint64 dataOffset() const
Returns the data offset of the element in the related stream.
The AvcConfiguration struct provides a parser for AVC configuration.
void discardBuffer()
Discards buffered data.
Mp4Track(Mp4Atom &trakAtom)
Constructs a new track for the specified trakAtom.
Definition: mp4track.cpp:130
void makeSampleTable(Diagnostics &diag)
Makes the sample table (stbl atom) for the track.
Definition: mp4track.cpp:1380
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:1246
void makeMediaInfo(Diagnostics &diag)
Makes a media information (minf atom) for the track.
Definition: mp4track.cpp:1314
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
static void seekBackAndWriteAtomSize(std::ostream &stream, const std::ostream::pos_type &startOffset)
This function helps to write the atom size after writing an atom to a stream.
Definition: mp4atom.cpp:130
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:558
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:193
TAG_PARSER_EXPORT MediaFormat streamObjectTypeFormat(byte streamObjectTypeId)
Returns the Media::MediaFormat denoted by the specified MPEG-4 stream ID.
Definition: mp4ids.cpp:202
void internalParseHeader(Diagnostics &diag) override
This method is internally called to parse header information.
Definition: mp4track.cpp:1439
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:1142
static void addInfo(const MpegAudioFrame &frame, AbstractTrack &track)
Adds the information from the specified frame to the specified track.
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:1088
uint32 sampleToChunkEntryCount() const
Returns the number of "sample to chunk" entries within the stsc atom.
Definition: mp4track.h:231
ChronoUtilities::DateTime m_modificationTime
Contains all classes and functions of the TagInfo library.
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:456
byte profileIndication
Definition: avcinfo.h:74
const IdentifierType & id() const
Returns the element ID.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
IoUtilities::BinaryWriter & writer()
Returns a binary writer for the associated stream.
TrackType
Specifies the track type.
Definition: abstracttrack.h:28
uint32 mpeg4SamplingFrequencyTable[13]
Definition: mp4ids.cpp:408
~Mp4Track() override
Destroys the track.
Definition: mp4track.cpp:156