658 lines
29 KiB
C++
658 lines
29 KiB
C++
#include "./matroskatrack.h"
|
|
#include "./ebmlelement.h"
|
|
#include "./matroskacontainer.h"
|
|
#include "./matroskaid.h"
|
|
#include "./matroskatag.h"
|
|
|
|
#include "../avi/bitmapinfoheader.h"
|
|
|
|
#include "../wav/waveaudiostream.h"
|
|
|
|
#include "../avc/avcconfiguration.h"
|
|
|
|
#include "../mp4/mp4ids.h"
|
|
#include "../mp4/mp4track.h"
|
|
|
|
#include "../exceptions.h"
|
|
#include "../mediaformat.h"
|
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
|
|
|
using namespace std;
|
|
using namespace CppUtilities;
|
|
|
|
namespace TagParser {
|
|
|
|
/*!
|
|
* \class TagParser::MatroskaTrack
|
|
* \brief Implementation of TagParser::AbstractTrack for the Matroska container.
|
|
*/
|
|
|
|
/*!
|
|
* \brief Constructs a new track for the specified \a trackElement.
|
|
*
|
|
* Each track element (ID: MatroskaId::TrackEntry) holds header information
|
|
* for one track in the Matroska file.
|
|
*/
|
|
MatroskaTrack::MatroskaTrack(EbmlElement &trackElement)
|
|
: AbstractTrack(trackElement.stream(), trackElement.startOffset())
|
|
, m_trackElement(&trackElement)
|
|
{
|
|
}
|
|
|
|
/*!
|
|
* \brief Destroys the track.
|
|
*/
|
|
MatroskaTrack::~MatroskaTrack()
|
|
{
|
|
}
|
|
|
|
TrackType MatroskaTrack::type() const
|
|
{
|
|
return TrackType::MatroskaTrack;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the MediaFormat for the specified Matroska codec ID.
|
|
* \todo Use an std::unordered_map here.
|
|
*/
|
|
MediaFormat MatroskaTrack::codecIdToMediaFormat(const string &codecId)
|
|
{
|
|
auto parts = splitString<vector<string>>(codecId, "/", EmptyPartsTreat::Keep, 3);
|
|
parts.resize(3);
|
|
const auto &part1 = parts[0], &part2 = parts[1], &part3 = parts[2];
|
|
MediaFormat fmt;
|
|
if (part1 == "V_MS" && part2 == "VFW" && part3 == "FOURCC") {
|
|
fmt.general = GeneralMediaFormat::MicrosoftVideoCodecManager;
|
|
} else if (part1 == "V_UNCOMPRESSED") {
|
|
fmt.general = GeneralMediaFormat::UncompressedVideoFrames;
|
|
} else if (part1 == "V_MPEG4") {
|
|
fmt.general = GeneralMediaFormat::Mpeg4Video;
|
|
if (part2 == "ISO") {
|
|
if (part3 == "SP") {
|
|
fmt.sub = SubFormats::Mpeg4SimpleProfile1;
|
|
} else if (part3 == "ASP") {
|
|
fmt.sub = SubFormats::Mpeg4AdvancedSimpleProfile1;
|
|
} else if (part3 == "AVC") {
|
|
fmt.general = GeneralMediaFormat::Avc;
|
|
}
|
|
} else if (part2 == "MS" && part3 == "V3") {
|
|
fmt.sub = SubFormats::Mpeg4SimpleProfile1;
|
|
}
|
|
} else if (part1 == "V_MPEG1") {
|
|
fmt.general = GeneralMediaFormat::Mpeg1Video;
|
|
} else if (part1 == "V_MPEG2") {
|
|
fmt.general = GeneralMediaFormat::Mpeg2Video;
|
|
} else if (part1 == "V_REAL") {
|
|
fmt.general = GeneralMediaFormat::RealVideo;
|
|
} else if (part1 == "V_QUICKTIME") {
|
|
fmt.general = GeneralMediaFormat::QuicktimeVideo;
|
|
} else if (part1 == "V_THEORA") {
|
|
fmt.general = GeneralMediaFormat::Theora;
|
|
} else if (part1 == "V_PRORES") {
|
|
fmt.general = GeneralMediaFormat::ProRes;
|
|
} else if (part1 == "V_VP8") {
|
|
fmt.general = GeneralMediaFormat::Vp8;
|
|
} else if (part1 == "V_VP9") {
|
|
fmt.general = GeneralMediaFormat::Vp9;
|
|
} else if (part1 == "V_AV1") {
|
|
fmt.general = GeneralMediaFormat::Av1;
|
|
} else if (part1 == "A_MPEG") {
|
|
fmt.general = GeneralMediaFormat::Mpeg1Audio;
|
|
if (part2 == "L1") {
|
|
fmt.sub = SubFormats::Mpeg1Layer1;
|
|
} else if (part2 == "L2") {
|
|
fmt.sub = SubFormats::Mpeg1Layer2;
|
|
} else if (part2 == "L3") {
|
|
fmt.sub = SubFormats::Mpeg1Layer3;
|
|
}
|
|
} else if (part1 == "V_MPEGH" && part2 == "ISO" && part3 == "HEVC") {
|
|
fmt.general = GeneralMediaFormat::Hevc;
|
|
} else if (part1 == "A_PCM") {
|
|
fmt.general = GeneralMediaFormat::Pcm;
|
|
if (part2 == "INT") {
|
|
if (part3 == "BIG") {
|
|
fmt.sub = SubFormats::PcmIntBe;
|
|
} else if (part3 == "LIT") {
|
|
fmt.sub = SubFormats::PcmIntLe;
|
|
}
|
|
} else if (part2 == "FLOAT" && part3 == "IEEE") {
|
|
fmt.sub = SubFormats::PcmFloatIeee;
|
|
}
|
|
} else if (part1 == "A_MPC") {
|
|
fmt.general = GeneralMediaFormat::Mpc;
|
|
} else if (part1 == "A_AC3") {
|
|
fmt.general = GeneralMediaFormat::Ac3;
|
|
} else if (part1 == "A_EAC3") {
|
|
fmt.general = GeneralMediaFormat::EAc3;
|
|
} else if (part1 == "A_ALAC") {
|
|
fmt.general = GeneralMediaFormat::Alac;
|
|
} else if (part1 == "A_DTS") {
|
|
fmt.general = GeneralMediaFormat::Dts;
|
|
if (part2 == "EXPRESS") {
|
|
fmt.sub = SubFormats::DtsExpress;
|
|
} else if (part2 == "LOSSLESS") {
|
|
fmt.sub = SubFormats::DtsLossless;
|
|
}
|
|
} else if (part1 == "A_VORBIS") {
|
|
fmt.general = GeneralMediaFormat::Vorbis;
|
|
} else if (part1 == "A_FLAC") {
|
|
fmt.general = GeneralMediaFormat::Flac;
|
|
} else if (part1 == "A_OPUS") {
|
|
fmt.general = GeneralMediaFormat::Opus;
|
|
} else if (part1 == "A_REAL") {
|
|
fmt.general = GeneralMediaFormat::RealAudio;
|
|
} else if (part1 == "A_MS" && part2 == "ACM") {
|
|
fmt.general = GeneralMediaFormat::MicrosoftAudioCodecManager;
|
|
} else if (part1 == "A_AAC") {
|
|
fmt.general = GeneralMediaFormat::Aac;
|
|
if (part2 == "MPEG2") {
|
|
if (part3 == "MAIN") {
|
|
fmt.sub = SubFormats::AacMpeg2MainProfile;
|
|
} else if (part3 == "LC") {
|
|
fmt.sub = SubFormats::AacMpeg2LowComplexityProfile;
|
|
} else if (part3 == "SBR") {
|
|
fmt.sub = SubFormats::AacMpeg2LowComplexityProfile;
|
|
fmt.extension = ExtensionFormats::SpectralBandReplication;
|
|
} else if (part3 == "SSR") {
|
|
fmt.sub = SubFormats::AacMpeg2ScalableSamplingRateProfile;
|
|
}
|
|
} else if (part2 == "MPEG4") {
|
|
if (part3 == "MAIN") {
|
|
fmt.sub = SubFormats::AacMpeg4MainProfile;
|
|
} else if (part3 == "LC") {
|
|
fmt.sub = SubFormats::AacMpeg4LowComplexityProfile;
|
|
} else if (part3 == "SBR") {
|
|
fmt.sub = SubFormats::AacMpeg4LowComplexityProfile;
|
|
fmt.extension = ExtensionFormats::SpectralBandReplication;
|
|
} else if (part3 == "SSR") {
|
|
fmt.sub = SubFormats::AacMpeg4ScalableSamplingRateProfile;
|
|
} else if (part3 == "LTP") {
|
|
fmt.sub = SubFormats::AacMpeg4LongTermPrediction;
|
|
}
|
|
}
|
|
} else if (part1 == "A_QUICKTIME") {
|
|
fmt.general = GeneralMediaFormat::QuicktimeAudio;
|
|
} else if (part1 == "A_TTA1") {
|
|
fmt.general = GeneralMediaFormat::Tta;
|
|
} else if (part1 == "A_WAVPACK4") {
|
|
fmt.general = GeneralMediaFormat::WavPack;
|
|
} else if (part1 == "S_TEXT") {
|
|
fmt.general = GeneralMediaFormat::TextSubtitle;
|
|
if (part2 == "UTF8") {
|
|
fmt.sub = SubFormats::PlainUtf8Subtitle;
|
|
} else if (part2 == "SSA") {
|
|
fmt.sub = SubFormats::SubStationAlpha;
|
|
} else if (part2 == "ASS") {
|
|
fmt.sub = SubFormats::AdvancedSubStationAlpha;
|
|
} else if (part2 == "USF") {
|
|
fmt.sub = SubFormats::UniversalSubtitleFormat;
|
|
} else if (part2 == "WEBVTT") {
|
|
fmt.sub = SubFormats::WebVideoTextTracksFormat;
|
|
}
|
|
} else if (part1 == "S_IMAGE") {
|
|
fmt.general = GeneralMediaFormat::ImageSubtitle;
|
|
if (part2 == "BMP") {
|
|
fmt.sub = SubFormats::ImgSubBmp;
|
|
}
|
|
} else if (part1 == "S_VOBSUB") {
|
|
fmt.general = GeneralMediaFormat::VobSub;
|
|
} else if (part1 == "S_KATE") {
|
|
fmt.general = GeneralMediaFormat::OggKate;
|
|
} else if (part1 == "B_VOBBTN") {
|
|
fmt.general = GeneralMediaFormat::VobBtn;
|
|
} else if (part1 == "S_DVBSUB") {
|
|
fmt.general = GeneralMediaFormat::DvbSub;
|
|
} else if (part1 == "V_MSWMV") {
|
|
fmt.general = GeneralMediaFormat::Vc1;
|
|
}
|
|
return fmt;
|
|
}
|
|
|
|
/// \cond
|
|
|
|
template <typename PropertyType, typename ConversionFunction>
|
|
void MatroskaTrack::assignPropertyFromTagValue(const std::unique_ptr<MatroskaTag> &tag, std::string_view fieldId, PropertyType &property,
|
|
const ConversionFunction &conversionFunction, Diagnostics &diag)
|
|
{
|
|
const TagValue &value = tag->value(std::string(fieldId));
|
|
if (!value.isEmpty()) {
|
|
try {
|
|
property = conversionFunction(value);
|
|
} catch (const ConversionException &) {
|
|
string message;
|
|
try {
|
|
message = argsToString("Ignoring invalid value \"", value.toString(TagTextEncoding::Utf8), "\" of \"", fieldId, '\"', '.');
|
|
} catch (const ConversionException &) {
|
|
message = argsToString("Ignoring invalid value of \"", fieldId, '\"', '.');
|
|
}
|
|
diag.emplace_back(DiagLevel::Warning, message, argsToString("reading track statatistic from \"", tag->toString(), '\"'));
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename NumberType, Traits::EnableIf<std::is_integral<NumberType>> * = nullptr> NumberType tagValueToNumber(const TagValue &tagValue)
|
|
{
|
|
// optimization for Latin1/UTF-8 strings
|
|
if (tagValue.type() == TagDataType::Text) {
|
|
switch (tagValue.dataEncoding()) {
|
|
case TagTextEncoding::Latin1:
|
|
case TagTextEncoding::Utf8:
|
|
return bufferToNumber<NumberType>(tagValue.dataPointer(), tagValue.dataSize());
|
|
default:;
|
|
}
|
|
}
|
|
// generic conversion
|
|
return stringToNumber<NumberType>(tagValue.toString(TagTextEncoding::Utf8));
|
|
}
|
|
|
|
template <typename NumberType, Traits::EnableIf<std::is_floating_point<NumberType>> * = nullptr>
|
|
NumberType tagValueToBitrate(const TagValue &tagValue)
|
|
{
|
|
return stringToNumber<NumberType>(tagValue.toString(TagTextEncoding::Utf8)) / 1000;
|
|
}
|
|
|
|
/// \endcond
|
|
|
|
/*!
|
|
* \brief Reads track-specific statistics from the specified \a tags.
|
|
* \remarks
|
|
* - Those statistics are generated might be generated by some Muxers, eg. mkvmerge 7.0.0 or newer.
|
|
* - Only tags targeting the track are considered. Hence the track ID must have been determined
|
|
* before (either by calling parseHeader() or setId()).
|
|
* \sa https://github.com/mbunkus/mkvtoolnix/wiki/Automatic-tag-generation for list of track-specific
|
|
* tag fields written by mkvmerge
|
|
*/
|
|
void MatroskaTrack::readStatisticsFromTags(const std::vector<std::unique_ptr<MatroskaTag>> &tags, Diagnostics &diag)
|
|
{
|
|
using namespace std::placeholders;
|
|
using namespace MatroskaTagIds::TrackSpecific;
|
|
for (const auto &tag : tags) {
|
|
const TagTarget &target = tag->target();
|
|
if (find(target.tracks().cbegin(), target.tracks().cend(), id()) == target.tracks().cend()) {
|
|
continue;
|
|
}
|
|
assignPropertyFromTagValue(tag, numberOfBytes(), m_size, &tagValueToNumber<std::uint64_t>, diag);
|
|
assignPropertyFromTagValue(tag, numberOfFrames(), m_sampleCount, &tagValueToNumber<std::uint64_t>, diag);
|
|
assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::duration(), m_duration, bind(&TagValue::toTimeSpan, _1), diag);
|
|
assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::bitrate(), m_bitrate, &tagValueToBitrate<double>, diag);
|
|
assignPropertyFromTagValue(tag, writingDate(), m_modificationTime, bind(&TagValue::toDateTime, _1), diag);
|
|
if (m_creationTime.isNull()) {
|
|
m_creationTime = m_modificationTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MatroskaTrack::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
|
|
{
|
|
CPP_UTILITIES_UNUSED(progress)
|
|
|
|
static const string context("parsing header of Matroska track");
|
|
try {
|
|
m_trackElement->parse(diag);
|
|
} catch (const Failure &) {
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to parse track element.", context);
|
|
throw;
|
|
}
|
|
// read information about the track from the children of the track entry element
|
|
auto hasIsoLanguage = false;
|
|
for (EbmlElement *trackInfoElement = m_trackElement->firstChild(), *subElement = nullptr; trackInfoElement;
|
|
trackInfoElement = trackInfoElement->nextSibling()) {
|
|
try {
|
|
trackInfoElement->parse(diag);
|
|
} catch (const Failure &) {
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to parse track information element.", context);
|
|
break;
|
|
}
|
|
std::uint64_t defaultDuration = 0;
|
|
switch (trackInfoElement->id()) {
|
|
case MatroskaIds::TrackType:
|
|
switch (trackInfoElement->readUInteger()) {
|
|
case MatroskaTrackType::Video:
|
|
m_mediaType = MediaType::Video;
|
|
break;
|
|
case MatroskaTrackType::Audio:
|
|
m_mediaType = MediaType::Audio;
|
|
break;
|
|
case MatroskaTrackType::Subtitle:
|
|
m_mediaType = MediaType::Text;
|
|
break;
|
|
case MatroskaTrackType::Buttons:
|
|
m_mediaType = MediaType::Buttons;
|
|
break;
|
|
case MatroskaTrackType::Control:
|
|
m_mediaType = MediaType::Control;
|
|
break;
|
|
default:
|
|
m_mediaType = MediaType::Unknown;
|
|
}
|
|
break;
|
|
case MatroskaIds::TrackVideo:
|
|
for (subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
|
|
try {
|
|
subElement->parse(diag);
|
|
} catch (const Failure &) {
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to parse video track element.", context);
|
|
break;
|
|
}
|
|
switch (subElement->id()) {
|
|
case MatroskaIds::DisplayWidth:
|
|
m_displaySize.setWidth(static_cast<std::uint32_t>(subElement->readUInteger()));
|
|
break;
|
|
case MatroskaIds::DisplayHeight:
|
|
m_displaySize.setHeight(static_cast<std::uint32_t>(subElement->readUInteger()));
|
|
break;
|
|
case MatroskaIds::PixelWidth:
|
|
m_pixelSize.setWidth(static_cast<std::uint32_t>(subElement->readUInteger()));
|
|
break;
|
|
case MatroskaIds::PixelHeight:
|
|
m_pixelSize.setHeight(static_cast<std::uint32_t>(subElement->readUInteger()));
|
|
break;
|
|
case MatroskaIds::PixelCropTop:
|
|
m_cropping.setTop(static_cast<std::uint32_t>(subElement->readUInteger()));
|
|
break;
|
|
case MatroskaIds::PixelCropLeft:
|
|
m_cropping.setLeft(static_cast<std::uint32_t>(subElement->readUInteger()));
|
|
break;
|
|
case MatroskaIds::PixelCropBottom:
|
|
m_cropping.setBottom(static_cast<std::uint32_t>(subElement->readUInteger()));
|
|
break;
|
|
case MatroskaIds::PixelCropRight:
|
|
m_cropping.setRight(static_cast<std::uint32_t>(subElement->readUInteger()));
|
|
break;
|
|
case MatroskaIds::FrameRate:
|
|
m_fps = static_cast<std::uint32_t>(subElement->readFloat());
|
|
break;
|
|
case MatroskaIds::FlagInterlaced:
|
|
modFlagEnum(m_flags, TrackFlags::Interlaced, subElement->readUInteger());
|
|
break;
|
|
case MatroskaIds::ColorSpace:
|
|
m_colorSpace = static_cast<std::uint32_t>(subElement->readUInteger());
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
break;
|
|
case MatroskaIds::TrackAudio:
|
|
for (subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
|
|
try {
|
|
subElement->parse(diag);
|
|
} catch (const Failure &) {
|
|
diag.emplace_back(DiagLevel::Critical, "Unable to parse audio track element.", context);
|
|
break;
|
|
}
|
|
switch (subElement->id()) {
|
|
case MatroskaIds::BitDepth:
|
|
m_bitsPerSample = static_cast<std::uint16_t>(subElement->readUInteger());
|
|
break;
|
|
case MatroskaIds::Channels:
|
|
m_channelCount = static_cast<std::uint16_t>(subElement->readUInteger());
|
|
break;
|
|
case MatroskaIds::SamplingFrequency:
|
|
if (!m_samplingFrequency) {
|
|
m_samplingFrequency = static_cast<std::uint32_t>(subElement->readFloat());
|
|
}
|
|
break;
|
|
case MatroskaIds::OutputSamplingFrequency:
|
|
if (!m_extensionSamplingFrequency) {
|
|
m_extensionSamplingFrequency = static_cast<std::uint32_t>(subElement->readFloat());
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
break;
|
|
case MatroskaIds::TrackNumber:
|
|
m_trackNumber = static_cast<std::uint32_t>(trackInfoElement->readUInteger());
|
|
break;
|
|
case MatroskaIds::TrackUID:
|
|
m_id = trackInfoElement->readUInteger();
|
|
break;
|
|
case MatroskaIds::TrackName:
|
|
m_name = trackInfoElement->readString();
|
|
break;
|
|
case MatroskaIds::TrackLanguage:
|
|
m_locale.emplace_back(trackInfoElement->readString(), LocaleFormat::ISO_639_2_B);
|
|
hasIsoLanguage = true;
|
|
break;
|
|
case MatroskaIds::TrackLanguageIETF:
|
|
m_locale.emplace_back(trackInfoElement->readString(), LocaleFormat::BCP_47);
|
|
break;
|
|
case MatroskaIds::CodecID:
|
|
m_format = codecIdToMediaFormat(m_formatId = trackInfoElement->readString());
|
|
break;
|
|
case MatroskaIds::CodecName:
|
|
m_formatName = trackInfoElement->readString();
|
|
break;
|
|
case MatroskaIds::CodecDelay:
|
|
break; // TODO
|
|
case MatroskaIds::TrackFlagEnabled:
|
|
modFlagEnum(m_flags, TrackFlags::Enabled, trackInfoElement->readUInteger());
|
|
break;
|
|
case MatroskaIds::TrackFlagDefault:
|
|
modFlagEnum(m_flags, TrackFlags::Default, trackInfoElement->readUInteger());
|
|
break;
|
|
case MatroskaIds::TrackFlagForced:
|
|
modFlagEnum(m_flags, TrackFlags::Forced, trackInfoElement->readUInteger());
|
|
break;
|
|
case MatroskaIds::TrackFlagLacing:
|
|
modFlagEnum(m_flags, TrackFlags::Lacing, trackInfoElement->readUInteger());
|
|
break;
|
|
case MatroskaIds::DefaultDuration:
|
|
defaultDuration = trackInfoElement->readUInteger();
|
|
break;
|
|
default:;
|
|
}
|
|
switch (m_mediaType) {
|
|
case MediaType::Video:
|
|
if (!m_fps && defaultDuration) {
|
|
m_fps = static_cast<std::uint32_t>(1000000000.0 / static_cast<double>(defaultDuration));
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
|
|
// read further information from the CodecPrivate element for some codecs
|
|
EbmlElement *codecPrivateElement;
|
|
switch (m_format.general) {
|
|
case GeneralMediaFormat::MicrosoftVideoCodecManager:
|
|
if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
|
|
// parse bitmap info header to determine actual format
|
|
if (codecPrivateElement->dataSize() >= 0x28) {
|
|
m_istream->seekg(static_cast<streamoff>(codecPrivateElement->dataOffset()));
|
|
BitmapInfoHeader bitmapInfoHeader;
|
|
bitmapInfoHeader.parse(reader());
|
|
m_formatId.reserve(m_formatId.size() + 7);
|
|
m_formatId += " \"";
|
|
m_formatId += interpretIntegerAsString(bitmapInfoHeader.compression);
|
|
m_formatId += "\"";
|
|
m_format += FourccIds::fourccToMediaFormat(bitmapInfoHeader.compression);
|
|
} else {
|
|
diag.emplace_back(DiagLevel::Critical, "BITMAPINFOHEADER structure (in \"CodecPrivate\"-element) is truncated.", context);
|
|
}
|
|
}
|
|
break;
|
|
case GeneralMediaFormat::MicrosoftAudioCodecManager:
|
|
if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
|
|
// parse WAVE header to determine actual format
|
|
m_istream->seekg(static_cast<streamoff>(codecPrivateElement->dataOffset()));
|
|
WaveFormatHeader waveFormatHeader;
|
|
waveFormatHeader.parse(reader(), codecPrivateElement->dataSize(), diag);
|
|
WaveAudioStream::addInfo(waveFormatHeader, *this);
|
|
}
|
|
break;
|
|
case GeneralMediaFormat::Aac:
|
|
if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
|
|
auto audioSpecificConfig
|
|
= Mp4Track::parseAudioSpecificConfig(*m_istream, codecPrivateElement->dataOffset(), codecPrivateElement->dataSize(), diag);
|
|
m_format += Mpeg4AudioObjectIds::idToMediaFormat(
|
|
audioSpecificConfig->audioObjectType, audioSpecificConfig->sbrPresent, audioSpecificConfig->psPresent);
|
|
if (audioSpecificConfig->sampleFrequencyIndex == 0xF) {
|
|
//m_samplingFrequency = audioSpecificConfig->sampleFrequency;
|
|
} else if (audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
|
|
//m_samplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->sampleFrequencyIndex];
|
|
} else {
|
|
diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid sample frequency index.", context);
|
|
}
|
|
if (audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
|
|
//m_extensionSamplingFrequency = audioSpecificConfig->extensionSampleFrequency;
|
|
} else if (audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
|
|
//m_extensionSamplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->extensionSampleFrequencyIndex];
|
|
} else {
|
|
diag.emplace_back(DiagLevel::Warning, "Audio specific config has invalid extension sample frequency index.", context);
|
|
}
|
|
m_channelConfig = audioSpecificConfig->channelConfiguration;
|
|
m_extensionChannelConfig = audioSpecificConfig->extensionChannelConfiguration;
|
|
}
|
|
break;
|
|
case GeneralMediaFormat::Avc:
|
|
if ((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate, diag))) {
|
|
auto avcConfig = make_unique<TagParser::AvcConfiguration>();
|
|
try {
|
|
m_istream->seekg(static_cast<streamoff>(codecPrivateElement->dataOffset()));
|
|
avcConfig->parse(m_reader, codecPrivateElement->dataSize(), diag);
|
|
Mp4Track::addInfo(*avcConfig, *this);
|
|
} catch (const TruncatedDataException &) {
|
|
diag.emplace_back(DiagLevel::Critical, "AVC configuration is truncated.", context);
|
|
} catch (const Failure &) {
|
|
diag.emplace_back(DiagLevel::Critical, "AVC configuration is invalid.", context);
|
|
}
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
|
|
// parse format name for unknown formats
|
|
if (m_format.general == GeneralMediaFormat::Unknown && m_formatName.empty()) {
|
|
if (startsWith<string>(m_formatId, "V_") || startsWith<string>(m_formatId, "A_") || startsWith<string>(m_formatId, "S_")) {
|
|
m_formatName = m_formatId.substr(2);
|
|
} else {
|
|
m_formatName = m_formatId;
|
|
}
|
|
m_formatName.append(" (unknown)");
|
|
}
|
|
|
|
// use pixel size as display size if display size not specified
|
|
if (!m_displaySize.width()) {
|
|
m_displaySize.setWidth(m_pixelSize.width());
|
|
}
|
|
if (!m_displaySize.height()) {
|
|
m_displaySize.setHeight(m_pixelSize.height());
|
|
}
|
|
|
|
// set English if no ISO language has been specified (it is the default value of MatroskaIds::TrackLanguage)
|
|
if (!hasIsoLanguage) {
|
|
m_locale.emplace_back("eng"sv, LocaleFormat::ISO_639_2_B);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \class TagParser::MatroskaTrackHeaderMaker
|
|
* \brief The MatroskaTrackHeaderMaker class helps writing Matroska "TrackEntry"-elements storing track header information.
|
|
*
|
|
* An instance can be obtained using the MatroskaTrack::prepareMakingHeader() method.
|
|
*/
|
|
|
|
/*!
|
|
* \brief Prepares making the header for the specified \a track.
|
|
* \sa See MatroskaTrack::prepareMakingHeader() for more information.
|
|
*/
|
|
MatroskaTrackHeaderMaker::MatroskaTrackHeaderMaker(const MatroskaTrack &track, Diagnostics &diag)
|
|
: m_track(track)
|
|
, m_language(m_track.locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::Unknown))
|
|
, m_languageIETF(m_track.locale().abbreviatedName(LocaleFormat::BCP_47))
|
|
, m_dataSize(0)
|
|
{
|
|
CPP_UTILITIES_UNUSED(diag);
|
|
|
|
// calculate size for recognized elements
|
|
m_dataSize += 2u + 1u + EbmlElement::calculateUIntegerLength(m_track.id());
|
|
m_dataSize += 1u + 1u + EbmlElement::calculateUIntegerLength(m_track.trackNumber());
|
|
m_dataSize += 1u + 1u + EbmlElement::calculateUIntegerLength(m_track.isEnabled());
|
|
m_dataSize += 1u + 1u + EbmlElement::calculateUIntegerLength(m_track.isDefault());
|
|
m_dataSize += 2u + 1u + EbmlElement::calculateUIntegerLength(m_track.isForced());
|
|
if (!m_track.name().empty()) {
|
|
m_dataSize += 2u + EbmlElement::calculateSizeDenotationLength(m_track.name().size()) + m_track.name().size();
|
|
}
|
|
|
|
// compute size of the mandatory "Language" element (if there's no language set, the 3 byte long value "und" is used)
|
|
const auto languageSize = m_language.empty() ? 3 : m_language.size();
|
|
const auto languageElementSize = 3 + EbmlElement::calculateSizeDenotationLength(languageSize) + languageSize;
|
|
// compute size of the optional "LanguageIETF" element
|
|
const auto languageIETFElementSize
|
|
= m_languageIETF.empty() ? 0 : (3 + EbmlElement::calculateSizeDenotationLength(m_languageIETF.size()) + m_languageIETF.size());
|
|
m_dataSize += languageElementSize + languageIETFElementSize;
|
|
|
|
// calculate size for other elements
|
|
for (EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
|
|
switch (trackInfoElement->id()) {
|
|
case MatroskaIds::TrackNumber:
|
|
case MatroskaIds::TrackUID:
|
|
case MatroskaIds::TrackName:
|
|
case MatroskaIds::TrackLanguage:
|
|
case MatroskaIds::TrackLanguageIETF:
|
|
case MatroskaIds::TrackFlagEnabled:
|
|
case MatroskaIds::TrackFlagDefault:
|
|
case MatroskaIds::TrackFlagForced:
|
|
// skip recognized elements
|
|
break;
|
|
default:
|
|
trackInfoElement->makeBuffer();
|
|
m_dataSize += trackInfoElement->totalSize();
|
|
}
|
|
}
|
|
m_sizeDenotationLength = EbmlElement::calculateSizeDenotationLength(m_dataSize);
|
|
m_requiredSize = 1u + m_sizeDenotationLength + m_dataSize;
|
|
}
|
|
|
|
/*!
|
|
* \brief Saves the header for the track (specified when constructing the object) to the
|
|
* specified \a stream (makes a "TrackEntry"-element).
|
|
* \throws Throws std::ios_base::failure when an IO error occurs.
|
|
* \throws Throws Assumes the data is already validated and thus does NOT
|
|
* throw TagParser::Failure or a derived exception.
|
|
*/
|
|
void MatroskaTrackHeaderMaker::make(ostream &stream) const
|
|
{
|
|
// make ID and size
|
|
char buffer[9];
|
|
*buffer = static_cast<char>(MatroskaIds::TrackEntry);
|
|
EbmlElement::makeSizeDenotation(m_dataSize, buffer + 1);
|
|
stream.write(buffer, 1 + m_sizeDenotationLength);
|
|
|
|
// make recognized elements
|
|
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackUID, m_track.id());
|
|
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackNumber, m_track.trackNumber());
|
|
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackFlagEnabled, m_track.isEnabled());
|
|
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackFlagDefault, m_track.isDefault());
|
|
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackFlagForced, m_track.isForced());
|
|
if (!m_track.name().empty()) {
|
|
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackName, m_track.name());
|
|
}
|
|
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackLanguage, m_language.empty() ? "und" : m_language);
|
|
if (!m_languageIETF.empty()) {
|
|
EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackLanguageIETF, m_languageIETF);
|
|
}
|
|
|
|
// make other elements
|
|
for (EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
|
|
switch (trackInfoElement->id()) {
|
|
case MatroskaIds::TrackNumber:
|
|
case MatroskaIds::TrackUID:
|
|
case MatroskaIds::TrackName:
|
|
case MatroskaIds::TrackLanguage:
|
|
case MatroskaIds::TrackLanguageIETF:
|
|
case MatroskaIds::TrackFlagEnabled:
|
|
case MatroskaIds::TrackFlagDefault:
|
|
case MatroskaIds::TrackFlagForced:
|
|
// skip recognized elements
|
|
break;
|
|
default:
|
|
trackInfoElement->copyBuffer(stream);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace TagParser
|