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