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