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>
|
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;
|
2019-06-10 22:49:11 +02:00
|
|
|
using namespace CppUtilities;
|
2015-04-22 19:22:01 +02:00
|
|
|
|
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.
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t requiredSize;
|
2017-09-14 01:37:15 +02:00
|
|
|
/// \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.
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t version;
|
2017-09-14 01:37:15 +02:00
|
|
|
/// \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.
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t additionalDataOffset;
|
2017-09-14 01:37:15 +02:00
|
|
|
/// \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();
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
std::vector<std::uint64_t> 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();
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
vector<std::uint64_t> 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
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t 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;
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t actualChunkCount = chunkCount();
|
|
|
|
std::uint64_t 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);
|
2019-03-13 19:06:42 +01:00
|
|
|
actualChunkCount = static_cast<std::uint32_t>(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:
|
2019-03-13 19:06:42 +01:00
|
|
|
for (std::uint32_t i = 0; i < actualChunkCount; ++i) {
|
2015-04-22 19:22:01 +02:00
|
|
|
offsets.push_back(reader().readUInt32BE());
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 8:
|
2019-03-13 19:06:42 +01:00
|
|
|
for (std::uint32_t 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) {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t 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);
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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;
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t defaultSampleDuration = 0;
|
|
|
|
std::uint32_t 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)) {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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;
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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);
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
for (std::uint32_t i = 0; i < sampleCount; ++i) {
|
2018-03-07 01:17:50 +01:00
|
|
|
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.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t 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()) {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t 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;
|
2019-03-13 19:06:42 +01:00
|
|
|
return static_cast<std::uint64_t>(m_sampleSizes.front()) * count;
|
2015-04-22 19:22:01 +02:00
|
|
|
} 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
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Mp4Track::addChunkSizeEntries(
|
|
|
|
std::vector<std::uint64_t> &chunkSizeTable, size_t count, size_t &sampleIndex, std::uint32_t 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
|
2019-03-13 19:06:42 +01:00
|
|
|
switch (info.version = static_cast<std::uint8_t>(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)
|
2019-05-04 21:03:09 +02:00
|
|
|
&& (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.
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> 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
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t 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
|
2019-03-13 19:06:42 +01:00
|
|
|
vector<tuple<std::uint32_t, std::uint32_t, std::uint32_t>> 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
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t firstChunk = reader().readUInt32BE();
|
|
|
|
std::uint32_t samplesPerChunk = reader().readUInt32BE();
|
|
|
|
std::uint32_t sampleDescriptionIndex = reader().readUInt32BE();
|
2015-04-22 19:22:01 +02:00
|
|
|
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();
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
vector<std::uint64_t> 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
|
2019-03-13 19:06:42 +01:00
|
|
|
vector<std::uint64_t> 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;
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t samplesPerChunk = get<1>(*tableIterator);
|
2015-04-22 19:22:01 +02:00
|
|
|
// read the following entries
|
|
|
|
++tableIterator;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (const auto tableEnd = sampleToChunkTable.cend(); tableIterator != tableEnd; ++tableIterator) {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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.
|
|
|
|
* \sa mpeg4ElementaryStreamInfo()
|
|
|
|
*/
|
2018-03-07 01:17:50 +01:00
|
|
|
std::unique_ptr<Mpeg4ElementaryStreamInfo> Mp4Track::parseMpeg4ElementaryStreamInfo(
|
2019-06-10 22:49:11 +02:00
|
|
|
CppUtilities::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-05-04 21:03:09 +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
|
|
|
* \sa mpeg4ElementaryStreamInfo()
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
unique_ptr<Mpeg4AudioSpecificConfig> Mp4Track::parseAudioSpecificConfig(
|
|
|
|
istream &stream, std::uint64_t startOffset, std::uint64_t 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] {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t objType = bitReader.readBits<std::uint8_t>(5);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (objType == 31) {
|
2019-03-13 19:06:42 +01:00
|
|
|
objType = 32 + bitReader.readBits<std::uint8_t>(6);
|
2015-06-10 01:28:22 +02:00
|
|
|
}
|
|
|
|
return objType;
|
|
|
|
};
|
|
|
|
audioCfg->audioObjectType = getAudioObjectType();
|
|
|
|
// read sampling frequency
|
2019-03-13 19:06:42 +01:00
|
|
|
if ((audioCfg->sampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
|
|
|
|
audioCfg->sampleFrequency = bitReader.readBits<std::uint32_t>(24);
|
2015-06-10 01:28:22 +02:00
|
|
|
}
|
|
|
|
// read channel config
|
2019-03-13 19:06:42 +01:00
|
|
|
audioCfg->channelConfiguration = bitReader.readBits<std::uint8_t>(4);
|
2015-06-10 01:28:22 +02:00
|
|
|
// 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;
|
2019-03-13 19:06:42 +01:00
|
|
|
if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
|
|
|
|
audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
|
2015-06-10 01:28:22 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if ((audioCfg->audioObjectType = getAudioObjectType()) == ErBsac) {
|
2019-03-13 19:06:42 +01:00
|
|
|
audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(4);
|
2015-06-10 01:28:22 +02:00
|
|
|
}
|
|
|
|
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:
|
2019-03-13 19:06:42 +01:00
|
|
|
audioCfg->frameLengthFlag = bitReader.readBits<std::uint8_t>(1);
|
2018-03-07 01:17:50 +01:00
|
|
|
if ((audioCfg->dependsOnCoreCoder = bitReader.readBit())) {
|
2019-03-13 19:06:42 +01:00
|
|
|
audioCfg->coreCoderDelay = bitReader.readBits<std::uint8_t>(14);
|
2015-06-10 01:28:22 +02:00
|
|
|
}
|
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:
|
2019-03-13 19:06:42 +01:00
|
|
|
audioCfg->layerNr = bitReader.readBits<std::uint8_t>(3);
|
2015-06-10 01:28:22 +02:00
|
|
|
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:
|
2019-03-13 19:06:42 +01:00
|
|
|
audioCfg->numOfSubFrame = bitReader.readBits<std::uint8_t>(5);
|
|
|
|
audioCfg->layerLength = bitReader.readBits<std::uint16_t>(11);
|
2015-06-10 01:28:22 +02:00
|
|
|
break;
|
2018-03-07 01:17:50 +01:00
|
|
|
case ErAacLc:
|
|
|
|
case ErAacLtp:
|
|
|
|
case ErAacScalable:
|
|
|
|
case ErAacLd:
|
2019-03-13 19:06:42 +01:00
|
|
|
audioCfg->resilienceFlags = bitReader.readBits<std::uint8_t>(3);
|
2015-06-10 01:28:22 +02:00
|
|
|
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:
|
2019-03-13 19:06:42 +01:00
|
|
|
switch (audioCfg->epConfig = bitReader.readBits<std::uint8_t>(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) {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint16_t syncExtensionType = bitReader.readBits<std::uint16_t>(11);
|
2018-03-07 01:17:50 +01:00
|
|
|
if (syncExtensionType == 0x2B7) {
|
|
|
|
if ((audioCfg->extensionAudioObjectType = getAudioObjectType()) == Sbr) {
|
|
|
|
if ((audioCfg->sbrPresent = bitReader.readBit())) {
|
2019-03-13 19:06:42 +01:00
|
|
|
if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
|
|
|
|
audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
|
2015-06-10 01:28:22 +02:00
|
|
|
}
|
2018-03-07 01:17:50 +01:00
|
|
|
if (bitReader.bitsAvailable() >= 12) {
|
2019-03-13 19:06:42 +01:00
|
|
|
if ((syncExtensionType = bitReader.readBits<std::uint16_t>(11)) == 0x548) {
|
|
|
|
audioCfg->psPresent = bitReader.readBits<std::uint8_t>(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())) {
|
2019-03-13 19:06:42 +01:00
|
|
|
if ((audioCfg->extensionSampleFrequencyIndex = bitReader.readBits<std::uint8_t>(4)) == 0xF) {
|
|
|
|
audioCfg->extensionSampleFrequency = bitReader.readBits<std::uint32_t>(24);
|
2015-09-24 01:15:27 +02:00
|
|
|
}
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
audioCfg->extensionChannelConfiguration = bitReader.readBits<std::uint8_t>(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);
|
2019-03-13 19:06:42 +01:00
|
|
|
} catch (const std::ios_base::failure &) {
|
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
|
2019-03-13 19:06:42 +01:00
|
|
|
throw;
|
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.
|
|
|
|
* \sa mpeg4ElementaryStreamInfo()
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
std::unique_ptr<Mpeg4VideoSpecificConfig> Mp4Track::parseVideoSpecificConfig(
|
|
|
|
BinaryReader &reader, std::uint64_t startOffset, std::uint64_t 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;
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Mp4Track::updateChunkOffsets(const vector<std::int64_t> &oldMdatOffsets, const vector<std::int64_t> &newMdatOffsets)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
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: {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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) {
|
2019-03-13 19:06:42 +01:00
|
|
|
if (off > static_cast<std::uint64_t>(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: {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t 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) {
|
2019-03-13 19:06:42 +01:00
|
|
|
if (off > static_cast<std::uint64_t>(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".
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Mp4Track::updateChunkOffsets(const std::vector<std::uint64_t> &chunkOffsets)
|
2015-11-07 15:23:36 +01:00
|
|
|
{
|
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
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
void Mp4Track::updateChunkOffset(std::uint32_t chunkIndex, std::uint64_t offset)
|
2015-04-22 19:22:01 +02:00
|
|
|
{
|
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)
|
|
|
|
{
|
2019-06-12 20:40:45 +02:00
|
|
|
CPP_UTILITIES_UNUSED(av1Config)
|
|
|
|
CPP_UTILITIES_UNUSED(track)
|
2018-08-23 23:18:57 +02:00
|
|
|
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-06-12 20:40:45 +02:00
|
|
|
CPP_UTILITIES_UNUSED(diag)
|
2019-04-18 17:54:56 +02:00
|
|
|
|
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().
|
|
|
|
*/
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t Mp4Track::requiredSize(Diagnostics &diag) const
|
2017-05-28 21:01:16 +02:00
|
|
|
{
|
2019-06-12 20:40:45 +02:00
|
|
|
CPP_UTILITIES_UNUSED(diag)
|
2019-04-17 17:51:04 +02:00
|
|
|
|
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()
|
2019-05-04 21:03:09 +02:00
|
|
|
|| 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()) {
|
2019-04-18 17:54:56 +02:00
|
|
|
// write version 1 where those fields are 64-bit
|
2019-05-04 21:03:09 +02:00
|
|
|
size += 44;
|
2019-04-18 17:54:56 +02:00
|
|
|
} 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;
|
2019-12-30 22:54:11 +01:00
|
|
|
// ... minf children
|
2017-05-28 21:01:16 +02:00
|
|
|
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
|
2019-03-13 19:06:42 +01:00
|
|
|
if (info.requiredSize > numeric_limits<std::uint32_t>::max()) {
|
2017-09-14 01:37:15 +02:00
|
|
|
writer().writeUInt32BE(1);
|
|
|
|
writer().writeUInt32BE(Mp4AtomIds::TrackHeader);
|
|
|
|
writer().writeUInt64BE(info.requiredSize);
|
|
|
|
} else {
|
2019-03-13 19:06:42 +01:00
|
|
|
writer().writeUInt32BE(static_cast<std::uint32_t>(info.requiredSize));
|
2017-09-14 01:37:15 +02:00
|
|
|
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);
|
2019-05-04 21:03:09 +02:00
|
|
|
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;
|
2019-04-19 21:12:35 +02:00
|
|
|
|
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;
|
2020-12-14 20:27:54 +01:00
|
|
|
if (isEnabled()) {
|
2015-04-22 19:22:01 +02:00
|
|
|
flags |= 0x000001;
|
|
|
|
}
|
2020-12-14 20:27:54 +01:00
|
|
|
if (m_flags & TrackFlags::UsedInPresentation) {
|
2015-04-22 19:22:01 +02:00
|
|
|
flags |= 0x000002;
|
|
|
|
}
|
2020-12-14 20:27:54 +01:00
|
|
|
if (m_flags & TrackFlags::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
|
2019-03-13 19:06:42 +01:00
|
|
|
writer().writeUInt32BE(static_cast<std::uint32_t>(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
|
2019-03-13 19:06:42 +01:00
|
|
|
for (const std::int32_t 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);
|
2019-05-04 21:03:09 +02:00
|
|
|
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;
|
2019-04-18 17:54:56 +02:00
|
|
|
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
|
2020-12-13 18:37:15 +01:00
|
|
|
const std::string &language = m_locale.abbreviatedName(LocaleFormat::ISO_639_2_T, LocaleFormat::Unknown);
|
|
|
|
std::uint16_t codedLanguage = 0;
|
2018-03-07 01:17:50 +01:00
|
|
|
for (size_t charIndex = 0; charIndex != 3; ++charIndex) {
|
2020-12-13 18:37:15 +01:00
|
|
|
const char langChar = charIndex < language.size() ? language[charIndex] : 0;
|
2018-03-07 01:17:50 +01:00
|
|
|
if (langChar >= 'a' && langChar <= 'z') {
|
2020-12-13 18:37:15 +01:00
|
|
|
codedLanguage |= static_cast<std::uint16_t>(langChar - 0x60) << (0xA - charIndex * 0x5);
|
2019-04-21 17:43:21 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle invalid characters
|
2020-12-13 18:37:15 +01:00
|
|
|
if (language.empty()) {
|
2019-04-21 17:43:21 +02:00
|
|
|
// preserve empty language field
|
2020-12-13 18:37:15 +01:00
|
|
|
codedLanguage = 0;
|
2015-04-22 19:22:01 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-12-13 18:37:15 +01:00
|
|
|
diag.emplace_back(
|
|
|
|
DiagLevel::Warning, "Assigned language \"" % language + "\" is of an invalid format. Setting language to undefined.", "making mdhd atom");
|
|
|
|
codedLanguage = 0x55C4; // und(efined)
|
2019-04-21 17:43:21 +02:00
|
|
|
break;
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
2020-12-13 18:37:15 +01:00
|
|
|
if (language.size() > 3) {
|
2018-03-07 01:17:50 +01:00
|
|
|
diag.emplace_back(
|
2020-12-13 18:37:15 +01:00
|
|
|
DiagLevel::Warning, "Assigned language \"" % language + "\" is longer than 3 byte and hence will be truncated.", "making mdhd atom");
|
2017-06-17 00:32:38 +02:00
|
|
|
}
|
2020-12-13 18:37:15 +01:00
|
|
|
writer().writeUInt16BE(codedLanguage);
|
2015-04-22 19:22:01 +02:00
|
|
|
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();
|
2020-12-14 20:27:54 +01:00
|
|
|
modFlagEnum(m_flags, TrackFlags::Enabled, flags & 0x000001);
|
|
|
|
modFlagEnum(m_flags, TrackFlags::UsedInPresentation, flags & 0x000002);
|
|
|
|
modFlagEnum(m_flags, TrackFlags::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();
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint16_t 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),
|
|
|
|
};
|
2020-12-13 18:37:15 +01:00
|
|
|
m_locale.emplace_back(std::string(buff, 3), LocaleFormat::ISO_639_2_T);
|
2016-03-14 21:56:27 +01:00
|
|
|
} else {
|
2020-12-13 18:37:15 +01:00
|
|
|
m_locale.clear();
|
2016-03-14 21:56:27 +01:00
|
|
|
}
|
|
|
|
|
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);
|
2019-09-25 21:52:44 +02:00
|
|
|
|
2015-06-07 00:18:28 +02:00
|
|
|
// parse FOURCC
|
2019-03-13 19:06:42 +01:00
|
|
|
m_formatId = interpretIntegerAsString<std::uint32_t>(codecConfigContainerAtom->id());
|
2015-06-12 02:35:50 +02:00
|
|
|
m_format = FourccIds::fourccToMediaFormat(codecConfigContainerAtom->id());
|
2019-09-25 21:52:44 +02:00
|
|
|
|
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:
|
2019-09-25 21:52:44 +02:00
|
|
|
case FourccIds::Flac:
|
|
|
|
case FourccIds::Opus:
|
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:
|
2019-09-25 22:05:25 +02:00
|
|
|
case FourccIds::Vp9_2:
|
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());
|
2019-03-13 19:06:42 +01:00
|
|
|
m_resolution.setWidth(static_cast<std::uint32_t>(reader.readFixed16BE()));
|
|
|
|
m_resolution.setHeight(static_cast<std::uint32_t>(reader.readFixed16BE()));
|
2015-07-07 03:01:48 +02:00
|
|
|
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;
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t 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;
|
2019-03-13 19:06:42 +01:00
|
|
|
const auto calculatedSampleSizeTableSize = static_cast<std::uint64_t>(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);
|
2019-03-13 19:06:42 +01:00
|
|
|
actualSampleCount = static_cast<std::uint64_t>(floor(static_cast<double>(actualSampleSizeTableSize) / (0.125 * fieldSize)));
|
2015-04-22 19:22:01 +02:00
|
|
|
}
|
|
|
|
m_sampleSizes.reserve(actualSampleCount);
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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) {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint8_t val = reader.readByte();
|
2015-04-22 19:22:01 +02:00
|
|
|
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
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint64_t 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);
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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;
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t defaultSampleDuration = 0;
|
|
|
|
std::uint32_t 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)) {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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;
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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);
|
|
|
|
}
|
2019-03-13 19:06:42 +01:00
|
|
|
for (std::uint32_t i = 0; i < sampleCount; ++i) {
|
2018-03-07 01:17:50 +01:00
|
|
|
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) {
|
2019-03-13 19:06:42 +01:00
|
|
|
std::uint32_t 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
|