Fix messing track header of MP4 files (tkhd atom)
This commit is contained in:
parent
d4a406ba57
commit
ba8c9204a9
153
mp4/mp4track.cpp
153
mp4/mp4track.cpp
|
@ -28,6 +28,44 @@ using namespace ChronoUtilities;
|
||||||
|
|
||||||
namespace Media {
|
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 <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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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.
|
/// \brief Dates within MP4 tracks are expressed as the number of seconds since this date.
|
||||||
const DateTime startDate = DateTime::fromDate(1904, 1, 1);
|
const DateTime startDate = DateTime::fromDate(1904, 1, 1);
|
||||||
|
|
||||||
|
@ -358,6 +396,62 @@ void Mp4Track::addChunkSizeEntries(std::vector<uint64> &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<byte>(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<uint32>::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.
|
* \brief Reads the sample to chunk table.
|
||||||
* \returns Returns a vector with the table entries wrapped using the tuple container. The first value
|
* \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
|
uint64 Mp4Track::requiredSize() const
|
||||||
{
|
{
|
||||||
// add size of
|
// add size of
|
||||||
// ... trak header and tkhd total size
|
// ... trak header
|
||||||
uint64 size = 8 + 100;
|
uint64 size = 8;
|
||||||
|
// ... tkhd atom (TODO: buffer TrackHeaderInfo in v7)
|
||||||
|
size += verifyPresentTrackHeader().requiredSize;
|
||||||
// ... tref atom (if one exists)
|
// ... tref atom (if one exists)
|
||||||
if(Mp4Atom *trefAtom = m_trakAtom->childById(Mp4AtomIds::TrackReference)) {
|
if(Mp4Atom *trefAtom = m_trakAtom->childById(Mp4AtomIds::TrackReference)) {
|
||||||
size += trefAtom->totalSize();
|
size += trefAtom->totalSize();
|
||||||
|
@ -1050,9 +1146,31 @@ void Mp4Track::makeTrack()
|
||||||
*/
|
*/
|
||||||
void Mp4Track::makeTrackHeader()
|
void Mp4Track::makeTrackHeader()
|
||||||
{
|
{
|
||||||
writer().writeUInt32BE(100); // size
|
// verify the existing track header to make the new one based on it (if possible)
|
||||||
writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
|
const TrackHeaderInfo info(verifyPresentTrackHeader());
|
||||||
writer().writeByte(1); // version
|
|
||||||
|
// 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<uint32>::max()) {
|
||||||
|
writer().writeUInt32BE(1);
|
||||||
|
writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
|
||||||
|
writer().writeUInt64BE(info.requiredSize);
|
||||||
|
} else {
|
||||||
|
writer().writeUInt32BE(static_cast<uint32>(info.requiredSize));
|
||||||
|
writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make version and flags
|
||||||
|
writer().writeByte(1);
|
||||||
uint32 flags = 0;
|
uint32 flags = 0;
|
||||||
if(m_enabled) {
|
if(m_enabled) {
|
||||||
flags |= 0x000001;
|
flags |= 0x000001;
|
||||||
|
@ -1064,28 +1182,31 @@ void Mp4Track::makeTrackHeader()
|
||||||
flags |= 0x000004;
|
flags |= 0x000004;
|
||||||
}
|
}
|
||||||
writer().writeUInt24BE(flags);
|
writer().writeUInt24BE(flags);
|
||||||
|
|
||||||
|
// make creation and modification time
|
||||||
writer().writeUInt64BE(static_cast<uint64>((m_creationTime - startDate).totalSeconds()));
|
writer().writeUInt64BE(static_cast<uint64>((m_creationTime - startDate).totalSeconds()));
|
||||||
writer().writeUInt64BE(static_cast<uint64>((m_modificationTime - startDate).totalSeconds()));
|
writer().writeUInt64BE(static_cast<uint64>((m_modificationTime - startDate).totalSeconds()));
|
||||||
writer().writeUInt32BE(m_id);
|
|
||||||
|
// make track ID and duration
|
||||||
|
writer().writeUInt32BE(static_cast<uint32>(m_id));
|
||||||
writer().writeUInt32BE(0); // reserved
|
writer().writeUInt32BE(0); // reserved
|
||||||
writer().writeUInt64BE(static_cast<uint64>(m_duration.totalSeconds() * m_timeScale));
|
writer().writeUInt64BE(static_cast<uint64>(m_duration.totalSeconds() * m_timeScale));
|
||||||
writer().writeUInt32BE(0); // reserved
|
writer().writeUInt32BE(0); // reserved
|
||||||
writer().writeUInt32BE(0); // reserved
|
writer().writeUInt32BE(0); // reserved
|
||||||
if(m_tkhdAtom) {
|
|
||||||
// use existing values
|
// make further values, either from existing tkhd atom or just some defaults
|
||||||
if(m_tkhdAtom->buffer()) {
|
if(info.canUseExisting) {
|
||||||
m_ostream->write(m_tkhdAtom->buffer().get() + 52, 48);
|
// write all bytes after the previously determined additionalDataOffset
|
||||||
} else {
|
m_ostream->write(m_tkhdAtom->buffer().get() + m_tkhdAtom->headerSize() + info.additionalDataOffset, m_tkhdAtom->dataSize() - info.additionalDataOffset);
|
||||||
char buffer[48];
|
// discard the buffer again if it wasn't present before
|
||||||
m_istream->seekg(m_tkhdAtom->startOffset() + 52);
|
if(info.discardBuffer) {
|
||||||
m_istream->read(buffer, sizeof(buffer));
|
m_tkhdAtom->discardBuffer();
|
||||||
m_ostream->write(buffer, sizeof(buffer));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// write default values
|
// write default values
|
||||||
writer().writeInt16BE(0); // layer
|
writer().writeInt16BE(0); // layer
|
||||||
writer().writeInt16BE(0); // alternate group
|
writer().writeInt16BE(0); // alternate group
|
||||||
writer().writeFixed8BE(1.0); // volume
|
writer().writeFixed8BE(1.0); // volume (fixed 8.8 - 2 byte)
|
||||||
writer().writeUInt16BE(0); // reserved
|
writer().writeUInt16BE(0); // reserved
|
||||||
for(const int32 value : {0x00010000,0,0,0,0x00010000,0,0,0,0x40000000}) { // unity matrix
|
for(const int32 value : {0x00010000,0,0,0,0x00010000,0,0,0,0x40000000}) { // unity matrix
|
||||||
writer().writeInt32BE(value);
|
writer().writeInt32BE(value);
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace Media
|
||||||
class Mp4Atom;
|
class Mp4Atom;
|
||||||
class Mpeg4Descriptor;
|
class Mpeg4Descriptor;
|
||||||
struct AvcConfiguration;
|
struct AvcConfiguration;
|
||||||
|
struct TrackHeaderInfo;
|
||||||
|
|
||||||
class TAG_PARSER_EXPORT Mpeg4AudioSpecificConfig
|
class TAG_PARSER_EXPORT Mpeg4AudioSpecificConfig
|
||||||
{
|
{
|
||||||
|
@ -166,6 +167,7 @@ private:
|
||||||
// private helper methods
|
// private helper methods
|
||||||
uint64 accumulateSampleSizes(size_t &sampleIndex, size_t count);
|
uint64 accumulateSampleSizes(size_t &sampleIndex, size_t count);
|
||||||
void addChunkSizeEntries(std::vector<uint64> &chunkSizeTable, size_t count, size_t &sampleIndex, uint32 sampleCount);
|
void addChunkSizeEntries(std::vector<uint64> &chunkSizeTable, size_t count, size_t &sampleIndex, uint32 sampleCount);
|
||||||
|
TrackHeaderInfo verifyPresentTrackHeader() const;
|
||||||
|
|
||||||
Mp4Atom *m_trakAtom;
|
Mp4Atom *m_trakAtom;
|
||||||
Mp4Atom *m_tkhdAtom;
|
Mp4Atom *m_tkhdAtom;
|
||||||
|
|
Loading…
Reference in New Issue