Tag Parser 12.4.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
oggstream.cpp
Go to the documentation of this file.
1#include "./oggstream.h"
2#include "./oggcontainer.h"
3
6
8
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
20using namespace std;
21using namespace std::placeholders;
22using namespace CppUtilities;
23
24namespace TagParser {
25
34OggStream::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
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];
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();
97 if (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
194 const FlacMetaDataBlockStreamInfo &streamInfo = mapping.streamInfo();
195 mapping.parseHeader(iterator);
196 m_bitsPerSample = streamInfo.bitsPerSample();
197 m_channelCount = streamInfo.channelCount();
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 constexpr auto headerSize = 4;
211 char buff[headerSize];
212 iterator.read(buff, headerSize);
214 header.parseHeader(std::string_view(buff, headerSize));
216 m_container.announceComment(
217 iterator.currentPageIndex(), iterator.currentSegmentIndex(), header.isLast(), GeneralMediaFormat::Flac);
218 hasCommentHeader = true;
219 } else {
220 diag.emplace_back(
221 DiagLevel::Critical, "Ogg page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment.", context);
222 }
223 } else {
224 diag.emplace_back(
225 DiagLevel::Critical, "No more Ogg pages after FLAC-to-Ogg mapping header (Vorbis comment expected).", context);
226 }
227 }
228
229 } else if ((sig & 0x00ffffffffffff00u) == 0x007468656F726100u) {
230 // Theora header detected
231 switch (m_format.general) {
235 break;
237 break;
238 default:
239 diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
240 continue;
241 }
242 // TODO: read more information about Theora stream
243
244 } else if ((sig & 0xFFFFFFFFFFFF0000u) == 0x5370656578200000u) {
245 // Speex header detected
246 switch (m_format.general) {
250 break;
252 break;
253 default:
254 diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
255 continue;
256 }
257 // TODO: read more information about Speex stream
258 } else if (sig == 0x595556344D504547u) {
259 // YUV4MPEG header detected
260 switch (m_format.general) {
264 m_chromaFormat = "YUV";
265 break;
267 break;
268 default:
269 diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
270 continue;
271 }
272 // TODO: read more information about YUV4MPEG stream
273 }
274 // currently only Vorbis, Opus, Theora, Speex and YUV4MPEG can be detected, TODO: detect more formats
275
276 } else {
277 // just ignore segments of only 8 byte or even less
278 // TODO: print warning?
279 }
280
281 // TODO: reduce code duplication
282 }
283
284 // estimate duration from size and bitrate if sample count and sample rate could not be determined
285 if (m_duration.isNull() && m_size && m_bitrate != 0.0) {
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}
290
291void OggStream::calculateDurationViaSampleCount(std::uint16_t preSkip)
292{
293 // define predicate for finding pages of this stream by its stream serial number
294 const auto pred = bind(&OggPage::matchesStreamSerialNumber, _1, m_id);
295
296 // determine sample count
297 const auto &iterator = m_container.m_iterator;
298 if (!m_sampleCount && iterator.isLastPageFetched()) {
299 const auto &pages = iterator.pages();
300 const auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
301 const auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
302 if (firstPage != pages.cend() && lastPage != pages.crend()) {
303 m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
304 // must apply "pre-skip" here to calculate effective sample count and duration?
305 if (m_sampleCount > preSkip) {
306 m_sampleCount -= preSkip;
307 } else {
308 m_sampleCount = 0;
309 }
310 }
311 }
312
313 // actually calculate the duration
314 if (m_sampleCount && m_samplingFrequency != 0.0) {
315 m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
316 }
317}
318
319} // 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...
std::string_view m_chromaFormat
std::uint16_t m_bitsPerSample
std::istream & inputStream()
Returns the associated input stream.
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.
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
constexpr std::uint8_t type() const
Returns the block type.
constexpr std::uint8_t isLast() const
Returns whether this is the last metadata block before the audio blocks.
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.
constexpr std::uint64_t totalSampleCount() const
Returns the total samples in stream.
constexpr std::uint32_t samplingFrequency() const
Returns the sampling frequency in Hz.
constexpr std::uint8_t bitsPerSample() const
Returns the bits per sample.
constexpr std::uint8_t channelCount() const
Returns the number of channels.
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
Implementation of TagParser::AbstractContainer for Ogg files.
The OggIterator class helps iterating through all segments of an Ogg bitstream.
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...
std::uint64_t currentSegmentOffset() const
Returns the start offset of the current segment in the input stream if the iterator is valid; otherwi...
const std::vector< OggPage > & pages() const
Returns a vector of containing the Ogg pages that have been fetched yet.
void setPageIndex(std::vector< OggPage >::size_type index)
Sets the current page index.
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...
std::uint32_t currentSegmentSize() const
Returns the size of the current segment.
void setFilter(std::uint32_t streamSerialId)
Allows to filter pages by the specified streamSerialId.
The OggPage class is used to parse Ogg pages.
std::uint32_t streamSerialNumber() const
Returns the stream serial number.
Definition oggpage.h:166
bool matchesStreamSerialNumber(std::uint32_t streamSerialNumber) const
Returns whether the stream serial number of the current instance matches the specified one.
Definition oggpage.h:175
std::uint8_t streamStructureVersion() const
Returns the stream structure version.
Definition oggpage.h:94
~OggStream() override
Destroys the track.
Definition oggstream.cpp:45
OggStream(OggContainer &container, std::vector< OggPage >::size_type startPage)
Constructs a new track for the stream at the specified startOffset.
Definition oggstream.cpp:34
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.
void parseHeader(OggIterator &iterator)
Parses the Vorbis identification header which is read using the specified iterator.
constexpr std::uint32_t nominalBitrate() const
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10