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