Tag Parser  7.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mp4track.cpp
Go to the documentation of this file.
1 #include "./mp4track.h"
2 #include "./mp4atom.h"
3 #include "./mp4container.h"
4 #include "./mp4ids.h"
5 #include "./mpeg4descriptor.h"
6 
7 #include "../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(m_tkhdAtom->buffer().get() + m_tkhdAtom->headerSize() + info.additionalDataOffset,
1223  static_cast<streamoff>(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
This exception is thrown when the an operation is invoked that has not been implemented yet...
Definition: exceptions.h:53
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:32
ugolomb chromaFormatIndication
Definition: avcinfo.h:77
IoUtilities::BinaryReader m_reader
const DateTime startDate
Dates within MP4 tracks are expressed as the number of seconds since this date.
Definition: mp4track.cpp:70
void parseHeader(IoUtilities::BinaryReader &reader)
Parses the header read using the specified reader.
The MpegAudioFrame class is used to parse MPEG audio frames.
void updateChunkOffsets(const std::vector< int64 > &oldMdatOffsets, const std::vector< int64 > &newMdatOffsets)
Updates the chunk offsets of the track.
Definition: mp4track.cpp: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.
The Mpeg4Descriptor class helps to parse MPEG-4 descriptors.
DataSizeType dataSize() const
Returns the data size of the element in byte.
STL namespace.
uint32 chunkCount() const
Returns the number of chunks denoted by the stco atom.
Definition: mp4track.h: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
The Mp4Atom class helps to parse MP4 files.
Definition: mp4atom.h:38
unsigned int chunkOffsetSize() const
Returns the size of a single chunk offset denotation within the stco atom.
Definition: mp4track.h: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 SpsInfo struct holds the sequence parameter set.
Definition: avcinfo.h:71
The AvcConfiguration struct provides a parser for AVC configuration.
Implementation of TagParser::AbstractTrack for the MP4 container.
Definition: mp4track.h:117
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:135
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 TagParser::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
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
static void addInfo(const MpegAudioFrame &frame, AbstractTrack &track)
Adds the information from the specified frame to the specified track.
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:39
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
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
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
const IdentifierType & id() const
Returns the element ID.
ImplementationType * childById(const IdentifierType &id, Diagnostics &diag)
Returns the first child with the specified id.
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
IoUtilities::BinaryWriter & writer()
Returns a binary writer for the associated stream.
TrackType
Specifies the track type.
Definition: abstracttrack.h:28
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:154
uint32 mpeg4SamplingFrequencyTable[13]
Definition: mp4ids.cpp:408
~Mp4Track() override
Destroys the track.
Definition: mp4track.cpp:156