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