Tag Parser  6.4.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
matroskatrack.cpp
Go to the documentation of this file.
1 #include "./ebmlelement.h"
2 #include "./matroskatrack.h"
3 #include "./matroskacontainer.h"
4 #include "./matroskaid.h"
5 #include "./matroskatag.h"
6 
7 #include "../avi/bitmapinfoheader.h"
8 
9 #include "../wav/waveaudiostream.h"
10 
11 #include "../avc/avcconfiguration.h"
12 
13 #include "../mp4/mp4ids.h"
14 #include "../mp4/mp4track.h"
15 
16 #include "../mediaformat.h"
17 #include "../exceptions.h"
18 
19 #include <c++utilities/conversion/stringconversion.h>
20 
21 using namespace std;
22 using namespace ConversionUtilities;
23 
24 namespace Media {
25 
37 MatroskaTrack::MatroskaTrack(EbmlElement &trackElement) :
38  AbstractTrack(trackElement.stream(), trackElement.startOffset()),
39  m_trackElement(&trackElement)
40 {}
41 
46 {}
47 
49 {
51 }
52 
57 {
58  auto parts = splitString<vector<string> >(codecId, "/", EmptyPartsTreat::Keep, 3);
59  parts.resize(3);
60  const auto &part1 = parts[0], &part2 = parts[1], &part3 = parts[2];
61  MediaFormat fmt;
62  if(part1 == "V_MS" && part2 == "VFW" && part3 == "FOURCC") {
64  } else if(part1 == "V_UNCOMPRESSED") {
66  } else if(part1 == "V_MPEG4") {
68  if(part2 == "ISO") {
69  if(part3 == "SP") {
71  } else if(part3 == "ASP") {
73  } else if(part3 == "AVC") {
75  }
76  } else if(part2 == "MS" && part3 == "V3") {
78  }
79  } else if(part1 == "V_MPEG1") {
81  } else if(part1 == "V_MPEG2") {
83  } else if(part1 == "V_REAL") {
85  } else if(part1 == "V_QUICKTIME") {
87  } else if(part1 == "V_THEORA") {
89  } else if(part1 == "V_PRORES") {
91  } else if(part1 == "V_VP8") {
93  } else if(part1 == "V_VP9") {
95  } else if(part1 == "A_MPEG") {
97  if(part2 == "L1") {
99  } else if(part2 == "L2") {
101  } else if(part2 == "L3") {
103  }
104  } else if(part1 == "V_MPEGH" && part2 == "ISO" && part3 == "HEVC") {
106  } else if(part1 == "A_PCM") {
108  if(part2 == "INT") {
109  if(part3 == "BIG") {
111  } else if(part3 == "LIT") {
113  }
114  } else if (part2 == "FLOAT" && part3 == "IEEE") {
116  }
117  } else if(part1 == "A_MPC") {
119  } else if(part1 == "A_AC3") {
121  } else if(part1 == "A_ALAC") {
123  } else if(part1 == "A_DTS") {
125  if(part2 == "EXPRESS") {
127  } else if(part2 == "LOSSLESS") {
129  }
130  } else if(part1 == "A_VORBIS") {
132  } else if(part1 == "A_FLAC") {
134  } else if(part1 == "A_OPUS") {
136  } else if(part1 == "A_REAL") {
138  } else if(part1 == "A_MS" && part2 == "ACM") {
140  } else if(part1 == "A_AAC") {
142  if(part2 == "MPEG2") {
143  if(part3 == "MAIN") {
145  } else if(part3 == "LC") {
147  } else if(part3 == "SBR") {
150  } else if(part3 == "SSR") {
152  }
153  } else if(part2 == "MPEG4") {
154  if(part3 == "MAIN") {
156  } else if(part3 == "LC") {
158  } else if(part3 == "SBR") {
161  } else if(part3 == "SSR") {
163  } else if(part3 == "LTP") {
165  }
166  }
167  } else if(part1 == "A_QUICKTIME") {
169  } else if(part1 == "A_TTA1") {
171  } else if(part1 == "A_WAVPACK4") {
173  } else if(part1 == "S_TEXT") {
175  if(part2 == "UTF8") {
177  } else if(part2 == "SSA") {
179  } else if(part2 == "ASS") {
181  } else if(part2 == "USF") {
183  } else if(part2 == "WEBVTT") {
185  }
186  } else if(part1 == "S_IMAGE") {
188  if(part2 == "BMP") {
190  }
191  } else if(part1 == "S_VOBSUB") {
193  } else if(part1 == "S_KATE") {
195  } else if(part1 == "B_VOBBTN") {
197  } else if(part1 == "S_DVBSUB") {
199  } else if(part1 == "V_MSWMV") {
201  }
202  return fmt;
203 }
204 
206 
207 template<typename PropertyType, typename ConversionFunction>
208 void MatroskaTrack::assignPropertyFromTagValue(const std::unique_ptr<MatroskaTag> &tag, const char *fieldId, PropertyType &property, const ConversionFunction &conversionFunction)
209 {
210  const TagValue &value = tag->value(fieldId);
211  if(!value.isEmpty()) {
212  try {
213  property = conversionFunction(value);
214  } catch(const ConversionException &) {
215  string message;
216  try {
217  message = argsToString("Ignoring invalid value \"", value.toString(TagTextEncoding::Utf8), "\" of \"", fieldId, '\"', '.');
218  } catch(const ConversionException &) {
219  message = argsToString("Ignoring invalid value of \"", fieldId, '\"', '.');
220  }
221  addNotification(NotificationType::Warning, message, argsToString("reading track statatistic from \"", tag->toString(), '\"'));
222  }
223  }
224 }
225 
226 template<typename NumberType, Traits::EnableIf<std::is_integral<NumberType>>...>
227 NumberType tagValueToNumber(const TagValue &tagValue)
228 {
229  // optimization for Latin1/UTF-8 strings
230  if(tagValue.type() == TagDataType::Text) {
231  switch(tagValue.dataEncoding()) {
234  return bufferToNumber<NumberType>(tagValue.dataPointer(), tagValue.dataSize());
235  default:
236  ;
237  }
238  }
239  // generic conversion
240  return stringToNumber<NumberType>(tagValue.toString(TagTextEncoding::Utf8));
241 }
242 
243 template<typename NumberType, Traits::EnableIf<std::is_floating_point<NumberType>>...>
244 NumberType tagValueToBitrate(const TagValue &tagValue)
245 {
246  return stringToNumber<NumberType>(tagValue.toString(TagTextEncoding::Utf8)) / 1000;
247 }
248 
250 
260 void MatroskaTrack::readStatisticsFromTags(const std::vector<std::unique_ptr<MatroskaTag> > &tags)
261 {
262  using namespace std::placeholders;
263  using namespace MatroskaTagIds::TrackSpecific;
264  for(const auto &tag : tags) {
265  const TagTarget &target = tag->target();
266  if(find(target.tracks().cbegin(), target.tracks().cend(), id()) == target.tracks().cend()) {
267  continue;
268  }
269  assignPropertyFromTagValue(tag, numberOfBytes(), m_size, &tagValueToNumber<uint64>);
270  assignPropertyFromTagValue(tag, numberOfFrames(), m_sampleCount, &tagValueToNumber<uint64>);
271  assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::duration(), m_duration, bind(&TagValue::toTimeSpan, _1));
272  assignPropertyFromTagValue(tag, MatroskaTagIds::TrackSpecific::bitrate(), m_bitrate, &tagValueToBitrate<double>);
273  assignPropertyFromTagValue(tag, writingDate(), m_modificationTime, bind(&TagValue::toDateTime, _1));
274  if(m_creationTime.isNull()) {
276  }
277  }
278 }
279 
281 {
282  static const string context("parsing header of Matroska track");
283  try {
284  m_trackElement->parse();
285  } catch(const Failure &) {
286  addNotification(NotificationType::Critical, "Unable to parse track element.", context);
287  throw;
288  }
289  // read information about the track from the childs of the track entry element
290  for(EbmlElement *trackInfoElement = m_trackElement->firstChild(), *subElement = nullptr; trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
291  try {
292  trackInfoElement->parse();
293  } catch(const Failure &) {
294  addNotification(NotificationType::Critical, "Unable to parse track information element.", context);
295  break;
296  }
297  uint32 defaultDuration = 0;
298  switch(trackInfoElement->id()) {
300  switch(trackInfoElement->readUInteger()) {
303  break;
306  break;
309  break;
312  break;
315  break;
316  default:
318  }
319  break;
321  for(subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
322  try {
323  subElement->parse();
324  } catch(const Failure &) {
325  addNotification(NotificationType::Critical, "Unable to parse video track element.", context);
326  break;
327  }
328  switch(subElement->id()) {
330  m_displaySize.setWidth(subElement->readUInteger());
331  break;
333  m_displaySize.setHeight(subElement->readUInteger());
334  break;
336  m_pixelSize.setWidth(subElement->readUInteger());
337  break;
339  m_pixelSize.setHeight(subElement->readUInteger());
340  break;
342  m_cropping.setTop(subElement->readUInteger());
343  break;
345  m_cropping.setLeft(subElement->readUInteger());
346  break;
348  m_cropping.setBottom(subElement->readUInteger());
349  break;
351  m_cropping.setRight(subElement->readUInteger());
352  break;
354  m_fps = subElement->readFloat();
355  break;
357  m_interlaced = subElement->readUInteger();
358  break;
360  m_colorSpace = subElement->readUInteger();
361  break;
362  default:
363  ;
364  }
365  }
366  break;
368  for(subElement = trackInfoElement->firstChild(); subElement; subElement = subElement->nextSibling()) {
369  try {
370  subElement->parse();
371  } catch(const Failure &) {
372  addNotification(NotificationType::Critical, "Unable to parse audio track element.", context);
373  break;
374  }
375  switch(subElement->id()) {
377  m_bitsPerSample = subElement->readUInteger();
378  break;
380  m_channelCount = subElement->readUInteger();
381  break;
383  if(!m_samplingFrequency) {
384  m_samplingFrequency = subElement->readFloat();
385  }
386  break;
389  m_extensionSamplingFrequency = subElement->readFloat();
390  }
391  break;
392  default:
393  ;
394  }
395  }
396  break;
398  m_trackNumber = trackInfoElement->readUInteger();
399  break;
401  m_id = trackInfoElement->readUInteger();
402  break;
404  m_name = trackInfoElement->readString();
405  break;
407  m_language = trackInfoElement->readString();
408  break;
410  m_format = codecIdToMediaFormat(m_formatId = trackInfoElement->readString());
411  break;
413  m_formatName = trackInfoElement->readString();
414  break;
416  break; // TODO
418  m_enabled = trackInfoElement->readUInteger();
419  break;
421  m_default = trackInfoElement->readUInteger();
422  break;
424  m_forced = trackInfoElement->readUInteger();
425  break;
427  m_lacing = trackInfoElement->readUInteger();
428  break;
430  defaultDuration = trackInfoElement->readUInteger();
431  break;
432  default:
433  ;
434  }
435  switch(m_mediaType) {
436  case MediaType::Video:
437  if(!m_fps && defaultDuration) {
438  m_fps = 1000000000.0 / static_cast<double>(defaultDuration);
439  }
440  break;
441  default:
442  ;
443  }
444  }
445 
446  // read further information from the CodecPrivate element for some codecs
447  switch(m_format.general) {
448  EbmlElement *codecPrivateElement;
450  if((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate))) {
451  // parse bitmap info header to determine actual format
452  if(codecPrivateElement->dataSize() >= 0x28) {
453  m_istream->seekg(codecPrivateElement->dataOffset());
454  BitmapInfoHeader bitmapInfoHeader;
455  bitmapInfoHeader.parse(reader());
456  m_formatId.reserve(m_formatId.size() + 7);
457  m_formatId += " \"";
458  m_formatId += interpretIntegerAsString(bitmapInfoHeader.compression);
459  m_formatId += "\"";
461  } else {
462  addNotification(NotificationType::Critical, "BITMAPINFOHEADER structure (in \"CodecPrivate\"-element) is truncated.", context);
463  }
464  }
465  break;
467  if((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate))) {
468  // parse WAVE header to determine actual format
469  if(codecPrivateElement->dataSize() >= 16) {
470  m_istream->seekg(codecPrivateElement->dataOffset());
471  WaveFormatHeader waveFormatHeader;
472  waveFormatHeader.parse(reader());
473  WaveAudioStream::addInfo(waveFormatHeader, *this);
474  } else {
475  addNotification(NotificationType::Critical, "BITMAPINFOHEADER structure (in \"CodecPrivate\"-element) is truncated.", context);
476  }
477  }
478  break;
480  if((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate))) {
481  auto audioSpecificConfig = Mp4Track::parseAudioSpecificConfig(*this, *m_istream, codecPrivateElement->dataOffset(), codecPrivateElement->dataSize());
482  m_format += Mpeg4AudioObjectIds::idToMediaFormat(audioSpecificConfig->audioObjectType, audioSpecificConfig->sbrPresent, audioSpecificConfig->psPresent);
483  if(audioSpecificConfig->sampleFrequencyIndex == 0xF) {
484  //m_samplingFrequency = audioSpecificConfig->sampleFrequency;
485  } else if(audioSpecificConfig->sampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
486  //m_samplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->sampleFrequencyIndex];
487  } else {
488  addNotification(NotificationType::Warning, "Audio specific config has invalid sample frequency index.", context);
489  }
490  if(audioSpecificConfig->extensionSampleFrequencyIndex == 0xF) {
491  //m_extensionSamplingFrequency = audioSpecificConfig->extensionSampleFrequency;
492  } else if(audioSpecificConfig->extensionSampleFrequencyIndex < sizeof(mpeg4SamplingFrequencyTable)) {
493  //m_extensionSamplingFrequency = mpeg4SamplingFrequencyTable[audioSpecificConfig->extensionSampleFrequencyIndex];
494  } else {
495  addNotification(NotificationType::Warning, "Audio specific config has invalid extension sample frequency index.", context);
496  }
497  m_channelConfig = audioSpecificConfig->channelConfiguration;
498  m_extensionChannelConfig = audioSpecificConfig->extensionChannelConfiguration;
499  }
500  break;
502  if((codecPrivateElement = m_trackElement->childById(MatroskaIds::CodecPrivate))) {
503  auto avcConfig = make_unique<Media::AvcConfiguration>();
504  try {
505  m_istream->seekg(codecPrivateElement->dataOffset());
506  avcConfig->parse(m_reader, codecPrivateElement->dataSize());
507  Mp4Track::addInfo(*avcConfig, *this);
508  } catch(const TruncatedDataException &) {
509  addNotification(NotificationType::Critical, "AVC configuration is truncated.", context);
510  } catch(const Failure &) {
511  addNotification(NotificationType::Critical, "AVC configuration is invalid.", context);
512  }
513  }
514  break;
515  default:
516  ;
517  }
518 
519  // parse format name for unknown formats
521  if(startsWith<string>(m_formatId, "V_") || startsWith<string>(m_formatId, "A_") || startsWith<string>(m_formatId, "S_")) {
522  m_formatName = m_formatId.substr(2);
523  } else {
525  }
526  m_formatName.append(" (unknown)");
527  }
528 
529  // use pixel size as display size if display size not specified
530  if(!m_displaySize.width()) {
532  }
533  if(!m_displaySize.height()) {
535  }
536 
537  // set English if no language has been specified (it is default value of MatroskaIds::TrackLanguage)
538  if(m_language.empty()) {
539  m_language = "eng";
540  }
541 }
542 
554 MatroskaTrackHeaderMaker::MatroskaTrackHeaderMaker(const MatroskaTrack &track) :
555  m_track(track),
556  m_dataSize(0)
557 {
558  // calculate size for recognized elements
559  m_dataSize += 2 + 1 + EbmlElement::calculateUIntegerLength(m_track.id());
560  m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.trackNumber());
561  m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.isEnabled());
562  m_dataSize += 1 + 1 + EbmlElement::calculateUIntegerLength(m_track.isDefault());
563  m_dataSize += 2 + 1 + EbmlElement::calculateUIntegerLength(m_track.isForced());
564  if(!m_track.name().empty()) {
565  m_dataSize += 2 + EbmlElement::calculateSizeDenotationLength(m_track.name().size()) + m_track.name().size();
566  }
567  if(!m_track.language().empty()) {
568  m_dataSize += 3 + EbmlElement::calculateSizeDenotationLength(m_track.language().size()) + m_track.language().size();
569  }
570 
571  // calculate size for other elements
572  for(EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
573  switch(trackInfoElement->id()) {
581  // skip recognized elements
582  break;
583  default:
584  trackInfoElement->makeBuffer();
585  m_dataSize += trackInfoElement->totalSize();
586  }
587  }
588  m_sizeDenotationLength = EbmlElement::calculateSizeDenotationLength(m_dataSize);
589  m_requiredSize = 2 + m_sizeDenotationLength + m_dataSize;
590 }
591 
599 void MatroskaTrackHeaderMaker::make(ostream &stream) const
600 {
601  // make ID and size
602  char buffer[10];
603  BE::getBytes(static_cast<uint16>(MatroskaIds::TrackEntry), buffer);
604  EbmlElement::makeSizeDenotation(m_dataSize, buffer + 2);
605  stream.write(buffer, 2 + m_sizeDenotationLength);
606 
607  // make recognized elements
609  EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackNumber, m_track.trackNumber());
613  if(!m_track.name().empty()) {
615  }
616  if(!m_track.language().empty()) {
617  EbmlElement::makeSimpleElement(stream, MatroskaIds::TrackLanguage, m_track.language());
618  }
619 
620  // make other elements
621  for(EbmlElement *trackInfoElement = m_track.m_trackElement->firstChild(); trackInfoElement; trackInfoElement = trackInfoElement->nextSibling()) {
622  switch(trackInfoElement->id()) {
630  // skip recognized elements
631  break;
632  default:
633  trackInfoElement->copyBuffer(stream);
634  }
635  }
636 }
637 
638 }
implementationType * childById(const identifierType &id)
Returns the first child with the specified id.
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
constexpr uint32 width() const
Returns the width.
Definition: size.h:58
The WaveFormatHeader class parses the WAVEFORMATEX structure defined by MS.
uint64 dataOffset() const
Returns the data offset of the element in the related stream.
void setBottom(uint32 bottom)
Sets the bottom margin to bottom.
Definition: margin.h:91
static byte calculateSizeDenotationLength(uint64 size)
Returns the length of the size denotation for the specified size in byte.
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:344
The EbmlElement class helps to parse EBML files such as Matroska files.
Definition: ebmlelement.h:50
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:479
TAG_PARSER_EXPORT const char * numberOfBytes()
void setWidth(uint32 value)
Sets the width.
Definition: size.h:74
char * dataPointer() const
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:376
void make(std::ostream &stream) const
Saves the header for the track (specified when constructing the object) to the specified stream (make...
dataSizeType dataSize() const
Returns the data size of the element in byte.
std::string m_formatId
GeneralMediaFormat general
Definition: mediaformat.h:271
~MatroskaTrack()
Destroys the track.
TrackType
Specifies the track type.
Definition: abstracttrack.h:28
unsigned char sub
Definition: mediaformat.h:272
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:40
STL namespace.
static MediaFormat codecIdToMediaFormat(const std::string &codecId)
Returns the MediaFormat for the specified Matroska codec ID.
IoUtilities::BinaryReader m_reader
void addNotification(const Notification &notification)
This protected method is meant to be called by the derived class to add a notification.
void setRight(uint32 right)
Sets the right margin to right.
Definition: margin.h:107
static void makeSimpleElement(std::ostream &stream, identifierType id, uint64 content)
Makes a simple EBML element.
constexpr uint32 height() const
Returns the height.
Definition: size.h:66
uint32 mpeg4SamplingFrequencyTable[13]
Definition: mp4ids.cpp:302
unsigned char extension
Definition: mediaformat.h:273
ChronoUtilities::DateTime m_creationTime
void parse(IoUtilities::BinaryReader &reader)
Parses the WAVE "fmt " header segment using the specified reader.
uint64 id() const
Returns the track ID if known; otherwise returns 0.
const IdContainerType & tracks() const
Returns the tracks.
Definition: tagtarget.h:113
IoUtilities::BinaryReader & reader()
Returns a binary reader for the associated stream.
static void addInfo(const AvcConfiguration &avcConfig, AbstractTrack &track)
Adds the information from the specified avcConfig to the specified track.
Definition: mp4track.cpp:899
uint32 m_extensionSamplingFrequency
TAG_PARSER_EXPORT const char * bitrate()
The track&#39;s bit rate in bits per second.
static byte calculateUIntegerLength(uint64 integer)
Returns the length of the specified unsigned integer in byte.
static byte makeSizeDenotation(uint64 size, char *buff)
Makes the size denotation for the specified size and stores it to buff.
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:308
std::string m_language
Implementation of Media::AbstractTrack for the Matroska container.
Definition: matroskatrack.h:47
void setHeight(uint32 value)
Sets the height.
Definition: size.h:82
ChronoUtilities::DateTime toDateTime() const
Converts the value of the current TagValue object to its equivalent DateTime representation.
Definition: tagvalue.cpp:309
implementationType * firstChild()
Returns the first child of the element.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
void setTop(uint32 top)
Sets the top margin to top.
Definition: margin.h:59
void readStatisticsFromTags(const std::vector< std::unique_ptr< MatroskaTag > > &tags)
Reads track-specific statistics from the specified tags.
void setLeft(uint32 left)
Sets the left margin to left.
Definition: margin.h:75
TAG_PARSER_EXPORT MediaFormat fourccToMediaFormat(uint32 fourccId)
Definition: mp4ids.cpp:46
ChronoUtilities::DateTime m_modificationTime
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:35
TAG_PARSER_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent=false, bool psPresent=false)
Definition: mp4ids.cpp:245
static std::unique_ptr< Mpeg4AudioSpecificConfig > parseAudioSpecificConfig(StatusProvider &statusProvider, std::istream &stream, uint64 startOffset, uint64 size)
Parses the audio specific configuration for the track.
Definition: mp4track.cpp:552
ChronoUtilities::TimeSpan m_duration
TrackType type() const
Returns the type of the track if known; otherwise returns TrackType::Unspecified. ...
The BitmapInfoHeader class parses the BITMAPINFOHEADER structure defined by MS.
static void addInfo(const WaveFormatHeader &waveHeader, AbstractTrack &track)
Adds the information from the specified waveHeader to the specified track.
ChronoUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:281
The TagTarget class specifies the target of a tag.
Definition: tagtarget.h:31
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
void internalParseHeader()
This method is internally called to parse header information.
TAG_PARSER_EXPORT const char * duration()
std::string m_formatName
std::istream * m_istream
TAG_PARSER_EXPORT const char * writingDate()
void parse(IoUtilities::BinaryReader &reader)
Parses the BITMAPINFOHEADER structure using the specified reader.
size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:365
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
Definition: tagvalue.h:320
The MediaFormat class specifies the format of media data.
Definition: mediaformat.h:257
TAG_PARSER_EXPORT const char * numberOfFrames()