Tag Parser  10.0.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
oggstream.cpp
Go to the documentation of this file.
1 #include "./oggstream.h"
2 #include "./oggcontainer.h"
3 
4 #include "../vorbis/vorbisidentificationheader.h"
5 #include "../vorbis/vorbispackagetypes.h"
6 
7 #include "../opus/opusidentificationheader.h"
8 
9 #include "../flac/flactooggmappingheader.h"
10 
11 #include "../exceptions.h"
12 #include "../mediafileinfo.h"
13 #include "../mediaformat.h"
14 
15 #include <c++utilities/chrono/timespan.h>
16 
17 #include <functional>
18 #include <iostream>
19 
20 using namespace std;
21 using namespace std::placeholders;
22 using namespace CppUtilities;
23 
24 namespace TagParser {
25 
34 OggStream::OggStream(OggContainer &container, vector<OggPage>::size_type startPage)
35  : AbstractTrack(container.stream(), container.m_iterator.pages()[startPage].startOffset())
36  , m_startPage(startPage)
37  , m_container(container)
38  , m_currentSequenceNumber(0)
39 {
40 }
41 
46 {
47 }
48 
50 {
51  CPP_UTILITIES_UNUSED(progress)
52 
53  static const string context("parsing OGG page header");
54 
55  // read basic information from first page
56  OggIterator &iterator = m_container.m_iterator;
57  const OggPage &firstPage = iterator.pages()[m_startPage];
58  m_version = firstPage.streamStructureVersion();
59  m_id = firstPage.streamSerialNumber();
60 
61  // ensure iterator is setup properly
62  iterator.setFilter(firstPage.streamSerialNumber());
63  iterator.setPageIndex(m_startPage);
64 
65  // iterate through segments using OggIterator
66  for (bool hasIdentificationHeader = false, hasCommentHeader = false; iterator && (!hasIdentificationHeader || !hasCommentHeader); ++iterator) {
67  const std::uint32_t currentSize = iterator.currentSegmentSize();
68  if (currentSize >= 8) {
69  // determine stream format
70  inputStream().seekg(static_cast<streamoff>(iterator.currentSegmentOffset()));
71  const std::uint64_t sig = reader().readUInt64BE();
72 
73  if ((sig & 0x00ffffffffffff00u) == 0x00766F7262697300u) {
74  // Vorbis header detected
75  switch (m_format.general) {
79  break;
81  break;
82  default:
83  diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
84  continue;
85  }
86 
87  // check header type
88  switch (sig >> 56) {
90  if (!hasIdentificationHeader) {
91  // parse identification header
93  ind.parseHeader(iterator);
94  m_version = ind.version();
95  m_channelCount = ind.channels();
97  if (ind.nominalBitrate()) {
98  m_bitrate = ind.nominalBitrate();
99  } else if (ind.maxBitrate() == ind.minBitrate()) {
100  m_bitrate = ind.maxBitrate();
101  }
102  if (m_bitrate != 0.0) {
103  m_bitrate /= 1000.0;
104  }
105  calculateDurationViaSampleCount();
106  hasIdentificationHeader = true;
107  } else {
108  diag.emplace_back(DiagLevel::Critical,
109  "Vorbis identification header appears more than once. Oversupplied occurrence will be ignored.", context);
110  }
111  break;
113  // Vorbis comment found -> notify container about comment
114  if (!hasCommentHeader) {
115  m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Vorbis);
116  hasCommentHeader = true;
117  } else {
118  diag.emplace_back(
119  DiagLevel::Critical, "Vorbis comment header appears more than once. Oversupplied occurrence will be ignored.", context);
120  }
121  break;
123  break; // TODO
124  default:;
125  }
126 
127  } else if (sig == 0x4F70757348656164u) {
128  // Opus header detected
129  switch (m_format.general) {
133  break;
135  break;
136  default:
137  diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
138  continue;
139  }
140  if (!hasIdentificationHeader) {
141  // parse identification header
143  ind.parseHeader(iterator);
144  m_version = ind.version();
145  m_channelCount = ind.channels();
147  calculateDurationViaSampleCount(ind.preSkip());
148  hasIdentificationHeader = true;
149  } else {
150  diag.emplace_back(
151  DiagLevel::Critical, "Opus identification header appears more than once. Oversupplied occurrence will be ignored.", context);
152  }
153 
154  } else if (sig == 0x4F70757354616773u) {
155  // Opus comment detected
156  switch (m_format.general) {
160  break;
162  break;
163  default:
164  diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
165  continue;
166  }
167 
168  // notify container about comment
169  if (!hasCommentHeader) {
170  m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Opus);
171  hasCommentHeader = true;
172  } else {
173  diag.emplace_back(
174  DiagLevel::Critical, "Opus tags/comment header appears more than once. Oversupplied occurrence will be ignored.", context);
175  }
176 
177  } else if ((sig & 0xFFFFFFFFFF000000u) == 0x7F464C4143000000u) {
178  // FLAC header detected
179  switch (m_format.general) {
183  break;
185  break;
186  default:
187  diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
188  continue;
189  }
190 
191  if (!hasIdentificationHeader) {
192  // parse FLAC-to-Ogg mapping header
193  FlacToOggMappingHeader mapping;
194  const FlacMetaDataBlockStreamInfo &streamInfo = mapping.streamInfo();
195  mapping.parseHeader(iterator);
196  m_bitsPerSample = streamInfo.bitsPerSample();
197  m_channelCount = streamInfo.channelCount();
198  m_samplingFrequency = streamInfo.samplingFrequency();
199  m_sampleCount = streamInfo.totalSampleCount();
200  calculateDurationViaSampleCount();
201  hasIdentificationHeader = true;
202  } else {
203  diag.emplace_back(
204  DiagLevel::Critical, "FLAC-to-Ogg mapping header appears more than once. Oversupplied occurrence will be ignored.", context);
205  }
206 
207  if (!hasCommentHeader) {
208  // a Vorbis comment should be following
209  if (++iterator) {
210  char buff[4];
211  iterator.read(buff, 4);
213  header.parseHeader(buff);
214  if (header.type() == FlacMetaDataBlockType::VorbisComment) {
215  m_container.announceComment(
216  iterator.currentPageIndex(), iterator.currentSegmentIndex(), header.isLast(), GeneralMediaFormat::Flac);
217  hasCommentHeader = true;
218  } else {
219  diag.emplace_back(
220  DiagLevel::Critical, "OGG page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment.", context);
221  }
222  } else {
223  diag.emplace_back(
224  DiagLevel::Critical, "No more OGG pages after FLAC-to-Ogg mapping header (Vorbis comment expected).", context);
225  }
226  }
227 
228  } else if ((sig & 0x00ffffffffffff00u) == 0x007468656F726100u) {
229  // Theora header detected
230  switch (m_format.general) {
234  break;
236  break;
237  default:
238  diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
239  continue;
240  }
241  // TODO: read more information about Theora stream
242 
243  } else if ((sig & 0xFFFFFFFFFFFF0000u) == 0x5370656578200000u) {
244  // Speex header detected
245  switch (m_format.general) {
249  break;
251  break;
252  default:
253  diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
254  continue;
255  }
256  // TODO: read more information about Speex stream
257  } else if (sig == 0x595556344D504547u) {
258  // YUV4MPEG header detected
259  switch (m_format.general) {
263  m_chromaFormat = "YUV";
264  break;
266  break;
267  default:
268  diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
269  continue;
270  }
271  // TODO: read more information about YUV4MPEG stream
272  }
273  // currently only Vorbis, Opus, Theora, Speex and YUV4MPEG can be detected, TODO: detect more formats
274 
275  } else {
276  // just ignore segments of only 8 byte or even less
277  // TODO: print warning?
278  }
279 
280  // TODO: reduce code duplication
281  }
282 
283  // estimate duration from size and bitrate if sample count and sample rate could not be determined
284  if (m_duration.isNull() && m_size && m_bitrate != 0.0) {
285  // calculate duration from stream size and bitrate, assuming 1 % overhead
286  m_duration = TimeSpan::fromSeconds(static_cast<double>(m_size) / (m_bitrate * 125.0) * 1.1);
287  }
288 }
289 
290 void OggStream::calculateDurationViaSampleCount(std::uint16_t preSkip)
291 {
292  // define predicate for finding pages of this stream by its stream serial number
293  const auto pred = bind(&OggPage::matchesStreamSerialNumber, _1, m_id);
294 
295  // determine sample count
296  const auto &iterator = m_container.m_iterator;
297  if (!m_sampleCount && iterator.isLastPageFetched()) {
298  const auto &pages = iterator.pages();
299  const auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
300  const auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
301  if (firstPage != pages.cend() && lastPage != pages.crend()) {
302  m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
303  // must apply "pre-skip" here to calculate effective sample count and duration?
304  if (m_sampleCount > preSkip) {
305  m_sampleCount -= preSkip;
306  } else {
307  m_sampleCount = 0;
308  }
309  }
310  }
311 
312  // actually calculate the duration
313  if (m_sampleCount && m_samplingFrequency != 0.0) {
314  m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
315  }
316 }
317 
318 } // namespace TagParser
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:65
std::string_view m_chromaFormat
std::uint64_t m_sampleCount
std::uint16_t m_bitsPerSample
std::istream & inputStream()
Returns the associated input stream.
std::uint16_t m_channelCount
CppUtilities::BinaryReader & reader()
Returns a binary reader for the associated stream.
CppUtilities::TimeSpan m_duration
std::uint32_t m_samplingFrequency
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
Definition: flacmetadata.h:28
constexpr std::uint8_t type() const
Returns the block type.
Definition: flacmetadata.h:79
constexpr std::uint8_t isLast() const
Returns whether this is the last metadata block before the audio blocks.
Definition: flacmetadata.h:62
void parseHeader(std::string_view buffer)
Parses the FLAC "METADATA_BLOCK_HEADER" which is read using the specified iterator.
The FlacMetaDataBlockStreamInfo class is a FLAC "METADATA_BLOCK_STREAMINFO" parser.
Definition: flacmetadata.h:109
constexpr std::uint64_t totalSampleCount() const
Returns the total samples in stream.
Definition: flacmetadata.h:232
constexpr std::uint32_t samplingFrequency() const
Returns the sampling frequency in Hz.
Definition: flacmetadata.h:197
constexpr std::uint8_t bitsPerSample() const
Returns the bits per sample.
Definition: flacmetadata.h:219
constexpr std::uint8_t channelCount() const
Returns the number of channels.
Definition: flacmetadata.h:207
The FlacToOggMappingHeader class is a FLAC-to-Ogg mapping header parser.
constexpr const FlacMetaDataBlockStreamInfo & streamInfo() const
Returns the stream info.
void parseHeader(OggIterator &iterator)
Parses the FLAC-to-Ogg mapping header which is read using the specified iterator.
GeneralMediaFormat general
Definition: mediaformat.h:259
Implementation of TagParser::AbstractContainer for OGG files.
Definition: oggcontainer.h:129
The OggIterator class helps iterating through all segments of an OGG bitstream.
Definition: oggiterator.h:11
void read(char *buffer, std::size_t count)
Reads count bytes from the OGG stream and writes it to the specified buffer.
std::vector< std::uint32_t >::size_type currentSegmentIndex() const
Returns the index of the current segment (in the current page) if the iterator is valid; otherwise an...
Definition: oggiterator.h:193
std::uint64_t currentSegmentOffset() const
Returns the start offset of the current segment in the input stream if the iterator is valid; otherwi...
Definition: oggiterator.h:202
const std::vector< OggPage > & pages() const
Returns a vector of containing the OGG pages that have been fetched yet.
Definition: oggiterator.h:122
void setPageIndex(std::vector< OggPage >::size_type index)
Sets the current page index.
Definition: oggiterator.h:172
std::vector< OggPage >::size_type currentPageIndex() const
Returns the index of the current page if the iterator is valid; otherwise an undefined index is retur...
Definition: oggiterator.h:163
std::uint32_t currentSegmentSize() const
Returns the size of the current segment.
Definition: oggiterator.h:229
void setFilter(std::uint32_t streamSerialId)
Allows to filter pages by the specified streamSerialId.
Definition: oggiterator.h:242
The OggPage class is used to parse OGG pages.
Definition: oggpage.h:13
std::uint32_t streamSerialNumber() const
Returns the stream serial number.
Definition: oggpage.h:155
bool matchesStreamSerialNumber(std::uint32_t streamSerialNumber) const
Returns whether the stream serial number of the current instance matches the specified one.
Definition: oggpage.h:164
std::uint8_t streamStructureVersion() const
Returns the stream structure version.
Definition: oggpage.h:91
~OggStream() override
Destroys the track.
Definition: oggstream.cpp:45
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
This method is internally called to parse header information.
Definition: oggstream.cpp:49
The OpusIdentificationHeader class is an Opus identification header parser.
constexpr std::uint16_t preSkip() const
Returns "pre-skip" value for the Opus stream.
constexpr std::uint8_t channels() const
Returns the number of channels for the Opus stream.
constexpr std::uint8_t version() const
Returns the version (which should be 1 currently).
constexpr std::uint32_t sampleRate() const
Returns the INPUT sample rate.
void parseHeader(OggIterator &iterator)
Parses the Opus identification header which is read using the specified iterator.
The VorbisIdentificationHeader class is a Vorbis identification header parser.
constexpr std::uint32_t sampleRate() const
constexpr std::uint32_t minBitrate() const
void parseHeader(OggIterator &iterator)
Parses the Vorbis identification header which is read using the specified iterator.
constexpr std::uint32_t maxBitrate() const
constexpr std::uint32_t nominalBitrate() const
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10