2018-03-07 01:17:50 +01:00
# include "./mp4track.h"
2015-09-06 19:57:33 +02:00
# include "./mp4atom.h"
# include "./mp4container.h"
# include "./mp4ids.h"
# include "./mpeg4descriptor.h"
2015-04-22 19:22:01 +02:00
2018-08-23 23:18:57 +02:00
# include "../av1/av1configuration.h"
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
2017-01-27 18:59:22 +01:00
# include <c++utilities/conversion/stringbuilder.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>
2016-06-14 22:53:43 +02:00
# include <c++utilities/io/catchiofailure.h>
2015-04-22 19:22:01 +02:00
# include <cmath>
2018-03-07 01:17:50 +01:00
# include <locale>
2015-04-22 19:22:01 +02:00
using namespace std ;
using namespace IoUtilities ;
using namespace ConversionUtilities ;
using namespace ChronoUtilities ;
2018-03-06 23:09:15 +01:00
namespace TagParser {
2015-04-22 19:22:01 +02:00
2017-09-14 01:37:15 +02:00
/*!
* \ brief The TrackHeaderInfo struct holds information about the present track header ( tkhd atom ) and
* information for making a new track header based on it .
* \ sa TrackHeaderInfo Mp4Track : : verifyPresentTrackHeader ( ) for obtaining an instance .
* \ remarks The struct is only used internally by the Mp4Track class .
*/
2018-03-07 01:17:50 +01:00
struct TrackHeaderInfo {
2017-09-14 01:37:15 +02:00
friend class Mp4Track ;
private :
2018-07-10 14:11:11 +02:00
constexpr TrackHeaderInfo ( ) ;
2017-09-14 01:37:15 +02:00
/// \brief Specifies the size which is required for <i>making a new</i> track header based one the existing one.
uint64 requiredSize ;
/// \brief Specifies whether there actually a track header exists and whether it can be used as basis for a new one.
bool canUseExisting ;
/// \brief Specifies whether the existing track header is truncated.
bool truncated ;
/// \brief Specifies the version of the existing track header.
byte version ;
/// \brief Specifies whether the version of the existing track header is unknown (and assumed to be 1).
bool versionUnknown ;
/// \brief Specifies the additional data offset of the existing header. Unspecified if canUseExisting is false.
byte additionalDataOffset ;
/// \brief Specifies whether the buffered header data should be discarded when making a new track header.
bool discardBuffer ;
} ;
2018-07-10 14:11:11 +02:00
constexpr TrackHeaderInfo : : TrackHeaderInfo ( )
2018-03-07 01:17:50 +01:00
: requiredSize ( 100 )
, canUseExisting ( false )
, truncated ( false )
, version ( 0 )
, versionUnknown ( false )
2018-07-10 14:11:11 +02:00
, additionalDataOffset ( 0 )
2018-03-07 01:17:50 +01:00
, discardBuffer ( false )
{
}
2017-09-14 01:37:15 +02:00
2016-08-04 00:16:19 +02:00
/// \brief Dates within MP4 tracks are expressed as the number of seconds since this date.
const DateTime startDate = DateTime : : fromDate ( 1904 , 1 , 1 ) ;
/*!
* \ class Mpeg4AudioSpecificConfig
* \ brief The Mpeg4AudioSpecificConfig class holds MPEG - 4 audio specific config parsed using Mp4Track : : parseAudioSpecificConfig ( ) .
* \ remarks Is part of Mpeg4ElementaryStreamInfo ( audio streams only ) .
*/
2015-04-22 19:22:01 +02:00
2018-03-07 01:17:50 +01: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 )
{
}
2015-06-10 01:28:22 +02:00
2016-08-04 00:16:19 +02:00
/*!
* \ class Mpeg4VideoSpecificConfig
* \ brief The Mpeg4VideoSpecificConfig class holds MPEG - 4 video specific config parsed using Mp4Track : : parseVideoSpecificConfig ( ) .
* \ remarks
* - Is part of Mpeg4ElementaryStreamInfo ( video streams only ) .
* - AVC configuration is another thing and covered by the AvcConfiguration class .
*/
2018-03-07 01:17:50 +01:00
Mpeg4VideoSpecificConfig : : Mpeg4VideoSpecificConfig ( )
: profile ( 0 )
{
}
2015-07-07 03:01:48 +02:00
2016-08-04 00:16:19 +02:00
/*!
* \ class Mpeg4ElementaryStreamInfo
* \ brief The Mpeg4ElementaryStreamInfo class holds MPEG - 4 elementary stream info parsed using Mp4Track : : parseMpeg4ElementaryStreamInfo ( ) .
*/
2015-04-22 19:22:01 +02:00
/*!
2018-06-03 20:38:32 +02:00
* \ class TagParser : : Mp4Track
* \ brief Implementation of TagParser : : AbstractTrack for the MP4 container .
2015-04-22 19:22:01 +02:00
*/
/*!
* \ 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 .
*/
2018-03-07 01:17:50 +01:00
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 )
2019-04-16 21:47:08 +02:00
, m_framesPerSample ( 1 )
2018-03-07 01:17:50 +01:00
, m_chunkOffsetSize ( 4 )
, m_chunkCount ( 0 )
, m_sampleToChunkEntryCount ( 0 )
{
}
2015-04-22 19:22:01 +02:00
/*!
* \ brief Destroys the track .
*/
Mp4Track : : ~ Mp4Track ( )
2018-03-07 01:17:50 +01:00
{
}
2015-04-22 19:22:01 +02:00
TrackType Mp4Track : : type ( ) const
{
return TrackType : : Mp4Track ;
}
2017-05-28 21:03:45 +02:00
/*!
* \ brief Reads the chunk offsets from the stco atom and fragments if \ a parseFragments is true .
* \ 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 ( ) ;
*/
2018-03-05 17:49:29 +01:00
std : : vector < uint64 > Mp4Track : : readChunkOffsets ( bool parseFragments , Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
static const string context ( " reading chunk offset table of MP4 track " ) ;
2018-03-07 01:17:50 +01:00
if ( ! isHeaderValid ( ) | | ! m_istream ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Track has not been parsed. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
vector < uint64 > offsets ;
2018-03-07 01:17:50 +01:00
if ( m_stcoAtom ) {
2015-04-22 19:22:01 +02:00
// verify integrity of the chunk offset table
uint64 actualTableSize = m_stcoAtom - > dataSize ( ) ;
2018-03-07 01:17:50 +01:00
if ( actualTableSize < ( 8 + chunkOffsetSize ( ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " The stco atom is truncated. There are no chunk offsets present. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
} else {
actualTableSize - = 8 ;
}
uint32 actualChunkCount = chunkCount ( ) ;
uint64 calculatedTableSize = chunkCount ( ) * chunkOffsetSize ( ) ;
2018-03-07 01:17:50 +01:00
if ( calculatedTableSize < actualTableSize ) {
diag . emplace_back (
DiagLevel : : Critical , " The stco atom stores more chunk offsets as denoted. The additional chunk offsets will be ignored. " , context ) ;
} else if ( calculatedTableSize > actualTableSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " The stco atom is truncated. It stores less chunk offsets as denoted. " , context ) ;
2018-08-23 23:20:29 +02:00
actualChunkCount = static_cast < uint32 > ( floor ( static_cast < double > ( actualTableSize ) / static_cast < double > ( chunkOffsetSize ( ) ) ) ) ;
2015-04-22 19:22:01 +02:00
}
// read the table
offsets . reserve ( actualChunkCount ) ;
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_stcoAtom - > dataOffset ( ) + 8 ) ) ;
2018-03-07 01:17:50 +01:00
switch ( chunkOffsetSize ( ) ) {
2015-04-22 19:22:01 +02:00
case 4 :
2018-03-07 01:17:50 +01:00
for ( uint32 i = 0 ; i < actualChunkCount ; + + i ) {
2015-04-22 19:22:01 +02:00
offsets . push_back ( reader ( ) . readUInt32BE ( ) ) ;
}
break ;
case 8 :
2018-03-07 01:17:50 +01:00
for ( uint32 i = 0 ; i < actualChunkCount ; + + i ) {
2015-04-22 19:22:01 +02:00
offsets . push_back ( reader ( ) . readUInt64BE ( ) ) ;
}
break ;
default :
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " The determined chunk offset size is invalid. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
}
// read sample offsets of fragments
2018-03-07 01:17:50 +01:00
if ( parseFragments ) {
2017-05-28 21:03:45 +02:00
uint64 totalDuration = 0 ;
2018-03-07 01:17:50 +01:00
for ( Mp4Atom * moofAtom = m_trakAtom - > container ( ) . firstElement ( ) - > siblingByIdIncludingThis ( Mp4AtomIds : : MovieFragment , diag ) ; moofAtom ;
moofAtom = moofAtom - > siblingById ( Mp4AtomIds : : MovieFragment , diag ) ) {
2018-03-05 17:49:29 +01:00
moofAtom - > parse ( diag ) ;
2018-03-07 01:17:50 +01:00
for ( Mp4Atom * trafAtom = moofAtom - > childById ( Mp4AtomIds : : TrackFragment , diag ) ; trafAtom ;
trafAtom = trafAtom - > siblingById ( Mp4AtomIds : : TrackFragment , diag ) ) {
2018-03-05 17:49:29 +01:00
trafAtom - > parse ( diag ) ;
2018-03-07 01:17:50 +01:00
for ( Mp4Atom * tfhdAtom = trafAtom - > childById ( Mp4AtomIds : : TrackFragmentHeader , diag ) ; tfhdAtom ;
tfhdAtom = tfhdAtom - > siblingById ( Mp4AtomIds : : TrackFragmentHeader , diag ) ) {
2018-03-05 17:49:29 +01:00
tfhdAtom - > parse ( diag ) ;
2017-05-28 21:03:45 +02:00
uint32 calculatedDataSize = 0 ;
2018-03-07 01:17:50 +01:00
if ( tfhdAtom - > dataSize ( ) < calculatedDataSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " tfhd atom is truncated. " , context ) ;
2017-05-28 21:03:45 +02:00
} else {
2019-04-21 18:14:20 +02:00
inputStream ( ) . seekg ( static_cast < streamoff > ( tfhdAtom - > dataOffset ( ) + 1 ) ) ;
const std : : uint32_t flags = reader ( ) . readUInt24BE ( ) ;
2018-03-07 01:17:50 +01:00
if ( m_id = = reader ( ) . readUInt32BE ( ) ) { // check track ID
if ( flags & 0x000001 ) { // base-data-offset present
2017-05-28 21:03:45 +02:00
calculatedDataSize + = 8 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000002 ) { // sample-description-index present
2017-05-28 21:03:45 +02:00
calculatedDataSize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000008 ) { // default-sample-duration present
2017-05-28 21:03:45 +02:00
calculatedDataSize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000010 ) { // default-sample-size present
2017-05-28 21:03:45 +02:00
calculatedDataSize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000020 ) { // default-sample-flags present
2017-05-28 21:03:45 +02:00
calculatedDataSize + = 4 ;
}
2017-06-11 01:22:30 +02:00
// some variables are currently skipped because they are currently not interesting
2017-05-28 21:03:45 +02:00
//uint64 baseDataOffset = moofAtom->startOffset();
//uint32 defaultSampleDescriptionIndex = 0;
uint32 defaultSampleDuration = 0 ;
uint32 defaultSampleSize = 0 ;
2017-06-11 01:22:30 +02:00
//uint32 defaultSampleFlags = 0;
2018-03-07 01:17:50 +01:00
if ( tfhdAtom - > dataSize ( ) < calculatedDataSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " tfhd atom is truncated (presence of fields denoted). " , context ) ;
2017-05-28 21:03:45 +02:00
} else {
2018-03-07 01:17:50 +01:00
if ( flags & 0x000001 ) { // base-data-offset present
2017-05-28 21:03:45 +02:00
//baseDataOffset = reader.readUInt64();
inputStream ( ) . seekg ( 8 , ios_base : : cur ) ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000002 ) { // sample-description-index present
2017-05-28 21:03:45 +02:00
//defaultSampleDescriptionIndex = reader.readUInt32();
inputStream ( ) . seekg ( 4 , ios_base : : cur ) ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000008 ) { // default-sample-duration present
2017-05-28 21:03:45 +02:00
defaultSampleDuration = reader ( ) . readUInt32BE ( ) ;
2017-06-11 01:22:30 +02:00
//inputStream().seekg(4, ios_base::cur);
2017-05-28 21:03:45 +02:00
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000010 ) { // default-sample-size present
2017-05-28 21:03:45 +02:00
defaultSampleSize = reader ( ) . readUInt32BE ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000020 ) { // default-sample-flags present
2017-06-11 01:22:30 +02:00
//defaultSampleFlags = reader().readUInt32BE();
inputStream ( ) . seekg ( 4 , ios_base : : cur ) ;
2017-05-28 21:03:45 +02:00
}
}
2018-03-07 01:17:50 +01:00
for ( Mp4Atom * trunAtom = trafAtom - > childById ( Mp4AtomIds : : TrackFragmentRun , diag ) ; trunAtom ;
trunAtom = trunAtom - > siblingById ( Mp4AtomIds : : TrackFragmentRun , diag ) ) {
2017-05-28 21:03:45 +02:00
uint32 calculatedDataSize = 8 ;
2018-03-07 01:17:50 +01:00
if ( trunAtom - > dataSize ( ) < calculatedDataSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " trun atom is truncated. " , context ) ;
2017-05-28 21:03:45 +02:00
} else {
2019-04-21 18:14:20 +02:00
inputStream ( ) . seekg ( static_cast < streamoff > ( trunAtom - > dataOffset ( ) + 1 ) ) ;
std : : uint32_t flags = reader ( ) . readUInt24BE ( ) ;
std : : uint32_t sampleCount = reader ( ) . readUInt32BE ( ) ;
2017-05-28 21:03:45 +02:00
m_sampleCount + = sampleCount ;
2018-03-07 01:17:50 +01:00
if ( flags & 0x000001 ) { // data offset present
2017-05-28 21:03:45 +02:00
calculatedDataSize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000004 ) { // first-sample-flags present
2017-05-28 21:03:45 +02:00
calculatedDataSize + = 4 ;
}
uint32 entrySize = 0 ;
2018-03-07 01:17:50 +01:00
if ( flags & 0x000100 ) { // sample-duration present
2017-05-28 21:03:45 +02:00
entrySize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000200 ) { // sample-size present
2017-05-28 21:03:45 +02:00
entrySize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000400 ) { // sample-flags present
2017-05-28 21:03:45 +02:00
entrySize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000800 ) { // sample-composition-time-offsets present
2017-05-28 21:03:45 +02:00
entrySize + = 4 ;
}
calculatedDataSize + = entrySize * sampleCount ;
2018-03-07 01:17:50 +01:00
if ( trunAtom - > dataSize ( ) < calculatedDataSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " trun atom is truncated (presence of fields denoted). " , context ) ;
2017-05-28 21:03:45 +02:00
} else {
2018-03-07 01:17:50 +01:00
if ( flags & 0x000001 ) { // data offset present
2017-05-28 21:03:45 +02:00
inputStream ( ) . seekg ( 4 , ios_base : : cur ) ;
//int32 dataOffset = reader().readInt32BE();
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000004 ) { // first-sample-flags present
2017-05-28 21:03:45 +02:00
inputStream ( ) . seekg ( 4 , ios_base : : cur ) ;
}
2018-03-07 01:17:50 +01:00
for ( uint32 i = 0 ; i < sampleCount ; + + i ) {
if ( flags & 0x000100 ) { // sample-duration present
2017-05-28 21:03:45 +02:00
totalDuration + = reader ( ) . readUInt32BE ( ) ;
} else {
totalDuration + = defaultSampleDuration ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000200 ) { // sample-size present
2017-05-28 21:03:45 +02:00
m_sampleSizes . push_back ( reader ( ) . readUInt32BE ( ) ) ;
m_size + = m_sampleSizes . back ( ) ;
} else {
m_size + = defaultSampleSize ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000400 ) { // sample-flags present
2017-05-28 21:03:45 +02:00
inputStream ( ) . seekg ( 4 , ios_base : : cur ) ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000800 ) { // sample-composition-time-offsets present
2017-05-28 21:03:45 +02:00
inputStream ( ) . seekg ( 4 , ios_base : : cur ) ;
}
}
}
}
}
2018-03-07 01:17:50 +01:00
if ( m_sampleSizes . empty ( ) & & defaultSampleSize ) {
2017-05-28 21:03:45 +02:00
m_sampleSizes . push_back ( defaultSampleSize ) ;
}
}
}
}
}
}
}
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 .
*/
2018-03-05 17:49:29 +01:00
uint64 Mp4Track : : accumulateSampleSizes ( size_t & sampleIndex , size_t count , Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
2018-03-07 01:17:50 +01:00
if ( sampleIndex + count < = m_sampleSizes . size ( ) ) {
2015-04-22 19:22:01 +02:00
uint64 sum = 0 ;
2018-03-07 01:17:50 +01:00
for ( size_t end = sampleIndex + count ; sampleIndex < end ; + + sampleIndex ) {
2015-04-22 19:22:01 +02:00
sum + = m_sampleSizes [ sampleIndex ] ;
}
return sum ;
2018-03-07 01:17:50 +01:00
} else if ( m_sampleSizes . size ( ) = = 1 ) {
2015-04-22 19:22:01 +02:00
sampleIndex + = count ;
return static_cast < uint64 > ( m_sampleSizes . front ( ) ) * count ;
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " There are not as many sample size entries as samples. " , " reading chunk sizes of MP4 track " ) ;
2015-04-22 19:22:01 +02:00
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 .
2017-05-28 21:03:45 +02:00
* \ remarks This helper function is used by the readChunkSizes ( ) method .
2015-04-22 19:22:01 +02:00
*/
2018-03-05 17:49:29 +01:00
void Mp4Track : : addChunkSizeEntries ( std : : vector < uint64 > & chunkSizeTable , size_t count , size_t & sampleIndex , uint32 sampleCount , Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
2018-03-07 01:17:50 +01:00
for ( size_t i = 0 ; i < count ; + + i ) {
2018-03-05 17:49:29 +01:00
chunkSizeTable . push_back ( accumulateSampleSizes ( sampleIndex , sampleCount , diag ) ) ;
2015-04-22 19:22:01 +02:00
}
}
2017-09-14 01:37:15 +02:00
/*!
* \ brief Verifies the present track header ( tkhd atom ) and returns relevant information for making a new track header
* based on it .
*/
TrackHeaderInfo Mp4Track : : verifyPresentTrackHeader ( ) const
{
TrackHeaderInfo info ;
// return the default TrackHeaderInfo in case there is no track header prsent
2018-03-07 01:17:50 +01:00
if ( ! m_tkhdAtom ) {
2017-09-14 01:37:15 +02:00
return info ;
}
// ensure the tkhd atom is buffered but mark the buffer to be discarded again if it has not been present
info . discardBuffer = m_tkhdAtom - > buffer ( ) = = nullptr ;
2018-03-07 01:17:50 +01:00
if ( info . discardBuffer ) {
2017-09-14 01:37:15 +02:00
m_tkhdAtom - > makeBuffer ( ) ;
}
// check the version of the existing tkhd atom to determine where additional data starts
2018-03-07 01:17:50 +01:00
switch ( info . version = static_cast < byte > ( m_tkhdAtom - > buffer ( ) [ m_tkhdAtom - > headerSize ( ) ] ) ) {
2017-09-14 01:37:15 +02:00
case 0 :
info . additionalDataOffset = 32 ;
break ;
case 1 :
info . additionalDataOffset = 44 ;
break ;
default :
info . additionalDataOffset = 44 ;
info . versionUnknown = true ;
}
// check whether the existing tkhd atom is not truncated
2018-03-07 01:17:50 +01:00
if ( info . additionalDataOffset + 48u < = m_tkhdAtom - > dataSize ( ) ) {
2017-09-14 01:37:15 +02:00
info . canUseExisting = true ;
} else {
info . truncated = true ;
info . canUseExisting = info . additionalDataOffset < m_tkhdAtom - > dataSize ( ) ;
2018-03-07 01:17:50 +01:00
if ( ! info . canUseExisting & & info . discardBuffer ) {
2017-09-14 01:37:15 +02:00
m_tkhdAtom - > discardBuffer ( ) ;
}
}
// determine required size
info . requiredSize = m_tkhdAtom - > dataSize ( ) + 8 ;
2019-04-19 21:12:35 +02:00
// -> add 12 byte to size if update from version 0 to version 1 is required (which needs 12 byte more)
if ( ( info . version = = 0 )
& & ( static_cast < std : : uint64_t > ( ( m_creationTime - startDate ) . totalSeconds ( ) ) > numeric_limits < std : : uint32_t > : : max ( )
| | static_cast < std : : uint64_t > ( ( m_modificationTime - startDate ) . totalSeconds ( ) ) > numeric_limits < std : : uint32_t > : : max ( )
| | static_cast < std : : uint64_t > ( m_duration . totalSeconds ( ) * m_timeScale ) > numeric_limits < std : : uint32_t > : : max ( ) ) ) {
2017-09-14 01:37:15 +02:00
info . requiredSize + = 12 ;
}
2019-04-19 21:12:35 +02:00
// -> add 8 byte to the size because it must be denoted using a 64-bit integer
if ( info . requiredSize > numeric_limits < std : : uint32_t > : : max ( ) ) {
2017-09-14 01:37:15 +02:00
info . requiredSize + = 8 ;
}
return info ;
}
2015-04-22 19:22:01 +02:00
/*!
* \ 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 .
*/
2018-03-07 01:17:50 +01:00
vector < tuple < uint32 , uint32 , uint32 > > Mp4Track : : readSampleToChunkTable ( Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
static const string context ( " reading sample to chunk table of MP4 track " ) ;
2018-03-07 01:17:50 +01:00
if ( ! isHeaderValid ( ) | | ! m_istream | | ! m_stscAtom ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Track has not been parsed or is invalid. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
// verify integrity of the sample to chunk table
uint64 actualTableSize = m_stscAtom - > dataSize ( ) ;
2018-03-07 01:17:50 +01:00
if ( actualTableSize < 20 ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " The stsc atom is truncated. There are no \" sample to chunk \" entries present. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
} else {
actualTableSize - = 8 ;
}
2019-04-21 18:14:20 +02:00
std : : uint64_t actualSampleToChunkEntryCount = sampleToChunkEntryCount ( ) ;
std : : uint64_t calculatedTableSize = actualSampleToChunkEntryCount * 12 ;
2018-03-07 01:17:50 +01:00
if ( calculatedTableSize < actualTableSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " The stsc atom stores more entries as denoted. The additional entries will be ignored. " , context ) ;
2018-03-07 01:17:50 +01:00
} else if ( calculatedTableSize > actualTableSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " The stsc atom is truncated. It stores less entries as denoted. " , context ) ;
2019-04-21 18:14:20 +02:00
actualSampleToChunkEntryCount = actualTableSize / 12 ;
2015-04-22 19:22:01 +02:00
}
// prepare reading
2018-03-07 01:17:50 +01:00
vector < tuple < uint32 , uint32 , uint32 > > sampleToChunkTable ;
2015-04-22 19:22:01 +02:00
sampleToChunkTable . reserve ( actualSampleToChunkEntryCount ) ;
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_stscAtom - > dataOffset ( ) + 8 ) ) ;
for ( std : : uint32_t i = 0 ; i < actualSampleToChunkEntryCount ; + + i ) {
2015-04-22 19:22:01 +02:00
// 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 ( ) ;
*/
2018-03-05 17:49:29 +01:00
vector < uint64 > Mp4Track : : readChunkSizes ( Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
static const string context ( " reading chunk sizes of MP4 track " ) ;
2018-03-07 01:17:50 +01:00
if ( ! isHeaderValid ( ) | | ! m_istream | | ! m_stcoAtom ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Track has not been parsed or is invalid. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
// read sample to chunk table
2018-03-05 17:49:29 +01:00
const auto sampleToChunkTable = readSampleToChunkTable ( diag ) ;
2015-04-22 19:22:01 +02:00
// accumulate chunk sizes from the table
vector < uint64 > chunkSizes ;
2018-03-07 01:17:50 +01:00
if ( ! sampleToChunkTable . empty ( ) ) {
2015-04-22 19:22:01 +02:00
// 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!
2018-03-07 01:17:50 +01:00
if ( previousChunkIndex ! = 1 ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " The first chunk of the first \" sample to chunk \" entry must be 1. " , context ) ;
2015-04-22 19:22:01 +02:00
previousChunkIndex = 1 ; // try to read the entry anyway
}
uint32 samplesPerChunk = get < 1 > ( * tableIterator ) ;
// read the following entries
+ + tableIterator ;
2018-03-07 01:17:50 +01:00
for ( const auto tableEnd = sampleToChunkTable . cend ( ) ; tableIterator ! = tableEnd ; + + tableIterator ) {
2015-04-22 19:22:01 +02:00
uint32 firstChunkIndex = get < 0 > ( * tableIterator ) ;
2018-03-07 01:17:50 +01:00
if ( firstChunkIndex > previousChunkIndex & & firstChunkIndex < = m_chunkCount ) {
2018-03-05 17:49:29 +01:00
addChunkSizeEntries ( chunkSizes , firstChunkIndex - previousChunkIndex , sampleIndex , samplesPerChunk , diag ) ;
2015-04-22 19:22:01 +02:00
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical ,
2018-03-07 01:17:50 +01:00
" 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 ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
previousChunkIndex = firstChunkIndex ;
samplesPerChunk = get < 1 > ( * tableIterator ) ;
}
2018-03-07 01:17:50 +01:00
if ( m_chunkCount > = previousChunkIndex ) {
2018-03-05 17:49:29 +01:00
addChunkSizeEntries ( chunkSizes , m_chunkCount + 1 - previousChunkIndex , sampleIndex , samplesPerChunk , diag ) ;
2015-04-22 19:22:01 +02:00
}
}
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 ( )
*/
2018-03-07 01:17:50 +01:00
std : : unique_ptr < Mpeg4ElementaryStreamInfo > Mp4Track : : parseMpeg4ElementaryStreamInfo (
IoUtilities : : BinaryReader & reader , Mp4Atom * esDescAtom , Diagnostics & diag )
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 ;
2018-03-07 01:17:50 +01:00
if ( esDescAtom - > dataSize ( ) > = 12 ) {
2019-04-21 18:14:20 +02:00
reader . stream ( ) - > seekg ( static_cast < streamoff > ( esDescAtom - > dataOffset ( ) ) ) ;
2015-06-10 01:28:22 +02:00
// read version/flags
2018-03-07 01:17:50 +01:00
if ( reader . readUInt32BE ( ) ! = 0 ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Warning , " Unknown version/flags. " , context ) ;
2015-06-10 01:28:22 +02:00
}
// read extended descriptor
2019-04-21 18:14:20 +02:00
Mpeg4Descriptor esDesc ( esDescAtom - > container ( ) , static_cast < std : : uint64_t > ( reader . stream ( ) - > tellg ( ) ) , esDescAtom - > dataSize ( ) - 4 ) ;
2015-06-10 01:28:22 +02:00
try {
2018-03-05 17:49:29 +01:00
esDesc . parse ( diag ) ;
2015-06-10 01:28:22 +02:00
// check ID
2018-03-07 01:17:50 +01:00
if ( esDesc . id ( ) ! = Mpeg4DescriptorIds : : ElementaryStreamDescr ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : 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
2019-04-21 18:14:20 +02:00
reader . stream ( ) - > seekg ( static_cast < streamoff > ( 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 ( ) ;
2018-03-07 01:17:50 +01:00
if ( esInfo - > dependencyFlag ( ) ) {
2015-08-13 03:23:28 +02:00
esInfo - > dependsOnId = reader . readUInt16BE ( ) ;
2015-06-10 01:28:22 +02:00
}
2018-03-07 01:17:50 +01: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
}
2018-03-07 01:17:50 +01:00
if ( esInfo - > ocrFlag ( ) ) {
2015-08-13 03:23:28 +02:00
esInfo - > ocrId = reader . readUInt16BE ( ) ;
2015-06-10 01:28:22 +02:00
}
2019-04-21 18:14:20 +02:00
for ( Mpeg4Descriptor * esDescChild = esDesc . denoteFirstChild ( static_cast < std : : uint32_t > ( static_cast < std : : uint64_t > ( reader . stream ( ) - > tellg ( ) ) - esDesc . startOffset ( ) ) ) ;
2018-03-07 01:17:50 +01:00
esDescChild ; esDescChild = esDescChild - > nextSibling ( ) ) {
2018-03-05 17:49:29 +01:00
esDescChild - > parse ( diag ) ;
2018-03-07 01:17:50 +01:00
switch ( esDescChild - > id ( ) ) {
2015-06-10 01:28:22 +02:00
case Mpeg4DescriptorIds : : DecoderConfigDescr :
// read decoder config descriptor
2019-04-21 18:14:20 +02:00
reader . stream ( ) - > seekg ( static_cast < streamoff > ( esDescChild - > dataOffset ( ) ) ) ;
2015-08-13 03:23:28 +02:00
esInfo - > objectTypeId = reader . readByte ( ) ;
esInfo - > decCfgDescFlags = reader . readByte ( ) ;
esInfo - > bufferSize = reader . readUInt24BE ( ) ;
esInfo - > maxBitrate = reader . readUInt32BE ( ) ;
esInfo - > averageBitrate = reader . readUInt32BE ( ) ;
2018-03-07 01:17:50 +01:00
for ( Mpeg4Descriptor * decCfgDescChild = esDescChild - > denoteFirstChild ( esDescChild - > headerSize ( ) + 13 ) ; decCfgDescChild ;
decCfgDescChild = decCfgDescChild - > nextSibling ( ) ) {
2018-03-05 17:49:29 +01:00
decCfgDescChild - > parse ( diag ) ;
2018-03-07 01:17:50 +01:00
switch ( decCfgDescChild - > id ( ) ) {
2015-06-10 01:28:22 +02:00
case Mpeg4DescriptorIds : : DecoderSpecificInfo :
// read decoder specific info
2018-03-07 01:17:50 +01:00
switch ( esInfo - > objectTypeId ) {
case Aac :
case Mpeg2AacMainProfile :
case Mpeg2AacLowComplexityProfile :
case Mpeg2AacScaleableSamplingRateProfile :
case Mpeg2Audio :
case Mpeg1Audio :
esInfo - > audioSpecificConfig
= parseAudioSpecificConfig ( * reader . stream ( ) , decCfgDescChild - > dataOffset ( ) , decCfgDescChild - > dataSize ( ) , diag ) ;
2015-07-07 03:01:48 +02:00
break ;
case Mpeg4Visual :
2018-03-07 01:17:50 +01:00
esInfo - > videoSpecificConfig
= parseVideoSpecificConfig ( reader , decCfgDescChild - > dataOffset ( ) , decCfgDescChild - > dataSize ( ) , diag ) ;
2015-07-07 03:01:48 +02:00
break ;
2018-03-07 01:17:50 +01:00
default : ; // 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
}
2018-03-05 17:49:29 +01:00
} catch ( const Failure & ) {
diag . emplace_back ( DiagLevel : : Critical , " The MPEG-4 descriptor element structure is invalid. " , context ) ;
2015-06-10 01:28:22 +02:00
}
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : 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 ( )
*/
2018-03-05 17:49:29 +01:00
unique_ptr < Mpeg4AudioSpecificConfig > Mp4Track : : parseAudioSpecificConfig ( istream & stream , uint64 startOffset , uint64 size , Diagnostics & diag )
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
2019-04-21 18:14:20 +02:00
stream . seekg ( static_cast < streamoff > ( startOffset ) ) ;
2018-03-07 01:17:50 +01:00
auto buff = make_unique < char [ ] > ( size ) ;
2019-04-21 18:14:20 +02:00
stream . read ( buff . get ( ) , static_cast < streamoff > ( size ) ) ;
2015-08-13 03:23:28 +02:00
BitReader bitReader ( buff . get ( ) , size ) ;
2015-06-10 01:28:22 +02:00
auto audioCfg = make_unique < Mpeg4AudioSpecificConfig > ( ) ;
try {
// read audio object type
2017-09-20 19:39:04 +02:00
auto getAudioObjectType = [ & bitReader ] {
2015-06-10 01:28:22 +02:00
byte objType = bitReader . readBits < byte > ( 5 ) ;
2018-03-07 01:17:50 +01:00
if ( objType = = 31 ) {
2015-06-10 01:28:22 +02:00
objType = 32 + bitReader . readBits < byte > ( 6 ) ;
}
return objType ;
} ;
audioCfg - > audioObjectType = getAudioObjectType ( ) ;
// read sampling frequency
2018-03-07 01:17:50 +01:00
if ( ( audioCfg - > sampleFrequencyIndex = bitReader . readBits < byte > ( 4 ) ) = = 0xF ) {
2015-06-10 01:28:22 +02:00
audioCfg - > sampleFrequency = bitReader . readBits < uint32 > ( 24 ) ;
}
// read channel config
audioCfg - > channelConfiguration = bitReader . readBits < byte > ( 4 ) ;
// read extension header
2018-03-07 01:17:50 +01:00
switch ( audioCfg - > audioObjectType ) {
2015-06-10 01:28:22 +02:00
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 ;
2018-03-07 01:17:50 +01:00
if ( ( audioCfg - > extensionSampleFrequencyIndex = bitReader . readBits < byte > ( 4 ) ) = = 0xF ) {
2015-06-10 01:28:22 +02:00
audioCfg - > extensionSampleFrequency = bitReader . readBits < uint32 > ( 24 ) ;
}
2018-03-07 01:17:50 +01:00
if ( ( audioCfg - > audioObjectType = getAudioObjectType ( ) ) = = ErBsac ) {
2015-06-10 01:28:22 +02:00
audioCfg - > extensionChannelConfiguration = bitReader . readBits < byte > ( 4 ) ;
}
break ;
}
2018-03-07 01:17:50 +01: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
2018-03-07 01:17:50 +01:00
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 :
2015-06-10 01:28:22 +02:00
audioCfg - > frameLengthFlag = bitReader . readBits < byte > ( 1 ) ;
2018-03-07 01:17:50 +01: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 ( ) ;
2018-03-07 01:17:50 +01:00
if ( audioCfg - > channelConfiguration = = 0 ) {
2015-06-10 01:28:22 +02:00
throw NotImplementedException ( ) ; // TODO: parse program_config_element
}
2018-03-07 01:17:50 +01:00
switch ( audioCfg - > audioObjectType ) {
case AacScalable :
case ErAacScalable :
2015-06-10 01:28:22 +02:00
audioCfg - > layerNr = bitReader . readBits < byte > ( 3 ) ;
break ;
2018-03-07 01:17:50 +01:00
default : ;
2015-06-10 01:28:22 +02:00
}
2018-03-07 01:17:50 +01:00
if ( audioCfg - > extensionFlag = = 1 ) {
switch ( audioCfg - > audioObjectType ) {
2015-06-10 01:28:22 +02:00
case ErBsac :
audioCfg - > numOfSubFrame = bitReader . readBits < byte > ( 5 ) ;
audioCfg - > layerLength = bitReader . readBits < uint16 > ( 11 ) ;
break ;
2018-03-07 01:17:50 +01:00
case ErAacLc :
case ErAacLtp :
case ErAacScalable :
case ErAacLd :
2015-06-10 01:28:22 +02:00
audioCfg - > resilienceFlags = bitReader . readBits < byte > ( 3 ) ;
break ;
2018-03-07 01:17:50 +01:00
default : ;
2015-06-07 00:18:28 +02:00
}
2018-03-07 01:17:50 +01: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
2018-03-07 01:17:50 +01:00
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 ;
}
2018-03-07 01:17:50 +01: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 ) ;
2018-03-07 01:17:50 +01:00
if ( syncExtensionType = = 0x2B7 ) {
if ( ( audioCfg - > extensionAudioObjectType = getAudioObjectType ( ) ) = = Sbr ) {
if ( ( audioCfg - > sbrPresent = bitReader . readBit ( ) ) ) {
if ( ( audioCfg - > extensionSampleFrequencyIndex = bitReader . readBits < byte > ( 4 ) ) = = 0xF ) {
2015-06-10 01:28:22 +02:00
audioCfg - > extensionSampleFrequency = bitReader . readBits < uint32 > ( 24 ) ;
}
2018-03-07 01:17:50 +01:00
if ( bitReader . bitsAvailable ( ) > = 12 ) {
if ( ( syncExtensionType = bitReader . readBits < uint16 > ( 11 ) ) = = 0x548 ) {
2015-06-10 01:28:22 +02:00
audioCfg - > psPresent = bitReader . readBits < byte > ( 1 ) ;
2015-06-07 00:18:28 +02:00
}
}
}
2018-03-07 01:17:50 +01:00
} else if ( audioCfg - > extensionAudioObjectType = = ErBsac ) {
if ( ( audioCfg - > sbrPresent = bitReader . readBit ( ) ) ) {
if ( ( audioCfg - > extensionSampleFrequencyIndex = bitReader . readBits < byte > ( 4 ) ) = = 0xF ) {
2015-09-24 01:15:27 +02:00
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
}
}
2018-03-07 01:17:50 +01:00
} catch ( const NotImplementedException & ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Information , " Not implemented for the format of audio track. " , context ) ;
2018-03-07 01:17:50 +01:00
} catch ( . . . ) {
2016-06-14 22:53:43 +02:00
const char * what = catchIoFailure ( ) ;
2018-03-07 01:17:50 +01:00
if ( stream . fail ( ) ) {
2015-07-07 03:01:48 +02:00
// IO error caused by input stream
2016-06-14 22:53:43 +02:00
throwIoFailure ( what ) ;
2015-07-07 03:01:48 +02:00
} else {
// IO error caused by bitReader
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Audio specific configuration is truncated. " , context ) ;
2015-06-10 01:28:22 +02:00
}
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 ( )
*/
2018-03-05 17:49:29 +01:00
std : : unique_ptr < Mpeg4VideoSpecificConfig > Mp4Track : : parseVideoSpecificConfig ( BinaryReader & reader , uint64 startOffset , uint64 size , Diagnostics & diag )
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
2019-04-21 18:14:20 +02:00
reader . stream ( ) - > seekg ( static_cast < streamoff > ( startOffset ) ) ;
2018-03-07 01:17:50 +01:00
if ( size > 3 & & ( reader . readUInt24BE ( ) = = 1 ) ) {
2015-08-13 03:23:28 +02:00
size - = 3 ;
2015-07-07 03:01:48 +02:00
uint32 buff1 ;
2018-03-07 01:17:50 +01:00
while ( size ) {
2015-08-13 03:23:28 +02:00
- - size ;
2018-03-07 01:17:50 +01:00
switch ( reader . readByte ( ) ) { // read start code
2015-07-07 03:01:48 +02:00
case Mpeg4VideoCodes : : VisualObjectSequenceStart :
2018-03-07 01:17:50 +01:00
if ( size ) {
2015-08-13 03:23:28 +02:00
videoCfg - > profile = reader . readByte ( ) ;
- - size ;
2015-07-07 03:01:48 +02:00
}
break ;
case Mpeg4VideoCodes : : VideoObjectLayerStart :
break ;
case Mpeg4VideoCodes : : UserDataStart :
buff1 = 0 ;
2018-03-07 01:17:50 +01:00
while ( size > = 3 ) {
if ( ( buff1 = reader . readUInt24BE ( ) ) ! = 1 ) {
2015-08-13 03:23:28 +02:00
reader . stream ( ) - > seekg ( - 2 , ios_base : : cur ) ;
2019-04-21 18:14:20 +02:00
videoCfg - > userData . push_back ( static_cast < char > ( 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 ;
}
}
2018-03-07 01:17:50 +01:00
if ( buff1 ! = 1 & & size > 0 ) {
2015-08-13 03:23:28 +02:00
videoCfg - > userData + = reader . readString ( size ) ;
size = 0 ;
2015-07-07 03:01:48 +02:00
}
break ;
2018-03-07 01:17:50 +01:00
default : ;
2015-07-07 03:01:48 +02:00
}
2016-02-17 20:19:05 +01:00
// skip remainging values to get the start of the next video object
2018-03-07 01:17:50 +01:00
while ( size > = 3 ) {
if ( reader . readUInt24BE ( ) ! = 1 ) {
2015-08-13 03:23:28 +02:00
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 {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : 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 )
{
2018-03-07 01:17:50 +01:00
if ( ! isHeaderValid ( ) | | ! m_ostream | | ! m_istream | | ! m_stcoAtom ) {
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( oldMdatOffsets . size ( ) = = 0 | | oldMdatOffsets . size ( ) ! = newMdatOffsets . size ( ) ) {
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2015-06-10 01:28:22 +02:00
static const unsigned int stcoDataBegin = 8 ;
2019-04-21 18:14:20 +02:00
std : : uint64_t startPos = m_stcoAtom - > dataOffset ( ) + stcoDataBegin ;
std : : uint64_t endPos = startPos + m_stcoAtom - > dataSize ( ) - stcoDataBegin ;
m_istream - > seekg ( static_cast < streamoff > ( startPos ) ) ;
m_ostream - > seekp ( static_cast < streamoff > ( startPos ) ) ;
vector < std : : int64_t > : : size_type i ;
vector < std : : int64_t > : : size_type size ;
auto currentPos = static_cast < std : : uint64_t > ( m_istream - > tellg ( ) ) ;
2018-03-07 01:17:50 +01:00
switch ( m_stcoAtom - > id ( ) ) {
2015-04-22 19:22:01 +02:00
case Mp4AtomIds : : ChunkOffset : {
uint32 off ;
2018-03-07 01:17:50 +01:00
while ( ( currentPos + 4 ) < = endPos ) {
2015-04-22 19:22:01 +02:00
off = m_reader . readUInt32BE ( ) ;
2018-03-07 01:17:50 +01:00
for ( i = 0 , size = oldMdatOffsets . size ( ) ; i < size ; + + i ) {
if ( off > static_cast < uint64 > ( oldMdatOffsets [ i ] ) ) {
2015-04-22 19:22:01 +02:00
off + = ( newMdatOffsets [ i ] - oldMdatOffsets [ i ] ) ;
break ;
}
}
2019-04-21 18:14:20 +02:00
m_ostream - > seekp ( static_cast < streamoff > ( currentPos ) ) ;
2015-04-22 19:22:01 +02:00
m_writer . writeUInt32BE ( off ) ;
2019-04-21 18:14:20 +02:00
currentPos + = static_cast < std : : uint64_t > ( m_istream - > gcount ( ) ) ;
2015-04-22 19:22:01 +02:00
}
break ;
2018-03-07 01:17:50 +01:00
}
case Mp4AtomIds : : ChunkOffset64 : {
2015-04-22 19:22:01 +02:00
uint64 off ;
2018-03-07 01:17:50 +01:00
while ( ( currentPos + 8 ) < = endPos ) {
2015-04-22 19:22:01 +02:00
off = m_reader . readUInt64BE ( ) ;
2018-03-07 01:17:50 +01:00
for ( i = 0 , size = oldMdatOffsets . size ( ) ; i < size ; + + i ) {
if ( off > static_cast < uint64 > ( oldMdatOffsets [ i ] ) ) {
2015-04-22 19:22:01 +02:00
off + = ( newMdatOffsets [ i ] - oldMdatOffsets [ i ] ) ;
break ;
}
}
2019-04-21 18:14:20 +02:00
m_ostream - > seekp ( static_cast < streamoff > ( currentPos ) ) ;
2015-04-22 19:22:01 +02:00
m_writer . writeUInt64BE ( off ) ;
2019-04-21 18:14:20 +02:00
currentPos + = static_cast < std : : uint64_t > ( m_istream - > gcount ( ) ) ;
2015-04-22 19:22:01 +02:00
}
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 .
2019-04-21 18:14:20 +02:00
* \ param chunkOffsets Specifies the new chunk offset table . If the " stco " atom is used the values
* must fit into an 32 - bit unsigned int .
2015-11-07 15:23:36 +01:00
*
* \ 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 )
{
2018-03-07 01:17:50 +01:00
if ( ! isHeaderValid ( ) | | ! m_ostream | | ! m_istream | | ! m_stcoAtom ) {
2015-11-07 15:23:36 +01:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( chunkOffsets . size ( ) ! = chunkCount ( ) ) {
2015-11-07 15:23:36 +01:00
throw InvalidDataException ( ) ;
}
2019-04-21 18:14:20 +02:00
m_ostream - > seekp ( static_cast < streamoff > ( m_stcoAtom - > dataOffset ( ) + 8 ) ) ;
2018-03-07 01:17:50 +01:00
switch ( m_stcoAtom - > id ( ) ) {
2015-11-07 15:23:36 +01:00
case Mp4AtomIds : : ChunkOffset :
2018-03-07 01:17:50 +01:00
for ( auto offset : chunkOffsets ) {
2019-04-21 18:14:20 +02:00
m_writer . writeUInt32BE ( static_cast < std : : uint32_t > ( offset ) ) ;
2015-11-07 15:23:36 +01:00
}
break ;
case Mp4AtomIds : : ChunkOffset64 :
2018-03-07 01:17:50 +01:00
for ( auto offset : chunkOffsets ) {
2015-11-07 15:23:36 +01:00
m_writer . writeUInt64BE ( offset ) ;
}
2018-08-23 23:20:29 +02:00
break ;
2015-11-07 15:23:36 +01:00
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 .
2019-04-21 18:14:20 +02:00
* \ param offset Specifies the new chunk offset . If the " stco " atom is used the value must fit
* into a 32 - bit unsigned int .
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 )
{
2018-03-07 01:17:50 +01:00
if ( ! isHeaderValid ( ) | | ! m_istream | | ! m_stcoAtom | | chunkIndex > = m_chunkCount ) {
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2019-04-21 18:14:20 +02:00
m_ostream - > seekp ( static_cast < streamoff > ( m_stcoAtom - > dataOffset ( ) + 8 + chunkOffsetSize ( ) * chunkIndex ) ) ;
2018-03-07 01:17:50 +01:00
switch ( chunkOffsetSize ( ) ) {
2015-04-22 19:22:01 +02:00
case 4 :
2019-04-21 18:14:20 +02:00
writer ( ) . writeUInt32BE ( static_cast < std : : uint32_t > ( offset ) ) ;
2015-04-22 19:22:01 +02:00
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 )
{
2018-03-07 01:17:50 +01:00
if ( ! avcConfig . spsInfos . empty ( ) ) {
2016-02-17 20:19:05 +01:00
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 ;
2018-03-07 01:17:50 +01:00
switch ( spsInfo . chromaFormatIndication ) {
2016-02-17 20:19:05 +01:00
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 ;
2018-03-07 01:17:50 +01:00
default : ;
2016-02-17 20:19:05 +01:00
}
track . m_pixelAspectRatio = spsInfo . pixelAspectRatio ;
} else {
track . m_format . sub = avcConfig . profileIndication ;
track . m_version = static_cast < double > ( avcConfig . levelIndication ) / 10 ;
}
}
2018-08-23 23:18:57 +02:00
/*!
* \ brief Adds the information from the specified \ a av1Config to the specified \ a track .
* \ todo Provide implementation
*/
void Mp4Track : : addInfo ( const Av1Configuration & av1Config , AbstractTrack & track )
{
VAR_UNUSED ( av1Config )
VAR_UNUSED ( track )
throw NotImplementedException ( ) ;
}
2017-06-17 00:31:35 +02:00
/*!
* \ brief Buffers all atoms required by the makeTrack ( ) method .
*
* This allows to invoke makeTrack ( ) also when the input stream is going to be
* modified ( eg . to apply changed tags without rewriting the file ) .
*/
2018-03-05 17:49:29 +01:00
void Mp4Track : : bufferTrackAtoms ( Diagnostics & diag )
2017-06-17 00:31:35 +02:00
{
2019-04-18 17:54:56 +02:00
VAR_UNUSED ( diag )
2018-03-07 01:17:50 +01:00
if ( m_tkhdAtom ) {
2017-06-17 00:31:35 +02:00
m_tkhdAtom - > makeBuffer ( ) ;
}
2019-04-17 17:51:04 +02:00
for ( Mp4Atom * trakChild = m_trakAtom - > firstChild ( ) ; trakChild ; trakChild = trakChild - > nextSibling ( ) ) {
if ( trakChild - > id ( ) = = Mp4AtomIds : : Media ) {
continue ;
}
trakChild - > makeBuffer ( ) ;
2017-06-17 00:31:35 +02:00
}
2018-03-07 01:17:50 +01:00
if ( m_minfAtom ) {
2019-04-15 18:18:44 +02:00
for ( Mp4Atom * childAtom = m_minfAtom - > firstChild ( ) ; childAtom ; childAtom = childAtom - > nextSibling ( ) ) {
childAtom - > makeBuffer ( ) ;
2017-06-17 00:31:35 +02:00
}
}
}
2017-05-28 21:01:16 +02:00
/*!
* \ brief Returns the number of bytes written when calling makeTrack ( ) .
*/
2018-03-05 17:49:29 +01:00
uint64 Mp4Track : : requiredSize ( Diagnostics & diag ) const
2017-05-28 21:01:16 +02:00
{
2019-04-17 17:51:04 +02:00
VAR_UNUSED ( diag )
2017-05-28 21:01:16 +02:00
// add size of
2017-09-14 01:37:15 +02:00
// ... trak header
2019-04-15 18:18:44 +02:00
std : : uint64_t size = 8 ;
// ... tkhd atom (TODO: buffer TrackHeaderInfo in next major release)
2017-09-14 01:37:15 +02:00
size + = verifyPresentTrackHeader ( ) . requiredSize ;
2019-04-17 17:51:04 +02:00
// ... children beside tkhd and mdia
for ( Mp4Atom * trakChild = m_trakAtom - > firstChild ( ) ; trakChild ; trakChild = trakChild - > nextSibling ( ) ) {
if ( trakChild - > id ( ) = = Mp4AtomIds : : Media | | trakChild - > id ( ) = = Mp4AtomIds : : TrackHeader ) {
continue ;
}
size + = trakChild - > totalSize ( ) ;
2017-05-28 21:01:16 +02:00
}
2019-04-18 17:54:56 +02:00
// ... mdhd total size
if ( static_cast < std : : uint64_t > ( ( m_creationTime - startDate ) . totalSeconds ( ) ) > numeric_limits < std : : uint32_t > : : max ( )
| | static_cast < std : : uint64_t > ( ( m_modificationTime - startDate ) . totalSeconds ( ) ) > numeric_limits < std : : uint32_t > : : max ( )
| | static_cast < std : : uint64_t > ( m_duration . totalSeconds ( ) * m_timeScale ) > numeric_limits < std : : uint32_t > : : max ( ) ) {
// write version 1 where those fields are 64-bit
size + = 44 ;
} else {
// write version 0 where those fields are 32-bit
size + = 32 ;
}
// ... mdia header + hdlr total size + minf header
size + = 8 + ( 33 + m_name . size ( ) ) + 8 ;
2017-05-28 21:01:16 +02:00
// ... minf childs
bool dinfAtomWritten = false ;
2018-03-07 01:17:50 +01:00
if ( m_minfAtom ) {
2019-04-15 18:18:44 +02:00
for ( Mp4Atom * childAtom = m_minfAtom - > firstChild ( ) ; childAtom ; childAtom = childAtom - > nextSibling ( ) ) {
if ( childAtom - > id ( ) = = Mp4AtomIds : : DataInformation ) {
dinfAtomWritten = true ;
}
size + = childAtom - > totalSize ( ) ;
2017-05-28 21:01:16 +02:00
}
}
2018-03-07 01:17:50 +01:00
if ( ! dinfAtomWritten ) {
2019-04-15 18:18:44 +02:00
// take 36 bytes for a self-made dinf atom into account if the file lacks one
2017-05-28 21:01:16 +02:00
size + = 36 ;
}
return size ;
}
2015-04-22 19:22:01 +02:00
/*!
2017-06-17 00:31:35 +02:00
* \ brief Makes the track entry ( " trak " - atom ) for the track .
*
* The data is written to the assigned output stream at the current position . Note that this method
* uses the assigned input stream to copy some parts from the source file . Hence the input stream must
* still be valid when calling this method . To avoid this limitation call bufferTrackAtoms ( ) before
* invalidating the input stream .
2015-04-22 19:22:01 +02:00
*/
2018-03-05 17:49:29 +01:00
void Mp4Track : : makeTrack ( Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
// write header
ostream : : pos_type trakStartOffset = outputStream ( ) . tellp ( ) ;
2017-05-28 21:01:16 +02:00
m_writer . writeUInt32BE ( 0 ) ; // write size later
m_writer . writeUInt32BE ( Mp4AtomIds : : Track ) ;
2019-04-17 17:51:04 +02:00
2015-04-22 19:22:01 +02:00
// write tkhd atom
2018-03-05 17:49:29 +01:00
makeTrackHeader ( diag ) ;
2019-04-17 17:51:04 +02:00
// write children of trak atom except mdia
for ( Mp4Atom * trakChild = trakAtom ( ) . firstChild ( ) ; trakChild ; trakChild = trakChild - > nextSibling ( ) ) {
if ( trakChild - > id ( ) = = Mp4AtomIds : : Media | | trakChild - > id ( ) = = Mp4AtomIds : : TrackHeader ) {
continue ;
}
trakChild - > copyPreferablyFromBuffer ( outputStream ( ) , diag , nullptr ) ;
2015-04-22 19:22:01 +02:00
}
2019-04-17 17:51:04 +02:00
2015-04-22 19:22:01 +02:00
// write mdia atom
2018-03-05 17:49:29 +01:00
makeMedia ( diag ) ;
2019-04-17 17:51:04 +02:00
2015-04-22 19:22:01 +02:00
// write size (of trak atom)
2018-07-10 17:07:34 +02:00
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream ( ) , trakStartOffset , diag ) ;
2015-12-21 00:04:56 +01:00
}
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 .
*/
2018-03-05 17:49:29 +01:00
void Mp4Track : : makeTrackHeader ( Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
2017-09-14 01:37:15 +02:00
// verify the existing track header to make the new one based on it (if possible)
const TrackHeaderInfo info ( verifyPresentTrackHeader ( ) ) ;
// add notifications in case the present track header could not be parsed
2018-03-07 01:17:50 +01:00
if ( info . versionUnknown ) {
diag . emplace_back ( DiagLevel : : Critical ,
argsToString ( " The version of the present \" tkhd \" -atom ( " , info . version , " ) is unknown. Assuming version 1. " ) ,
argsToString ( " making \" tkhd \" -atom of track " , m_id ) ) ;
2017-09-14 01:37:15 +02:00
}
2018-03-07 01:17:50 +01:00
if ( info . truncated ) {
diag . emplace_back (
DiagLevel : : Critical , argsToString ( " The present \" tkhd \" -atom is truncated. " ) , argsToString ( " making \" tkhd \" -atom of track " , m_id ) ) ;
2017-09-14 01:37:15 +02:00
}
// make size and element ID
2018-03-07 01:17:50 +01:00
if ( info . requiredSize > numeric_limits < uint32 > : : max ( ) ) {
2017-09-14 01:37:15 +02:00
writer ( ) . writeUInt32BE ( 1 ) ;
writer ( ) . writeUInt32BE ( Mp4AtomIds : : TrackHeader ) ;
writer ( ) . writeUInt64BE ( info . requiredSize ) ;
} else {
writer ( ) . writeUInt32BE ( static_cast < uint32 > ( info . requiredSize ) ) ;
writer ( ) . writeUInt32BE ( Mp4AtomIds : : TrackHeader ) ;
}
2019-04-19 21:12:35 +02:00
// determine time-related values and version
const auto creationTime = static_cast < std : : uint64_t > ( ( m_creationTime - startDate ) . totalSeconds ( ) ) ;
const auto modificationTime = static_cast < std : : uint64_t > ( ( m_modificationTime - startDate ) . totalSeconds ( ) ) ;
const auto duration = static_cast < std : : uint64_t > ( m_duration . totalSeconds ( ) * m_timeScale ) ;
const std : : uint8_t version = ( info . version = = 0 ) & & ( creationTime > numeric_limits < std : : uint32_t > : : max ( )
| | modificationTime > numeric_limits < std : : uint32_t > : : max ( )
| | duration > numeric_limits < std : : uint32_t > : : max ( ) ) ? 1 : info . version ;
2017-09-14 01:37:15 +02:00
// make version and flags
2019-04-19 21:12:35 +02:00
writer ( ) . writeByte ( version ) ;
std : : uint32_t flags = 0 ;
2018-03-07 01:17:50 +01:00
if ( m_enabled ) {
2015-04-22 19:22:01 +02:00
flags | = 0x000001 ;
}
2018-03-07 01:17:50 +01:00
if ( m_usedInPresentation ) {
2015-04-22 19:22:01 +02:00
flags | = 0x000002 ;
}
2018-03-07 01:17:50 +01:00
if ( m_usedWhenPreviewing ) {
2015-04-22 19:22:01 +02:00
flags | = 0x000004 ;
}
writer ( ) . writeUInt24BE ( flags ) ;
2017-09-14 01:37:15 +02:00
// make creation and modification time
2019-04-19 21:12:35 +02:00
if ( version ! = 0 ) {
writer ( ) . writeUInt64BE ( creationTime ) ;
writer ( ) . writeUInt64BE ( modificationTime ) ;
} else {
writer ( ) . writeUInt32BE ( static_cast < std : : uint32_t > ( creationTime ) ) ;
writer ( ) . writeUInt32BE ( static_cast < std : : uint32_t > ( modificationTime ) ) ;
}
2017-09-14 01:37:15 +02:00
// make track ID and duration
writer ( ) . writeUInt32BE ( static_cast < uint32 > ( m_id ) ) ;
2015-04-22 19:22:01 +02:00
writer ( ) . writeUInt32BE ( 0 ) ; // reserved
2019-04-19 21:12:35 +02:00
if ( version ! = 0 ) {
writer ( ) . writeUInt64BE ( duration ) ;
} else {
writer ( ) . writeUInt32BE ( static_cast < std : : uint32_t > ( duration ) ) ;
}
2015-04-22 19:22:01 +02:00
writer ( ) . writeUInt32BE ( 0 ) ; // reserved
writer ( ) . writeUInt32BE ( 0 ) ; // reserved
2017-09-14 01:37:15 +02:00
// make further values, either from existing tkhd atom or just some defaults
2018-03-07 01:17:50 +01:00
if ( info . canUseExisting ) {
2017-09-14 01:37:15 +02:00
// write all bytes after the previously determined additionalDataOffset
2018-06-02 22:56:08 +02:00
m_ostream - > write ( m_tkhdAtom - > buffer ( ) . get ( ) + m_tkhdAtom - > headerSize ( ) + info . additionalDataOffset ,
static_cast < streamoff > ( m_tkhdAtom - > dataSize ( ) - info . additionalDataOffset ) ) ;
2017-09-14 01:37:15 +02:00
// discard the buffer again if it wasn't present before
2018-03-07 01:17:50 +01:00
if ( info . discardBuffer ) {
2017-09-14 01:37:15 +02:00
m_tkhdAtom - > discardBuffer ( ) ;
2017-06-17 00:31:35 +02:00
}
2015-04-22 19:22:01 +02:00
} else {
// write default values
2019-04-19 21:12:35 +02:00
diag . emplace_back ( DiagLevel : : Warning , " Writing some default values because the existing tkhd atom is truncated. " , " making tkhd atom " ) ;
2015-04-22 19:22:01 +02:00
writer ( ) . writeInt16BE ( 0 ) ; // layer
writer ( ) . writeInt16BE ( 0 ) ; // alternate group
2017-09-14 01:37:15 +02:00
writer ( ) . writeFixed8BE ( 1.0 ) ; // volume (fixed 8.8 - 2 byte)
2015-04-22 19:22:01 +02:00
writer ( ) . writeUInt16BE ( 0 ) ; // reserved
2018-03-07 01:17:50 +01:00
for ( const int32 value : { 0x00010000 , 0 , 0 , 0 , 0x00010000 , 0 , 0 , 0 , 0x40000000 } ) { // unity matrix
2015-04-22 19:22:01 +02:00
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 .
*/
2018-03-05 17:49:29 +01:00
void Mp4Track : : makeMedia ( Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
ostream : : pos_type mdiaStartOffset = outputStream ( ) . tellp ( ) ;
writer ( ) . writeUInt32BE ( 0 ) ; // write size later
writer ( ) . writeUInt32BE ( Mp4AtomIds : : Media ) ;
// write mdhd atom
2019-04-18 17:54:56 +02:00
const auto creationTime = static_cast < std : : uint64_t > ( ( m_creationTime - startDate ) . totalSeconds ( ) ) ;
const auto modificationTime = static_cast < std : : uint64_t > ( ( m_modificationTime - startDate ) . totalSeconds ( ) ) ;
const auto duration = static_cast < std : : uint64_t > ( m_duration . totalSeconds ( ) * m_timeScale ) ;
const std : : uint8_t version = ( creationTime > numeric_limits < std : : uint32_t > : : max ( )
| | modificationTime > numeric_limits < std : : uint32_t > : : max ( )
| | duration > numeric_limits < std : : uint32_t > : : max ( ) ) ? 1 : 0 ;
writer ( ) . writeUInt32BE ( version ! = 0 ? 44 : 32 ) ; // size
2017-05-28 21:01:16 +02:00
writer ( ) . writeUInt32BE ( Mp4AtomIds : : MediaHeader ) ;
2019-04-18 17:54:56 +02:00
writer ( ) . writeByte ( version ) ; // version
2015-04-22 19:22:01 +02:00
writer ( ) . writeUInt24BE ( 0 ) ; // flags
2019-04-18 17:54:56 +02:00
if ( version ! = 0 ) {
writer ( ) . writeUInt64BE ( creationTime ) ;
writer ( ) . writeUInt64BE ( modificationTime ) ;
} else {
writer ( ) . writeUInt32BE ( static_cast < std : : uint32_t > ( creationTime ) ) ;
writer ( ) . writeUInt32BE ( static_cast < std : : uint32_t > ( modificationTime ) ) ;
}
2015-04-22 19:22:01 +02:00
writer ( ) . writeUInt32BE ( m_timeScale ) ;
2019-04-18 17:54:56 +02:00
if ( version ! = 0 ) {
writer ( ) . writeUInt64BE ( duration ) ;
} else {
writer ( ) . writeUInt32BE ( static_cast < std : : uint32_t > ( duration ) ) ;
}
2015-04-22 19:22:01 +02:00
// convert and write language
uint16 language = 0 ;
2018-03-07 01:17:50 +01:00
for ( size_t charIndex = 0 ; charIndex ! = 3 ; + + charIndex ) {
2017-06-17 00:32:38 +02:00
const char langChar = charIndex < m_language . size ( ) ? m_language [ charIndex ] : 0 ;
2018-03-07 01:17:50 +01:00
if ( langChar > = ' a ' & & langChar < = ' z ' ) {
2019-04-21 17:43:21 +02:00
language | = static_cast < std : : uint16_t > ( langChar - 0x60 ) < < ( 0xA - charIndex * 0x5 ) ;
continue ;
}
// handle invalid characters
if ( m_language . empty ( ) ) {
// preserve empty language field
language = 0 ;
2015-04-22 19:22:01 +02:00
break ;
}
2019-04-21 17:43:21 +02:00
diag . emplace_back ( DiagLevel : : Warning , " Assigned language \" " % m_language + " \" is of an invalid format. Setting language to undefined. " , " making mdhd atom " ) ;
language = 0x55C4 ; // und(efined)
break ;
2015-04-22 19:22:01 +02:00
}
2018-03-07 01:17:50 +01:00
if ( m_language . size ( ) > 3 ) {
diag . emplace_back (
DiagLevel : : Warning , " Assigned language \" " % m_language + " \" is longer than 3 byte and hence will be truncated. " , " making mdhd atom " ) ;
2017-06-17 00:32:38 +02:00
}
2015-04-22 19:22:01 +02:00
writer ( ) . writeUInt16BE ( language ) ;
writer ( ) . writeUInt16BE ( 0 ) ; // pre defined
// write hdlr atom
2017-05-28 21:01:16 +02:00
writer ( ) . writeUInt32BE ( 33 + m_name . size ( ) ) ; // size
2015-04-22 19:22:01 +02:00
writer ( ) . writeUInt32BE ( Mp4AtomIds : : HandlerReference ) ;
writer ( ) . writeUInt64BE ( 0 ) ; // version, flags, pre defined
2018-03-07 01:17:50 +01:00
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 :
2019-04-19 21:51:13 +02:00
outputStream ( ) . write ( " text " , 4 ) ;
break ;
case MediaType : : Meta :
2015-04-22 19:22:01 +02:00
outputStream ( ) . write ( " meta " , 4 ) ;
break ;
default :
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Media type is invalid; The media type video is assumed. " , " making hdlr atom " ) ;
2015-04-22 19:22:01 +02:00
outputStream ( ) . write ( " vide " , 4 ) ;
break ;
}
2018-03-07 01:17:50 +01:00
for ( int i = 0 ; i < 3 ; + + i )
writer ( ) . writeUInt32BE ( 0 ) ; // reserved
2015-04-22 19:22:01 +02:00
writer ( ) . writeTerminatedString ( m_name ) ;
// write minf atom
2018-03-05 17:49:29 +01:00
makeMediaInfo ( diag ) ;
2015-04-22 19:22:01 +02:00
// write size (of mdia atom)
2018-07-10 17:07:34 +02:00
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream ( ) , mdiaStartOffset , diag ) ;
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 .
*/
2018-03-05 17:49:29 +01:00
void Mp4Track : : makeMediaInfo ( Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
ostream : : pos_type minfStartOffset = outputStream ( ) . tellp ( ) ;
writer ( ) . writeUInt32BE ( 0 ) ; // write size later
writer ( ) . writeUInt32BE ( Mp4AtomIds : : MediaInformation ) ;
bool dinfAtomWritten = false ;
2018-03-07 01:17:50 +01:00
if ( m_minfAtom ) {
2019-04-15 18:18:44 +02:00
// copy existing atoms except sample table which is handled separately
for ( Mp4Atom * childAtom = m_minfAtom - > firstChild ( ) ; childAtom ; childAtom = childAtom - > nextSibling ( ) ) {
if ( childAtom - > id ( ) = = Mp4AtomIds : : SampleTable ) {
continue ;
}
if ( childAtom - > id ( ) = = Mp4AtomIds : : DataInformation ) {
dinfAtomWritten = true ;
}
childAtom - > copyPreferablyFromBuffer ( outputStream ( ) , diag , nullptr ) ;
2015-04-22 19:22:01 +02:00
}
}
// write dinf atom if not written yet
2018-03-07 01:17:50 +01:00
if ( ! dinfAtomWritten ) {
2015-04-22 19:22:01 +02:00
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
2017-05-28 21:01:16 +02:00
// -> just copy existing stbl atom because makeSampleTable() is not fully implemented (yet)
bool stblAtomWritten = false ;
2018-03-07 01:17:50 +01:00
if ( m_minfAtom ) {
2019-04-15 18:18:44 +02:00
if ( Mp4Atom * const stblAtom = m_minfAtom - > childById ( Mp4AtomIds : : SampleTable , diag ) ) {
2018-03-05 17:49:29 +01:00
stblAtom - > copyPreferablyFromBuffer ( outputStream ( ) , diag , nullptr ) ;
2017-05-28 21:01:16 +02:00
stblAtomWritten = true ;
}
}
2018-03-07 01:17:50 +01:00
if ( ! stblAtomWritten ) {
diag . emplace_back ( DiagLevel : : Critical ,
" Source track does not contain mandatory stbl atom and the tagparser lib is unable to make one from scratch. " , " making stbl atom " ) ;
2017-05-28 21:01:16 +02:00
}
2015-04-22 19:22:01 +02:00
// write size (of minf atom)
2018-07-10 17:07:34 +02:00
Mp4Atom : : seekBackAndWriteAtomSize ( outputStream ( ) , minfStartOffset , diag ) ;
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
*/
2018-03-05 17:49:29 +01:00
void Mp4Track : : makeSampleTable ( Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
2019-04-21 18:14:20 +02:00
// ostream::pos_type stblStartOffset = outputStream().tellp(); (enable when function is fully implemented)
2015-04-22 19:22:01 +02:00
writer ( ) . writeUInt32BE ( 0 ) ; // write size later
writer ( ) . writeUInt32BE ( Mp4AtomIds : : SampleTable ) ;
2019-04-21 18:14:20 +02:00
Mp4Atom * const stblAtom = m_minfAtom ? m_minfAtom - > childById ( Mp4AtomIds : : SampleTable , diag ) : nullptr ;
2015-04-22 19:22:01 +02:00
// write stsd atom
2018-03-07 01:17:50 +01:00
if ( m_stsdAtom ) {
2015-04-22 19:22:01 +02:00
// copy existing stsd atom
2018-03-05 17:49:29 +01:00
m_stsdAtom - > copyEntirely ( outputStream ( ) , diag , nullptr ) ;
2015-04-22 19:22:01 +02:00
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Unable to make stsd atom from scratch. " , " making stsd atom " ) ;
2015-04-22 19:22:01 +02:00
throw NotImplementedException ( ) ;
}
// write stts and ctts atoms
2019-04-21 18:14:20 +02:00
Mp4Atom * const sttsAtom = stblAtom ? stblAtom - > childById ( Mp4AtomIds : : DecodingTimeToSample , diag ) : nullptr ;
2018-03-07 01:17:50 +01:00
if ( sttsAtom ) {
2015-04-22 19:22:01 +02:00
// copy existing stts atom
2018-03-05 17:49:29 +01:00
sttsAtom - > copyEntirely ( outputStream ( ) , diag , nullptr ) ;
2015-04-22 19:22:01 +02:00
} else {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Unable to make stts atom from scratch. " , " making stts atom " ) ;
2015-04-22 19:22:01 +02:00
throw NotImplementedException ( ) ;
}
2019-04-21 18:14:20 +02:00
Mp4Atom * const cttsAtom = stblAtom ? stblAtom - > childById ( Mp4AtomIds : : CompositionTimeToSample , diag ) : nullptr ;
2018-03-07 01:17:50 +01:00
if ( cttsAtom ) {
2015-04-22 19:22:01 +02:00
// copy existing ctts atom
2018-03-05 17:49:29 +01:00
cttsAtom - > copyEntirely ( outputStream ( ) , diag , nullptr ) ;
2015-04-22 19:22:01 +02:00
}
// write stsc atom (sample-to-chunk table)
2017-05-28 21:01:16 +02:00
throw NotImplementedException ( ) ;
2015-04-22 19:22:01 +02:00
// 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)
2019-04-21 18:14:20 +02:00
// write size of stbl atom (enable when function is fully implemented)
// Mp4Atom::seekBackAndWriteAtomSize(outputStream(), stblStartOffset, diag);
2015-04-22 19:22:01 +02:00
}
2018-03-05 17:49:29 +01:00
void Mp4Track : : internalParseHeader ( Diagnostics & diag )
2015-04-22 19:22:01 +02:00
{
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 ;
2018-03-07 01:17:50 +01:00
if ( ! m_trakAtom ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " \" trak \" -atom is null. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2016-03-14 21:56:27 +01:00
2015-04-22 19:22:01 +02:00
// get atoms
try {
2018-03-07 01:17:50 +01:00
if ( ! ( m_tkhdAtom = m_trakAtom - > childById ( TrackHeader , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" tkhd \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! ( m_mdiaAtom = m_trakAtom - > childById ( Media , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" mdia \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! ( m_mdhdAtom = m_mdiaAtom - > childById ( MediaHeader , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" mdhd \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! ( m_hdlrAtom = m_mdiaAtom - > childById ( HandlerReference , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" hdlr \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! ( m_minfAtom = m_mdiaAtom - > childById ( MediaInformation , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" minf \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! ( m_stblAtom = m_minfAtom - > childById ( SampleTable , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" stbl \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! ( m_stsdAtom = m_stblAtom - > childById ( SampleDescription , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" stsd \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! ( m_stcoAtom = m_stblAtom - > childById ( ChunkOffset , diag ) ) & & ! ( m_stcoAtom = m_stblAtom - > childById ( ChunkOffset64 , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" stco \" / \" co64 \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! ( m_stscAtom = m_stblAtom - > childById ( SampleToChunk , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" stsc \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! ( m_stszAtom = m_stblAtom - > childById ( SampleSize , diag ) ) & & ! ( m_stszAtom = m_stblAtom - > childById ( CompactSampleSize , diag ) ) ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " No \" stsz \" / \" stz2 \" -atom found. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2018-03-07 01:17:50 +01:00
} catch ( const Failure & ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Unable to parse relevant atoms. " , context ) ;
2015-04-22 19:22:01 +02:00
throw InvalidDataException ( ) ;
}
2016-03-14 21:56:27 +01:00
2015-04-22 19:22:01 +02:00
BinaryReader & reader = m_trakAtom - > reader ( ) ;
2016-03-14 21:56:27 +01:00
2015-04-22 19:22:01 +02:00
// read tkhd atom
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_tkhdAtom - > startOffset ( ) + 8 ) ) ; // seek to beg, skip size and name
2018-08-23 23:20:29 +02:00
auto atomVersion = reader . readByte ( ) ; // read version
const auto flags = reader . readUInt24BE ( ) ;
2015-04-22 19:22:01 +02:00
m_enabled = flags & 0x000001 ;
m_usedInPresentation = flags & 0x000002 ;
m_usedWhenPreviewing = flags & 0x000004 ;
2018-03-07 01:17:50 +01:00
switch ( atomVersion ) {
2015-04-22 19:22:01 +02:00
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 :
2018-03-07 01:17:50 +01:00
diag . emplace_back ( DiagLevel : : 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 ;
}
2016-03-14 21:56:27 +01:00
2015-04-22 19:22:01 +02:00
// read mdhd atom
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_mdhdAtom - > dataOffset ( ) ) ) ; // seek to beg, skip size and name
2015-04-22 19:22:01 +02:00
atomVersion = reader . readByte ( ) ; // read version
m_istream - > seekg ( 3 , ios_base : : cur ) ; // skip flags
2018-03-07 01:17:50 +01:00
switch ( atomVersion ) {
2015-04-22 19:22:01 +02:00
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 :
2018-03-07 01:17:50 +01:00
diag . emplace_back ( DiagLevel : : 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 ( ) ;
}
2016-03-14 21:56:27 +01:00
uint16 tmp = reader . readUInt16BE ( ) ;
2018-03-07 01:17:50 +01:00
if ( tmp ) {
2017-05-28 21:01:16 +02:00
const char buff [ ] = {
static_cast < char > ( ( ( tmp & 0x7C00 ) > > 0xA ) + 0x60 ) ,
static_cast < char > ( ( ( tmp & 0x03E0 ) > > 0x5 ) + 0x60 ) ,
static_cast < char > ( ( ( tmp & 0x001F ) > > 0x0 ) + 0x60 ) ,
} ;
2016-03-14 21:56:27 +01:00
m_language = string ( buff , 3 ) ;
} else {
m_language . clear ( ) ;
}
2015-04-22 19:22:01 +02:00
// read hdlr atom
2016-02-20 01:42:01 +01:00
// -> seek to begin skipping size, name, version, flags and reserved bytes
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_hdlrAtom - > dataOffset ( ) + 8 ) ) ;
2016-02-20 01:42:01 +01:00
// -> track type
2018-03-07 01:17:50 +01:00
switch ( reader . readUInt32BE ( ) ) {
2016-02-20 01:42:01 +01:00
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 ;
2018-03-07 01:17:50 +01:00
case 0x6D657461 :
2019-04-19 21:51:13 +02:00
m_mediaType = MediaType : : Meta ;
break ;
2018-03-07 01:17:50 +01:00
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 ;
}
2019-04-19 21:51:13 +02:00
// FIXME: save raw media type in next major release so unknown ones can still be written correctly in Mp4Track::makeMedia()
2016-03-14 21:56:27 +01:00
// -> name
2015-04-22 19:22:01 +02:00
m_istream - > seekg ( 12 , ios_base : : cur ) ; // skip reserved bytes
2019-04-21 18:14:20 +02:00
if ( static_cast < std : : uint64_t > ( tmp = static_cast < std : : uint8_t > ( m_istream - > peek ( ) ) ) = = m_hdlrAtom - > dataSize ( ) - 12 - 4 - 8 - 1 ) {
2016-03-14 21:56:27 +01:00
// assume size prefixed string (seems to appear in QuickTime files)
m_istream - > seekg ( 1 , ios_base : : cur ) ;
m_name = reader . readString ( tmp ) ;
} else {
// assume null terminated string (appears in MP4 files)
m_name = reader . readTerminatedString ( m_hdlrAtom - > dataSize ( ) - 12 - 4 - 8 , 0 ) ;
}
2015-06-10 01:28:22 +02:00
// read stco atom (only chunk count)
m_chunkOffsetSize = ( m_stcoAtom - > id ( ) = = Mp4AtomIds : : ChunkOffset64 ) ? 8 : 4 ;
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_stcoAtom - > dataOffset ( ) + 4 ) ) ;
2015-06-10 01:28:22 +02:00
m_chunkCount = reader . readUInt32BE ( ) ;
2016-03-14 21:56:27 +01:00
2015-04-22 19:22:01 +02:00
// read stsd atom
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_stsdAtom - > dataOffset ( ) + 4 ) ) ; // seek to beg, skip size, name, version and flags
2018-08-23 23:20:29 +02:00
const auto entryCount = reader . readUInt32BE ( ) ;
2015-07-07 03:01:48 +02:00
Mp4Atom * esDescParentAtom = nullptr ;
2018-03-07 01:17:50 +01:00
if ( entryCount ) {
2015-07-07 03:01:48 +02:00
try {
2018-03-07 01:17:50 +01:00
for ( Mp4Atom * codecConfigContainerAtom = m_stsdAtom - > firstChild ( ) ; codecConfigContainerAtom ;
codecConfigContainerAtom = codecConfigContainerAtom - > nextSibling ( ) ) {
2018-03-05 17:49:29 +01:00
codecConfigContainerAtom - > parse ( diag ) ;
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
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( codecConfigContainerAtom - > dataOffset ( ) ) ) ;
2018-03-07 01:17:50 +01:00
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 :
2015-07-07 03:01:48 +02:00
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)
2018-03-07 01:17:50 +01:00
if ( ! m_samplingFrequency ) {
2015-08-13 03:23:28 +02:00
m_samplingFrequency = reader . readUInt32BE ( ) > > 16 ;
2018-03-07 01:17:50 +01: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 ) ;
}
2018-03-07 01:17:50 +01:00
if ( codecConfigContainerAtom - > id ( ) ! = FourccIds : : WindowsMediaAudio ) {
switch ( tmp ) {
2015-07-07 03:01:48 +02:00
case 1 :
codecConfigContainerAtom - > denoteFirstChild ( codecConfigContainerAtom - > headerSize ( ) + 28 + 16 ) ;
break ;
case 2 :
codecConfigContainerAtom - > denoteFirstChild ( codecConfigContainerAtom - > headerSize ( ) + 28 + 32 ) ;
break ;
default :
codecConfigContainerAtom - > denoteFirstChild ( codecConfigContainerAtom - > headerSize ( ) + 28 ) ;
}
2018-03-07 01:17:50 +01:00
if ( ! esDescParentAtom ) {
2015-07-07 03:01:48 +02:00
esDescParentAtom = codecConfigContainerAtom ;
}
}
break ;
2018-03-07 01:17:50 +01:00
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 :
2018-08-23 23:18:57 +02:00
case FourccIds : : Av1_IVF :
case FourccIds : : Av1_ISOBMFF :
2015-07-07 03:01:48 +02:00
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 ) ;
2018-03-07 01:17:50 +01:00
if ( tmp = = 0 ) {
2015-07-07 03:01:48 +02:00
m_compressorName . clear ( ) ;
2018-03-07 01:17:50 +01:00
} else if ( tmp < 32 ) {
2015-07-07 03:01:48 +02:00
m_compressorName . resize ( tmp ) ;
}
m_depth = reader . readUInt16BE ( ) ; // 24: color without alpha
codecConfigContainerAtom - > denoteFirstChild ( codecConfigContainerAtom - > headerSize ( ) + 78 ) ;
2018-03-07 01:17:50 +01:00
if ( ! esDescParentAtom ) {
2015-07-07 03:01:48 +02:00
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 ) ;
2018-03-07 01:17:50 +01:00
if ( ! esDescParentAtom ) {
2015-07-13 00:57:38 +02:00
esDescParentAtom = codecConfigContainerAtom ;
}
break ;
2015-07-07 03:01:48 +02:00
case Mp4AtomIds : : PixalAspectRatio :
break ; // TODO
case Mp4AtomIds : : CleanAperature :
break ; // TODO
2018-03-07 01:17:50 +01:00
default : ;
2015-07-07 03:01:48 +02:00
}
}
2016-02-17 20:19:05 +01:00
2018-03-07 01:17:50 +01:00
if ( esDescParentAtom ) {
2016-02-17 20:19:05 +01:00
// parse AVC configuration
2018-08-23 23:20:29 +02:00
if ( auto * const avcConfigAtom = esDescParentAtom - > childById ( Mp4AtomIds : : AvcConfiguration , diag ) ) {
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( avcConfigAtom - > dataOffset ( ) ) ) ;
2018-03-06 23:09:15 +01:00
m_avcConfig = make_unique < TagParser : : AvcConfiguration > ( ) ;
2016-02-17 20:19:05 +01:00
try {
2018-08-23 23:19:44 +02:00
m_avcConfig - > parse ( reader , avcConfigAtom - > dataSize ( ) , diag ) ;
2016-02-17 20:19:05 +01:00
addInfo ( * m_avcConfig , * this ) ;
2018-03-07 01:17:50 +01:00
} catch ( const TruncatedDataException & ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " AVC configuration is truncated. " , context ) ;
2018-03-07 01:17:50 +01:00
} catch ( const Failure & ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " AVC configuration is invalid. " , context ) ;
2016-02-17 20:19:05 +01:00
}
}
2018-08-23 23:18:57 +02:00
// parse AV1 configuration
if ( auto * const av1ConfigAtom = esDescParentAtom - > childById ( Mp4AtomIds : : Av1Configuration , diag ) ) {
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( av1ConfigAtom - > dataOffset ( ) ) ) ;
2018-08-23 23:18:57 +02:00
m_av1Config = make_unique < TagParser : : Av1Configuration > ( ) ;
try {
m_av1Config - > parse ( reader , av1ConfigAtom - > dataSize ( ) , diag ) ;
addInfo ( * m_av1Config , * this ) ;
} catch ( const NotImplementedException & ) {
2018-11-08 14:31:35 +01:00
diag . emplace_back ( DiagLevel : : Information , " Parsing AV1 configuration is not supported yet. " , context ) ;
2018-08-23 23:18:57 +02:00
} catch ( const TruncatedDataException & ) {
diag . emplace_back ( DiagLevel : : Critical , " AV1 configuration is truncated. " , context ) ;
} catch ( const Failure & ) {
diag . emplace_back ( DiagLevel : : Critical , " AV1 configuration is invalid. " , context ) ;
}
}
2016-02-17 20:19:05 +01:00
// parse MPEG-4 elementary stream descriptor
2018-08-23 23:20:29 +02:00
auto * esDescAtom = esDescParentAtom - > childById ( Mp4FormatExtensionIds : : Mpeg4ElementaryStreamDescriptor , diag ) ;
2018-03-07 01:17:50 +01:00
if ( ! esDescAtom ) {
2018-03-05 17:49:29 +01:00
esDescAtom = esDescParentAtom - > childById ( Mp4FormatExtensionIds : : Mpeg4ElementaryStreamDescriptor2 , diag ) ;
2015-06-07 00:18:28 +02:00
}
2018-03-07 01:17:50 +01:00
if ( esDescAtom ) {
2015-06-10 01:28:22 +02:00
try {
2018-03-07 01:17:50 +01:00
if ( ( m_esInfo = parseMpeg4ElementaryStreamInfo ( m_reader , esDescAtom , diag ) ) ) {
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 ;
2018-03-07 01:17:50 +01:00
if ( m_esInfo - > audioSpecificConfig ) {
2015-06-10 01:28:22 +02:00
// check the audio specific config for useful information
2018-03-07 01:17:50 +01:00
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 ;
2018-03-07 01:17:50 +01: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 {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Warning , " Audio specific config has invalid sample frequency index. " , context ) ;
2015-06-10 01:28:22 +02:00
}
2018-03-07 01:17:50 +01:00
if ( m_esInfo - > audioSpecificConfig - > extensionSampleFrequencyIndex = = 0xF ) {
2015-08-13 03:23:28 +02:00
m_extensionSamplingFrequency = m_esInfo - > audioSpecificConfig - > extensionSampleFrequency ;
2018-03-07 01:17:50 +01:00
} else if ( m_esInfo - > audioSpecificConfig - > extensionSampleFrequencyIndex < sizeof ( mpeg4SamplingFrequencyTable ) ) {
m_extensionSamplingFrequency
= mpeg4SamplingFrequencyTable [ m_esInfo - > audioSpecificConfig - > extensionSampleFrequencyIndex ] ;
2015-06-10 01:28:22 +02:00
} else {
2018-03-07 01:17:50 +01:00
diag . emplace_back (
DiagLevel : : Warning , " Audio specific config has invalid extension sample frequency index. " , context ) ;
2015-06-10 01:28:22 +02:00
}
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
}
2018-03-07 01:17:50 +01:00
if ( m_esInfo - > videoSpecificConfig ) {
2015-07-07 03:01:48 +02:00
// check the video specific config for useful information
2018-03-07 01:17:50 +01:00
if ( m_format . general = = GeneralMediaFormat : : Mpeg4Video & & m_esInfo - > videoSpecificConfig - > profile ) {
2015-07-07 03:01:48 +02:00
m_format . sub = m_esInfo - > videoSpecificConfig - > profile ;
2018-03-07 01:17:50 +01:00
if ( ! m_esInfo - > videoSpecificConfig - > userData . empty ( ) ) {
2017-01-27 18:59:22 +01:00
m_formatId + = " / " ;
m_formatId + = m_esInfo - > videoSpecificConfig - > userData ;
2015-07-07 03:01:48 +02:00
}
}
}
2015-06-10 01:28:22 +02:00
// check the stream data for missing information
2018-03-07 01:17:50 +01:00
switch ( m_format . general ) {
case GeneralMediaFormat : : Mpeg1Audio :
case GeneralMediaFormat : : Mpeg2Audio : {
2015-06-10 01:28:22 +02:00
MpegAudioFrame frame ;
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_stcoAtom - > dataOffset ( ) + 8 ) ) ;
m_istream - > seekg ( static_cast < streamoff > ( m_chunkOffsetSize = = 8 ? reader . readUInt64BE ( ) : reader . readUInt32BE ( ) ) ) ;
2018-07-28 14:56:00 +02:00
frame . parseHeader ( reader , diag ) ;
2015-06-10 01:28:22 +02:00
MpegAudioFrameStream : : addInfo ( frame , * this ) ;
break ;
2018-03-07 01:17:50 +01:00
}
default : ;
2015-06-10 01:28:22 +02:00
}
2015-06-07 00:18:28 +02:00
}
2018-03-07 01:17:50 +01:00
} catch ( const Failure & ) {
2015-06-07 00:18:28 +02:00
}
2015-04-22 19:22:01 +02:00
}
}
2018-03-07 01:11:42 +01:00
} catch ( const Failure & ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " Unable to parse child atoms of \" stsd \" -atom. " , context ) ;
2015-04-22 19:22:01 +02:00
}
}
2016-03-14 21:56:27 +01:00
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 ( ) ;
2018-03-07 01:17:50 +01:00
if ( actualSampleSizeTableSize < 12 ) {
diag . emplace_back ( DiagLevel : : Critical ,
" The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined. " , context ) ;
2015-04-22 19:22:01 +02:00
} else {
actualSampleSizeTableSize - = 12 ; // subtract size of version and flags
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_stszAtom - > dataOffset ( ) + 4 ) ) ; // seek to beg, skip size, name, version and flags
std : : uint32_t fieldSize ;
std : : uint32_t constantSize ;
2018-03-07 01:17:50 +01:00
if ( m_stszAtom - > id ( ) = = Mp4AtomIds : : CompactSampleSize ) {
2015-04-22 19:22:01 +02:00
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 ;
}
2018-03-07 01:17:50 +01:00
if ( constantSize ) {
2015-04-22 19:22:01 +02:00
m_sampleSizes . push_back ( constantSize ) ;
m_size = constantSize * m_sampleCount ;
} else {
2018-08-23 23:20:29 +02:00
auto actualSampleCount = m_sampleCount ;
const auto calculatedSampleSizeTableSize = static_cast < uint64 > ( ceil ( ( 0.125 * fieldSize ) * m_sampleCount ) ) ;
2018-03-07 01:17:50 +01:00
if ( calculatedSampleSizeTableSize < actualSampleSizeTableSize ) {
diag . emplace_back (
DiagLevel : : Critical , " The stsz atom stores more entries as denoted. The additional entries will be ignored. " , context ) ;
} else if ( calculatedSampleSizeTableSize > actualSampleSizeTableSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " The stsz atom is truncated. It stores less entries as denoted. " , context ) ;
2018-08-23 23:20:29 +02:00
actualSampleCount = static_cast < uint64 > ( floor ( static_cast < double > ( actualSampleSizeTableSize ) / ( 0.125 * fieldSize ) ) ) ;
2015-04-22 19:22:01 +02:00
}
m_sampleSizes . reserve ( actualSampleCount ) ;
uint32 i = 1 ;
2018-03-07 01:17:50 +01:00
switch ( fieldSize ) {
2015-04-22 19:22:01 +02:00
case 4 :
2018-03-07 01:17:50 +01:00
for ( ; i < = actualSampleCount ; i + = 2 ) {
2015-04-22 19:22:01 +02:00
byte val = reader . readByte ( ) ;
m_sampleSizes . push_back ( val > > 4 ) ;
m_sampleSizes . push_back ( val & 0xF0 ) ;
m_size + = ( val > > 4 ) + ( val & 0xF0 ) ;
}
2018-03-07 01:17:50 +01:00
if ( i < = actualSampleCount + 1 ) {
2015-04-22 19:22:01 +02:00
m_sampleSizes . push_back ( reader . readByte ( ) > > 4 ) ;
m_size + = m_sampleSizes . back ( ) ;
}
break ;
case 8 :
2018-03-07 01:17:50 +01:00
for ( ; i < = actualSampleCount ; + + i ) {
2015-04-22 19:22:01 +02:00
m_sampleSizes . push_back ( reader . readByte ( ) ) ;
m_size + = m_sampleSizes . back ( ) ;
}
break ;
case 16 :
2018-03-07 01:17:50 +01:00
for ( ; i < = actualSampleCount ; + + i ) {
2015-04-22 19:22:01 +02:00
m_sampleSizes . push_back ( reader . readUInt16BE ( ) ) ;
m_size + = m_sampleSizes . back ( ) ;
}
break ;
case 32 :
2018-03-07 01:17:50 +01:00
for ( ; i < = actualSampleCount ; + + i ) {
2015-04-22 19:22:01 +02:00
m_sampleSizes . push_back ( reader . readUInt32BE ( ) ) ;
m_size + = m_sampleSizes . back ( ) ;
}
break ;
default :
2018-03-07 01:17:50 +01:00
diag . emplace_back ( DiagLevel : : 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 ) ;
2015-04-22 19:22:01 +02:00
}
}
}
2016-03-14 21:56:27 +01:00
2015-04-22 19:22:01 +02:00
// no sample sizes found, search for trun atoms
uint64 totalDuration = 0 ;
2018-03-07 01:17:50 +01:00
for ( Mp4Atom * moofAtom = m_trakAtom - > container ( ) . firstElement ( ) - > siblingByIdIncludingThis ( MovieFragment , diag ) ; moofAtom ;
moofAtom = moofAtom - > siblingById ( MovieFragment , diag ) ) {
2018-03-05 17:49:29 +01:00
moofAtom - > parse ( diag ) ;
2018-03-07 01:17:50 +01:00
for ( Mp4Atom * trafAtom = moofAtom - > childById ( TrackFragment , diag ) ; trafAtom ; trafAtom = trafAtom - > siblingById ( TrackFragment , diag ) ) {
2018-03-05 17:49:29 +01:00
trafAtom - > parse ( diag ) ;
2018-03-07 01:17:50 +01:00
for ( Mp4Atom * tfhdAtom = trafAtom - > childById ( TrackFragmentHeader , diag ) ; tfhdAtom ;
tfhdAtom = tfhdAtom - > siblingById ( TrackFragmentHeader , diag ) ) {
2018-03-05 17:49:29 +01:00
tfhdAtom - > parse ( diag ) ;
2015-04-22 19:22:01 +02:00
uint32 calculatedDataSize = 0 ;
2018-03-07 01:17:50 +01:00
if ( tfhdAtom - > dataSize ( ) < calculatedDataSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " tfhd atom is truncated. " , context ) ;
2015-04-22 19:22:01 +02:00
} else {
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( tfhdAtom - > dataOffset ( ) + 1 ) ) ;
std : : uint32_t flags = reader . readUInt24BE ( ) ;
2018-03-07 01:17:50 +01:00
if ( m_id = = reader . readUInt32BE ( ) ) { // check track ID
if ( flags & 0x000001 ) { // base-data-offset present
2015-04-22 19:22:01 +02:00
calculatedDataSize + = 8 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000002 ) { // sample-description-index present
2015-04-22 19:22:01 +02:00
calculatedDataSize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000008 ) { // default-sample-duration present
2015-04-22 19:22:01 +02:00
calculatedDataSize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000010 ) { // default-sample-size present
2015-04-22 19:22:01 +02:00
calculatedDataSize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000020 ) { // default-sample-flags present
2015-04-22 19:22:01 +02:00
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;
2018-03-07 01:17:50 +01:00
if ( tfhdAtom - > dataSize ( ) < calculatedDataSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " tfhd atom is truncated (presence of fields denoted). " , context ) ;
2015-04-22 19:22:01 +02:00
} else {
2018-03-07 01:17:50 +01:00
if ( flags & 0x000001 ) { // base-data-offset present
2015-04-22 19:22:01 +02:00
//baseDataOffset = reader.readUInt64();
m_istream - > seekg ( 8 , ios_base : : cur ) ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000002 ) { // sample-description-index present
2015-04-22 19:22:01 +02:00
//defaultSampleDescriptionIndex = reader.readUInt32();
m_istream - > seekg ( 4 , ios_base : : cur ) ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000008 ) { // default-sample-duration present
2015-04-22 19:22:01 +02:00
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
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000010 ) { // default-sample-size present
2015-04-22 19:22:01 +02:00
defaultSampleSize = reader . readUInt32BE ( ) ;
}
2018-03-07 01:17:50 +01:00
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
}
}
2018-03-07 01:17:50 +01:00
for ( Mp4Atom * trunAtom = trafAtom - > childById ( TrackFragmentRun , diag ) ; trunAtom ;
trunAtom = trunAtom - > siblingById ( TrackFragmentRun , diag ) ) {
2015-04-22 19:22:01 +02:00
uint32 calculatedDataSize = 8 ;
2018-03-07 01:17:50 +01:00
if ( trunAtom - > dataSize ( ) < calculatedDataSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " trun atom is truncated. " , context ) ;
2015-04-22 19:22:01 +02:00
} else {
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( trunAtom - > dataOffset ( ) + 1 ) ) ;
std : : uint32_t flags = reader . readUInt24BE ( ) ;
std : : uint32_t sampleCount = reader . readUInt32BE ( ) ;
2015-04-22 19:22:01 +02:00
m_sampleCount + = sampleCount ;
2018-03-07 01:17:50 +01:00
if ( flags & 0x000001 ) { // data offset present
2015-04-22 19:22:01 +02:00
calculatedDataSize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000004 ) { // first-sample-flags present
2015-04-22 19:22:01 +02:00
calculatedDataSize + = 4 ;
}
uint32 entrySize = 0 ;
2018-03-07 01:17:50 +01:00
if ( flags & 0x000100 ) { // sample-duration present
2015-04-22 19:22:01 +02:00
entrySize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000200 ) { // sample-size present
2015-04-22 19:22:01 +02:00
entrySize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000400 ) { // sample-flags present
2015-04-22 19:22:01 +02:00
entrySize + = 4 ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000800 ) { // sample-composition-time-offsets present
2015-04-22 19:22:01 +02:00
entrySize + = 4 ;
}
calculatedDataSize + = entrySize * sampleCount ;
2018-03-07 01:17:50 +01:00
if ( trunAtom - > dataSize ( ) < calculatedDataSize ) {
2018-03-05 17:49:29 +01:00
diag . emplace_back ( DiagLevel : : Critical , " trun atom is truncated (presence of fields denoted). " , context ) ;
2015-04-22 19:22:01 +02:00
} else {
2018-03-07 01:17:50 +01:00
if ( flags & 0x000001 ) { // data offset present
2015-04-22 19:22:01 +02:00
m_istream - > seekg ( 4 , ios_base : : cur ) ;
//int32 dataOffset = reader.readInt32();
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000004 ) { // first-sample-flags present
2015-04-22 19:22:01 +02:00
m_istream - > seekg ( 4 , ios_base : : cur ) ;
}
2018-03-07 01:17:50 +01:00
for ( uint32 i = 0 ; i < sampleCount ; + + i ) {
if ( flags & 0x000100 ) { // sample-duration present
2015-04-22 19:22:01 +02:00
totalDuration + = reader . readUInt32BE ( ) ;
} else {
totalDuration + = defaultSampleDuration ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000200 ) { // sample-size present
2015-04-22 19:22:01 +02:00
m_sampleSizes . push_back ( reader . readUInt32BE ( ) ) ;
m_size + = m_sampleSizes . back ( ) ;
} else {
m_size + = defaultSampleSize ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000400 ) { // sample-flags present
2015-04-22 19:22:01 +02:00
m_istream - > seekg ( 4 , ios_base : : cur ) ;
}
2018-03-07 01:17:50 +01:00
if ( flags & 0x000800 ) { // sample-composition-time-offsets present
2015-04-22 19:22:01 +02:00
m_istream - > seekg ( 4 , ios_base : : cur ) ;
}
}
}
}
}
2018-03-07 01:17:50 +01:00
if ( m_sampleSizes . empty ( ) & & defaultSampleSize ) {
2015-04-22 19:22:01 +02:00
m_sampleSizes . push_back ( defaultSampleSize ) ;
}
}
}
}
}
}
2016-03-14 21:56:27 +01:00
2015-04-22 19:22:01 +02:00
// set duration from "trun-information" if the duration has not been determined yet
2018-03-07 01:17:50 +01:00
if ( m_duration . isNull ( ) & & totalDuration ) {
2015-04-22 19:22:01 +02:00
uint32 timeScale = m_timeScale ;
2018-03-07 01:17:50 +01:00
if ( ! timeScale ) {
2015-04-22 19:22:01 +02:00
timeScale = trakAtom ( ) . container ( ) . timeScale ( ) ;
}
2018-03-07 01:17:50 +01:00
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
}
}
2016-03-14 21:56:27 +01:00
2015-04-22 19:22:01 +02:00
// caluculate average bitrate
2018-03-07 01:17:50 +01:00
if ( m_bitrate < 0.01 & & m_bitrate > - 0.01 ) {
2015-06-10 01:28:22 +02:00
m_bitrate = ( static_cast < double > ( m_size ) * 0.0078125 ) / m_duration . totalSeconds ( ) ;
}
2016-03-14 21:56:27 +01:00
2015-04-22 19:22:01 +02:00
// read stsc atom (only number of entries)
2019-04-21 18:14:20 +02:00
m_istream - > seekg ( static_cast < streamoff > ( m_stscAtom - > dataOffset ( ) + 4 ) ) ;
2015-04-22 19:22:01 +02:00
m_sampleToChunkEntryCount = reader . readUInt32BE ( ) ;
}
2018-03-07 01:17:50 +01:00
} // namespace TagParser