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