Tag Parser  6.2.1
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/vorbispackagetypes.h"
5 #include "../vorbis/vorbisidentificationheader.h"
6 
7 #include "../opus/opusidentificationheader.h"
8 
9 #include "../flac/flactooggmappingheader.h"
10 
11 #include "../mediafileinfo.h"
12 #include "../exceptions.h"
13 #include "../mediaformat.h"
14 
15 #include <c++utilities/chrono/timespan.h>
16 
17 #include <iostream>
18 #include <functional>
19 
20 using namespace std;
21 using namespace std::placeholders;
22 using namespace ChronoUtilities;
23 
24 namespace Media {
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 
45 {}
46 
48 {
49  static const string context("parsing OGG page header");
50 
51  // read basic information from first page
52  OggIterator &iterator = m_container.m_iterator;
53  const OggPage &firstPage = iterator.pages()[m_startPage];
54  m_version = firstPage.streamStructureVersion();
55  m_id = firstPage.streamSerialNumber();
56 
57  // ensure iterator is setup properly
58  iterator.setFilter(firstPage.streamSerialNumber());
59  iterator.setPageIndex(m_startPage);
60 
61  // predicate for finding pages of this stream by its stream serial number
62  const auto pred = bind(&OggPage::matchesStreamSerialNumber, _1, firstPage.streamSerialNumber());
63 
64  // iterate through segments using OggIterator
65  // -> iterate through ALL segments to calculate the precise stream size (hence the out-commented part in the loop-condition)
66  for(bool hasIdentificationHeader = false, hasCommentHeader = false; iterator /* && (!hasIdentificationHeader && !hasCommentHeader) */; ++iterator) {
67  const uint32 currentSize = iterator.currentSegmentSize();
68  m_size += currentSize;
69 
70  if(currentSize >= 8) {
71  // determine stream format
72  inputStream().seekg(iterator.currentSegmentOffset());
73  const uint64 sig = reader().readUInt64BE();
74 
75  if((sig & 0x00ffffffffffff00u) == 0x00766F7262697300u) {
76  // Vorbis header detected
77  // set Vorbis as format
78  switch(m_format.general) {
82  break;
84  break;
85  default:
86  addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
87  continue;
88  }
89 
90  // check header type
91  switch(sig >> 56) {
93  if(!hasIdentificationHeader) {
94  // parse identification header
96  ind.parseHeader(iterator);
97  m_version = ind.version();
98  m_channelCount = ind.channels();
100  if(ind.nominalBitrate()) {
101  m_bitrate = ind.nominalBitrate();
102  } else if(ind.maxBitrate() == ind.minBitrate()) {
103  m_bitrate = ind.maxBitrate();
104  }
105  if(m_bitrate) {
106  m_bitrate = static_cast<double>(m_bitrate) / 1000.0;
107  }
108  // determine sample count and duration if all pages have been fetched
109  if(iterator.areAllPagesFetched()) {
110  const auto &pages = iterator.pages();
111  const auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
112  const auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
113  if(firstPage != pages.cend() && lastPage != pages.crend()) {
114  m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
115  m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
116  }
117  }
118  hasIdentificationHeader = true;
119  } else {
120  addNotification(NotificationType::Critical, "Vorbis identification header appears more then once. Oversupplied occurrence will be ignored.", context);
121  }
122  break;
124  // Vorbis comment found -> notify container about comment
125  if(!hasCommentHeader) {
126  m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Vorbis);
127  hasCommentHeader = true;
128  } else {
129  addNotification(NotificationType::Critical, "Vorbis comment header appears more then once. Oversupplied occurrence will be ignored.", context);
130  }
131  break;
133  break; // TODO
134  default:
135  ;
136  }
137 
138  } else if(sig == 0x4F70757348656164u) {
139  // Opus header detected
140  // set Opus as format
141  switch(m_format.general) {
145  break;
147  break;
148  default:
149  addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
150  continue;
151  }
152  if(!hasIdentificationHeader) {
153  // parse identification header
155  ind.parseHeader(iterator);
156  m_version = ind.version();
157  m_channelCount = ind.channels();
159  // determine sample count and duration if all pages have been fetched
160  if(iterator.areAllPagesFetched()) {
161  const auto &pages = iterator.pages();
162  const auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
163  const auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
164  if(firstPage != pages.cend() && lastPage != pages.crend()) {
165  m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
166  // must apply "pre-skip" here do calculate effective sample count and duration?
167  if(m_sampleCount > ind.preSkip()) {
168  m_sampleCount -= ind.preSkip();
169  } else {
170  m_sampleCount = 0;
171  }
172  m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
173  }
174  }
175  hasIdentificationHeader = true;
176  } else {
177  addNotification(NotificationType::Critical, "Opus identification header appears more then once. Oversupplied occurrence will be ignored.", context);
178  }
179 
180  } else if(sig == 0x4F70757354616773u) {
181  // Opus comment detected
182  // set Opus as format
183  switch(m_format.general) {
187  break;
189  break;
190  default:
191  addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
192  continue;
193  }
194 
195  // notify container about comment
196  if(!hasCommentHeader) {
197  m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Opus);
198  hasCommentHeader = true;
199  } else {
200  addNotification(NotificationType::Critical, "Opus tags/comment header appears more then once. Oversupplied occurrence will be ignored.", context);
201  }
202 
203  } else if((sig & 0xFFFFFFFFFF000000u) == 0x7F464C4143000000u) {
204  // FLAC header detected
205  // set FLAC as format
206  switch(m_format.general) {
210  break;
212  break;
213  default:
214  addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
215  continue;
216  }
217 
218  if(!hasIdentificationHeader) {
219  // parse FLAC-to-Ogg mapping header
220  FlacToOggMappingHeader mapping;
221  const FlacMetaDataBlockStreamInfo &streamInfo = mapping.streamInfo();
222  mapping.parseHeader(iterator);
223  m_bitsPerSample = streamInfo.bitsPerSample();
224  m_channelCount = streamInfo.channelCount();
225  m_samplingFrequency = streamInfo.samplingFrequency();
226  m_sampleCount = streamInfo.totalSampleCount();
227  if(!m_sampleCount && iterator.areAllPagesFetched()) {
228  const auto &pages = iterator.pages();
229  const auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
230  const auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
231  if(firstPage != pages.cend() && lastPage != pages.crend()) {
232  m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
233  }
234  }
235  m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
236  hasIdentificationHeader = true;
237  } else {
238  addNotification(NotificationType::Critical, "FLAC-to-Ogg mapping header appears more then once. Oversupplied occurrence will be ignored.", context);
239  }
240 
241  if(!hasCommentHeader) {
242  // a Vorbis comment should be following
243  if(++iterator) {
244  char buff[4];
245  iterator.read(buff, 4);
247  header.parseHeader(buff);
249  m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), header.isLast(), GeneralMediaFormat::Flac);
250  hasCommentHeader = true;
251  } else {
252  addNotification(NotificationType::Critical, "OGG page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment.", context);
253  }
254  } else {
255  addNotification(NotificationType::Critical, "No more OGG pages after FLAC-to-Ogg mapping header (Vorbis comment expected).", context);
256  }
257  }
258 
259  } else if((sig & 0x00ffffffffffff00u) == 0x007468656F726100u) {
260  // Theora header detected
261  // set Theora as format
262  switch(m_format.general) {
266  break;
268  break;
269  default:
270  addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
271  continue;
272  }
273  // TODO: read more information about Theora stream
274 
275  } // currently only Vorbis, Opus and Theora can be detected, TODO: detect more formats
276 
277  } else {
278  // just ignore segments of only 8 byte or even less
279  // TODO: print warning?
280  }
281 
282  // TODO: reduce code duplication
283  }
284 
285  if(m_duration.isNull() && m_size && m_bitrate) {
286  // calculate duration from stream size and bitrate, assuming 1 % overhead
287  m_duration = TimeSpan::fromSeconds(static_cast<double>(m_size) / (m_bitrate * 125.0) * 1.1);
288  }
289  m_headerValid = true;
290 }
291 
292 }
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:183
uint32 currentSegmentSize() const
Returns the size of the current segment.
Definition: oggiterator.h:219
byte type() const
Returns the block type.
Definition: flacmetadata.h:89
void parseHeader(const char *buffer)
Parses the FLAC "METADATA_BLOCK_HEADER" which is read using the specified iterator.
byte isLast() const
Returns whether this is the last metadata block before the audio blocks.
Definition: flacmetadata.h:72
bool matchesStreamSerialNumber(uint32 streamSerialNumber) const
Returns whether the stream serial number of the current instance matches the specified one...
Definition: oggpage.h:164
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
Definition: flacmetadata.h:38
std::istream & inputStream()
Returns the associated input stream.
GeneralMediaFormat general
Definition: mediaformat.h:269
const std::vector< OggPage > & pages() const
Returns a vector of containing the OGG pages that have been fetched yet.
Definition: oggiterator.h:120
void setPageIndex(std::vector< OggPage >::size_type index)
Sets the current page index.
Definition: oggiterator.h:162
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:40
STL namespace.
~OggStream()
Destroys the track.
Definition: oggstream.cpp:44
void addNotification(const Notification &notification)
This protected method is meant to be called by the derived class to add a notification.
void setFilter(uint32 streamSerialId)
Allows to filter pages by the specified streamSerialId.
Definition: oggiterator.h:232
uint32 streamSerialNumber() const
Returns the stream serial number.
Definition: oggpage.h:155
The FlacToOggMappingHeader class is a FLAC-to-Ogg mapping header parser.
byte channels() const
Returns the number of channels for the Opus stream.
The FlacMetaDataBlockStreamInfo class is a FLAC "METADATA_BLOCK_STREAMINFO" parser.
Definition: flacmetadata.h:119
The OggPage class is used to parse OGG pages.
Definition: oggpage.h:14
IoUtilities::BinaryReader & reader()
Returns a binary reader for the associated stream.
void parseHeader(OggIterator &iterator)
Parses the Vorbis identification header which is read using the specified iterator.
byte streamStructureVersion() const
Returns the stream structure version.
Definition: oggpage.h:91
uint64 absoluteGranulePosition() const
Returns the absolute granule position.
Definition: oggpage.h:142
uint16 preSkip() const
Returns "pre-skip" value for the Opus stream.
uint32 sampleRate() const
Returns the INPUT sample rate.
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:152
byte bitsPerSample() const
Returns the bits per sample.
Definition: flacmetadata.h:229
uint64 currentSegmentOffset() const
Returns the start offset of the current segment in the input stream if the iterator is valid; otherwi...
Definition: oggiterator.h:192
void read(char *buffer, std::size_t count)
Reads count bytes from the OGG stream and writes it to the specified buffer.
The VorbisIdentificationHeader class is a Vorbis identification header parser.
uint64 totalSampleCount() const
Returns the total samples in stream.
Definition: flacmetadata.h:242
ChronoUtilities::TimeSpan m_duration
The OpusIdentificationHeader class is an Opus identification header parser.
byte channelCount() const
Returns the number of channels.
Definition: flacmetadata.h:217
byte version() const
Returns the version (which should be 1 currently).
Implementation of Media::AbstractContainer for OGG files.
Definition: oggcontainer.h:127
bool areAllPagesFetched() const
Returns an indication whether all pages have been fetched.
Definition: oggiterator.h:254
The OggIterator class helps iterating through all segments of an OGG bitstream.
Definition: oggiterator.h:11
void parseHeader(OggIterator &iterator)
Parses the FLAC-to-Ogg mapping header which is read using the specified iterator. ...
Contains all classes and functions of the TagInfo library.
Definition: exceptions.h:9
uint32 samplingFrequency() const
Returns the sampling frequency in Hz.
Definition: flacmetadata.h:207
void parseHeader(OggIterator &iterator)
Parses the Opus identification header which is read using the specified iterator. ...
const FlacMetaDataBlockStreamInfo & streamInfo() const
Returns the stream info.
void internalParseHeader()
This method is internally called to parse header information.
Definition: oggstream.cpp:47