diff --git a/mp4/mp4track.cpp b/mp4/mp4track.cpp index 4de2702..933e498 100644 --- a/mp4/mp4track.cpp +++ b/mp4/mp4track.cpp @@ -28,6 +28,44 @@ using namespace ChronoUtilities; namespace Media { +/*! + * \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. + */ +struct TrackHeaderInfo +{ + friend class Mp4Track; + +private: + TrackHeaderInfo(); + + /// \brief Specifies the size which is required for making a new 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; +}; + +inline TrackHeaderInfo::TrackHeaderInfo() : + requiredSize(100), + canUseExisting(false), + truncated(false), + version(0), + versionUnknown(false), + discardBuffer(false) +{} + /// \brief Dates within MP4 tracks are expressed as the number of seconds since this date. const DateTime startDate = DateTime::fromDate(1904, 1, 1); @@ -358,6 +396,62 @@ void Mp4Track::addChunkSizeEntries(std::vector &chunkSizeTable, size_t c } } +/*! + * \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 + if(!m_tkhdAtom) { + 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; + if(info.discardBuffer) { + m_tkhdAtom->makeBuffer(); + } + + // check the version of the existing tkhd atom to determine where additional data starts + switch(info.version = static_cast(m_tkhdAtom->buffer()[m_tkhdAtom->headerSize()])) { + 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 + if(info.additionalDataOffset + 48 <= m_tkhdAtom->dataSize()) { + info.canUseExisting = true; + } else { + info.truncated = true; + info.canUseExisting = info.additionalDataOffset < m_tkhdAtom->dataSize(); + if(!info.canUseExisting && info.discardBuffer) { + m_tkhdAtom->discardBuffer(); + } + } + + // determine required size + info.requiredSize = m_tkhdAtom->dataSize() + 8; + if(info.version == 0) { + // add 12 byte to size if the existing version is 0 because we always write version 1 which takes 12 byte more space + info.requiredSize += 12; + } + if(info.requiredSize > numeric_limits::max()) { + // add 8 byte to the size because it must be denoted using a 64-bit integer + info.requiredSize += 8; + } + return info; +} + /*! * \brief Reads the sample to chunk table. * \returns Returns a vector with the table entries wrapped using the tuple container. The first value @@ -973,8 +1067,10 @@ void Mp4Track::bufferTrackAtoms() uint64 Mp4Track::requiredSize() const { // add size of - // ... trak header and tkhd total size - uint64 size = 8 + 100; + // ... trak header + uint64 size = 8; + // ... tkhd atom (TODO: buffer TrackHeaderInfo in v7) + size += verifyPresentTrackHeader().requiredSize; // ... tref atom (if one exists) if(Mp4Atom *trefAtom = m_trakAtom->childById(Mp4AtomIds::TrackReference)) { size += trefAtom->totalSize(); @@ -1050,9 +1146,31 @@ void Mp4Track::makeTrack() */ void Mp4Track::makeTrackHeader() { - writer().writeUInt32BE(100); // size - writer().writeUInt32BE(Mp4AtomIds::TrackHeader); - writer().writeByte(1); // version + // 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 + if(info.versionUnknown) { + addNotification(NotificationType::Critical, argsToString("The version of the present \"tkhd\"-atom (", info.version, ") is unknown. Assuming version 1."), + argsToString("making \"tkhd\"-atom of track ", m_id)); + } + if(info.truncated) { + addNotification(NotificationType::Critical, argsToString("The present \"tkhd\"-atom is truncated."), + argsToString("making \"tkhd\"-atom of track ", m_id)); + } + + // make size and element ID + if(info.requiredSize > numeric_limits::max()) { + writer().writeUInt32BE(1); + writer().writeUInt32BE(Mp4AtomIds::TrackHeader); + writer().writeUInt64BE(info.requiredSize); + } else { + writer().writeUInt32BE(static_cast(info.requiredSize)); + writer().writeUInt32BE(Mp4AtomIds::TrackHeader); + } + + // make version and flags + writer().writeByte(1); uint32 flags = 0; if(m_enabled) { flags |= 0x000001; @@ -1064,28 +1182,31 @@ void Mp4Track::makeTrackHeader() flags |= 0x000004; } writer().writeUInt24BE(flags); + + // make creation and modification time writer().writeUInt64BE(static_cast((m_creationTime - startDate).totalSeconds())); writer().writeUInt64BE(static_cast((m_modificationTime - startDate).totalSeconds())); - writer().writeUInt32BE(m_id); + + // make track ID and duration + writer().writeUInt32BE(static_cast(m_id)); writer().writeUInt32BE(0); // reserved writer().writeUInt64BE(static_cast(m_duration.totalSeconds() * m_timeScale)); writer().writeUInt32BE(0); // reserved writer().writeUInt32BE(0); // reserved - if(m_tkhdAtom) { - // use existing values - if(m_tkhdAtom->buffer()) { - m_ostream->write(m_tkhdAtom->buffer().get() + 52, 48); - } else { - char buffer[48]; - m_istream->seekg(m_tkhdAtom->startOffset() + 52); - m_istream->read(buffer, sizeof(buffer)); - m_ostream->write(buffer, sizeof(buffer)); + + // make further values, either from existing tkhd atom or just some defaults + if(info.canUseExisting) { + // write all bytes after the previously determined additionalDataOffset + m_ostream->write(m_tkhdAtom->buffer().get() + m_tkhdAtom->headerSize() + info.additionalDataOffset, m_tkhdAtom->dataSize() - info.additionalDataOffset); + // discard the buffer again if it wasn't present before + if(info.discardBuffer) { + m_tkhdAtom->discardBuffer(); } } else { // write default values writer().writeInt16BE(0); // layer writer().writeInt16BE(0); // alternate group - writer().writeFixed8BE(1.0); // volume + writer().writeFixed8BE(1.0); // volume (fixed 8.8 - 2 byte) writer().writeUInt16BE(0); // reserved for(const int32 value : {0x00010000,0,0,0,0x00010000,0,0,0,0x40000000}) { // unity matrix writer().writeInt32BE(value); diff --git a/mp4/mp4track.h b/mp4/mp4track.h index 4f8a274..5ce6b17 100644 --- a/mp4/mp4track.h +++ b/mp4/mp4track.h @@ -12,6 +12,7 @@ namespace Media class Mp4Atom; class Mpeg4Descriptor; struct AvcConfiguration; +struct TrackHeaderInfo; class TAG_PARSER_EXPORT Mpeg4AudioSpecificConfig { @@ -166,6 +167,7 @@ private: // private helper methods uint64 accumulateSampleSizes(size_t &sampleIndex, size_t count); void addChunkSizeEntries(std::vector &chunkSizeTable, size_t count, size_t &sampleIndex, uint32 sampleCount); + TrackHeaderInfo verifyPresentTrackHeader() const; Mp4Atom *m_trakAtom; Mp4Atom *m_tkhdAtom;