Deduplicate code for computing timing values of MP4 track
This commit is contained in:
parent
41ddccb455
commit
d390e8c9cf
|
@ -10,7 +10,7 @@ set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
|
||||||
set(META_APP_DESCRIPTION "C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags")
|
set(META_APP_DESCRIPTION "C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags")
|
||||||
set(META_VERSION_MAJOR 10)
|
set(META_VERSION_MAJOR 10)
|
||||||
set(META_VERSION_MINOR 4)
|
set(META_VERSION_MINOR 4)
|
||||||
set(META_VERSION_PATCH 0)
|
set(META_VERSION_PATCH 1)
|
||||||
set(META_REQUIRED_CPP_UNIT_VERSION 1.14.0)
|
set(META_REQUIRED_CPP_UNIT_VERSION 1.14.0)
|
||||||
set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON)
|
set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON)
|
||||||
|
|
||||||
|
|
126
mp4/mp4track.cpp
126
mp4/mp4track.cpp
|
@ -27,6 +27,16 @@ using namespace CppUtilities;
|
||||||
|
|
||||||
namespace TagParser {
|
namespace TagParser {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The Mp4Timings struct holds timing values found in multiple MP4 atoms.
|
||||||
|
*/
|
||||||
|
struct Mp4Timings {
|
||||||
|
std::uint64_t creationTime = 0;
|
||||||
|
std::uint64_t modificationTime = 0;
|
||||||
|
std::uint64_t duration = 0;
|
||||||
|
constexpr std::uint8_t requiredVersion() const;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The TrackHeaderInfo struct holds information about the present track header (tkhd atom) and
|
* \brief The TrackHeaderInfo struct holds information about the present track header (tkhd atom) and
|
||||||
* information for making a new track header based on it.
|
* information for making a new track header based on it.
|
||||||
|
@ -47,8 +57,14 @@ private:
|
||||||
bool truncated;
|
bool truncated;
|
||||||
/// \brief Specifies the version of the existing track header.
|
/// \brief Specifies the version of the existing track header.
|
||||||
std::uint8_t version;
|
std::uint8_t version;
|
||||||
|
/// \brief Specifies the version the new track header is supposed to use.
|
||||||
|
std::uint8_t writeVersion;
|
||||||
/// \brief Specifies whether the version of the existing track header is unknown (and assumed to be 1).
|
/// \brief Specifies whether the version of the existing track header is unknown (and assumed to be 1).
|
||||||
bool versionUnknown;
|
bool versionUnknown;
|
||||||
|
/// \brief Specifies timing values for the track.
|
||||||
|
Mp4Timings timings;
|
||||||
|
/// \brief Specifies the minimum required version for timings.
|
||||||
|
std::uint8_t timingsVersion;
|
||||||
/// \brief Specifies the additional data offset of the existing header. Unspecified if canUseExisting is false.
|
/// \brief Specifies the additional data offset of the existing header. Unspecified if canUseExisting is false.
|
||||||
std::uint8_t additionalDataOffset;
|
std::uint8_t additionalDataOffset;
|
||||||
/// \brief Specifies whether the buffered header data should be discarded when making a new track header.
|
/// \brief Specifies whether the buffered header data should be discarded when making a new track header.
|
||||||
|
@ -60,12 +76,22 @@ constexpr TrackHeaderInfo::TrackHeaderInfo()
|
||||||
, canUseExisting(false)
|
, canUseExisting(false)
|
||||||
, truncated(false)
|
, truncated(false)
|
||||||
, version(0)
|
, version(0)
|
||||||
|
, writeVersion(0)
|
||||||
, versionUnknown(false)
|
, versionUnknown(false)
|
||||||
|
, timingsVersion(0)
|
||||||
, additionalDataOffset(0)
|
, additionalDataOffset(0)
|
||||||
, discardBuffer(false)
|
, discardBuffer(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr std::uint8_t Mp4Timings::requiredVersion() const
|
||||||
|
{
|
||||||
|
return (creationTime > std::numeric_limits<std::uint32_t>::max() || modificationTime > std::numeric_limits<std::uint32_t>::max()
|
||||||
|
|| duration > std::numeric_limits<std::uint32_t>::max())
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// \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);
|
||||||
|
|
||||||
|
@ -435,12 +461,16 @@ TrackHeaderInfo Mp4Track::verifyPresentTrackHeader() const
|
||||||
|
|
||||||
// determine required size
|
// determine required size
|
||||||
info.requiredSize = m_tkhdAtom->dataSize() + 8;
|
info.requiredSize = m_tkhdAtom->dataSize() + 8;
|
||||||
// -> add 12 byte to size if update from version 0 to version 1 is required (which needs 12 byte more)
|
info.timings = computeTimings();
|
||||||
if ((info.version == 0)
|
info.timingsVersion = info.timings.requiredVersion();
|
||||||
&& (static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
|
if (info.version == 0) {
|
||||||
|| static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
|
info.writeVersion = info.timingsVersion;
|
||||||
|| static_cast<std::uint64_t>(m_duration.totalSeconds() * m_timeScale) > numeric_limits<std::uint32_t>::max())) {
|
// add 12 byte to size if update from version 0 to version 1 is required (which needs 12 byte more)
|
||||||
info.requiredSize += 12;
|
if (info.writeVersion != 0) {
|
||||||
|
info.requiredSize += 12;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info.writeVersion = info.version;
|
||||||
}
|
}
|
||||||
// -> add 8 byte to the size because it must be denoted using a 64-bit integer
|
// -> 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()) {
|
if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
|
||||||
|
@ -449,6 +479,18 @@ TrackHeaderInfo Mp4Track::verifyPresentTrackHeader() const
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Computes timing values for the track.
|
||||||
|
*/
|
||||||
|
Mp4Timings Mp4Track::computeTimings() const
|
||||||
|
{
|
||||||
|
auto timings = Mp4Timings();
|
||||||
|
timings.creationTime = static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds());
|
||||||
|
timings.modificationTime = static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds());
|
||||||
|
timings.duration = static_cast<std::uint64_t>(m_duration.totalTicks() * m_timeScale / TimeSpan::ticksPerSecond);
|
||||||
|
return timings;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \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
|
||||||
|
@ -1088,11 +1130,12 @@ std::uint64_t Mp4Track::requiredSize(Diagnostics &diag) const
|
||||||
{
|
{
|
||||||
CPP_UTILITIES_UNUSED(diag)
|
CPP_UTILITIES_UNUSED(diag)
|
||||||
|
|
||||||
|
const auto info = verifyPresentTrackHeader();
|
||||||
// add size of
|
// add size of
|
||||||
// ... trak header
|
// ... trak header
|
||||||
std::uint64_t size = 8;
|
std::uint64_t size = 8;
|
||||||
// ... tkhd atom (TODO: buffer TrackHeaderInfo in next major release)
|
// ... tkhd atom (TODO: buffer TrackHeaderInfo in next major release)
|
||||||
size += verifyPresentTrackHeader().requiredSize;
|
size += info.requiredSize;
|
||||||
// ... children beside tkhd and mdia
|
// ... children beside tkhd and mdia
|
||||||
for (Mp4Atom *trakChild = m_trakAtom->firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
|
for (Mp4Atom *trakChild = m_trakAtom->firstChild(); trakChild; trakChild = trakChild->nextSibling()) {
|
||||||
if (trakChild->id() == Mp4AtomIds::Media || trakChild->id() == Mp4AtomIds::TrackHeader) {
|
if (trakChild->id() == Mp4AtomIds::Media || trakChild->id() == Mp4AtomIds::TrackHeader) {
|
||||||
|
@ -1101,14 +1144,12 @@ std::uint64_t Mp4Track::requiredSize(Diagnostics &diag) const
|
||||||
size += trakChild->totalSize();
|
size += trakChild->totalSize();
|
||||||
}
|
}
|
||||||
// ... mdhd total size
|
// ... mdhd total size
|
||||||
if (static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
|
if (info.timingsVersion == 0) {
|
||||||
|| static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds()) > numeric_limits<std::uint32_t>::max()
|
// write version 0 where timing fields are 32-bit
|
||||||
|| 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;
|
size += 32;
|
||||||
|
} else {
|
||||||
|
// write version 1 where timing fields are 64-bit
|
||||||
|
size += 44;
|
||||||
}
|
}
|
||||||
// ... mdia header + hdlr total size + minf header
|
// ... mdia header + hdlr total size + minf header
|
||||||
size += 8 + (33 + m_name.size()) + 8;
|
size += 8 + (33 + m_name.size()) + 8;
|
||||||
|
@ -1192,18 +1233,8 @@ void Mp4Track::makeTrackHeader(Diagnostics &diag)
|
||||||
writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
|
writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// make version and flags
|
// make version and flags
|
||||||
writer().writeByte(version);
|
writer().writeByte(info.writeVersion);
|
||||||
std::uint32_t flags = 0;
|
std::uint32_t flags = 0;
|
||||||
if (isEnabled()) {
|
if (isEnabled()) {
|
||||||
flags |= 0x000001;
|
flags |= 0x000001;
|
||||||
|
@ -1217,21 +1248,21 @@ void Mp4Track::makeTrackHeader(Diagnostics &diag)
|
||||||
writer().writeUInt24BE(flags);
|
writer().writeUInt24BE(flags);
|
||||||
|
|
||||||
// make creation and modification time
|
// make creation and modification time
|
||||||
if (version != 0) {
|
if (info.writeVersion != 0) {
|
||||||
writer().writeUInt64BE(creationTime);
|
writer().writeUInt64BE(info.timings.creationTime);
|
||||||
writer().writeUInt64BE(modificationTime);
|
writer().writeUInt64BE(info.timings.modificationTime);
|
||||||
} else {
|
} else {
|
||||||
writer().writeUInt32BE(static_cast<std::uint32_t>(creationTime));
|
writer().writeUInt32BE(static_cast<std::uint32_t>(info.timings.creationTime));
|
||||||
writer().writeUInt32BE(static_cast<std::uint32_t>(modificationTime));
|
writer().writeUInt32BE(static_cast<std::uint32_t>(info.timings.modificationTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
// make track ID and duration
|
// make track ID and duration
|
||||||
writer().writeUInt32BE(static_cast<std::uint32_t>(m_id));
|
writer().writeUInt32BE(static_cast<std::uint32_t>(m_id));
|
||||||
writer().writeUInt32BE(0); // reserved
|
writer().writeUInt32BE(0); // reserved
|
||||||
if (version != 0) {
|
if (info.writeVersion != 0) {
|
||||||
writer().writeUInt64BE(duration);
|
writer().writeUInt64BE(info.timings.duration);
|
||||||
} else {
|
} else {
|
||||||
writer().writeUInt32BE(static_cast<std::uint32_t>(duration));
|
writer().writeUInt32BE(static_cast<std::uint32_t>(info.timings.duration));
|
||||||
}
|
}
|
||||||
writer().writeUInt32BE(0); // reserved
|
writer().writeUInt32BE(0); // reserved
|
||||||
writer().writeUInt32BE(0); // reserved
|
writer().writeUInt32BE(0); // reserved
|
||||||
|
@ -1270,29 +1301,24 @@ void Mp4Track::makeMedia(Diagnostics &diag)
|
||||||
writer().writeUInt32BE(0); // write size later
|
writer().writeUInt32BE(0); // write size later
|
||||||
writer().writeUInt32BE(Mp4AtomIds::Media);
|
writer().writeUInt32BE(Mp4AtomIds::Media);
|
||||||
// write mdhd atom
|
// write mdhd atom
|
||||||
const auto creationTime = static_cast<std::uint64_t>((m_creationTime - startDate).totalSeconds());
|
const auto timings = computeTimings();
|
||||||
const auto modificationTime = static_cast<std::uint64_t>((m_modificationTime - startDate).totalSeconds());
|
const auto timingsVersion = timings.requiredVersion();
|
||||||
const auto duration = static_cast<std::uint64_t>(m_duration.totalSeconds() * m_timeScale);
|
writer().writeUInt32BE(timingsVersion != 0 ? 44 : 32); // size
|
||||||
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
|
|
||||||
writer().writeUInt32BE(Mp4AtomIds::MediaHeader);
|
writer().writeUInt32BE(Mp4AtomIds::MediaHeader);
|
||||||
writer().writeByte(version); // version
|
writer().writeByte(timingsVersion); // version
|
||||||
writer().writeUInt24BE(0); // flags
|
writer().writeUInt24BE(0); // flags
|
||||||
if (version != 0) {
|
if (timingsVersion != 0) {
|
||||||
writer().writeUInt64BE(creationTime);
|
writer().writeUInt64BE(timings.creationTime);
|
||||||
writer().writeUInt64BE(modificationTime);
|
writer().writeUInt64BE(timings.modificationTime);
|
||||||
} else {
|
} else {
|
||||||
writer().writeUInt32BE(static_cast<std::uint32_t>(creationTime));
|
writer().writeUInt32BE(static_cast<std::uint32_t>(timings.creationTime));
|
||||||
writer().writeUInt32BE(static_cast<std::uint32_t>(modificationTime));
|
writer().writeUInt32BE(static_cast<std::uint32_t>(timings.modificationTime));
|
||||||
}
|
}
|
||||||
writer().writeUInt32BE(m_timeScale);
|
writer().writeUInt32BE(m_timeScale);
|
||||||
if (version != 0) {
|
if (timingsVersion != 0) {
|
||||||
writer().writeUInt64BE(duration);
|
writer().writeUInt64BE(timings.duration);
|
||||||
} else {
|
} else {
|
||||||
writer().writeUInt32BE(static_cast<std::uint32_t>(duration));
|
writer().writeUInt32BE(static_cast<std::uint32_t>(timings.duration));
|
||||||
}
|
}
|
||||||
// convert and write language
|
// convert and write language
|
||||||
const std::string &language = m_locale.abbreviatedName(LocaleFormat::ISO_639_2_T, LocaleFormat::Unknown);
|
const std::string &language = m_locale.abbreviatedName(LocaleFormat::ISO_639_2_T, LocaleFormat::Unknown);
|
||||||
|
|
|
@ -13,6 +13,7 @@ class Mpeg4Descriptor;
|
||||||
struct AvcConfiguration;
|
struct AvcConfiguration;
|
||||||
struct Av1Configuration;
|
struct Av1Configuration;
|
||||||
struct TrackHeaderInfo;
|
struct TrackHeaderInfo;
|
||||||
|
struct Mp4Timings;
|
||||||
|
|
||||||
class TAG_PARSER_EXPORT Mpeg4AudioSpecificConfig {
|
class TAG_PARSER_EXPORT Mpeg4AudioSpecificConfig {
|
||||||
public:
|
public:
|
||||||
|
@ -170,6 +171,7 @@ private:
|
||||||
void addChunkSizeEntries(
|
void addChunkSizeEntries(
|
||||||
std::vector<std::uint64_t> &chunkSizeTable, std::size_t count, std::size_t &sampleIndex, std::uint32_t sampleCount, Diagnostics &diag);
|
std::vector<std::uint64_t> &chunkSizeTable, std::size_t count, std::size_t &sampleIndex, std::uint32_t sampleCount, Diagnostics &diag);
|
||||||
TrackHeaderInfo verifyPresentTrackHeader() const;
|
TrackHeaderInfo verifyPresentTrackHeader() const;
|
||||||
|
Mp4Timings computeTimings() const;
|
||||||
|
|
||||||
Mp4Atom *m_trakAtom;
|
Mp4Atom *m_trakAtom;
|
||||||
Mp4Atom *m_tkhdAtom;
|
Mp4Atom *m_tkhdAtom;
|
||||||
|
|
Loading…
Reference in New Issue