Tag Parser  6.1.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
mp4track.cpp
Go to the documentation of this file.
1 #include "./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/io/binaryreader.h>
16 #include <c++utilities/io/binarywriter.h>
17 #include <c++utilities/io/bitreader.h>
18 #include <c++utilities/io/catchiofailure.h>
19 
20 #include <locale>
21 #include <cmath>
22 
23 using namespace std;
24 using namespace IoUtilities;
25 using namespace ConversionUtilities;
26 using namespace ChronoUtilities;
27 
28 namespace Media {
29 
31 const DateTime startDate = DateTime::fromDate(1904, 1, 1);
32 
39 Mpeg4AudioSpecificConfig::Mpeg4AudioSpecificConfig() :
40  audioObjectType(0),
41  sampleFrequencyIndex(0xF),
42  sampleFrequency(0),
43  channelConfiguration(0),
44  extensionAudioObjectType(0),
45  sbrPresent(false),
46  psPresent(false),
47  extensionSampleFrequencyIndex(0xF),
48  extensionSampleFrequency(0),
49  extensionChannelConfiguration(0),
50  frameLengthFlag(false),
51  dependsOnCoreCoder(false),
52  coreCoderDelay(0),
53  extensionFlag(0),
54  layerNr(0),
55  numOfSubFrame(0),
56  layerLength(0),
57  resilienceFlags(0),
58  epConfig(0)
59 {}
60 
70  profile(0)
71 {}
72 
90  AbstractTrack(trakAtom.stream(), trakAtom.startOffset()),
91  m_trakAtom(&trakAtom),
92  m_tkhdAtom(nullptr),
93  m_mdiaAtom(nullptr),
94  m_mdhdAtom(nullptr),
95  m_hdlrAtom(nullptr),
96  m_minfAtom(nullptr),
97  m_stblAtom(nullptr),
98  m_stsdAtom(nullptr),
99  m_stscAtom(nullptr),
100  m_stcoAtom(nullptr),
101  m_stszAtom(nullptr),
102  //m_codecConfigAtom(nullptr),
103  //m_esDescAtom(nullptr),
104  m_framesPerSample(1),
105  m_chunkOffsetSize(4),
106  m_chunkCount(0),
107  m_sampleToChunkEntryCount(0)
108 {}
109 
114 {}
115 
117 {
118  return TrackType::Mp4Track;
119 }
120 
133 {
134  static const string context("reading chunk offset table of MP4 track");
135  if(!isHeaderValid() || !m_istream) {
136  addNotification(NotificationType::Critical, "Track has not been parsed.", context);
137  throw InvalidDataException();
138  }
139  vector<uint64> offsets;
140  if(m_stcoAtom) {
141  // verify integrity of the chunk offset table
142  uint64 actualTableSize = m_stcoAtom->dataSize();
143  if(actualTableSize < (8 + chunkOffsetSize())) {
144  addNotification(NotificationType::Critical, "The stco atom is truncated. There are no chunk offsets present.", context);
145  throw InvalidDataException();
146  } else {
147  actualTableSize -= 8;
148  }
149  uint32 actualChunkCount = chunkCount();
150  uint64 calculatedTableSize = chunkCount() * chunkOffsetSize();
151  if(calculatedTableSize < actualTableSize) {
152  addNotification(NotificationType::Critical, "The stco atom stores more chunk offsets as denoted. The additional chunk offsets will be ignored.", context);
153  } else if(calculatedTableSize > actualTableSize) {
154  addNotification(NotificationType::Critical, "The stco atom is truncated. It stores less chunk offsets as denoted.", context);
155  actualChunkCount = floor(static_cast<double>(actualTableSize) / static_cast<double>(chunkOffsetSize()));
156  }
157  // read the table
158  offsets.reserve(actualChunkCount);
159  m_istream->seekg(m_stcoAtom->dataOffset() + 8);
160  switch(chunkOffsetSize()) {
161  case 4:
162  for(uint32 i = 0; i < actualChunkCount; ++i) {
163  offsets.push_back(reader().readUInt32BE());
164  }
165  break;
166  case 8:
167  for(uint32 i = 0; i < actualChunkCount; ++i) {
168  offsets.push_back(reader().readUInt64BE());
169  }
170  break;
171  default:
172  addNotification(NotificationType::Critical, "The determined chunk offset size is invalid.", context);
173  throw InvalidDataException();
174  }
175  }
176  // read sample offsets of fragments
177  // Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingById(moof, true);
178  // uint64 totalDuration = 0;
179  // while(moofAtom) {
180  // moofAtom->parse();
181  // Mp4Atom *trafAtom = moofAtom->childById(traf);
182  // while(trafAtom) {
183  // trafAtom->parse();
184  // Mp4Atom *tfhdAtom = trafAtom->childById(tfhd);
185  // while(tfhdAtom) {
186  // tfhdAtom->parse();
187  // uint32 calculatedDataSize = 0;
188  // if(tfhdAtom->dataSize() < calculatedDataSize) {
189  // addNotification(NotificationType::Critical, "tfhd atom is truncated.", context);
190  // } else {
191  // m_stream->seekg(tfhdAtom->dataOffset() + 1);
192  // uint32 flags = reader.readUInt24();
193  // if(m_id == reader.readUInt32()) { // check track ID
194  // if(flags & 0x000001) { // base-data-offset present
195  // calculatedDataSize += 8;
196  // }
197  // if(flags & 0x000002) { // sample-description-index present
198  // calculatedDataSize += 4;
199  // }
200  // if(flags & 0x000008) { // default-sample-duration present
201  // calculatedDataSize += 4;
202  // }
203  // if(flags & 0x000010) { // default-sample-size present
204  // calculatedDataSize += 4;
205  // }
206  // if(flags & 0x000020) { // default-sample-flags present
207  // calculatedDataSize += 4;
208  // }
209  // //uint64 baseDataOffset = moofAtom->startOffset();
210  // //uint32 defaultSampleDescriptionIndex = 0;
211  // uint32 defaultSampleDuration = 0;
212  // uint32 defaultSampleSize = 0;
213  // uint32 defaultSampleFlags = 0;
214  // if(tfhdAtom->dataSize() < calculatedDataSize) {
215  // addNotification(NotificationType::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
216  // } else {
217  // if(flags & 0x000001) { // base-data-offset present
218  // //baseDataOffset = reader.readUInt64();
219  // m_stream->seekg(8, ios_base::cur);
220  // }
221  // if(flags & 0x000002) { // sample-description-index present
222  // //defaultSampleDescriptionIndex = reader.readUInt32();
223  // m_stream->seekg(4, ios_base::cur);
224  // }
225  // if(flags & 0x000008) { // default-sample-duration present
226  // defaultSampleDuration = reader.readUInt32();
227  // //m_stream->seekg(4, ios_base::cur);
228  // }
229  // if(flags & 0x000010) { // default-sample-size present
230  // defaultSampleSize = reader.readUInt32();
231  // }
232  // if(flags & 0x000020) { // default-sample-flags present
233  // defaultSampleFlags = reader.readUInt32();
234  // //m_stream->seekg(4, ios_base::cur);
235  // }
236  // }
237  // Mp4Atom *trunAtom = trafAtom->childById(trun);
238  // while(trunAtom) {
239  // uint32 calculatedDataSize = 8;
240  // if(trunAtom->dataSize() < calculatedDataSize) {
241  // addNotification(NotificationType::Critical, "trun atom is truncated.", context);
242  // } else {
243  // m_stream->seekg(trunAtom->dataOffset() + 1);
244  // uint32 flags = reader.readUInt24();
245  // uint32 sampleCount = reader.readUInt32();
246  // m_sampleCount += sampleCount;
247  // if(flags & 0x000001) { // data offset present
248  // calculatedDataSize += 4;
249  // }
250  // if(flags & 0x000004) { // first-sample-flags present
251  // calculatedDataSize += 4;
252  // }
253  // uint32 entrySize = 0;
254  // if(flags & 0x000100) { // sample-duration present
255  // entrySize += 4;
256  // }
257  // if(flags & 0x000200) { // sample-size present
258  // entrySize += 4;
259  // }
260  // if(flags & 0x000400) { // sample-flags present
261  // entrySize += 4;
262  // }
263  // if(flags & 0x000800) { // sample-composition-time-offsets present
264  // entrySize += 4;
265  // }
266  // calculatedDataSize += entrySize * sampleCount;
267  // if(trunAtom->dataSize() < calculatedDataSize) {
268  // addNotification(NotificationType::Critical, "trun atom is truncated (presence of fields denoted).", context);
269  // } else {
270  // if(flags & 0x000001) { // data offset present
271  // m_stream->seekg(4, ios_base::cur);
272  // //int32 dataOffset = reader.readInt32();
273  // }
274  // if(flags & 0x000004) { // first-sample-flags present
275  // m_stream->seekg(4, ios_base::cur);
276  // }
277  // for(uint32 i = 0; i < sampleCount; ++i) {
278  // if(flags & 0x000100) { // sample-duration present
279  // totalDuration += reader.readUInt32();
280  // } else {
281  // totalDuration += defaultSampleDuration;
282  // }
283  // if(flags & 0x000200) { // sample-size present
284  // m_sampleSizes.push_back(reader.readUInt32());
285  // m_size += m_sampleSizes.back();
286  // } else {
287  // m_size += defaultSampleSize;
288  // }
289  // if(flags & 0x000400) { // sample-flags present
290  // m_stream->seekg(4, ios_base::cur);
291  // }
292  // if(flags & 0x000800) { // sample-composition-time-offsets present
293  // m_stream->seekg(4, ios_base::cur);
294  // }
295  // }
296  // }
297  // }
298  // trunAtom = trunAtom->siblingById(trun, false);
299  // }
300  // if(m_sampleSizes.empty() && defaultSampleSize) {
301  // m_sampleSizes.push_back(defaultSampleSize);
302  // }
303  // }
304  // }
305  // tfhdAtom = tfhdAtom->siblingById(tfhd, false);
306  // }
307  // trafAtom = trafAtom->siblingById(traf, false);
308  // }
309  // moofAtom = moofAtom->siblingById(moof, false);
310  // }
311  return offsets;
312 }
313 
318 uint64 Mp4Track::accumulateSampleSizes(size_t &sampleIndex, size_t count)
319 {
320  if(sampleIndex + count <= m_sampleSizes.size()) {
321  uint64 sum = 0;
322  for(size_t end = sampleIndex + count; sampleIndex < end; ++sampleIndex) {
323  sum += m_sampleSizes[sampleIndex];
324  }
325  return sum;
326  } else if(m_sampleSizes.size() == 1) {
327  sampleIndex += count;
328  return static_cast<uint64>(m_sampleSizes.front()) * count;
329  } else {
330  addNotification(NotificationType::Critical, "There are not as many sample size entries as samples.", "reading chunk sizes of MP4 track");
331  throw InvalidDataException();
332  }
333 }
334 
343 void Mp4Track::addChunkSizeEntries(std::vector<uint64> &chunkSizeTable, size_t count, size_t &sampleIndex, uint32 sampleCount)
344 {
345  for(size_t i = 0; i < count; ++i) {
346  chunkSizeTable.push_back(accumulateSampleSizes(sampleIndex, sampleCount));
347  }
348 }
349 
357 vector<tuple<uint32, uint32, uint32> > Mp4Track::readSampleToChunkTable()
358 {
359  static const string context("reading sample to chunk table of MP4 track");
360  if(!isHeaderValid() || !m_istream || !m_stscAtom) {
361  addNotification(NotificationType::Critical, "Track has not been parsed or is invalid.", context);
362  throw InvalidDataException();
363  }
364  // verify integrity of the sample to chunk table
365  uint64 actualTableSize = m_stscAtom->dataSize();
366  if(actualTableSize < 20) {
367  addNotification(NotificationType::Critical, "The stsc atom is truncated. There are no \"sample to chunk\" entries present.", context);
368  throw InvalidDataException();
369  } else {
370  actualTableSize -= 8;
371  }
372  uint32 actualSampleToChunkEntryCount = sampleToChunkEntryCount();
373  uint64 calculatedTableSize = actualSampleToChunkEntryCount * 12;
374  if(calculatedTableSize < actualTableSize) {
375  addNotification(NotificationType::Critical, "The stsc atom stores more entries as denoted. The additional entries will be ignored.", context);
376  } else if(calculatedTableSize > actualTableSize) {
377  addNotification(NotificationType::Critical, "The stsc atom is truncated. It stores less entries as denoted.", context);
378  actualSampleToChunkEntryCount = floor(static_cast<double>(actualTableSize) / 12.0);
379  }
380  // prepare reading
381  vector<tuple<uint32, uint32, uint32> > sampleToChunkTable;
382  sampleToChunkTable.reserve(actualSampleToChunkEntryCount);
383  m_istream->seekg(m_stscAtom->dataOffset() + 8);
384  for(uint32 i = 0; i < actualSampleToChunkEntryCount; ++i) {
385  // read entry
386  uint32 firstChunk = reader().readUInt32BE();
387  uint32 samplesPerChunk = reader().readUInt32BE();
388  uint32 sampleDescriptionIndex = reader().readUInt32BE();
389  sampleToChunkTable.emplace_back(firstChunk, samplesPerChunk, sampleDescriptionIndex);
390  }
391  return sampleToChunkTable;
392 }
393 
406 vector<uint64> Mp4Track::readChunkSizes()
407 {
408  static const string context("reading chunk sizes of MP4 track");
409  if(!isHeaderValid() || !m_istream || !m_stcoAtom) {
410  addNotification(NotificationType::Critical, "Track has not been parsed or is invalid.", context);
411  throw InvalidDataException();
412  }
413  // read sample to chunk table
414  const auto sampleToChunkTable = readSampleToChunkTable();
415  // accumulate chunk sizes from the table
416  vector<uint64> chunkSizes;
417  if(!sampleToChunkTable.empty()) {
418  // prepare reading
419  auto tableIterator = sampleToChunkTable.cbegin();
420  chunkSizes.reserve(m_chunkCount);
421  // read first entry
422  size_t sampleIndex = 0;
423  uint32 previousChunkIndex = get<0>(*tableIterator); // the first chunk has the index 1 and not zero!
424  if(previousChunkIndex != 1) {
425  addNotification(NotificationType::Critical, "The first chunk of the first \"sample to chunk\" entry must be 1.", context);
426  previousChunkIndex = 1; // try to read the entry anyway
427  }
428  uint32 samplesPerChunk = get<1>(*tableIterator);
429  // read the following entries
430  ++tableIterator;
431  for(const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
432  uint32 firstChunkIndex = get<0>(*tableIterator);
433  if(firstChunkIndex > previousChunkIndex && firstChunkIndex <= m_chunkCount) {
434  addChunkSizeEntries(chunkSizes, firstChunkIndex - previousChunkIndex, sampleIndex, samplesPerChunk);
435  } else {
437  "The first chunk index of a \"sample to chunk\" entry must be greather then the first chunk of the previous entry and not greather then the chunk count.", context);
438  throw InvalidDataException();
439  }
440  previousChunkIndex = firstChunkIndex;
441  samplesPerChunk = get<1>(*tableIterator);
442  }
443  if(m_chunkCount >= previousChunkIndex) {
444  addChunkSizeEntries(chunkSizes, m_chunkCount + 1 - previousChunkIndex, sampleIndex, samplesPerChunk);
445  }
446  }
447  return chunkSizes;
448 }
449 
456 std::unique_ptr<Mpeg4ElementaryStreamInfo> Mp4Track::parseMpeg4ElementaryStreamInfo(StatusProvider &statusProvider, IoUtilities::BinaryReader &reader, Mp4Atom *esDescAtom)
457 {
458  static const string context("parsing MPEG-4 elementary stream descriptor");
459  using namespace Mpeg4ElementaryStreamObjectIds;
460  unique_ptr<Mpeg4ElementaryStreamInfo> esInfo;
461  if(esDescAtom->dataSize() >= 12) {
462  reader.stream()->seekg(esDescAtom->dataOffset());
463  // read version/flags
464  if(reader.readUInt32BE() != 0) {
465  statusProvider.addNotification(NotificationType::Warning, "Unknown version/flags.", context);
466  }
467  // read extended descriptor
468  Mpeg4Descriptor esDesc(esDescAtom->container(), reader.stream()->tellg(), esDescAtom->dataSize() - 4);
469  try {
470  esDesc.parse();
471  // check ID
472  if(esDesc.id() != Mpeg4DescriptorIds::ElementaryStreamDescr) {
473  statusProvider.addNotification(NotificationType::Critical, "Invalid descriptor found.", context);
474  throw Failure();
475  }
476  // read stream info
477  reader.stream()->seekg(esDesc.dataOffset());
478  esInfo = make_unique<Mpeg4ElementaryStreamInfo>();
479  esInfo->id = reader.readUInt16BE();
480  esInfo->esDescFlags = reader.readByte();
481  if(esInfo->dependencyFlag()) {
482  esInfo->dependsOnId = reader.readUInt16BE();
483  }
484  if(esInfo->urlFlag()) {
485  esInfo->url = reader.readString(reader.readByte());
486  }
487  if(esInfo->ocrFlag()) {
488  esInfo->ocrId = reader.readUInt16BE();
489  }
490  for(Mpeg4Descriptor *esDescChild = esDesc.denoteFirstChild(static_cast<uint64>(reader.stream()->tellg()) - esDesc.startOffset()); esDescChild; esDescChild = esDescChild->nextSibling()) {
491  esDescChild->parse();
492  switch(esDescChild->id()) {
494  // read decoder config descriptor
495  reader.stream()->seekg(esDescChild->dataOffset());
496  esInfo->objectTypeId = reader.readByte();
497  esInfo->decCfgDescFlags = reader.readByte();
498  esInfo->bufferSize = reader.readUInt24BE();
499  esInfo->maxBitrate = reader.readUInt32BE();
500  esInfo->averageBitrate = reader.readUInt32BE();
501  for(Mpeg4Descriptor *decCfgDescChild = esDescChild->denoteFirstChild(esDescChild->headerSize() + 13); decCfgDescChild; decCfgDescChild = decCfgDescChild->nextSibling()) {
502  decCfgDescChild->parse();
503  switch(decCfgDescChild->id()) {
505  // read decoder specific info
506  switch(esInfo->objectTypeId) {
509  esInfo->audioSpecificConfig = parseAudioSpecificConfig(statusProvider, *reader.stream(), decCfgDescChild->dataOffset(), decCfgDescChild->dataSize());
510  break;
511  case Mpeg4Visual:
512  esInfo->videoSpecificConfig = parseVideoSpecificConfig(statusProvider, reader, decCfgDescChild->dataOffset(), decCfgDescChild->dataSize());
513  break;
514  default:
515  ; // TODO: cover more object types
516  }
517  break;
518  }
519  }
520  break;
522  // uninteresting
523  break;
524  }
525  }
526  } catch (Failure &) {
527  statusProvider.addNotification(NotificationType::Critical, "The MPEG-4 descriptor element structure is invalid.", context);
528  }
529  } else {
530  statusProvider.addNotification(NotificationType::Warning, "Elementary stream descriptor atom (esds) is truncated.", context);
531  }
532  return esInfo;
533 }
534 
541 unique_ptr<Mpeg4AudioSpecificConfig> Mp4Track::parseAudioSpecificConfig(StatusProvider &statusProvider, istream &stream, uint64 startOffset, uint64 size)
542 {
543  static const string context("parsing MPEG-4 audio specific config from elementary stream descriptor");
544  using namespace Mpeg4AudioObjectIds;
545  // read config into buffer and construct BitReader for bitwise reading
546  stream.seekg(startOffset);
547  auto buff = make_unique<char []>(size);
548  stream.read(buff.get(), size);
549  BitReader bitReader(buff.get(), size);
550  auto audioCfg = make_unique<Mpeg4AudioSpecificConfig>();
551  try {
552  // read audio object type
553  auto getAudioObjectType = [&audioCfg, &bitReader] {
554  byte objType = bitReader.readBits<byte>(5);
555  if(objType == 31) {
556  objType = 32 + bitReader.readBits<byte>(6);
557  }
558  return objType;
559  };
560  audioCfg->audioObjectType = getAudioObjectType();
561  // read sampling frequency
562  if((audioCfg->sampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
563  audioCfg->sampleFrequency = bitReader.readBits<uint32>(24);
564  }
565  // read channel config
566  audioCfg->channelConfiguration = bitReader.readBits<byte>(4);
567  // read extension header
568  switch(audioCfg->audioObjectType) {
569  case Sbr:
570  case Ps:
571  audioCfg->extensionAudioObjectType = audioCfg->audioObjectType;
572  audioCfg->sbrPresent = true;
573  if((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
574  audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
575  }
576  if((audioCfg->audioObjectType = getAudioObjectType()) == ErBsac) {
577  audioCfg->extensionChannelConfiguration = bitReader.readBits<byte>(4);
578  }
579  break;
580  }
581  switch(audioCfg->extensionAudioObjectType) {
582  case Ps:
583  audioCfg->psPresent = true;
584  audioCfg->extensionChannelConfiguration = Mpeg4ChannelConfigs::FrontLeftFrontRight;
585  break;
586  }
587  // read GA specific config
588  switch(audioCfg->audioObjectType) {
589  case AacMain: case AacLc: case AacLtp: case AacScalable:
590  case TwinVq: case ErAacLc: case ErAacLtp: case ErAacScalable:
591  case ErTwinVq: case ErBsac: case ErAacLd:
592  audioCfg->frameLengthFlag = bitReader.readBits<byte>(1);
593  if((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
594  audioCfg->coreCoderDelay = bitReader.readBits<byte>(14);
595  }
596  audioCfg->extensionFlag = bitReader.readBit();
597  if(audioCfg->channelConfiguration == 0) {
598  throw NotImplementedException(); // TODO: parse program_config_element
599  }
600  switch(audioCfg->audioObjectType) {
601  case AacScalable: case ErAacScalable:
602  audioCfg->layerNr = bitReader.readBits<byte>(3);
603  break;
604  default:
605  ;
606  }
607  if(audioCfg->extensionFlag == 1) {
608  switch(audioCfg->audioObjectType) {
609  case ErBsac:
610  audioCfg->numOfSubFrame = bitReader.readBits<byte>(5);
611  audioCfg->layerLength = bitReader.readBits<uint16>(11);
612  break;
613  case ErAacLc: case ErAacLtp: case ErAacScalable: case ErAacLd:
614  audioCfg->resilienceFlags = bitReader.readBits<byte>(3);
615  break;
616  default:
617  ;
618  }
619  if(bitReader.readBit() == 1) { // extension flag 3
620  throw NotImplementedException(); // TODO
621  }
622  }
623  break;
624  default:
625  throw NotImplementedException(); // TODO: cover remaining object types
626  }
627  // read error specific config
628  switch(audioCfg->audioObjectType) {
629  case ErAacLc: case ErAacLtp: case ErAacScalable: case ErTwinVq:
630  case ErBsac: case ErAacLd: case ErCelp: case ErHvxc: case ErHiln:
631  case ErParametric: case ErAacEld:
632  switch(audioCfg->epConfig = bitReader.readBits<byte>(2)) {
633  case 2:
634  break;
635  case 3:
636  bitReader.skipBits(1);
637  break;
638  default:
639  throw NotImplementedException(); // TODO
640  }
641  break;
642  }
643  if(audioCfg->extensionAudioObjectType != Sbr && audioCfg->extensionAudioObjectType != Ps && bitReader.bitsAvailable() >= 16) {
644  uint16 syncExtensionType = bitReader.readBits<uint16>(11);
645  if(syncExtensionType == 0x2B7) {
646  if((audioCfg->extensionAudioObjectType = getAudioObjectType()) == Sbr) {
647  if((audioCfg->sbrPresent = bitReader.readBit())) {
648  if((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
649  audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
650  }
651  if(bitReader.bitsAvailable() >= 12) {
652  if((syncExtensionType = bitReader.readBits<uint16>(11)) == 0x548) {
653  audioCfg->psPresent = bitReader.readBits<byte>(1);
654  }
655  }
656  }
657  } else if(audioCfg->extensionAudioObjectType == ErBsac) {
658  if((audioCfg->sbrPresent = bitReader.readBit())) {
659  if((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<byte>(4)) == 0xF) {
660  audioCfg->extensionSampleFrequency = bitReader.readBits<uint32>(24);
661  }
662  }
663  audioCfg->extensionChannelConfiguration = bitReader.readBits<byte>(4);
664  }
665  } else if (syncExtensionType == 0x548) {
666  audioCfg->psPresent = bitReader.readBit();
667  }
668  }
669  } catch(const NotImplementedException &) {
670  statusProvider.addNotification(NotificationType::Information, "Not implemented for the format of audio track.", context);
671  } catch(...) {
672  const char *what = catchIoFailure();
673  if(stream.fail()) {
674  // IO error caused by input stream
675  throwIoFailure(what);
676  } else {
677  // IO error caused by bitReader
678  statusProvider.addNotification(NotificationType::Critical, "Audio specific configuration is truncated.", context);
679  }
680  }
681  return audioCfg;
682 }
683 
690 std::unique_ptr<Mpeg4VideoSpecificConfig> Mp4Track::parseVideoSpecificConfig(StatusProvider &statusProvider, BinaryReader &reader, uint64 startOffset, uint64 size)
691 {
692  static const string context("parsing MPEG-4 video specific config from elementary stream descriptor");
693  using namespace Mpeg4AudioObjectIds;
694  auto videoCfg = make_unique<Mpeg4VideoSpecificConfig>();
695  // seek to start
696  reader.stream()->seekg(startOffset);
697  if(size > 3 && (reader.readUInt24BE() == 1)) {
698  size -= 3;
699  uint32 buff1;
700  while(size) {
701  --size;
702  switch(reader.readByte()) { // read start code
704  if(size) {
705  videoCfg->profile = reader.readByte();
706  --size;
707  }
708  break;
710 
711  break;
713  buff1 = 0;
714  while(size >= 3) {
715  if((buff1 = reader.readUInt24BE()) != 1) {
716  reader.stream()->seekg(-2, ios_base::cur);
717  videoCfg->userData.push_back(buff1 >> 16);
718  --size;
719  } else {
720  size -= 3;
721  break;
722  }
723  }
724  if(buff1 != 1 && size > 0) {
725  videoCfg->userData += reader.readString(size);
726  size = 0;
727  }
728  break;
729  default:
730  ;
731  }
732  // skip remainging values to get the start of the next video object
733  while(size >= 3) {
734  if(reader.readUInt24BE() != 1) {
735  reader.stream()->seekg(-2, ios_base::cur);
736  --size;
737  } else {
738  size -= 3;
739  break;
740  }
741  }
742  }
743  } else {
744  statusProvider.addNotification(NotificationType::Critical, "\"Visual Object Sequence Header\" not found.", context);
745  }
746  return videoCfg;
747 }
748 
766 void Mp4Track::updateChunkOffsets(const vector<int64> &oldMdatOffsets, const vector<int64> &newMdatOffsets)
767 {
768  if(!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
769  throw InvalidDataException();
770  }
771  if(oldMdatOffsets.size() == 0 || oldMdatOffsets.size() != newMdatOffsets.size()) {
772  throw InvalidDataException();
773  }
774  static const unsigned int stcoDataBegin = 8;
775  uint64 startPos = m_stcoAtom->dataOffset() + stcoDataBegin;
776  uint64 endPos = startPos + m_stcoAtom->dataSize() - stcoDataBegin;
777  m_istream->seekg(startPos);
778  m_ostream->seekp(startPos);
779  vector<int64>::size_type i;
780  vector<int64>::size_type size;
781  uint64 currentPos = m_istream->tellg();
782  switch(m_stcoAtom->id()) {
784  uint32 off;
785  while((currentPos + 4) <= endPos) {
786  off = m_reader.readUInt32BE();
787  for(i = 0, size = oldMdatOffsets.size(); i < size; ++i) {
788  if(off > static_cast<uint64>(oldMdatOffsets[i])) {
789  off += (newMdatOffsets[i] - oldMdatOffsets[i]);
790  break;
791  }
792  }
793  m_ostream->seekp(currentPos);
794  m_writer.writeUInt32BE(off);
795  currentPos += m_istream->gcount();
796  }
797  break;
798  } case Mp4AtomIds::ChunkOffset64: {
799  uint64 off;
800  while((currentPos + 8) <= endPos) {
801  off = m_reader.readUInt64BE();
802  for(i = 0, size = oldMdatOffsets.size(); i < size; ++i) {
803  if(off > static_cast<uint64>(oldMdatOffsets[i])) {
804  off += (newMdatOffsets[i] - oldMdatOffsets[i]);
805  break;
806  }
807  }
808  m_ostream->seekp(currentPos);
809  m_writer.writeUInt64BE(off);
810  currentPos += m_istream->gcount();
811  }
812  break;
813  }
814  default:
815  throw InvalidDataException();
816  }
817 }
818 
831 void Mp4Track::updateChunkOffsets(const std::vector<uint64> &chunkOffsets)
832 {
833  if(!isHeaderValid() || !m_ostream || !m_istream || !m_stcoAtom) {
834  throw InvalidDataException();
835  }
836  if(chunkOffsets.size() != chunkCount()) {
837  throw InvalidDataException();
838  }
839  m_ostream->seekp(m_stcoAtom->dataOffset() + 8);
840  switch(m_stcoAtom->id()) {
842  for(auto offset : chunkOffsets) {
843  m_writer.writeUInt32BE(offset);
844  }
845  break;
847  for(auto offset : chunkOffsets) {
848  m_writer.writeUInt64BE(offset);
849  }
850  default:
851  throw InvalidDataException();
852  }
853 }
854 
867 void Mp4Track::updateChunkOffset(uint32 chunkIndex, uint64 offset)
868 {
869  if(!isHeaderValid() || !m_istream || !m_stcoAtom || chunkIndex >= m_chunkCount) {
870  throw InvalidDataException();
871  }
872  m_ostream->seekp(m_stcoAtom->dataOffset() + 8 + chunkOffsetSize() * chunkIndex);
873  switch(chunkOffsetSize()) {
874  case 4:
875  writer().writeUInt32BE(offset);
876  break;
877  case 8:
878  writer().writeUInt64BE(offset);
879  break;
880  default:
881  throw InvalidDataException();
882  }
883 }
884 
888 void Mp4Track::addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
889 {
890  if(!avcConfig.spsInfos.empty()) {
891  const SpsInfo &spsInfo = avcConfig.spsInfos.back();
892  track.m_format.sub = spsInfo.profileIndication;
893  track.m_version = static_cast<double>(spsInfo.levelIndication) / 10;
894  track.m_cropping = spsInfo.cropping;
895  track.m_pixelSize = spsInfo.pictureSize;
896  switch(spsInfo.chromaFormatIndication) {
897  case 0:
898  track.m_chromaFormat = "monochrome";
899  break;
900  case 1:
901  track.m_chromaFormat = "YUV 4:2:0";
902  break;
903  case 2:
904  track.m_chromaFormat = "YUV 4:2:2";
905  break;
906  case 3:
907  track.m_chromaFormat = "YUV 4:4:4";
908  break;
909  default:
910  ;
911  }
912  track.m_pixelAspectRatio = spsInfo.pixelAspectRatio;
913  } else {
914  track.m_format.sub = avcConfig.profileIndication;
915  track.m_version = static_cast<double>(avcConfig.levelIndication) / 10;
916  }
917 
918 }
919 
926 {
927  /*
928  // write header
929  ostream::pos_type trakStartOffset = outputStream().tellp();
930  writer.writeUInt32(0); // write size later
931  writer.writeUInt32(Mp4AtomIds::Track);
932  // write tkhd atom
933  makeTrackHeader();
934  // write tref atom (if one exists)
935  if(Mp4Atom *trefAtom = trakAtom().childById(Mp4AtomIds::TrackReference)) {
936  trefAtom->copyEntireAtomToStream(outputStream());
937  }
938  // write edts atom (if one exists)
939  if(Mp4Atom *edtsAtom = trakAtom().childById(Mp4AtomIds::Edit)) {
940  edtsAtom->copyEntireAtomToStream(outputStream());
941  }
942  // write mdia atom
943  makeMedia();
944  // write size (of trak atom)
945  Mp4Atom::seekBackAndWriteAtomSize(outputStream(), trakStartOffset, false);
946  */
948 }
949 
954 {
955  return m_trakAtom->totalSize();
956 }
957 
963 {
964  writer().writeUInt32BE(100); // size
965  writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
966  writer().writeByte(1); // version
967  uint32 flags = 0;
968  if(m_enabled) {
969  flags |= 0x000001;
970  }
972  flags |= 0x000002;
973  }
975  flags |= 0x000004;
976  }
977  writer().writeUInt24BE(flags);
978  writer().writeUInt64BE(static_cast<uint64>((m_creationTime - startDate).totalSeconds()));
979  writer().writeUInt64BE(static_cast<uint64>((m_modificationTime - startDate).totalSeconds()));
980  writer().writeUInt32BE(m_id);
981  writer().writeUInt32BE(0); // reserved
982  writer().writeUInt64BE(static_cast<uint64>(m_duration.totalSeconds() * m_timeScale));
983  writer().writeUInt32BE(0); // reserved
984  writer().writeUInt32BE(0); // reserved
985  if(m_tkhdAtom) {
986  // use existing values
987  char buffer[48];
988  m_istream->seekg(m_tkhdAtom->startOffset() + 52);
989  m_istream->read(buffer, sizeof(buffer));
990  m_ostream->write(buffer, sizeof(buffer));
991  } else {
992  // write default values
993  writer().writeInt16BE(0); // layer
994  writer().writeInt16BE(0); // alternate group
995  writer().writeFixed8BE(1.0); // volume
996  writer().writeUInt16BE(0); // reserved
997  for(int32 value : {0x00010000,0,0,0,0x00010000,0,0,0,0x40000000}) { // unity matrix
998  writer().writeInt32BE(value);
999  }
1000  writer().writeFixed16BE(1.0); // width
1001  writer().writeFixed16BE(1.0); // height
1002  }
1003 }
1004 
1010 {
1011  ostream::pos_type mdiaStartOffset = outputStream().tellp();
1012  writer().writeUInt32BE(0); // write size later
1013  writer().writeUInt32BE(Mp4AtomIds::Media);
1014  // write mdhd atom
1015  writer().writeUInt32BE(36); // size
1016  writer().writeByte(1); // version
1017  writer().writeUInt24BE(0); // flags
1018  writer().writeUInt64BE(static_cast<uint64>((m_creationTime - startDate).totalSeconds()));
1019  writer().writeUInt64BE(static_cast<uint64>((m_modificationTime - startDate).totalSeconds()));
1020  writer().writeUInt32BE(m_timeScale);
1021  writer().writeUInt64BE(static_cast<uint64>(m_duration.totalSeconds() * m_timeScale));
1022  // convert and write language
1023  uint16 language = 0;
1024  for(size_t charIndex = 0; charIndex < m_language.length() && charIndex < 3; ++charIndex) {
1025  if(m_language[charIndex] >= 'a' && m_language[charIndex] <= 'z') {
1026  language |= static_cast<uint16>(m_language[charIndex]) << (0xA - charIndex * 0x5);
1027  } else { // invalid character
1028  addNotification(NotificationType::Warning, "Assigned language \"" + m_language + "\" is of an invalid format and will be ignored.", "making mdhd atom");
1029  language = 0x55C4; // und
1030  break;
1031  }
1032  }
1033  writer().writeUInt16BE(language);
1034  writer().writeUInt16BE(0); // pre defined
1035  // write hdlr atom
1036  writer().writeUInt32BE(33 + m_name.length()); // size
1037  writer().writeUInt32BE(Mp4AtomIds::HandlerReference);
1038  writer().writeUInt64BE(0); // version, flags, pre defined
1039  switch(m_mediaType) {
1040  case MediaType::Video:
1041  outputStream().write("vide", 4);
1042  break;
1043  case MediaType::Audio:
1044  outputStream().write("soun", 4);
1045  break;
1046  case MediaType::Hint:
1047  outputStream().write("hint", 4);
1048  break;
1049  case MediaType::Text:
1050  outputStream().write("meta", 4);
1051  break;
1052  default:
1053  addNotification(NotificationType::Critical, "Media type is invalid; The media type video is assumed.", "making hdlr atom");
1054  outputStream().write("vide", 4);
1055  break;
1056  }
1057  for(int i = 0; i < 3; ++i) writer().writeUInt32BE(0); // reserved
1058  writer().writeTerminatedString(m_name);
1059  // write minf atom
1060  makeMediaInfo();
1061  // write size (of mdia atom)
1062  Mp4Atom::seekBackAndWriteAtomSize(outputStream(), mdiaStartOffset);
1063 }
1064 
1070 {
1071  ostream::pos_type minfStartOffset = outputStream().tellp();
1072  writer().writeUInt32BE(0); // write size later
1073  writer().writeUInt32BE(Mp4AtomIds::MediaInformation);
1074  bool dinfAtomWritten = false;
1075  if(m_minfAtom) {
1076  // copy existing vmhd atom
1077  if(Mp4Atom *vmhdAtom = m_minfAtom->childById(Mp4AtomIds::VideoMediaHeader)) {
1078  vmhdAtom->copyEntirely(outputStream());
1079  }
1080  // copy existing smhd atom
1081  if(Mp4Atom *smhdAtom = m_minfAtom->childById(Mp4AtomIds::SoundMediaHeader)) {
1082  smhdAtom->copyEntirely(outputStream());
1083  }
1084  // copy existing hmhd atom
1085  if(Mp4Atom *hmhdAtom = m_minfAtom->childById(Mp4AtomIds::HintMediaHeader)) {
1086  hmhdAtom->copyEntirely(outputStream());
1087  }
1088  // copy existing nmhd atom
1089  if(Mp4Atom *nmhdAtom = m_minfAtom->childById(Mp4AtomIds::NullMediaHeaderBox)) {
1090  nmhdAtom->copyEntirely(outputStream());
1091  }
1092  // copy existing dinf atom
1093  if(Mp4Atom *dinfAtom = m_minfAtom->childById(Mp4AtomIds::DataInformation)) {
1094  dinfAtom->copyEntirely(outputStream());
1095  dinfAtomWritten = true;
1096  }
1097  }
1098  // write dinf atom if not written yet
1099  if(!dinfAtomWritten) {
1100  writer().writeUInt32BE(36); // size
1101  writer().writeUInt32BE(Mp4AtomIds::DataInformation);
1102  // write dref atom
1103  writer().writeUInt32BE(28); // size
1104  writer().writeUInt32BE(Mp4AtomIds::DataReference);
1105  writer().writeUInt32BE(0); // version and flags
1106  writer().writeUInt32BE(1); // entry count
1107  // write url atom
1108  writer().writeUInt32BE(12); // size
1109  writer().writeUInt32BE(Mp4AtomIds::DataEntryUrl);
1110  writer().writeByte(0); // version
1111  writer().writeUInt24BE(0x000001); // flags (media data is in the same file as the movie box)
1112  }
1113  // write stbl atom
1114  makeSampleTable();
1115  // write size (of minf atom)
1116  Mp4Atom::seekBackAndWriteAtomSize(outputStream(), minfStartOffset);
1117 }
1118 
1125 {
1126  ostream::pos_type stblStartOffset = outputStream().tellp();
1127  writer().writeUInt32BE(0); // write size later
1128  writer().writeUInt32BE(Mp4AtomIds::SampleTable);
1129  Mp4Atom *stblAtom = m_minfAtom ? m_minfAtom->childById(Mp4AtomIds::SampleTable) : nullptr;
1130  // write stsd atom
1131  if(m_stsdAtom) {
1132  // copy existing stsd atom
1133  m_stsdAtom->copyEntirely(outputStream());
1134  } else {
1135  addNotification(NotificationType::Critical, "Unable to make stsd atom from scratch.", "making stsd atom");
1136  throw NotImplementedException();
1137  }
1138  // write stts and ctts atoms
1139  Mp4Atom *sttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::DecodingTimeToSample) : nullptr;
1140  if(sttsAtom) {
1141  // copy existing stts atom
1142  sttsAtom->copyEntirely(outputStream());
1143  } else {
1144  addNotification(NotificationType::Critical, "Unable to make stts atom from scratch.", "making stts atom");
1145  throw NotImplementedException();
1146  }
1147  Mp4Atom *cttsAtom = stblAtom ? stblAtom->childById(Mp4AtomIds::CompositionTimeToSample) : nullptr;
1148  if(cttsAtom) {
1149  // copy existing ctts atom
1150  cttsAtom->copyEntirely(outputStream());
1151  }
1152  // write stsc atom (sample-to-chunk table)
1153 
1154  // write stsz atom (sample sizes)
1155 
1156  // write stz2 atom (compact sample sizes)
1157 
1158  // write stco/co64 atom (chunk offset table)
1159 
1160  // write stss atom (sync sample table)
1161 
1162  // write stsh atom (shadow sync sample table)
1163 
1164  // write padb atom (sample padding bits)
1165 
1166  // write stdp atom (sample degradation priority)
1167 
1168  // write sdtp atom (independent and disposable samples)
1169 
1170  // write sbgp atom (sample group description)
1171 
1172  // write sbgp atom (sample-to-group)
1173 
1174  // write sgpd atom (sample group description)
1175 
1176  // write subs atom (sub-sample information)
1177 
1178  // write size (of stbl atom)
1179  Mp4Atom::seekBackAndWriteAtomSize(outputStream(), stblStartOffset);
1180 }
1181 
1183 {
1184  static const string context("parsing MP4 track");
1185  using namespace Mp4AtomIds;
1186  if(!m_trakAtom) {
1187  addNotification(NotificationType::Critical, "\"trak\"-atom is null.", context);
1188  throw InvalidDataException();
1189  }
1190 
1191  // get atoms
1192  try {
1193  if(!(m_tkhdAtom = m_trakAtom->childById(TrackHeader))) {
1194  addNotification(NotificationType::Critical, "No \"tkhd\"-atom found.", context);
1195  throw InvalidDataException();
1196  }
1197  if(!(m_mdiaAtom = m_trakAtom->childById(Media))) {
1198  addNotification(NotificationType::Critical, "No \"mdia\"-atom found.", context);
1199  throw InvalidDataException();
1200  }
1201  if(!(m_mdhdAtom = m_mdiaAtom->childById(MediaHeader))) {
1202  addNotification(NotificationType::Critical, "No \"mdhd\"-atom found.", context);
1203  throw InvalidDataException();
1204  }
1205  if(!(m_hdlrAtom = m_mdiaAtom->childById(HandlerReference))) {
1206  addNotification(NotificationType::Critical, "No \"hdlr\"-atom found.", context);
1207  throw InvalidDataException();
1208  }
1209  if(!(m_minfAtom = m_mdiaAtom->childById(MediaInformation))) {
1210  addNotification(NotificationType::Critical, "No \"minf\"-atom found.", context);
1211  throw InvalidDataException();
1212  }
1213  if(!(m_stblAtom = m_minfAtom->childById(SampleTable))) {
1214  addNotification(NotificationType::Critical, "No \"stbl\"-atom found.", context);
1215  throw InvalidDataException();
1216  }
1217  if(!(m_stsdAtom = m_stblAtom->childById(SampleDescription))) {
1218  addNotification(NotificationType::Critical, "No \"stsd\"-atom found.", context);
1219  throw InvalidDataException();
1220  }
1221  if(!(m_stcoAtom = m_stblAtom->childById(ChunkOffset)) && !(m_stcoAtom = m_stblAtom->childById(ChunkOffset64))) {
1222  addNotification(NotificationType::Critical, "No \"stco\"/\"co64\"-atom found.", context);
1223  throw InvalidDataException();
1224  }
1225  if(!(m_stscAtom = m_stblAtom->childById(SampleToChunk))) {
1226  addNotification(NotificationType::Critical, "No \"stsc\"-atom found.", context);
1227  throw InvalidDataException();
1228  }
1229  if(!(m_stszAtom = m_stblAtom->childById(SampleSize)) && !(m_stszAtom = m_stblAtom->childById(CompactSampleSize))) {
1230  addNotification(NotificationType::Critical, "No \"stsz\"/\"stz2\"-atom found.", context);
1231  throw InvalidDataException();
1232  }
1233  } catch(const Failure &) {
1234  addNotification(NotificationType::Critical, "Unable to parse relevant atoms.", context);
1235  throw InvalidDataException();
1236  }
1237 
1238  BinaryReader &reader = m_trakAtom->reader();
1239 
1240  // read tkhd atom
1241  m_istream->seekg(m_tkhdAtom->startOffset() + 8); // seek to beg, skip size and name
1242  byte atomVersion = reader.readByte(); // read version
1243  uint32 flags = reader.readUInt24BE();
1244  m_enabled = flags & 0x000001;
1245  m_usedInPresentation = flags & 0x000002;
1246  m_usedWhenPreviewing = flags & 0x000004;
1247  switch(atomVersion) {
1248  case 0:
1249  m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1250  m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1251  m_id = reader.readUInt32BE();
1252  break;
1253  case 1:
1254  m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt64BE());
1255  m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt64BE());
1256  m_id = reader.readUInt32BE();
1257  break;
1258  default:
1259  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);
1261  m_modificationTime = DateTime();
1262  m_id = 0;
1263  }
1264 
1265  // read mdhd atom
1266  m_istream->seekg(m_mdhdAtom->dataOffset()); // seek to beg, skip size and name
1267  atomVersion = reader.readByte(); // read version
1268  m_istream->seekg(3, ios_base::cur); // skip flags
1269  switch(atomVersion) {
1270  case 0:
1271  m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1272  m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt32BE());
1273  m_timeScale = reader.readUInt32BE();
1274  m_duration = TimeSpan::fromSeconds(static_cast<double>(reader.readUInt32BE()) / static_cast<double>(m_timeScale));
1275  break;
1276  case 1:
1277  m_creationTime = startDate + TimeSpan::fromSeconds(reader.readUInt64BE());
1278  m_modificationTime = startDate + TimeSpan::fromSeconds(reader.readUInt64BE());
1279  m_timeScale = reader.readUInt32BE();
1280  m_duration = TimeSpan::fromSeconds(static_cast<double>(reader.readUInt64BE()) / static_cast<double>(m_timeScale));
1281  break;
1282  default:
1283  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);
1284  m_timeScale = 0;
1285  m_duration = TimeSpan();
1286  }
1287  uint16 tmp = reader.readUInt16BE();
1288  if(tmp) {
1289  char buff[3];
1290  buff[0] = ((tmp & 0x7C00) >> 0xA) + 0x60;
1291  buff[1] = ((tmp & 0x03E0) >> 0x5) + 0x60;
1292  buff[2] = ((tmp & 0x001F) >> 0x0) + 0x60;
1293  m_language = string(buff, 3);
1294  } else {
1295  m_language.clear();
1296  }
1297 
1298  // read hdlr atom
1299  // -> seek to begin skipping size, name, version, flags and reserved bytes
1300  m_istream->seekg(m_hdlrAtom->dataOffset() + 8);
1301  // -> track type
1302  switch(reader.readUInt32BE()) {
1303  case 0x76696465:
1305  break;
1306  case 0x736F756E:
1308  break;
1309  case 0x68696E74:
1311  break;
1312  case 0x6D657461: case 0x74657874:
1314  break;
1315  default:
1317  }
1318  // -> name
1319  m_istream->seekg(12, ios_base::cur); // skip reserved bytes
1320  if((tmp = m_istream->peek()) == m_hdlrAtom->dataSize() - 12 - 4 - 8 - 1) {
1321  // assume size prefixed string (seems to appear in QuickTime files)
1322  m_istream->seekg(1, ios_base::cur);
1323  m_name = reader.readString(tmp);
1324  } else {
1325  // assume null terminated string (appears in MP4 files)
1326  m_name = reader.readTerminatedString(m_hdlrAtom->dataSize() - 12 - 4 - 8, 0);
1327  }
1328 
1329  // read stco atom (only chunk count)
1330  m_chunkOffsetSize = (m_stcoAtom->id() == Mp4AtomIds::ChunkOffset64) ? 8 : 4;
1331  m_istream->seekg(m_stcoAtom->dataOffset() + 4);
1332  m_chunkCount = reader.readUInt32BE();
1333 
1334  // read stsd atom
1335  m_istream->seekg(m_stsdAtom->dataOffset() + 4); // seek to beg, skip size, name, version and flags
1336  uint32 entryCount = reader.readUInt32BE();
1337  Mp4Atom *esDescParentAtom = nullptr;
1338  if(entryCount > 0) {
1339  try {
1340  for(Mp4Atom *codecConfigContainerAtom = m_stsdAtom->firstChild(); codecConfigContainerAtom; codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) {
1341  codecConfigContainerAtom->parse();
1342  // parse FOURCC
1343  m_formatId = interpretIntegerAsString<uint32>(codecConfigContainerAtom->id());
1344  m_format = FourccIds::fourccToMediaFormat(codecConfigContainerAtom->id());
1345  // parse codecConfigContainerAtom
1346  m_istream->seekg(codecConfigContainerAtom->dataOffset());
1347  switch(codecConfigContainerAtom->id()) {
1352  m_istream->seekg(6 + 2, ios_base::cur); // skip reserved bytes, data reference index
1353  tmp = reader.readUInt16BE(); // read sound version
1354  m_istream->seekg(6, ios_base::cur);
1355  m_channelCount = reader.readUInt16BE();
1356  m_bitsPerSample = reader.readUInt16BE();
1357  m_istream->seekg(4, ios_base::cur); // skip reserved bytes (again)
1358  if(!m_samplingFrequency) {
1359  m_samplingFrequency = reader.readUInt32BE() >> 16;
1360  if(codecConfigContainerAtom->id() != FourccIds::DolbyMpl) {
1361  m_samplingFrequency >>= 16;
1362  }
1363  } else {
1364  m_istream->seekg(4, ios_base::cur);
1365  }
1366  if(codecConfigContainerAtom->id() != FourccIds::WindowsMediaAudio) {
1367  switch(tmp) {
1368  case 1:
1369  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16);
1370  break;
1371  case 2:
1372  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32);
1373  break;
1374  default:
1375  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28);
1376  }
1377  if(!esDescParentAtom) {
1378  esDescParentAtom = codecConfigContainerAtom;
1379  }
1380  }
1381  break;
1385  m_istream->seekg(6 + 2 + 16, ios_base::cur); // skip reserved bytes, data reference index, and reserved bytes (again)
1386  m_pixelSize.setWidth(reader.readUInt16BE());
1387  m_pixelSize.setHeight(reader.readUInt16BE());
1388  m_resolution.setWidth(static_cast<uint32>(reader.readFixed16BE()));
1389  m_resolution.setHeight(static_cast<uint32>(reader.readFixed16BE()));
1390  m_istream->seekg(4, ios_base::cur); // skip reserved bytes
1391  m_framesPerSample = reader.readUInt16BE();
1392  tmp = reader.readByte();
1393  m_compressorName = reader.readString(31);
1394  if(tmp == 0) {
1395  m_compressorName.clear();
1396  } else if(tmp < 32) {
1397  m_compressorName.resize(tmp);
1398  }
1399  m_depth = reader.readUInt16BE(); // 24: color without alpha
1400  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78);
1401  if(!esDescParentAtom) {
1402  esDescParentAtom = codecConfigContainerAtom;
1403  }
1404  break;
1406  // skip reserved bytes and data reference index
1407  codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 8);
1408  if(!esDescParentAtom) {
1409  esDescParentAtom = codecConfigContainerAtom;
1410  }
1411  break;
1413  break; // TODO
1415  break; // TODO
1416  default:
1417  ;
1418  }
1419  }
1420 
1421  if(esDescParentAtom) {
1422  // parse AVC configuration
1423  if(Mp4Atom *avcConfigAtom = esDescParentAtom->childById(Mp4AtomIds::AvcConfiguration)) {
1424  m_istream->seekg(avcConfigAtom->dataOffset());
1425  m_avcConfig = make_unique<Media::AvcConfiguration>();
1426  try {
1427  m_avcConfig->parse(reader, avcConfigAtom->dataSize());
1428  addInfo(*m_avcConfig, *this);
1429  } catch(const TruncatedDataException &) {
1430  addNotification(NotificationType::Critical, "AVC configuration is truncated.", context);
1431  } catch(const Failure &) {
1432  addNotification(NotificationType::Critical, "AVC configuration is invalid.", context);
1433  }
1434  }
1435 
1436  // parse MPEG-4 elementary stream descriptor
1438  if(!esDescAtom) {
1440  }
1441  if(esDescAtom) {
1442  try {
1443  if((m_esInfo = parseMpeg4ElementaryStreamInfo(*this, m_reader, esDescAtom))) {
1445  m_bitrate = static_cast<double>(m_esInfo->averageBitrate) / 1000;
1446  m_maxBitrate = static_cast<double>(m_esInfo->maxBitrate) / 1000;
1447  if(m_esInfo->audioSpecificConfig) {
1448  // check the audio specific config for useful information
1449  m_format += Mpeg4AudioObjectIds::idToMediaFormat(m_esInfo->audioSpecificConfig->audioObjectType, m_esInfo->audioSpecificConfig->sbrPresent, m_esInfo->audioSpecificConfig->psPresent);
1450  if(m_esInfo->audioSpecificConfig->sampleFrequencyIndex == 0xF) {
1451  m_samplingFrequency = m_esInfo->audioSpecificConfig->sampleFrequency;
1452  } else if(m_esInfo->audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1453  m_samplingFrequency = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->sampleFrequencyIndex];
1454  } else {
1455  addNotification(NotificationType::Warning, "Audio specific config has invalid sample frequency index.", context);
1456  }
1457  if(m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
1458  m_extensionSamplingFrequency = m_esInfo->audioSpecificConfig->extensionSampleFrequency;
1459  } else if(m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
1460  m_extensionSamplingFrequency = mpeg4SamplingFrequencyTable[m_esInfo->audioSpecificConfig->extensionSampleFrequencyIndex];
1461  } else {
1462  addNotification(NotificationType::Warning, "Audio specific config has invalid extension sample frequency index.", context);
1463  }
1464  m_channelConfig = m_esInfo->audioSpecificConfig->channelConfiguration;
1465  m_extensionChannelConfig = m_esInfo->audioSpecificConfig->extensionChannelConfiguration;
1466  }
1467  if(m_esInfo->videoSpecificConfig) {
1468  // check the video specific config for useful information
1469  if(m_format.general == GeneralMediaFormat::Mpeg4Video && m_esInfo->videoSpecificConfig->profile) {
1470  m_format.sub = m_esInfo->videoSpecificConfig->profile;
1471  if(!m_esInfo->videoSpecificConfig->userData.empty()) {
1472  m_formatId += " / " + m_esInfo->videoSpecificConfig->userData;
1473  }
1474  }
1475  }
1476  // check the stream data for missing information
1477  switch(m_format.general) {
1479  MpegAudioFrame frame;
1480  m_istream->seekg(m_stcoAtom->dataOffset() + 8);
1481  m_istream->seekg(m_chunkOffsetSize == 8 ? reader.readUInt64BE() : reader.readUInt32BE());
1482  frame.parseHeader(reader);
1483  MpegAudioFrameStream::addInfo(frame, *this);
1484  break;
1485  } default:
1486  ;
1487  }
1488  }
1489  } catch(Failure &) {
1490  }
1491  }
1492  }
1493  } catch (Failure &) {
1494  addNotification(NotificationType::Critical, "Unable to parse child atoms of \"stsd\"-atom.", context);
1495  }
1496  }
1497 
1498  // read stsz atom which holds the sample size table
1499  m_sampleSizes.clear();
1500  m_size = m_sampleCount = 0;
1501  uint64 actualSampleSizeTableSize = m_stszAtom->dataSize();
1502  if(actualSampleSizeTableSize < 12) {
1503  addNotification(NotificationType::Critical, "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context);
1504  } else {
1505  actualSampleSizeTableSize -= 12; // subtract size of version and flags
1506  m_istream->seekg(m_stszAtom->dataOffset() + 4); // seek to beg, skip size, name, version and flags
1507  uint32 fieldSize;
1508  uint32 constantSize;
1509  if(m_stszAtom->id() == Mp4AtomIds::CompactSampleSize) {
1510  constantSize = 0;
1511  m_istream->seekg(3, ios_base::cur); // seek reserved bytes
1512  fieldSize = reader.readByte();
1513  m_sampleCount = reader.readUInt32BE();
1514  } else {
1515  constantSize = reader.readUInt32BE();
1516  m_sampleCount = reader.readUInt32BE();
1517  fieldSize = 32;
1518  }
1519  if(constantSize) {
1520  m_sampleSizes.push_back(constantSize);
1521  m_size = constantSize * m_sampleCount;
1522  } else {
1523  uint64 actualSampleCount = m_sampleCount;
1524  uint64 calculatedSampleSizeTableSize = ceil((0.125 * fieldSize) * m_sampleCount);
1525  if(calculatedSampleSizeTableSize < actualSampleSizeTableSize) {
1526  addNotification(NotificationType::Critical, "The stsz atom stores more entries as denoted. The additional entries will be ignored.", context);
1527  } else if(calculatedSampleSizeTableSize > actualSampleSizeTableSize) {
1528  addNotification(NotificationType::Critical, "The stsz atom is truncated. It stores less entries as denoted.", context);
1529  actualSampleCount = floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize));
1530  }
1531  m_sampleSizes.reserve(actualSampleCount);
1532  uint32 i = 1;
1533  switch(fieldSize) {
1534  case 4:
1535  for(; i <= actualSampleCount; i += 2) {
1536  byte val = reader.readByte();
1537  m_sampleSizes.push_back(val >> 4);
1538  m_sampleSizes.push_back(val & 0xF0);
1539  m_size += (val >> 4) + (val & 0xF0);
1540  }
1541  if(i <= actualSampleCount + 1) {
1542  m_sampleSizes.push_back(reader.readByte() >> 4);
1543  m_size += m_sampleSizes.back();
1544  }
1545  break;
1546  case 8:
1547  for(; i <= actualSampleCount; ++i) {
1548  m_sampleSizes.push_back(reader.readByte());
1549  m_size += m_sampleSizes.back();
1550  }
1551  break;
1552  case 16:
1553  for(; i <= actualSampleCount; ++i) {
1554  m_sampleSizes.push_back(reader.readUInt16BE());
1555  m_size += m_sampleSizes.back();
1556  }
1557  break;
1558  case 32:
1559  for(; i <= actualSampleCount; ++i) {
1560  m_sampleSizes.push_back(reader.readUInt32BE());
1561  m_size += m_sampleSizes.back();
1562  }
1563  break;
1564  default:
1565  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);
1566  }
1567  }
1568  }
1569 
1570  // no sample sizes found, search for trun atoms
1571  uint64 totalDuration = 0;
1572  for(Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingById(MovieFragment, true); moofAtom; moofAtom = moofAtom->siblingById(MovieFragment, false)) {
1573  moofAtom->parse();
1574  for(Mp4Atom *trafAtom = moofAtom->childById(TrackFragment); trafAtom; trafAtom = trafAtom->siblingById(TrackFragment, false)) {
1575  trafAtom->parse();
1576  for(Mp4Atom *tfhdAtom = trafAtom->childById(TrackFragmentHeader); tfhdAtom; tfhdAtom = tfhdAtom->siblingById(TrackFragmentHeader, false)) {
1577  tfhdAtom->parse();
1578  uint32 calculatedDataSize = 0;
1579  if(tfhdAtom->dataSize() < calculatedDataSize) {
1580  addNotification(NotificationType::Critical, "tfhd atom is truncated.", context);
1581  } else {
1582  m_istream->seekg(tfhdAtom->dataOffset() + 1);
1583  uint32 flags = reader.readUInt24BE();
1584  if(m_id == reader.readUInt32BE()) { // check track ID
1585  if(flags & 0x000001) { // base-data-offset present
1586  calculatedDataSize += 8;
1587  }
1588  if(flags & 0x000002) { // sample-description-index present
1589  calculatedDataSize += 4;
1590  }
1591  if(flags & 0x000008) { // default-sample-duration present
1592  calculatedDataSize += 4;
1593  }
1594  if(flags & 0x000010) { // default-sample-size present
1595  calculatedDataSize += 4;
1596  }
1597  if(flags & 0x000020) { // default-sample-flags present
1598  calculatedDataSize += 4;
1599  }
1600  //uint64 baseDataOffset = moofAtom->startOffset();
1601  //uint32 defaultSampleDescriptionIndex = 0;
1602  uint32 defaultSampleDuration = 0;
1603  uint32 defaultSampleSize = 0;
1604  //uint32 defaultSampleFlags = 0;
1605  if(tfhdAtom->dataSize() < calculatedDataSize) {
1606  addNotification(NotificationType::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
1607  } else {
1608  if(flags & 0x000001) { // base-data-offset present
1609  //baseDataOffset = reader.readUInt64();
1610  m_istream->seekg(8, ios_base::cur);
1611  }
1612  if(flags & 0x000002) { // sample-description-index present
1613  //defaultSampleDescriptionIndex = reader.readUInt32();
1614  m_istream->seekg(4, ios_base::cur);
1615  }
1616  if(flags & 0x000008) { // default-sample-duration present
1617  defaultSampleDuration = reader.readUInt32BE();
1618  //m_istream->seekg(4, ios_base::cur);
1619  }
1620  if(flags & 0x000010) { // default-sample-size present
1621  defaultSampleSize = reader.readUInt32BE();
1622  }
1623  if(flags & 0x000020) { // default-sample-flags present
1624  //defaultSampleFlags = reader.readUInt32BE();
1625  m_istream->seekg(4, ios_base::cur);
1626  }
1627  }
1628  for(Mp4Atom *trunAtom = trafAtom->childById(TrackFragmentRun); trunAtom; trunAtom = trunAtom->siblingById(TrackFragmentRun, false)) {
1629  uint32 calculatedDataSize = 8;
1630  if(trunAtom->dataSize() < calculatedDataSize) {
1631  addNotification(NotificationType::Critical, "trun atom is truncated.", context);
1632  } else {
1633  m_istream->seekg(trunAtom->dataOffset() + 1);
1634  uint32 flags = reader.readUInt24BE();
1635  uint32 sampleCount = reader.readUInt32BE();
1637  if(flags & 0x000001) { // data offset present
1638  calculatedDataSize += 4;
1639  }
1640  if(flags & 0x000004) { // first-sample-flags present
1641  calculatedDataSize += 4;
1642  }
1643  uint32 entrySize = 0;
1644  if(flags & 0x000100) { // sample-duration present
1645  entrySize += 4;
1646  }
1647  if(flags & 0x000200) { // sample-size present
1648  entrySize += 4;
1649  }
1650  if(flags & 0x000400) { // sample-flags present
1651  entrySize += 4;
1652  }
1653  if(flags & 0x000800) { // sample-composition-time-offsets present
1654  entrySize += 4;
1655  }
1656  calculatedDataSize += entrySize * sampleCount;
1657  if(trunAtom->dataSize() < calculatedDataSize) {
1658  addNotification(NotificationType::Critical, "trun atom is truncated (presence of fields denoted).", context);
1659  } else {
1660  if(flags & 0x000001) { // data offset present
1661  m_istream->seekg(4, ios_base::cur);
1662  //int32 dataOffset = reader.readInt32();
1663  }
1664  if(flags & 0x000004) { // first-sample-flags present
1665  m_istream->seekg(4, ios_base::cur);
1666  }
1667  for(uint32 i = 0; i < sampleCount; ++i) {
1668  if(flags & 0x000100) { // sample-duration present
1669  totalDuration += reader.readUInt32BE();
1670  } else {
1671  totalDuration += defaultSampleDuration;
1672  }
1673  if(flags & 0x000200) { // sample-size present
1674  m_sampleSizes.push_back(reader.readUInt32BE());
1675  m_size += m_sampleSizes.back();
1676  } else {
1677  m_size += defaultSampleSize;
1678  }
1679  if(flags & 0x000400) { // sample-flags present
1680  m_istream->seekg(4, ios_base::cur);
1681  }
1682  if(flags & 0x000800) { // sample-composition-time-offsets present
1683  m_istream->seekg(4, ios_base::cur);
1684  }
1685  }
1686  }
1687  }
1688  }
1689  if(m_sampleSizes.empty() && defaultSampleSize) {
1690  m_sampleSizes.push_back(defaultSampleSize);
1691  }
1692  }
1693  }
1694  }
1695  }
1696  }
1697 
1698  // set duration from "trun-information" if the duration has not been determined yet
1699  if(m_duration.isNull() && totalDuration) {
1700  uint32 timeScale = m_timeScale;
1701  if(!timeScale) {
1702  timeScale = trakAtom().container().timeScale();
1703  }
1704  if(timeScale) {
1705  m_duration = TimeSpan::fromSeconds(static_cast<double>(totalDuration) / static_cast<double>(timeScale));
1706  }
1707  }
1708 
1709  // caluculate average bitrate
1710  if(m_bitrate < 0.01 && m_bitrate > -0.01) {
1711  m_bitrate = (static_cast<double>(m_size) * 0.0078125) / m_duration.totalSeconds();
1712  }
1713 
1714  // read stsc atom (only number of entries)
1715  m_istream->seekg(m_stscAtom->dataOffset() + 4);
1716  m_sampleToChunkEntryCount = reader.readUInt32BE();
1717 }
1718 
1719 }
implementationType * childById(const identifierType &id)
Returns the first child with the specified id.
std::ostream & outputStream()
Returns the associated input 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:962
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:406
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:71
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:690
dataSizeType dataSize() const
Returns the data size of the element in byte.
Size pictureSize
Definition: avcinfo.h:88
std::string m_formatId
GeneralMediaFormat general
Definition: mediaformat.h:269
void parse()
Parses the header information of the element which is read from the related stream at the start offse...
TrackType
Specifies the track type.
Definition: abstracttrack.h:28
unsigned char sub
Definition: mediaformat.h:270
void makeMediaInfo()
Makes a media information (minf atom) for the track.
Definition: mp4track.cpp:1069
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 protected 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:89
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:357
Margin cropping
Definition: avcinfo.h:87
AspectRatio pixelAspectRatio
Definition: avcinfo.h:85
uint32 mpeg4SamplingFrequencyTable[13]
Definition: mp4ids.cpp:302
ChronoUtilities::DateTime m_creationTime
IoUtilities::BinaryWriter m_writer
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:215
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:888
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:116
void makeTrack()
Makes the track entry ("trak"-atom) for the track.
Definition: mp4track.cpp:925
Contains utility classes helping to read and write streams.
void internalParseHeader()
This method is internally called to parse header information.
Definition: mp4track.cpp:1182
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:124
void makeMedia()
Makes the media information (mdia atom) for the track.
Definition: mp4track.cpp:1009
The AvcConfiguration struct provides a parser for AVC configuration.
std::vector< uint64 > readChunkOffsets()
Reads the chunk offsets from the stco atom.
Definition: mp4track.cpp:132
void setHeight(uint32 value)
Sets the height.
Definition: size.h:79
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:1124
uint64 sampleCount() const
Returns the number of samples 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:456
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:231
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:154
uint32 chunkCount() const
Returns the number of chunks denoted by the stco atom.
Definition: mp4track.h:223
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:35
TAG_PARSER_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent=false, bool psPresent=false)
Definition: mp4ids.cpp:245
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:541
ChronoUtilities::TimeSpan m_duration
std::vector< SpsInfo > spsInfos
uint64 requiredSize() const
Returns the number of bytes written when calling makeTrack().
Definition: mp4track.cpp:953
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:766
Mp4Atom & trakAtom()
Returns the trak atom for the current instance.
Definition: mp4track.h:193
std::ostream * m_ostream
const DateTime startDate
Dates within MP4 tracks are expressed as the number of seconds since this date.
Definition: mp4track.cpp:31
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:867
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.
~Mp4Track()
Destroys the track.
Definition: mp4track.cpp:113