2015-09-06 19:57:33 +02:00
# include "./mp4atom.h"
# include "./mp4container.h"
# include "./mp4track.h"
# include "./mp4ids.h"
# include "./mpeg4descriptor.h"
2015-04-22 19:22:01 +02:00
2016-02-17 20:19:05 +01:00
# include "../avc/avcconfiguration.h"
2015-09-06 19:57:33 +02:00
# include "../mpegaudio/mpegaudioframe.h"
# include "../mpegaudio/mpegaudioframestream.h"
2015-06-10 01:28:22 +02:00
2015-09-06 19:57:33 +02:00
# include "../exceptions.h"
# include "../mediaformat.h"
2015-04-22 19:22:01 +02:00
# include <c++utilities/io/binaryreader.h>
# include <c++utilities/io/binarywriter.h>
2015-06-10 01:28:22 +02:00
# include <c++utilities/io/bitreader.h>
2015-04-22 19:22:01 +02:00
# include <locale>
# include <cmath>
using namespace std ;
using namespace IoUtilities ;
using namespace ConversionUtilities ;
using namespace ChronoUtilities ;
namespace Media {
DateTime startDate = DateTime : : fromDate ( 1904 , 1 , 1 ) ;
2015-06-10 01:28:22 +02:00
Mpeg4AudioSpecificConfig : : Mpeg4AudioSpecificConfig ( ) :
audioObjectType ( 0 ) ,
sampleFrequencyIndex ( 0xF ) ,
sampleFrequency ( 0 ) ,
channelConfiguration ( 0 ) ,
extensionAudioObjectType ( 0 ) ,
sbrPresent ( false ) ,
psPresent ( false ) ,
extensionSampleFrequencyIndex ( 0xF ) ,
extensionSampleFrequency ( 0 ) ,
extensionChannelConfiguration ( 0 ) ,
frameLengthFlag ( false ) ,
dependsOnCoreCoder ( false ) ,
coreCoderDelay ( 0 ) ,
extensionFlag ( 0 ) ,
layerNr ( 0 ) ,
numOfSubFrame ( 0 ) ,
layerLength ( 0 ) ,
resilienceFlags ( 0 ) ,
epConfig ( 0 )
{ }
2016-02-17 20:19:05 +01:00
Mpeg4VideoSpecificConfig : : Mpeg4VideoSpecificConfig ( ) :
profile ( 0 )
2015-07-07 03:01:48 +02:00
{ }
2015-04-22 19:22:01 +02:00
/*!
* \ class Media : : Mp4Track
* \ brief Implementation of Media : : AbstractTrack for the MP4 container .
*/
/*!
* \ brief Constructs a new track for the specified \ a trakAtom .
*
* " trak " - atoms are stored in the top - level atom " move " . Each " trak " - atom holds
* header information for one track in the MP4 file .
*/
Mp4Track : : Mp4Track ( Mp4Atom & trakAtom ) :
AbstractTrack ( trakAtom . stream ( ) , trakAtom . startOffset ( ) ) ,
m_trakAtom ( & trakAtom ) ,
m_tkhdAtom ( nullptr ) ,
m_mdiaAtom ( nullptr ) ,
m_mdhdAtom ( nullptr ) ,
m_hdlrAtom ( nullptr ) ,
m_minfAtom ( nullptr ) ,
m_stblAtom ( nullptr ) ,
m_stsdAtom ( nullptr ) ,
m_stscAtom ( nullptr ) ,
m_stcoAtom ( nullptr ) ,
m_stszAtom ( nullptr ) ,
2015-06-10 01:28:22 +02:00
//m_codecConfigAtom(nullptr),
//m_esDescAtom(nullptr),
2015-04-22 19:22:01 +02:00
m_framesPerSample ( 1 ) ,
m_chunkOffsetSize ( 4 ) ,
m_chunkCount ( 0 ) ,
m_sampleToChunkEntryCount ( 0 )
{ }
/*!
* \ brief Destroys the track .
*/
Mp4Track : : ~ Mp4Track ( )
{ }
TrackType Mp4Track : : type ( ) const
{
return TrackType : : Mp4Track ;
}
/*!
* \ brief Reads the chunk offsets from the stco atom .
* \ returns Returns the chunk offset table for the track .
* \ throws Throws InvalidDataException when
* - there is no stream assigned .
* - the header has been considered as invalid when parsing the header information .
* - the determined chunk offset size is invalid .
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
*
* \ sa readChunkSizes ( ) ;
*/
vector < uint64 > Mp4Track : : readChunkOffsets ( )
{
static const string context ( " reading chunk offset table of MP4 track " ) ;
if ( ! isHeaderValid ( ) | | ! m_istream ) {
addNotification ( NotificationType : : Critical , " Track has not been parsed. " , context ) ;
throw InvalidDataException ( ) ;
}
vector < uint64 > offsets ;
if ( m_stcoAtom ) {
// verify integrity of the chunk offset table
uint64 actualTableSize = m_stcoAtom - > dataSize ( ) ;
if ( actualTableSize < ( 8 + chunkOffsetSize ( ) ) ) {
addNotification ( NotificationType : : Critical , " The stco atom is truncated. There are no chunk offsets present. " , context ) ;
throw InvalidDataException ( ) ;
} else {
actualTableSize - = 8 ;
}
uint32 actualChunkCount = chunkCount ( ) ;
uint64 calculatedTableSize = chunkCount ( ) * chunkOffsetSize ( ) ;
if ( calculatedTableSize < actualTableSize ) {
addNotification ( NotificationType : : Critical , " The stco atom stores more chunk offsets as denoted. The additional chunk offsets will be ignored. " , context ) ;
} else if ( calculatedTableSize > actualTableSize ) {
addNotification ( NotificationType : : Critical , " The stco atom is truncated. It stores less chunk offsets as denoted. " , context ) ;
actualChunkCount = floor ( static_cast < double > ( actualTableSize ) / static_cast < double > ( chunkOffsetSize ( ) ) ) ;
}
// read the table
offsets . reserve ( actualChunkCount ) ;
m_istream - > seekg ( m_stcoAtom - > dataOffset ( ) + 8 ) ;
switch ( chunkOffsetSize ( ) ) {
case 4 :
for ( uint32 i = 0 ; i < actualChunkCount ; + + i ) {
offsets . push_back ( reader ( ) . readUInt32BE ( ) ) ;
}
break ;
case 8 :
for ( uint32 i = 0 ; i < actualChunkCount ; + + i ) {
offsets . push_back ( reader ( ) . readUInt64BE ( ) ) ;
}
break ;
default :
addNotification ( NotificationType : : Critical , " The determined chunk offset size is invalid. " , context ) ;
throw InvalidDataException ( ) ;
}
}
// read sample offsets of fragments
2015-07-07 03:01:48 +02:00
// Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingById(moof, true);
// uint64 totalDuration = 0;
// while(moofAtom) {
// moofAtom->parse();
// Mp4Atom *trafAtom = moofAtom->childById(traf);
// while(trafAtom) {
// trafAtom->parse();
// Mp4Atom *tfhdAtom = trafAtom->childById(tfhd);
// while(tfhdAtom) {
// tfhdAtom->parse();
// uint32 calculatedDataSize = 0;
// if(tfhdAtom->dataSize() < calculatedDataSize) {
// addNotification(NotificationType::Critical, "tfhd atom is truncated.", context);
// } else {
// m_stream->seekg(tfhdAtom->dataOffset() + 1);
// uint32 flags = reader.readUInt24();
// if(m_id == reader.readUInt32()) { // check track ID
// if(flags & 0x000001) { // base-data-offset present
// calculatedDataSize += 8;
// }
// if(flags & 0x000002) { // sample-description-index present
// calculatedDataSize += 4;
// }
// if(flags & 0x000008) { // default-sample-duration present
// calculatedDataSize += 4;
// }
// if(flags & 0x000010) { // default-sample-size present
// calculatedDataSize += 4;
// }
// if(flags & 0x000020) { // default-sample-flags present
// calculatedDataSize += 4;
// }
// //uint64 baseDataOffset = moofAtom->startOffset();
// //uint32 defaultSampleDescriptionIndex = 0;
// uint32 defaultSampleDuration = 0;
// uint32 defaultSampleSize = 0;
// uint32 defaultSampleFlags = 0;
// if(tfhdAtom->dataSize() < calculatedDataSize) {
// addNotification(NotificationType::Critical, "tfhd atom is truncated (presence of fields denoted).", context);
// } else {
// if(flags & 0x000001) { // base-data-offset present
// //baseDataOffset = reader.readUInt64();
// m_stream->seekg(8, ios_base::cur);
// }
// if(flags & 0x000002) { // sample-description-index present
// //defaultSampleDescriptionIndex = reader.readUInt32();
// m_stream->seekg(4, ios_base::cur);
// }
// if(flags & 0x000008) { // default-sample-duration present
// defaultSampleDuration = reader.readUInt32();
// //m_stream->seekg(4, ios_base::cur);
// }
// if(flags & 0x000010) { // default-sample-size present
// defaultSampleSize = reader.readUInt32();
// }
// if(flags & 0x000020) { // default-sample-flags present
// defaultSampleFlags = reader.readUInt32();
// //m_stream->seekg(4, ios_base::cur);
// }
// }
// Mp4Atom *trunAtom = trafAtom->childById(trun);
// while(trunAtom) {
// uint32 calculatedDataSize = 8;
// if(trunAtom->dataSize() < calculatedDataSize) {
// addNotification(NotificationType::Critical, "trun atom is truncated.", context);
// } else {
// m_stream->seekg(trunAtom->dataOffset() + 1);
// uint32 flags = reader.readUInt24();
// uint32 sampleCount = reader.readUInt32();
// m_sampleCount += sampleCount;
// if(flags & 0x000001) { // data offset present
// calculatedDataSize += 4;
// }
// if(flags & 0x000004) { // first-sample-flags present
// calculatedDataSize += 4;
// }
// uint32 entrySize = 0;
// if(flags & 0x000100) { // sample-duration present
// entrySize += 4;
// }
// if(flags & 0x000200) { // sample-size present
// entrySize += 4;
// }
// if(flags & 0x000400) { // sample-flags present
// entrySize += 4;
// }
// if(flags & 0x000800) { // sample-composition-time-offsets present
// entrySize += 4;
// }
// calculatedDataSize += entrySize * sampleCount;
// if(trunAtom->dataSize() < calculatedDataSize) {
// addNotification(NotificationType::Critical, "trun atom is truncated (presence of fields denoted).", context);
// } else {
// if(flags & 0x000001) { // data offset present
// m_stream->seekg(4, ios_base::cur);
// //int32 dataOffset = reader.readInt32();
// }
// if(flags & 0x000004) { // first-sample-flags present
// m_stream->seekg(4, ios_base::cur);
// }
// for(uint32 i = 0; i < sampleCount; ++i) {
// if(flags & 0x000100) { // sample-duration present
// totalDuration += reader.readUInt32();
// } else {
// totalDuration += defaultSampleDuration;
// }
// if(flags & 0x000200) { // sample-size present
// m_sampleSizes.push_back(reader.readUInt32());
// m_size += m_sampleSizes.back();
// } else {
// m_size += defaultSampleSize;
// }
// if(flags & 0x000400) { // sample-flags present
// m_stream->seekg(4, ios_base::cur);
// }
// if(flags & 0x000800) { // sample-composition-time-offsets present
// m_stream->seekg(4, ios_base::cur);
// }
// }
// }
// }
// trunAtom = trunAtom->siblingById(trun, false);
// }
// if(m_sampleSizes.empty() && defaultSampleSize) {
// m_sampleSizes.push_back(defaultSampleSize);
// }
// }
// }
// tfhdAtom = tfhdAtom->siblingById(tfhd, false);
// }
// trafAtom = trafAtom->siblingById(traf, false);
// }
// moofAtom = moofAtom->siblingById(moof, false);
// }
2015-04-22 19:22:01 +02:00
return offsets ;
}
/*!
* \ brief Accumulates \ a count sample sizes from the specified \ a sampleSizeTable starting at the specified \ a sampleIndex .
* \ remarks This helper function is used by the addChunkSizeEntries ( ) method .
*/
uint64 Mp4Track : : accumulateSampleSizes ( size_t & sampleIndex , size_t count )
{
if ( sampleIndex + count < = m_sampleSizes . size ( ) ) {
uint64 sum = 0 ;
for ( size_t end = sampleIndex + count ; sampleIndex < end ; + + sampleIndex ) {
sum + = m_sampleSizes [ sampleIndex ] ;
}
return sum ;
} else if ( m_sampleSizes . size ( ) = = 1 ) {
sampleIndex + = count ;
return static_cast < uint64 > ( m_sampleSizes . front ( ) ) * count ;
} else {
addNotification ( NotificationType : : Critical , " There are not as many sample size entries as samples. " , " reading chunk sizes of MP4 track " ) ;
throw InvalidDataException ( ) ;
}
}
/*!
* \ brief Adds chunks size entries to the specified \ a chunkSizeTable .
* \ param chunkSizeTable Specifies the chunk size table . The chunks sizes will be added to this table .
* \ param count Specifies the number of chunks to be added . The size of \ a chunkSizeTable is increased this value .
* \ param sampleIndex Specifies the index of the first sample in the \ a sampleSizeTable ; is increased by \ a count * \ a sampleCount .
* \ param sampleSizeTable Specifies the table holding the sample sizes .
* \ remarks This helper function is used by the readChunkSize ( ) method .
*/
void Mp4Track : : addChunkSizeEntries ( std : : vector < uint64 > & chunkSizeTable , size_t count , size_t & sampleIndex , uint32 sampleCount )
{
for ( size_t i = 0 ; i < count ; + + i ) {
chunkSizeTable . push_back ( accumulateSampleSizes ( sampleIndex , sampleCount ) ) ;
}
}
/*!
* \ brief Reads the sample to chunk table .
* \ returns Returns a vector with the table entries wrapped using the tuple container . The first value
* is an integer that gives the first chunk that share the same samples count and sample description index .
* The second value is sample cound and the third value the sample description index .
* \ remarks The table is not validated .
*/
vector < tuple < uint32 , uint32 , uint32 > > Mp4Track : : readSampleToChunkTable ( )
{
static const string context ( " reading sample to chunk table of MP4 track " ) ;
if ( ! isHeaderValid ( ) | | ! m_istream | | ! m_stscAtom ) {
addNotification ( NotificationType : : Critical , " Track has not been parsed or is invalid. " , context ) ;
throw InvalidDataException ( ) ;
}
// verify integrity of the sample to chunk table
uint64 actualTableSize = m_stscAtom - > dataSize ( ) ;
if ( actualTableSize < 20 ) {
addNotification ( NotificationType : : Critical , " The stsc atom is truncated. There are no \" sample to chunk \" entries present. " , context ) ;
throw InvalidDataException ( ) ;
} else {
actualTableSize - = 8 ;
}
uint32 actualSampleToChunkEntryCount = sampleToChunkEntryCount ( ) ;
uint64 calculatedTableSize = actualSampleToChunkEntryCount * 12 ;
if ( calculatedTableSize < actualTableSize ) {
addNotification ( NotificationType : : Critical , " The stsc atom stores more entries as denoted. The additional entries will be ignored. " , context ) ;
} else if ( calculatedTableSize > actualTableSize ) {
addNotification ( NotificationType : : Critical , " The stsc atom is truncated. It stores less entries as denoted. " , context ) ;
actualSampleToChunkEntryCount = floor ( static_cast < double > ( actualTableSize ) / 12.0 ) ;
}
// prepare reading
vector < tuple < uint32 , uint32 , uint32 > > sampleToChunkTable ;
sampleToChunkTable . reserve ( actualSampleToChunkEntryCount ) ;
m_istream - > seekg ( m_stscAtom - > dataOffset ( ) + 8 ) ;
for ( uint32 i = 0 ; i < actualSampleToChunkEntryCount ; + + i ) {
// read entry
uint32 firstChunk = reader ( ) . readUInt32BE ( ) ;
uint32 samplesPerChunk = reader ( ) . readUInt32BE ( ) ;
uint32 sampleDescriptionIndex = reader ( ) . readUInt32BE ( ) ;
sampleToChunkTable . emplace_back ( firstChunk , samplesPerChunk , sampleDescriptionIndex ) ;
}
return sampleToChunkTable ;
}
/*!
* \ brief Reads the chunk sizes from the stsz ( sample sizes ) and stsc ( samples per chunk ) atom .
* \ returns Returns the chunk sizes for the track .
*
* \ throws Throws InvalidDataException when
* - there is no stream assigned .
* - the header has been considered as invalid when parsing the header information .
* - the determined chunk offset size is invalid .
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
*
* \ sa readChunkOffsets ( ) ;
*/
vector < uint64 > Mp4Track : : readChunkSizes ( )
{
static const string context ( " reading chunk sizes of MP4 track " ) ;
if ( ! isHeaderValid ( ) | | ! m_istream | | ! m_stcoAtom ) {
addNotification ( NotificationType : : Critical , " Track has not been parsed or is invalid. " , context ) ;
throw InvalidDataException ( ) ;
}
// read sample to chunk table
const auto sampleToChunkTable = readSampleToChunkTable ( ) ;
// accumulate chunk sizes from the table
vector < uint64 > chunkSizes ;
if ( ! sampleToChunkTable . empty ( ) ) {
// prepare reading
auto tableIterator = sampleToChunkTable . cbegin ( ) ;
chunkSizes . reserve ( m_chunkCount ) ;
// read first entry
size_t sampleIndex = 0 ;
uint32 previousChunkIndex = get < 0 > ( * tableIterator ) ; // the first chunk has the index 1 and not zero!
if ( previousChunkIndex ! = 1 ) {
addNotification ( NotificationType : : Critical , " The first chunk of the first \" sample to chunk \" entry must be 1. " , context ) ;
previousChunkIndex = 1 ; // try to read the entry anyway
}
uint32 samplesPerChunk = get < 1 > ( * tableIterator ) ;
// read the following entries
+ + tableIterator ;
for ( const auto tableEnd = sampleToChunkTable . cend ( ) ; tableIterator ! = tableEnd ; + + tableIterator ) {
uint32 firstChunkIndex = get < 0 > ( * tableIterator ) ;
if ( firstChunkIndex > previousChunkIndex & & firstChunkIndex < = m_chunkCount ) {
addChunkSizeEntries ( chunkSizes , firstChunkIndex - previousChunkIndex , sampleIndex , samplesPerChunk ) ;
} else {
addNotification ( NotificationType : : Critical ,
" The first chunk index of a \" sample to chunk \" entry must be greather then the first chunk of the previous entry and not greather then the chunk count. " , context ) ;
throw InvalidDataException ( ) ;
}
previousChunkIndex = firstChunkIndex ;
samplesPerChunk = get < 1 > ( * tableIterator ) ;
}
if ( m_chunkCount > = previousChunkIndex ) {
addChunkSizeEntries ( chunkSizes , m_chunkCount + 1 - previousChunkIndex , sampleIndex , samplesPerChunk ) ;
}
}
return chunkSizes ;
}
/*!
2015-06-07 00:18:28 +02:00
* \ brief Reads the MPEG - 4 elementary stream descriptor for the track .
* \ remarks
* - Notifications might be added .
* \ sa mpeg4ElementaryStreamInfo ( )
*/
2015-08-13 03:23:28 +02:00
std : : unique_ptr < Mpeg4ElementaryStreamInfo > Mp4Track : : parseMpeg4ElementaryStreamInfo ( StatusProvider & statusProvider , IoUtilities : : BinaryReader & reader , Mp4Atom * esDescAtom )
2015-06-07 00:18:28 +02:00
{
static const string context ( " parsing MPEG-4 elementary stream descriptor " ) ;
2015-06-10 01:28:22 +02:00
using namespace Mpeg4ElementaryStreamObjectIds ;
unique_ptr < Mpeg4ElementaryStreamInfo > esInfo ;
if ( esDescAtom - > dataSize ( ) > = 12 ) {
2015-08-13 03:23:28 +02:00
reader . stream ( ) - > seekg ( esDescAtom - > dataOffset ( ) ) ;
2015-06-10 01:28:22 +02:00
// read version/flags
2015-08-13 03:23:28 +02:00
if ( reader . readUInt32BE ( ) ! = 0 ) {
statusProvider . addNotification ( NotificationType : : Warning , " Unknown version/flags. " , context ) ;
2015-06-10 01:28:22 +02:00
}
// read extended descriptor
2015-08-13 03:23:28 +02:00
Mpeg4Descriptor esDesc ( esDescAtom - > container ( ) , reader . stream ( ) - > tellg ( ) , esDescAtom - > dataSize ( ) - 4 ) ;
2015-06-10 01:28:22 +02:00
try {
2015-07-07 03:01:48 +02:00
esDesc . parse ( ) ;
2015-06-10 01:28:22 +02:00
// check ID
if ( esDesc . id ( ) ! = Mpeg4DescriptorIds : : ElementaryStreamDescr ) {
2015-08-13 03:23:28 +02:00
statusProvider . addNotification ( NotificationType : : Critical , " Invalid descriptor found. " , context ) ;
2015-06-10 01:28:22 +02:00
throw Failure ( ) ;
2015-06-07 00:18:28 +02:00
}
2015-06-10 01:28:22 +02:00
// read stream info
2015-08-13 03:23:28 +02:00
reader . stream ( ) - > seekg ( esDesc . dataOffset ( ) ) ;
2015-06-10 01:28:22 +02:00
esInfo = make_unique < Mpeg4ElementaryStreamInfo > ( ) ;
2015-08-13 03:23:28 +02:00
esInfo - > id = reader . readUInt16BE ( ) ;
esInfo - > esDescFlags = reader . readByte ( ) ;
2015-06-10 01:28:22 +02:00
if ( esInfo - > dependencyFlag ( ) ) {
2015-08-13 03:23:28 +02:00
esInfo - > dependsOnId = reader . readUInt16BE ( ) ;
2015-06-10 01:28:22 +02:00
}
if ( esInfo - > urlFlag ( ) ) {
2015-08-13 03:23:28 +02:00
esInfo - > url = reader . readString ( reader . readByte ( ) ) ;
2015-06-10 01:28:22 +02:00
}
if ( esInfo - > ocrFlag ( ) ) {
2015-08-13 03:23:28 +02:00
esInfo - > ocrId = reader . readUInt16BE ( ) ;
2015-06-10 01:28:22 +02:00
}
2015-08-13 03:23:28 +02:00
for ( Mpeg4Descriptor * esDescChild = esDesc . denoteFirstChild ( static_cast < uint64 > ( reader . stream ( ) - > tellg ( ) ) - esDesc . startOffset ( ) ) ; esDescChild ; esDescChild = esDescChild - > nextSibling ( ) ) {
2015-06-10 01:28:22 +02:00
esDescChild - > parse ( ) ;
switch ( esDescChild - > id ( ) ) {
case Mpeg4DescriptorIds : : DecoderConfigDescr :
// read decoder config descriptor
2015-08-13 03:23:28 +02:00
reader . stream ( ) - > seekg ( esDescChild - > dataOffset ( ) ) ;
esInfo - > objectTypeId = reader . readByte ( ) ;
esInfo - > decCfgDescFlags = reader . readByte ( ) ;
esInfo - > bufferSize = reader . readUInt24BE ( ) ;
esInfo - > maxBitrate = reader . readUInt32BE ( ) ;
esInfo - > averageBitrate = reader . readUInt32BE ( ) ;
2015-06-10 01:28:22 +02:00
for ( Mpeg4Descriptor * decCfgDescChild = esDescChild - > denoteFirstChild ( esDescChild - > headerSize ( ) + 13 ) ; decCfgDescChild ; decCfgDescChild = decCfgDescChild - > nextSibling ( ) ) {
decCfgDescChild - > parse ( ) ;
switch ( decCfgDescChild - > id ( ) ) {
case Mpeg4DescriptorIds : : DecoderSpecificInfo :
// read decoder specific info
switch ( esInfo - > objectTypeId ) {
case Aac : case Mpeg2AacMainProfile : case Mpeg2AacLowComplexityProfile :
case Mpeg2AacScaleableSamplingRateProfile : case Mpeg2Audio : case Mpeg1Audio :
2015-08-13 03:23:28 +02:00
esInfo - > audioSpecificConfig = parseAudioSpecificConfig ( statusProvider , * reader . stream ( ) , decCfgDescChild - > dataOffset ( ) , decCfgDescChild - > dataSize ( ) ) ;
2015-07-07 03:01:48 +02:00
break ;
case Mpeg4Visual :
2015-08-13 03:23:28 +02:00
esInfo - > videoSpecificConfig = parseVideoSpecificConfig ( statusProvider , reader , decCfgDescChild - > dataOffset ( ) , decCfgDescChild - > dataSize ( ) ) ;
2015-07-07 03:01:48 +02:00
break ;
2015-06-10 01:28:22 +02:00
default :
2015-07-27 23:10:35 +02:00
; // TODO: cover more object types
2015-06-10 01:28:22 +02:00
}
break ;
}
}
break ;
case Mpeg4DescriptorIds : : SlConfigDescr :
// uninteresting
break ;
2015-06-07 00:18:28 +02:00
}
2015-06-10 01:28:22 +02:00
}
} catch ( Failure & ) {
2015-08-13 03:23:28 +02:00
statusProvider . addNotification ( NotificationType : : Critical , " The MPEG-4 descriptor element structure is invalid. " , context ) ;
2015-06-10 01:28:22 +02:00
}
} else {
2015-08-13 03:23:28 +02:00
statusProvider . addNotification ( NotificationType : : Warning , " Elementary stream descriptor atom (esds) is truncated. " , context ) ;
2015-06-10 01:28:22 +02:00
}
return esInfo ;
}
/*!
2015-07-07 03:01:48 +02:00
* \ brief Parses the audio specific configuration for the track .
2015-06-10 01:28:22 +02:00
* \ remarks
* - Notifications might be added .
* \ sa mpeg4ElementaryStreamInfo ( )
*/
2015-08-13 03:23:28 +02:00
unique_ptr < Mpeg4AudioSpecificConfig > Mp4Track : : parseAudioSpecificConfig ( StatusProvider & statusProvider , istream & stream , uint64 startOffset , uint64 size )
2015-06-10 01:28:22 +02:00
{
static const string context ( " parsing MPEG-4 audio specific config from elementary stream descriptor " ) ;
using namespace Mpeg4AudioObjectIds ;
// read config into buffer and construct BitReader for bitwise reading
2015-08-13 03:23:28 +02:00
stream . seekg ( startOffset ) ;
auto buff = make_unique < char [ ] > ( size ) ;
stream . read ( buff . get ( ) , size ) ;
BitReader bitReader ( buff . get ( ) , size ) ;
2015-06-10 01:28:22 +02:00
auto audioCfg = make_unique < Mpeg4AudioSpecificConfig > ( ) ;
try {
// read audio object type
auto getAudioObjectType = [ & audioCfg , & bitReader ] {
byte objType = bitReader . readBits < byte > ( 5 ) ;
if ( objType = = 31 ) {
objType = 32 + bitReader . readBits < byte > ( 6 ) ;
}
return objType ;
} ;
audioCfg - > audioObjectType = getAudioObjectType ( ) ;
// read sampling frequency
if ( ( audioCfg - > sampleFrequencyIndex = bitReader . readBits < byte > ( 4 ) ) = = 0xF ) {
audioCfg - > sampleFrequency = bitReader . readBits < uint32 > ( 24 ) ;
}
// read channel config
audioCfg - > channelConfiguration = bitReader . readBits < byte > ( 4 ) ;
// read extension header
switch ( audioCfg - > audioObjectType ) {
case Sbr :
case Ps :
2015-09-24 00:19:04 +02:00
audioCfg - > extensionAudioObjectType = audioCfg - > audioObjectType ;
2015-06-10 01:28:22 +02:00
audioCfg - > sbrPresent = true ;
if ( ( audioCfg - > extensionSampleFrequencyIndex = bitReader . readBits < byte > ( 4 ) ) = = 0xF ) {
audioCfg - > extensionSampleFrequency = bitReader . readBits < uint32 > ( 24 ) ;
}
if ( ( audioCfg - > audioObjectType = getAudioObjectType ( ) ) = = ErBsac ) {
audioCfg - > extensionChannelConfiguration = bitReader . readBits < byte > ( 4 ) ;
}
break ;
}
2015-09-24 00:19:04 +02:00
switch ( audioCfg - > extensionAudioObjectType ) {
2015-06-10 01:28:22 +02:00
case Ps :
audioCfg - > psPresent = true ;
2015-09-24 01:15:27 +02:00
audioCfg - > extensionChannelConfiguration = Mpeg4ChannelConfigs : : FrontLeftFrontRight ;
2015-06-10 01:28:22 +02:00
break ;
}
// read GA specific config
switch ( audioCfg - > audioObjectType ) {
case AacMain : case AacLc : case AacLtp : case AacScalable :
case TwinVq : case ErAacLc : case ErAacLtp : case ErAacScalable :
case ErTwinVq : case ErBsac : case ErAacLd :
audioCfg - > frameLengthFlag = bitReader . readBits < byte > ( 1 ) ;
2015-09-24 01:15:27 +02:00
if ( ( audioCfg - > dependsOnCoreCoder = bitReader . readBit ( ) ) ) {
2015-06-10 01:28:22 +02:00
audioCfg - > coreCoderDelay = bitReader . readBits < byte > ( 14 ) ;
}
2015-09-24 01:15:27 +02:00
audioCfg - > extensionFlag = bitReader . readBit ( ) ;
2015-06-10 01:28:22 +02:00
if ( audioCfg - > channelConfiguration = = 0 ) {
throw NotImplementedException ( ) ; // TODO: parse program_config_element
}
switch ( audioCfg - > audioObjectType ) {
case AacScalable : case ErAacScalable :
audioCfg - > layerNr = bitReader . readBits < byte > ( 3 ) ;
break ;
default :
;
}
if ( audioCfg - > extensionFlag = = 1 ) {
switch ( audioCfg - > audioObjectType ) {
case ErBsac :
audioCfg - > numOfSubFrame = bitReader . readBits < byte > ( 5 ) ;
audioCfg - > layerLength = bitReader . readBits < uint16 > ( 11 ) ;
break ;
case ErAacLc : case ErAacLtp : case ErAacScalable : case ErAacLd :
audioCfg - > resilienceFlags = bitReader . readBits < byte > ( 3 ) ;
break ;
default :
;
2015-06-07 00:18:28 +02:00
}
2015-09-24 01:15:27 +02:00
if ( bitReader . readBit ( ) = = 1 ) { // extension flag 3
2015-06-10 01:28:22 +02:00
throw NotImplementedException ( ) ; // TODO
2015-06-07 00:18:28 +02:00
}
2015-06-10 01:28:22 +02:00
}
break ;
default :
throw NotImplementedException ( ) ; // TODO: cover remaining object types
}
// read error specific config
switch ( audioCfg - > audioObjectType ) {
case ErAacLc : case ErAacLtp : case ErAacScalable : case ErTwinVq :
case ErBsac : case ErAacLd : case ErCelp : case ErHvxc : case ErHiln :
case ErParametric : case ErAacEld :
switch ( audioCfg - > epConfig = bitReader . readBits < byte > ( 2 ) ) {
2015-07-07 03:01:48 +02:00
case 2 :
break ;
case 3 :
bitReader . skipBits ( 1 ) ;
break ;
2015-06-10 01:28:22 +02:00
default :
throw NotImplementedException ( ) ; // TODO
}
break ;
}
2015-09-24 00:19:04 +02:00
if ( audioCfg - > extensionAudioObjectType ! = Sbr & & audioCfg - > extensionAudioObjectType ! = Ps & & bitReader . bitsAvailable ( ) > = 16 ) {
2015-06-10 01:28:22 +02:00
uint16 syncExtensionType = bitReader . readBits < uint16 > ( 11 ) ;
if ( syncExtensionType = = 0x2B7 ) {
if ( ( audioCfg - > extensionAudioObjectType = getAudioObjectType ( ) ) = = Sbr ) {
2015-09-24 01:15:27 +02:00
if ( ( audioCfg - > sbrPresent = bitReader . readBit ( ) ) ) {
2015-06-10 01:28:22 +02:00
if ( ( audioCfg - > extensionSampleFrequencyIndex = bitReader . readBits < byte > ( 4 ) ) = = 0xF ) {
audioCfg - > extensionSampleFrequency = bitReader . readBits < uint32 > ( 24 ) ;
}
if ( bitReader . bitsAvailable ( ) > = 12 ) {
if ( ( syncExtensionType = bitReader . readBits < uint16 > ( 11 ) ) = = 0x548 ) {
audioCfg - > psPresent = bitReader . readBits < byte > ( 1 ) ;
2015-06-07 00:18:28 +02:00
}
}
}
2015-09-24 01:15:27 +02:00
} else if ( audioCfg - > extensionAudioObjectType = = ErBsac ) {
if ( ( audioCfg - > sbrPresent = bitReader . readBit ( ) ) ) {
if ( ( audioCfg - > extensionSampleFrequencyIndex = bitReader . readBits < byte > ( 4 ) ) = = 0xF ) {
audioCfg - > extensionSampleFrequency = bitReader . readBits < uint32 > ( 24 ) ;
}
}
audioCfg - > extensionChannelConfiguration = bitReader . readBits < byte > ( 4 ) ;
2015-06-07 00:18:28 +02:00
}
2015-07-07 03:01:48 +02:00
} else if ( syncExtensionType = = 0x548 ) {
2015-09-24 01:15:27 +02:00
audioCfg - > psPresent = bitReader . readBit ( ) ;
2015-06-07 00:18:28 +02:00
}
}
2015-06-10 01:28:22 +02:00
} catch ( ios_base : : failure & ) {
2015-08-13 03:23:28 +02:00
if ( stream . fail ( ) ) {
2015-07-07 03:01:48 +02:00
// IO error caused by input stream
throw ;
} else {
// IO error caused by bitReader
2015-08-13 03:23:28 +02:00
statusProvider . addNotification ( NotificationType : : Critical , " Audio specific configuration is truncated. " , context ) ;
2015-06-10 01:28:22 +02:00
}
} catch ( NotImplementedException & ) {
2015-08-13 03:23:28 +02:00
statusProvider . addNotification ( NotificationType : : Information , " Not implemented for the format of audio track. " , context ) ;
2015-06-07 00:18:28 +02:00
}
2015-06-10 01:28:22 +02:00
return audioCfg ;
2015-06-07 00:18:28 +02:00
}
2015-07-07 03:01:48 +02:00
/*!
* \ brief Parses the video specific configuration for the track .
* \ remarks
* - Notifications might be added .
* \ sa mpeg4ElementaryStreamInfo ( )
*/
2015-08-13 03:23:28 +02:00
std : : unique_ptr < Mpeg4VideoSpecificConfig > Mp4Track : : parseVideoSpecificConfig ( StatusProvider & statusProvider , BinaryReader & reader , uint64 startOffset , uint64 size )
2015-07-07 03:01:48 +02:00
{
static const string context ( " parsing MPEG-4 video specific config from elementary stream descriptor " ) ;
using namespace Mpeg4AudioObjectIds ;
auto videoCfg = make_unique < Mpeg4VideoSpecificConfig > ( ) ;
// seek to start
2015-08-13 03:23:28 +02:00
reader . stream ( ) - > seekg ( startOffset ) ;
if ( size > 3 & & ( reader . readUInt24BE ( ) = = 1 ) ) {
size - = 3 ;
2015-07-07 03:01:48 +02:00
uint32 buff1 ;
2015-08-13 03:23:28 +02:00
while ( size ) {
- - size ;
switch ( reader . readByte ( ) ) { // read start code
2015-07-07 03:01:48 +02:00
case Mpeg4VideoCodes : : VisualObjectSequenceStart :
2015-08-13 03:23:28 +02:00
if ( size ) {
videoCfg - > profile = reader . readByte ( ) ;
- - size ;
2015-07-07 03:01:48 +02:00
}
break ;
case Mpeg4VideoCodes : : VideoObjectLayerStart :
break ;
case Mpeg4VideoCodes : : UserDataStart :
buff1 = 0 ;
2015-08-13 03:23:28 +02:00
while ( size > = 3 ) {
if ( ( buff1 = reader . readUInt24BE ( ) ) ! = 1 ) {
reader . stream ( ) - > seekg ( - 2 , ios_base : : cur ) ;
2015-07-07 03:01:48 +02:00
videoCfg - > userData . push_back ( buff1 > > 16 ) ;
2015-08-13 03:23:28 +02:00
- - size ;
2015-07-07 03:01:48 +02:00
} else {
2015-08-13 03:23:28 +02:00
size - = 3 ;
2015-07-07 03:01:48 +02:00
break ;
}
}
2015-08-13 03:23:28 +02:00
if ( buff1 ! = 1 & & size > 0 ) {
videoCfg - > userData + = reader . readString ( size ) ;
size = 0 ;
2015-07-07 03:01:48 +02:00
}
break ;
default :
;
}
2016-02-17 20:19:05 +01:00
// skip remainging values to get the start of the next video object
2015-08-13 03:23:28 +02:00
while ( size > = 3 ) {
if ( reader . readUInt24BE ( ) ! = 1 ) {
reader . stream ( ) - > seekg ( - 2 , ios_base : : cur ) ;
- - size ;
2015-07-07 03:01:48 +02:00
} else {
2015-08-13 03:23:28 +02:00
size - = 3 ;
2015-07-07 03:01:48 +02:00
break ;
}
}
}
} else {
2015-08-13 03:23:28 +02:00
statusProvider . addNotification ( NotificationType : : Critical , " \" Visual Object Sequence Header \" not found. " , context ) ;
2015-07-07 03:01:48 +02:00
}
return videoCfg ;
}
/*!
2015-11-07 15:23:36 +01:00
* \ brief Updates the chunk offsets of the track . This is necessary when the " mdat " - atom
* ( which contains the actual chunk data ) is moved .
2015-04-22 19:22:01 +02:00
* \ param oldMdatOffsets Specifies a vector holding the old offsets of the " mdat " - atoms .
* \ param newMdatOffsets Specifies a vector holding the new offsets of the " mdat " - atoms .
*
* \ throws Throws InvalidDataException when
* - there is no stream assigned .
* - the header has been considered as invalid when parsing the header information .
* - \ a oldMdatOffsets holds not the same number of offsets as \ a newMdatOffsets .
* - there is no atom holding these offsets .
* - the ID of the atom holding these offsets is not " stco " or " co64 "
*
* \ throws Throws std : : ios_base : : failure when an IO error occurs .
2015-07-27 23:10:35 +02:00
*
* \ remarks This method needs to be fixed .
2015-04-22 19:22:01 +02:00
*/
void Mp4Track : : updateChunkOffsets ( const vector < int64 > & oldMdatOffsets , const vector < int64 > & newMdatOffsets )
{
if ( ! isHeaderValid ( ) | | ! m_ostream | | ! m_istream | | ! m_stcoAtom ) {
throw InvalidDataException ( ) ;
}
if ( oldMdatOffsets . size ( ) = = 0 | | oldMdatOffsets . size ( ) ! = newMdatOffsets . size ( ) ) {
throw InvalidDataException ( ) ;
}
2015-06-10 01:28:22 +02:00
static const unsigned int stcoDataBegin = 8 ;
uint64 startPos = m_stcoAtom - > dataOffset ( ) + stcoDataBegin ;
2015-07-08 01:15:45 +02:00
uint64 endPos = startPos + m_stcoAtom - > dataSize ( ) - stcoDataBegin ;
2015-04-22 19:22:01 +02:00
m_istream - > seekg ( startPos ) ;
m_ostream - > seekp ( startPos ) ;
vector < int64 > : : size_type i ;
vector < int64 > : : size_type size ;
uint64 currentPos = m_istream - > tellg ( ) ;
switch ( m_stcoAtom - > id ( ) ) {
case Mp4AtomIds : : ChunkOffset : {
uint32 off ;
while ( ( currentPos + 4 ) < = endPos ) {
off = m_reader . readUInt32BE ( ) ;
for ( i = 0 , size = oldMdatOffsets . size ( ) ; i < size ; + + i ) {
if ( off > static_cast < uint64 > ( oldMdatOffsets [ i ] ) ) {
off + = ( newMdatOffsets [ i ] - oldMdatOffsets [ i ] ) ;
break ;
}
}
m_ostream - > seekp ( currentPos ) ;
m_writer . writeUInt32BE ( off ) ;
currentPos + = m_istream - > gcount ( ) ;
}
break ;
} case Mp4AtomIds : : ChunkOffset64 : {
uint64 off ;
while ( ( currentPos + 8 ) < = endPos ) {
off = m_reader . readUInt64BE ( ) ;
for ( i = 0 , size = oldMdatOffsets . size ( ) ; i < size ; + + i ) {
if ( off > static_cast < uint64 > ( oldMdatOffsets [ i ] ) ) {
off + = ( newMdatOffsets [ i ] - oldMdatOffsets [ i ] ) ;
break ;
}
}
m_ostream - > seekp ( currentPos ) ;
m_writer . writeUInt64BE ( off ) ;
currentPos + = m_istream - > gcount ( ) ;
}
break ;
}
default :
throw InvalidDataException ( ) ;
}
}
2015-11-07 15:23:36 +01:00
/*!
* \ brief Updates the chunk offsets of the track . This is necessary when the " mdat " - atom
* ( which contains the actual chunk data ) is moved .
* \ param chunkOffsets Specifies the new chunk offset table .
*
* \ throws Throws InvalidDataException when
* - there is no stream assigned .
* - the header has been considered as invalid when parsing the header information .
* - the size of \ a chunkOffsets does not match chunkCount ( ) .
* - there is no atom holding these offsets .
* - the ID of the atom holding these offsets is not " stco " or " co64 " .
*/
void Mp4Track : : updateChunkOffsets ( const std : : vector < uint64 > & chunkOffsets )
{
if ( ! isHeaderValid ( ) | | ! m_ostream | | ! m_istream | | ! m_stcoAtom ) {
throw InvalidDataException ( ) ;
}
if ( chunkOffsets . size ( ) ! = chunkCount ( ) ) {
throw InvalidDataException ( ) ;
}
m_ostream - > seekp ( m_stcoAtom - > dataOffset ( ) + 8 ) ;
switch ( m_stcoAtom - > id ( ) ) {
case Mp4AtomIds : : ChunkOffset :
for ( auto offset : chunkOffsets ) {
m_writer . writeUInt32BE ( offset ) ;
}
break ;
case Mp4AtomIds : : ChunkOffset64 :
for ( auto offset : chunkOffsets ) {
m_writer . writeUInt64BE ( offset ) ;
}
default :
throw InvalidDataException ( ) ;
}
}
2015-04-22 19:22:01 +02:00
/*!
* \ brief Updates a particular chunk offset .
* \ param chunkIndex Specifies the index of the chunk offset to be updated .
* \ param offset Specifies the new chunk offset .
2015-11-07 15:23:36 +01:00
* \ remarks This method seems to be obsolete .
* \ throws Throws InvalidDataException when
* - there is no stream assigned .
* - the header has been considered as invalid when parsing the header information .
* - \ a chunkIndex is not less than chunkCount ( ) .
* - there is no atom holding these offsets .
* - the ID of the atom holding these offsets is not " stco " or " co64 " .
2015-04-22 19:22:01 +02:00
*/
void Mp4Track : : updateChunkOffset ( uint32 chunkIndex , uint64 offset )
{
if ( ! isHeaderValid ( ) | | ! m_istream | | ! m_stcoAtom | | chunkIndex > = m_chunkCount ) {
throw InvalidDataException ( ) ;
}
m_ostream - > seekp ( m_stcoAtom - > dataOffset ( ) + 8 + chunkOffsetSize ( ) * chunkIndex ) ;
switch ( chunkOffsetSize ( ) ) {
case 4 :
writer ( ) . writeUInt32BE ( offset ) ;
break ;
case 8 :
writer ( ) . writeUInt64BE ( offset ) ;
break ;
default :
throw InvalidDataException ( ) ;
}
}
2016-02-17 20:19:05 +01:00
/*!
* \ brief Adds the information from the specified \ a avcConfig to the specified \ a track .
*/
void Mp4Track : : addInfo ( const AvcConfiguration & avcConfig , AbstractTrack & track )
{
if ( ! avcConfig . spsInfos . empty ( ) ) {
const SpsInfo & spsInfo = avcConfig . spsInfos . back ( ) ;
track . m_format . sub = spsInfo . profileIndication ;
track . m_version = static_cast < double > ( spsInfo . levelIndication ) / 10 ;
track . m_cropping = spsInfo . cropping ;
track . m_pixelSize = spsInfo . pictureSize ;
switch ( spsInfo . chromaFormatIndication ) {
case 0 :
track . m_chromaFormat = " monochrome " ;
break ;
case 1 :
track . m_chromaFormat = " YUV 4:2:0 " ;
break ;
case 2 :
track . m_chromaFormat = " YUV 4:2:2 " ;
break ;
case 3 :
track . m_chromaFormat = " YUV 4:4:4 " ;
break ;
default :
;
}
track . m_pixelAspectRatio = spsInfo . pixelAspectRatio ;
} else {
track . m_format . sub = avcConfig . profileIndication ;
track . m_version = static_cast < double > ( avcConfig . levelIndication ) / 10 ;
}
}
2015-04-22 19:22:01 +02:00
/*!
2015-07-07 03:01:48 +02:00
* \ brief Makes the track entry ( " trak " - atom ) for the track . The data is written to the assigned output stream
2015-04-22 19:22:01 +02:00
* at the current position .
2015-07-07 03:01:48 +02:00
* \ remarks Currently the " trak " - atom from the source file is just copied to the output stream .
2015-04-22 19:22:01 +02:00
*/
void Mp4Track : : makeTrack ( )
{
/*
// write header
ostream : : pos_type trakStartOffset = outputStream ( ) . tellp ( ) ;
writer . writeUInt32 ( 0 ) ; // write size later
writer . writeUInt32 ( Mp4AtomIds : : Track ) ;
// write tkhd atom
makeTrackHeader ( ) ;
// write tref atom (if one exists)
if ( Mp4Atom * trefAtom = trakAtom ( ) . childById ( Mp4AtomIds : : TrackReference ) ) {
trefAtom - > copyEntireAtomToStream ( outputStream ( ) ) ;
}
// write edts atom (if one exists)
if ( Mp4Atom * edtsAtom = trakAtom ( ) . childById ( Mp4AtomIds : : Edit ) ) {
edtsAtom - > copyEntireAtomToStream ( outputStream ( ) ) ;
}
// write mdia atom
makeMedia ( ) ;
// write size (of trak atom)
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream ( ) , trakStartOffset , false ) ;
*/
trakAtom ( ) . copyEntirely ( outputStream ( ) ) ;
}
2015-12-21 00:04:56 +01:00
/*!
* \ brief Returns the number of bytes written when calling makeTrack ( ) .
*/
uint64 Mp4Track : : requiredSize ( ) const
{
return m_trakAtom - > totalSize ( ) ;
}
2015-04-22 19:22:01 +02:00
/*!
* \ brief Makes the track header ( tkhd atom ) for the track . The data is written to the assigned output stream
* at the current position .
*/
void Mp4Track : : makeTrackHeader ( )
{
writer ( ) . writeUInt32BE ( 100 ) ; // size
writer ( ) . writeUInt32BE ( Mp4AtomIds : : TrackHeader ) ;
writer ( ) . writeByte ( 1 ) ; // version
uint32 flags = 0 ;
if ( m_enabled ) {
flags | = 0x000001 ;
}
if ( m_usedInPresentation ) {
flags | = 0x000002 ;
}
if ( m_usedWhenPreviewing ) {
flags | = 0x000004 ;
}
writer ( ) . writeUInt24BE ( flags ) ;
writer ( ) . writeUInt64BE ( static_cast < uint64 > ( ( m_creationTime - startDate ) . totalSeconds ( ) ) ) ;
writer ( ) . writeUInt64BE ( static_cast < uint64 > ( ( m_modificationTime - startDate ) . totalSeconds ( ) ) ) ;
writer ( ) . writeUInt32BE ( m_id ) ;
writer ( ) . writeUInt32BE ( 0 ) ; // reserved
writer ( ) . writeUInt64BE ( static_cast < uint64 > ( m_duration . totalSeconds ( ) * m_timeScale ) ) ;
writer ( ) . writeUInt32BE ( 0 ) ; // reserved
writer ( ) . writeUInt32BE ( 0 ) ; // reserved
if ( m_tkhdAtom ) {
// use existing values
char buffer [ 48 ] ;
m_istream - > seekg ( m_tkhdAtom - > startOffset ( ) + 52 ) ;
m_istream - > read ( buffer , sizeof ( buffer ) ) ;
m_ostream - > write ( buffer , sizeof ( buffer ) ) ;
} else {
// write default values
writer ( ) . writeInt16BE ( 0 ) ; // layer
writer ( ) . writeInt16BE ( 0 ) ; // alternate group
writer ( ) . writeFixed8BE ( 1.0 ) ; // volume
writer ( ) . writeUInt16BE ( 0 ) ; // reserved
for ( int32 value : { 0x00010000 , 0 , 0 , 0 , 0x00010000 , 0 , 0 , 0 , 0x40000000 } ) { // unity matrix
writer ( ) . writeInt32BE ( value ) ;
}
writer ( ) . writeFixed16BE ( 1.0 ) ; // width
writer ( ) . writeFixed16BE ( 1.0 ) ; // height
}
}
/*!
* \ brief Makes the media information ( mdia atom ) for the track . The data is written to the assigned output stream
* at the current position .
*/
void Mp4Track : : makeMedia ( )
{
ostream : : pos_type mdiaStartOffset = outputStream ( ) . tellp ( ) ;
writer ( ) . writeUInt32BE ( 0 ) ; // write size later
writer ( ) . writeUInt32BE ( Mp4AtomIds : : Media ) ;
// write mdhd atom
writer ( ) . writeUInt32BE ( 36 ) ; // size
writer ( ) . writeByte ( 1 ) ; // version
writer ( ) . writeUInt24BE ( 0 ) ; // flags
writer ( ) . writeUInt64BE ( static_cast < uint64 > ( ( m_creationTime - startDate ) . totalSeconds ( ) ) ) ;
writer ( ) . writeUInt64BE ( static_cast < uint64 > ( ( m_modificationTime - startDate ) . totalSeconds ( ) ) ) ;
writer ( ) . writeUInt32BE ( m_timeScale ) ;
writer ( ) . writeUInt64BE ( static_cast < uint64 > ( m_duration . totalSeconds ( ) * m_timeScale ) ) ;
// convert and write language
uint16 language = 0 ;
for ( size_t charIndex = 0 ; charIndex < m_language . length ( ) & & charIndex < 3 ; + + charIndex ) {
if ( m_language [ charIndex ] > = ' a ' & & m_language [ charIndex ] < = ' z ' ) {
language | = static_cast < uint16 > ( m_language [ charIndex ] ) < < ( 0xA - charIndex * 0x5 ) ;
} else { // invalid character
addNotification ( NotificationType : : Warning , " Assigned language \" " + m_language + " \" is of an invalid format and will be ignored. " , " making mdhd atom " ) ;
language = 0x55C4 ; // und
break ;
}
}
writer ( ) . writeUInt16BE ( language ) ;
writer ( ) . writeUInt16BE ( 0 ) ; // pre defined
// write hdlr atom
writer ( ) . writeUInt32BE ( 33 + m_name . length ( ) ) ; // size
writer ( ) . writeUInt32BE ( Mp4AtomIds : : HandlerReference ) ;
writer ( ) . writeUInt64BE ( 0 ) ; // version, flags, pre defined
switch ( m_mediaType ) {
2015-06-07 00:18:28 +02:00
case MediaType : : Video :
2015-04-22 19:22:01 +02:00
outputStream ( ) . write ( " vide " , 4 ) ;
break ;
2015-06-07 00:18:28 +02:00
case MediaType : : Audio :
2015-04-22 19:22:01 +02:00
outputStream ( ) . write ( " soun " , 4 ) ;
break ;
case MediaType : : Hint :
outputStream ( ) . write ( " hint " , 4 ) ;
break ;
2015-06-07 00:18:28 +02:00
case MediaType : : Text :
2015-04-22 19:22:01 +02:00
outputStream ( ) . write ( " meta " , 4 ) ;
break ;
default :
addNotification ( NotificationType : : Critical , " Media type is invalid; The media type video is assumed. " , " making hdlr atom " ) ;
outputStream ( ) . write ( " vide " , 4 ) ;
break ;
}
for ( int i = 0 ; i < 3 ; + + i ) writer ( ) . writeUInt32BE ( 0 ) ; // reserved
writer ( ) . writeTerminatedString ( m_name ) ;
// write minf atom
makeMediaInfo ( ) ;
// write size (of mdia atom)
2015-11-07 15:23:36 +01:00
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream ( ) , mdiaStartOffset ) ;
2015-04-22 19:22:01 +02:00
}
/*!
* \ brief Makes a media information ( minf atom ) for the track . The data is written to the assigned output stream
* at the current position .
*/
void Mp4Track : : makeMediaInfo ( )
{
ostream : : pos_type minfStartOffset = outputStream ( ) . tellp ( ) ;
writer ( ) . writeUInt32BE ( 0 ) ; // write size later
writer ( ) . writeUInt32BE ( Mp4AtomIds : : MediaInformation ) ;
bool dinfAtomWritten = false ;
if ( m_minfAtom ) {
// copy existing vmhd atom
if ( Mp4Atom * vmhdAtom = m_minfAtom - > childById ( Mp4AtomIds : : VideoMediaHeader ) ) {
vmhdAtom - > copyEntirely ( outputStream ( ) ) ;
}
// copy existing smhd atom
if ( Mp4Atom * smhdAtom = m_minfAtom - > childById ( Mp4AtomIds : : SoundMediaHeader ) ) {
smhdAtom - > copyEntirely ( outputStream ( ) ) ;
}
// copy existing hmhd atom
if ( Mp4Atom * hmhdAtom = m_minfAtom - > childById ( Mp4AtomIds : : HintMediaHeader ) ) {
hmhdAtom - > copyEntirely ( outputStream ( ) ) ;
}
// copy existing nmhd atom
if ( Mp4Atom * nmhdAtom = m_minfAtom - > childById ( Mp4AtomIds : : NullMediaHeaderBox ) ) {
nmhdAtom - > copyEntirely ( outputStream ( ) ) ;
}
// copy existing dinf atom
if ( Mp4Atom * dinfAtom = m_minfAtom - > childById ( Mp4AtomIds : : DataInformation ) ) {
dinfAtom - > copyEntirely ( outputStream ( ) ) ;
dinfAtomWritten = true ;
}
}
// write dinf atom if not written yet
if ( ! dinfAtomWritten ) {
writer ( ) . writeUInt32BE ( 36 ) ; // size
writer ( ) . writeUInt32BE ( Mp4AtomIds : : DataInformation ) ;
// write dref atom
writer ( ) . writeUInt32BE ( 28 ) ; // size
writer ( ) . writeUInt32BE ( Mp4AtomIds : : DataReference ) ;
writer ( ) . writeUInt32BE ( 0 ) ; // version and flags
writer ( ) . writeUInt32BE ( 1 ) ; // entry count
// write url atom
writer ( ) . writeUInt32BE ( 12 ) ; // size
writer ( ) . writeUInt32BE ( Mp4AtomIds : : DataEntryUrl ) ;
writer ( ) . writeByte ( 0 ) ; // version
writer ( ) . writeUInt24BE ( 0x000001 ) ; // flags (media data is in the same file as the movie box)
}
// write stbl atom
makeSampleTable ( ) ;
// write size (of minf atom)
2015-11-07 15:23:36 +01:00
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream ( ) , minfStartOffset ) ;
2015-04-22 19:22:01 +02:00
}
/*!
* \ brief Makes the sample table ( stbl atom ) for the track . The data is written to the assigned output stream
* at the current position .
2015-06-07 00:18:28 +02:00
* \ remarks Not fully implemented yet .
2015-04-22 19:22:01 +02:00
*/
void Mp4Track : : makeSampleTable ( )
{
ostream : : pos_type stblStartOffset = outputStream ( ) . tellp ( ) ;
writer ( ) . writeUInt32BE ( 0 ) ; // write size later
writer ( ) . writeUInt32BE ( Mp4AtomIds : : SampleTable ) ;
Mp4Atom * stblAtom = m_minfAtom ? m_minfAtom - > childById ( Mp4AtomIds : : SampleTable ) : nullptr ;
// write stsd atom
if ( m_stsdAtom ) {
// copy existing stsd atom
m_stsdAtom - > copyEntirely ( outputStream ( ) ) ;
} else {
addNotification ( NotificationType : : Critical , " Unable to make stsd atom from scratch. " , " making stsd atom " ) ;
throw NotImplementedException ( ) ;
}
// write stts and ctts atoms
Mp4Atom * sttsAtom = stblAtom ? stblAtom - > childById ( Mp4AtomIds : : DecodingTimeToSample ) : nullptr ;
if ( sttsAtom ) {
// copy existing stts atom
sttsAtom - > copyEntirely ( outputStream ( ) ) ;
} else {
addNotification ( NotificationType : : Critical , " Unable to make stts atom from scratch. " , " making stts atom " ) ;
throw NotImplementedException ( ) ;
}
Mp4Atom * cttsAtom = stblAtom ? stblAtom - > childById ( Mp4AtomIds : : CompositionTimeToSample ) : nullptr ;
if ( cttsAtom ) {
// copy existing ctts atom
cttsAtom - > copyEntirely ( outputStream ( ) ) ;
}
// write stsc atom (sample-to-chunk table)
// write stsz atom (sample sizes)
// write stz2 atom (compact sample sizes)
// write stco/co64 atom (chunk offset table)
// write stss atom (sync sample table)
// write stsh atom (shadow sync sample table)
// write padb atom (sample padding bits)
// write stdp atom (sample degradation priority)
// write sdtp atom (independent and disposable samples)
// write sbgp atom (sample group description)
// write sbgp atom (sample-to-group)
// write sgpd atom (sample group description)
// write subs atom (sub-sample information)
// write size (of stbl atom)
2015-11-07 15:23:36 +01:00
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream ( ) , stblStartOffset ) ;
2015-04-22 19:22:01 +02:00
}
void Mp4Track : : internalParseHeader ( )
{
2015-06-12 02:35:50 +02:00
static const string context ( " parsing MP4 track " ) ;
2015-04-22 19:22:01 +02:00
using namespace Mp4AtomIds ;
if ( ! m_trakAtom ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " \" trak \" -atom is null. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
// get atoms
try {
2015-06-12 02:35:50 +02:00
if ( ! ( m_tkhdAtom = m_trakAtom - > childById ( TrackHeader ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" tkhd \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-12 02:35:50 +02:00
if ( ! ( m_mdiaAtom = m_trakAtom - > childById ( Media ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" mdia \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-12 02:35:50 +02:00
if ( ! ( m_mdhdAtom = m_mdiaAtom - > childById ( MediaHeader ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" mdhd \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-12 02:35:50 +02:00
if ( ! ( m_hdlrAtom = m_mdiaAtom - > childById ( HandlerReference ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" hdlr \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-12 02:35:50 +02:00
if ( ! ( m_minfAtom = m_mdiaAtom - > childById ( MediaInformation ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" minf \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-12 02:35:50 +02:00
if ( ! ( m_stblAtom = m_minfAtom - > childById ( SampleTable ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" stbl \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-12 02:35:50 +02:00
if ( ! ( m_stsdAtom = m_stblAtom - > childById ( SampleDescription ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" stsd \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-12 02:35:50 +02:00
if ( ! ( m_stcoAtom = m_stblAtom - > childById ( ChunkOffset ) ) & & ! ( m_stcoAtom = m_stblAtom - > childById ( ChunkOffset64 ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" stco \" / \" co64 \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-12 02:35:50 +02:00
if ( ! ( m_stscAtom = m_stblAtom - > childById ( SampleToChunk ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" stsc \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-12 02:35:50 +02:00
if ( ! ( m_stszAtom = m_stblAtom - > childById ( SampleSize ) ) & & ! ( m_stszAtom = m_stblAtom - > childById ( CompactSampleSize ) ) ) {
2015-07-07 03:01:48 +02:00
addNotification ( NotificationType : : Critical , " No \" stsz \" / \" stz2 \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse relevant atoms. " , context ) ;
throw InvalidDataException ( ) ;
}
BinaryReader & reader = m_trakAtom - > reader ( ) ;
// read tkhd atom
m_istream - > seekg ( m_tkhdAtom - > startOffset ( ) + 8 ) ; // seek to beg, skip size and name
byte atomVersion = reader . readByte ( ) ; // read version
uint32 flags = reader . readUInt24BE ( ) ;
m_enabled = flags & 0x000001 ;
m_usedInPresentation = flags & 0x000002 ;
m_usedWhenPreviewing = flags & 0x000004 ;
switch ( atomVersion ) {
case 0 :
m_creationTime = startDate + TimeSpan : : fromSeconds ( reader . readUInt32BE ( ) ) ;
m_modificationTime = startDate + TimeSpan : : fromSeconds ( reader . readUInt32BE ( ) ) ;
m_id = reader . readUInt32BE ( ) ;
break ;
case 1 :
m_creationTime = startDate + TimeSpan : : fromSeconds ( reader . readUInt64BE ( ) ) ;
m_modificationTime = startDate + TimeSpan : : fromSeconds ( reader . readUInt64BE ( ) ) ;
m_id = reader . readUInt32BE ( ) ;
break ;
default :
2015-07-07 03:01:48 +02:00
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 ) ;
2015-04-22 19:22:01 +02:00
m_creationTime = DateTime ( ) ;
m_modificationTime = DateTime ( ) ;
m_id = 0 ;
}
// read mdhd atom
m_istream - > seekg ( m_mdhdAtom - > startOffset ( ) + 8 ) ; // seek to beg, skip size and name
atomVersion = reader . readByte ( ) ; // read version
m_istream - > seekg ( 3 , ios_base : : cur ) ; // skip flags
switch ( atomVersion ) {
case 0 :
m_creationTime = startDate + TimeSpan : : fromSeconds ( reader . readUInt32BE ( ) ) ;
m_modificationTime = startDate + TimeSpan : : fromSeconds ( reader . readUInt32BE ( ) ) ;
m_timeScale = reader . readUInt32BE ( ) ;
m_duration = TimeSpan : : fromSeconds ( static_cast < double > ( reader . readUInt32BE ( ) ) / static_cast < double > ( m_timeScale ) ) ;
break ;
case 1 :
m_creationTime = startDate + TimeSpan : : fromSeconds ( reader . readUInt64BE ( ) ) ;
m_modificationTime = startDate + TimeSpan : : fromSeconds ( reader . readUInt64BE ( ) ) ;
m_timeScale = reader . readUInt32BE ( ) ;
m_duration = TimeSpan : : fromSeconds ( static_cast < double > ( reader . readUInt64BE ( ) ) / static_cast < double > ( m_timeScale ) ) ;
break ;
default :
2015-07-07 03:01:48 +02:00
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 ) ;
2015-04-22 19:22:01 +02:00
m_timeScale = 0 ;
m_duration = TimeSpan ( ) ;
}
uint16 rawLanguage = reader . readUInt16BE ( ) ;
char buff [ 3 ] ;
buff [ 0 ] = ( ( rawLanguage & 0x7C00 ) > > 0xA ) + 0x60 ;
buff [ 1 ] = ( ( rawLanguage & 0x03E0 ) > > 0x5 ) + 0x60 ;
buff [ 2 ] = ( ( rawLanguage & 0x001F ) > > 0x0 ) + 0x60 ;
m_language = string ( buff , 3 ) ;
// read hdlr atom
2016-02-20 01:42:01 +01:00
// -> seek to begin skipping size, name, version, flags and reserved bytes
m_istream - > seekg ( m_hdlrAtom - > dataOffset ( ) + 8 ) ;
// -> track type
switch ( reader . readUInt32BE ( ) ) {
case 0x76696465 :
2015-06-07 00:18:28 +02:00
m_mediaType = MediaType : : Video ;
2016-02-20 01:42:01 +01:00
break ;
case 0x736F756E :
m_mediaType = MediaType : : Audio ;
break ;
case 0x68696E74 :
2015-04-22 19:22:01 +02:00
m_mediaType = MediaType : : Hint ;
2016-02-20 01:42:01 +01:00
break ;
case 0x6D657461 : case 0x74657874 :
2015-06-07 00:18:28 +02:00
m_mediaType = MediaType : : Text ;
2016-02-20 01:42:01 +01:00
break ;
default :
2015-04-22 19:22:01 +02:00
m_mediaType = MediaType : : Unknown ;
}
2016-02-20 01:42:01 +01:00
2015-04-22 19:22:01 +02:00
// name
m_istream - > seekg ( 12 , ios_base : : cur ) ; // skip reserved bytes
m_name = reader . readTerminatedString ( m_hdlrAtom - > totalSize ( ) - 12 - 4 - 12 , 0 ) ;
2015-06-10 01:28:22 +02:00
// read stco atom (only chunk count)
m_chunkOffsetSize = ( m_stcoAtom - > id ( ) = = Mp4AtomIds : : ChunkOffset64 ) ? 8 : 4 ;
m_istream - > seekg ( m_stcoAtom - > dataOffset ( ) + 4 ) ;
m_chunkCount = reader . readUInt32BE ( ) ;
2015-04-22 19:22:01 +02:00
// read stsd atom
2015-07-07 03:01:48 +02:00
m_istream - > seekg ( m_stsdAtom - > dataOffset ( ) + 4 ) ; // seek to beg, skip size, name, version and flags
2015-04-22 19:22:01 +02:00
uint32 entryCount = reader . readUInt32BE ( ) ;
2015-07-07 03:01:48 +02:00
Mp4Atom * esDescParentAtom = nullptr ;
uint16 tmp ;
2015-04-22 19:22:01 +02:00
if ( entryCount > 0 ) {
2015-07-07 03:01:48 +02:00
try {
for ( Mp4Atom * codecConfigContainerAtom = m_stsdAtom - > firstChild ( ) ; codecConfigContainerAtom ; codecConfigContainerAtom = codecConfigContainerAtom - > nextSibling ( ) ) {
2015-04-22 19:22:01 +02:00
codecConfigContainerAtom - > parse ( ) ;
2015-06-07 00:18:28 +02:00
// parse FOURCC
m_formatId = interpretIntegerAsString < uint32 > ( codecConfigContainerAtom - > id ( ) ) ;
2015-06-12 02:35:50 +02:00
m_format = FourccIds : : fourccToMediaFormat ( codecConfigContainerAtom - > id ( ) ) ;
2015-07-07 03:01:48 +02:00
// parse codecConfigContainerAtom
m_istream - > seekg ( codecConfigContainerAtom - > dataOffset ( ) ) ;
switch ( codecConfigContainerAtom - > id ( ) ) {
case FourccIds : : Mpeg4Audio : case FourccIds : : AmrNarrowband : case FourccIds : : Amr :
case FourccIds : : Drms : case FourccIds : : Alac : case FourccIds : : WindowsMediaAudio :
case FourccIds : : Ac3 : case FourccIds : : EAc3 : case FourccIds : : DolbyMpl :
case FourccIds : : Dts : case FourccIds : : DtsH : case FourccIds : : DtsE :
m_istream - > seekg ( 6 + 2 , ios_base : : cur ) ; // skip reserved bytes, data reference index
tmp = reader . readUInt16BE ( ) ; // read sound version
m_istream - > seekg ( 6 , ios_base : : cur ) ;
m_channelCount = reader . readUInt16BE ( ) ;
m_bitsPerSample = reader . readUInt16BE ( ) ;
m_istream - > seekg ( 4 , ios_base : : cur ) ; // skip reserved bytes (again)
2015-08-13 03:23:28 +02:00
if ( ! m_samplingFrequency ) {
m_samplingFrequency = reader . readUInt32BE ( ) > > 16 ;
2015-07-07 03:01:48 +02:00
if ( codecConfigContainerAtom - > id ( ) ! = FourccIds : : DolbyMpl ) {
2015-08-13 03:23:28 +02:00
m_samplingFrequency > > = 16 ;
2015-07-07 03:01:48 +02:00
}
} else {
m_istream - > seekg ( 4 , ios_base : : cur ) ;
}
if ( codecConfigContainerAtom - > id ( ) ! = FourccIds : : WindowsMediaAudio ) {
switch ( tmp ) {
case 1 :
codecConfigContainerAtom - > denoteFirstChild ( codecConfigContainerAtom - > headerSize ( ) + 28 + 16 ) ;
break ;
case 2 :
codecConfigContainerAtom - > denoteFirstChild ( codecConfigContainerAtom - > headerSize ( ) + 28 + 32 ) ;
break ;
default :
codecConfigContainerAtom - > denoteFirstChild ( codecConfigContainerAtom - > headerSize ( ) + 28 ) ;
}
if ( ! esDescParentAtom ) {
esDescParentAtom = codecConfigContainerAtom ;
}
}
break ;
case FourccIds : : Mpeg4Video : case FourccIds : : H263Quicktime : case FourccIds : : H2633GPP :
case FourccIds : : Avc1 : case FourccIds : : Avc2 : case FourccIds : : Avc3 : case FourccIds : : Avc4 :
case FourccIds : : Drmi : case FourccIds : : Hevc1 : case FourccIds : : Hevc2 :
m_istream - > seekg ( 6 + 2 + 16 , ios_base : : cur ) ; // skip reserved bytes, data reference index, and reserved bytes (again)
m_pixelSize . setWidth ( reader . readUInt16BE ( ) ) ;
m_pixelSize . setHeight ( reader . readUInt16BE ( ) ) ;
m_resolution . setWidth ( static_cast < uint32 > ( reader . readFixed16BE ( ) ) ) ;
m_resolution . setHeight ( static_cast < uint32 > ( reader . readFixed16BE ( ) ) ) ;
m_istream - > seekg ( 4 , ios_base : : cur ) ; // skip reserved bytes
m_framesPerSample = reader . readUInt16BE ( ) ;
tmp = reader . readByte ( ) ;
m_compressorName = reader . readString ( 31 ) ;
if ( tmp = = 0 ) {
m_compressorName . clear ( ) ;
} else if ( tmp < 32 ) {
m_compressorName . resize ( tmp ) ;
}
m_depth = reader . readUInt16BE ( ) ; // 24: color without alpha
codecConfigContainerAtom - > denoteFirstChild ( codecConfigContainerAtom - > headerSize ( ) + 78 ) ;
if ( ! esDescParentAtom ) {
esDescParentAtom = codecConfigContainerAtom ;
}
break ;
2015-07-13 00:57:38 +02:00
case FourccIds : : Mpeg4Sample :
2016-02-17 20:19:05 +01:00
// skip reserved bytes and data reference index
2015-07-13 00:57:38 +02:00
codecConfigContainerAtom - > denoteFirstChild ( codecConfigContainerAtom - > headerSize ( ) + 8 ) ;
if ( ! esDescParentAtom ) {
esDescParentAtom = codecConfigContainerAtom ;
}
break ;
2015-07-07 03:01:48 +02:00
case Mp4AtomIds : : PixalAspectRatio :
break ; // TODO
case Mp4AtomIds : : CleanAperature :
break ; // TODO
default :
;
}
}
2016-02-17 20:19:05 +01:00
2015-07-07 03:01:48 +02:00
if ( esDescParentAtom ) {
2016-02-17 20:19:05 +01:00
// parse AVC configuration
if ( Mp4Atom * avcConfigAtom = esDescParentAtom - > childById ( Mp4AtomIds : : AvcConfiguration ) ) {
m_istream - > seekg ( avcConfigAtom - > dataOffset ( ) ) ;
m_avcConfig = make_unique < Media : : AvcConfiguration > ( ) ;
try {
m_avcConfig - > parse ( reader , avcConfigAtom - > dataSize ( ) ) ;
addInfo ( * m_avcConfig , * this ) ;
} catch ( const TruncatedDataException & ) {
addNotification ( NotificationType : : Critical , " AVC configuration is truncated. " , context ) ;
} catch ( const Failure & ) {
addNotification ( NotificationType : : Critical , " AVC configuration is invalid. " , context ) ;
}
}
// parse MPEG-4 elementary stream descriptor
2015-07-07 03:01:48 +02:00
Mp4Atom * esDescAtom = esDescParentAtom - > childById ( Mp4FormatExtensionIds : : Mpeg4ElementaryStreamDescriptor ) ;
2015-06-10 01:28:22 +02:00
if ( ! esDescAtom ) {
2015-07-07 03:01:48 +02:00
esDescAtom = esDescParentAtom - > childById ( Mp4FormatExtensionIds : : Mpeg4ElementaryStreamDescriptor2 ) ;
2015-06-07 00:18:28 +02:00
}
2015-06-10 01:28:22 +02:00
if ( esDescAtom ) {
try {
2015-08-13 03:23:28 +02:00
if ( ( m_esInfo = parseMpeg4ElementaryStreamInfo ( * this , m_reader , esDescAtom ) ) ) {
2015-06-10 01:28:22 +02:00
m_format + = Mpeg4ElementaryStreamObjectIds : : streamObjectTypeFormat ( m_esInfo - > objectTypeId ) ;
m_bitrate = static_cast < double > ( m_esInfo - > averageBitrate ) / 1000 ;
m_maxBitrate = static_cast < double > ( m_esInfo - > maxBitrate ) / 1000 ;
if ( m_esInfo - > audioSpecificConfig ) {
// check the audio specific config for useful information
m_format + = Mpeg4AudioObjectIds : : idToMediaFormat ( m_esInfo - > audioSpecificConfig - > audioObjectType , m_esInfo - > audioSpecificConfig - > sbrPresent , m_esInfo - > audioSpecificConfig - > psPresent ) ;
if ( m_esInfo - > audioSpecificConfig - > sampleFrequencyIndex = = 0xF ) {
2015-08-13 03:23:28 +02:00
m_samplingFrequency = m_esInfo - > audioSpecificConfig - > sampleFrequency ;
2015-08-10 00:02:03 +02:00
} else if ( m_esInfo - > audioSpecificConfig - > sampleFrequencyIndex < sizeof ( mpeg4SamplingFrequencyTable ) ) {
2015-08-13 03:23:28 +02:00
m_samplingFrequency = mpeg4SamplingFrequencyTable [ m_esInfo - > audioSpecificConfig - > sampleFrequencyIndex ] ;
2015-06-10 01:28:22 +02:00
} else {
addNotification ( NotificationType : : Warning , " Audio specific config has invalid sample frequency index. " , context ) ;
}
if ( m_esInfo - > audioSpecificConfig - > extensionSampleFrequencyIndex = = 0xF ) {
2015-08-13 03:23:28 +02:00
m_extensionSamplingFrequency = m_esInfo - > audioSpecificConfig - > extensionSampleFrequency ;
2015-08-10 00:02:03 +02:00
} else if ( m_esInfo - > audioSpecificConfig - > extensionSampleFrequencyIndex < sizeof ( mpeg4SamplingFrequencyTable ) ) {
2015-08-13 03:23:28 +02:00
m_extensionSamplingFrequency = mpeg4SamplingFrequencyTable [ m_esInfo - > audioSpecificConfig - > extensionSampleFrequencyIndex ] ;
2015-06-10 01:28:22 +02:00
} else {
addNotification ( NotificationType : : Warning , " Audio specific config has invalid extension sample frequency index. " , context ) ;
}
2015-07-31 01:09:41 +02:00
m_channelConfig = m_esInfo - > audioSpecificConfig - > channelConfiguration ;
2015-09-24 01:15:27 +02:00
m_extensionChannelConfig = m_esInfo - > audioSpecificConfig - > extensionChannelConfiguration ;
2015-06-10 01:28:22 +02:00
}
2015-07-07 03:01:48 +02:00
if ( m_esInfo - > videoSpecificConfig ) {
// check the video specific config for useful information
if ( m_format . general = = GeneralMediaFormat : : Mpeg4Video & & m_esInfo - > videoSpecificConfig - > profile ) {
m_format . sub = m_esInfo - > videoSpecificConfig - > profile ;
if ( ! m_esInfo - > videoSpecificConfig - > userData . empty ( ) ) {
m_formatId + = " / " + m_esInfo - > videoSpecificConfig - > userData ;
}
}
}
2015-06-10 01:28:22 +02:00
// check the stream data for missing information
switch ( m_format . general ) {
case GeneralMediaFormat : : Mpeg1Audio : case GeneralMediaFormat : : Mpeg2Audio : {
MpegAudioFrame frame ;
m_istream - > seekg ( m_stcoAtom - > dataOffset ( ) + 8 ) ;
m_istream - > seekg ( m_chunkOffsetSize = = 8 ? reader . readUInt64BE ( ) : reader . readUInt32BE ( ) ) ;
2015-07-15 00:10:24 +02:00
frame . parseHeader ( reader ) ;
2015-06-10 01:28:22 +02:00
MpegAudioFrameStream : : addInfo ( frame , * this ) ;
break ;
} default :
;
}
2015-06-07 00:18:28 +02:00
}
2015-06-10 01:28:22 +02:00
} catch ( Failure & ) {
2015-06-07 00:18:28 +02:00
}
2015-04-22 19:22:01 +02:00
}
}
2015-07-07 03:01:48 +02:00
} catch ( Failure & ) {
addNotification ( NotificationType : : Critical , " Unable to parse child atoms of \" stsd \" -atom. " , context ) ;
2015-04-22 19:22:01 +02:00
}
}
// read stsz atom which holds the sample size table
m_sampleSizes . clear ( ) ;
2015-07-07 03:01:48 +02:00
m_size = m_sampleCount = 0 ;
2015-04-22 19:22:01 +02:00
uint64 actualSampleSizeTableSize = m_stszAtom - > dataSize ( ) ;
if ( actualSampleSizeTableSize < 12 ) {
addNotification ( NotificationType : : Critical , " The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined. " , context ) ;
} else {
actualSampleSizeTableSize - = 12 ; // subtract size of version and flags
m_istream - > seekg ( m_stszAtom - > dataOffset ( ) + 4 ) ; // seek to beg, skip size, name, version and flags
uint32 fieldSize ;
uint32 constantSize ;
if ( m_stszAtom - > id ( ) = = Mp4AtomIds : : CompactSampleSize ) {
constantSize = 0 ;
m_istream - > seekg ( 3 , ios_base : : cur ) ; // seek reserved bytes
fieldSize = reader . readByte ( ) ;
m_sampleCount = reader . readUInt32BE ( ) ;
} else {
constantSize = reader . readUInt32BE ( ) ;
m_sampleCount = reader . readUInt32BE ( ) ;
fieldSize = 32 ;
}
if ( constantSize ) {
m_sampleSizes . push_back ( constantSize ) ;
m_size = constantSize * m_sampleCount ;
} else {
uint64 actualSampleCount = m_sampleCount ;
uint64 calculatedSampleSizeTableSize = ceil ( ( 0.125 * fieldSize ) * m_sampleCount ) ;
if ( calculatedSampleSizeTableSize < actualSampleSizeTableSize ) {
addNotification ( NotificationType : : Critical , " The stsz atom stores more entries as denoted. The additional entries will be ignored. " , context ) ;
} else if ( calculatedSampleSizeTableSize > actualSampleSizeTableSize ) {
addNotification ( NotificationType : : Critical , " The stsz atom is truncated. It stores less entries as denoted. " , context ) ;
actualSampleCount = floor ( static_cast < double > ( actualSampleSizeTableSize ) / ( 0.125 * fieldSize ) ) ;
}
m_sampleSizes . reserve ( actualSampleCount ) ;
uint32 i = 1 ;
switch ( fieldSize ) {
case 4 :
for ( ; i < = actualSampleCount ; i + = 2 ) {
byte val = reader . readByte ( ) ;
m_sampleSizes . push_back ( val > > 4 ) ;
m_sampleSizes . push_back ( val & 0xF0 ) ;
m_size + = ( val > > 4 ) + ( val & 0xF0 ) ;
}
if ( i < = actualSampleCount + 1 ) {
m_sampleSizes . push_back ( reader . readByte ( ) > > 4 ) ;
m_size + = m_sampleSizes . back ( ) ;
}
break ;
case 8 :
for ( ; i < = actualSampleCount ; + + i ) {
m_sampleSizes . push_back ( reader . readByte ( ) ) ;
m_size + = m_sampleSizes . back ( ) ;
}
break ;
case 16 :
for ( ; i < = actualSampleCount ; + + i ) {
m_sampleSizes . push_back ( reader . readUInt16BE ( ) ) ;
m_size + = m_sampleSizes . back ( ) ;
}
break ;
case 32 :
for ( ; i < = actualSampleCount ; + + i ) {
m_sampleSizes . push_back ( reader . readUInt32BE ( ) ) ;
m_size + = m_sampleSizes . back ( ) ;
}
break ;
default :
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 ) ;
}
}
}
// no sample sizes found, search for trun atoms
uint64 totalDuration = 0 ;
2015-07-07 03:01:48 +02:00
for ( Mp4Atom * moofAtom = m_trakAtom - > container ( ) . firstElement ( ) - > siblingById ( MovieFragment , true ) ; moofAtom ; moofAtom = moofAtom - > siblingById ( MovieFragment , false ) ) {
2015-04-22 19:22:01 +02:00
moofAtom - > parse ( ) ;
2015-07-07 03:01:48 +02:00
for ( Mp4Atom * trafAtom = moofAtom - > childById ( TrackFragment ) ; trafAtom ; trafAtom = trafAtom - > siblingById ( TrackFragment , false ) ) {
2015-04-22 19:22:01 +02:00
trafAtom - > parse ( ) ;
2015-07-07 03:01:48 +02:00
for ( Mp4Atom * tfhdAtom = trafAtom - > childById ( TrackFragmentHeader ) ; tfhdAtom ; tfhdAtom = tfhdAtom - > siblingById ( TrackFragmentHeader , false ) ) {
2015-04-22 19:22:01 +02:00
tfhdAtom - > parse ( ) ;
uint32 calculatedDataSize = 0 ;
if ( tfhdAtom - > dataSize ( ) < calculatedDataSize ) {
addNotification ( NotificationType : : Critical , " tfhd atom is truncated. " , context ) ;
} else {
m_istream - > seekg ( tfhdAtom - > dataOffset ( ) + 1 ) ;
uint32 flags = reader . readUInt24BE ( ) ;
if ( m_id = = reader . readUInt32BE ( ) ) { // check track ID
if ( flags & 0x000001 ) { // base-data-offset present
calculatedDataSize + = 8 ;
}
if ( flags & 0x000002 ) { // sample-description-index present
calculatedDataSize + = 4 ;
}
if ( flags & 0x000008 ) { // default-sample-duration present
calculatedDataSize + = 4 ;
}
if ( flags & 0x000010 ) { // default-sample-size present
calculatedDataSize + = 4 ;
}
if ( flags & 0x000020 ) { // default-sample-flags present
calculatedDataSize + = 4 ;
}
//uint64 baseDataOffset = moofAtom->startOffset();
//uint32 defaultSampleDescriptionIndex = 0;
uint32 defaultSampleDuration = 0 ;
uint32 defaultSampleSize = 0 ;
2015-06-24 00:45:53 +02:00
//uint32 defaultSampleFlags = 0;
2015-04-22 19:22:01 +02:00
if ( tfhdAtom - > dataSize ( ) < calculatedDataSize ) {
addNotification ( NotificationType : : Critical , " tfhd atom is truncated (presence of fields denoted). " , context ) ;
} else {
if ( flags & 0x000001 ) { // base-data-offset present
//baseDataOffset = reader.readUInt64();
m_istream - > seekg ( 8 , ios_base : : cur ) ;
}
if ( flags & 0x000002 ) { // sample-description-index present
//defaultSampleDescriptionIndex = reader.readUInt32();
m_istream - > seekg ( 4 , ios_base : : cur ) ;
}
if ( flags & 0x000008 ) { // default-sample-duration present
defaultSampleDuration = reader . readUInt32BE ( ) ;
2015-06-24 00:45:53 +02:00
//m_istream->seekg(4, ios_base::cur);
2015-04-22 19:22:01 +02:00
}
if ( flags & 0x000010 ) { // default-sample-size present
defaultSampleSize = reader . readUInt32BE ( ) ;
}
if ( flags & 0x000020 ) { // default-sample-flags present
2015-06-24 00:45:53 +02:00
//defaultSampleFlags = reader.readUInt32BE();
m_istream - > seekg ( 4 , ios_base : : cur ) ;
2015-04-22 19:22:01 +02:00
}
}
2015-07-07 03:01:48 +02:00
for ( Mp4Atom * trunAtom = trafAtom - > childById ( TrackFragmentRun ) ; trunAtom ; trunAtom = trunAtom - > siblingById ( TrackFragmentRun , false ) ) {
2015-04-22 19:22:01 +02:00
uint32 calculatedDataSize = 8 ;
if ( trunAtom - > dataSize ( ) < calculatedDataSize ) {
addNotification ( NotificationType : : Critical , " trun atom is truncated. " , context ) ;
} else {
m_istream - > seekg ( trunAtom - > dataOffset ( ) + 1 ) ;
uint32 flags = reader . readUInt24BE ( ) ;
uint32 sampleCount = reader . readUInt32BE ( ) ;
m_sampleCount + = sampleCount ;
if ( flags & 0x000001 ) { // data offset present
calculatedDataSize + = 4 ;
}
if ( flags & 0x000004 ) { // first-sample-flags present
calculatedDataSize + = 4 ;
}
uint32 entrySize = 0 ;
if ( flags & 0x000100 ) { // sample-duration present
entrySize + = 4 ;
}
if ( flags & 0x000200 ) { // sample-size present
entrySize + = 4 ;
}
if ( flags & 0x000400 ) { // sample-flags present
entrySize + = 4 ;
}
if ( flags & 0x000800 ) { // sample-composition-time-offsets present
entrySize + = 4 ;
}
calculatedDataSize + = entrySize * sampleCount ;
if ( trunAtom - > dataSize ( ) < calculatedDataSize ) {
addNotification ( NotificationType : : Critical , " trun atom is truncated (presence of fields denoted). " , context ) ;
} else {
if ( flags & 0x000001 ) { // data offset present
m_istream - > seekg ( 4 , ios_base : : cur ) ;
//int32 dataOffset = reader.readInt32();
}
if ( flags & 0x000004 ) { // first-sample-flags present
m_istream - > seekg ( 4 , ios_base : : cur ) ;
}
for ( uint32 i = 0 ; i < sampleCount ; + + i ) {
if ( flags & 0x000100 ) { // sample-duration present
totalDuration + = reader . readUInt32BE ( ) ;
} else {
totalDuration + = defaultSampleDuration ;
}
if ( flags & 0x000200 ) { // sample-size present
m_sampleSizes . push_back ( reader . readUInt32BE ( ) ) ;
m_size + = m_sampleSizes . back ( ) ;
} else {
m_size + = defaultSampleSize ;
}
if ( flags & 0x000400 ) { // sample-flags present
m_istream - > seekg ( 4 , ios_base : : cur ) ;
}
if ( flags & 0x000800 ) { // sample-composition-time-offsets present
m_istream - > seekg ( 4 , ios_base : : cur ) ;
}
}
}
}
}
if ( m_sampleSizes . empty ( ) & & defaultSampleSize ) {
m_sampleSizes . push_back ( defaultSampleSize ) ;
}
}
}
}
}
}
// set duration from "trun-information" if the duration has not been determined yet
if ( m_duration . isNull ( ) & & totalDuration ) {
uint32 timeScale = m_timeScale ;
if ( ! timeScale ) {
timeScale = trakAtom ( ) . container ( ) . timeScale ( ) ;
}
if ( timeScale ) {
2015-07-07 03:01:48 +02:00
m_duration = TimeSpan : : fromSeconds ( static_cast < double > ( totalDuration ) / static_cast < double > ( timeScale ) ) ;
2015-04-22 19:22:01 +02:00
}
}
// caluculate average bitrate
2015-06-10 01:28:22 +02:00
if ( m_bitrate < 0.01 & & m_bitrate > - 0.01 ) {
m_bitrate = ( static_cast < double > ( m_size ) * 0.0078125 ) / m_duration . totalSeconds ( ) ;
}
2015-04-22 19:22:01 +02:00
// read stsc atom (only number of entries)
m_istream - > seekg ( m_stscAtom - > dataOffset ( ) + 4 ) ;
m_sampleToChunkEntryCount = reader . readUInt32BE ( ) ;
}
}