Tag Parser: ogg/oggstream.cpp Source File

Tag Parser 12.5.3 C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags

Loading...
Searching...
No Matches
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
20using namespace std;
21using namespace std::placeholders;
22using namespace CppUtilities;
23
24namespace TagParser {
25
30
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
45OggStream::~OggStream()
46{
47}
48
49 void OggStream::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
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) {
76 case GeneralMediaFormat::Unknown:
77 m_format = GeneralMediaFormat::Vorbis;
78 m_mediaType = MediaType::Audio;
79 break;
80 case GeneralMediaFormat::Vorbis:
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) {
89 case VorbisPackageTypes::Identification:
90 if (!hasIdentificationHeader) {
91 // parse identification header
92 VorbisIdentificationHeader ind;
93 ind.parseHeader(iterator);
94 m_version = ind.version();
95 m_channelCount = ind.channels();
96 m_samplingFrequency = ind.sampleRate();
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;
112 case VorbisPackageTypes::Comments:
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;
122 case VorbisPackageTypes::Setup:
123 break; // TODO
124 default:;
125 }
126
127 } else if (sig == 0x4F70757348656164u) {
128 // Opus header detected
129 switch (m_format.general) {
130 case GeneralMediaFormat::Unknown:
131 m_format = GeneralMediaFormat::Opus;
132 m_mediaType = MediaType::Audio;
133 break;
134 case GeneralMediaFormat::Opus:
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
142 OpusIdentificationHeader ind;
143 ind.parseHeader(iterator);
144 m_version = ind.version();
145 m_channelCount = ind.channels();
146 m_samplingFrequency = ind.sampleRate();
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) {
157 case GeneralMediaFormat::Unknown:
158 m_format = GeneralMediaFormat::Opus;
159 m_mediaType = MediaType::Audio;
160 break;
161 case GeneralMediaFormat::Opus:
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) {
180 case GeneralMediaFormat::Unknown:
181 m_format = GeneralMediaFormat::Flac;
182 m_mediaType = MediaType::Audio;
183 break;
184 case GeneralMediaFormat::Flac:
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 constexpr auto headerSize = 4;
211 char buff[headerSize];
212 iterator.read(buff, headerSize);
213 FlacMetaDataBlockHeader header;
214 header.parseHeader(std::string_view(buff, headerSize));
215 if (header.type() == FlacMetaDataBlockType::VorbisComment) {
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) {
232 case GeneralMediaFormat::Unknown:
233 m_format = GeneralMediaFormat::Theora;
234 m_mediaType = MediaType::Video;
235 break;
236 case GeneralMediaFormat::Theora:
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) {
247 case GeneralMediaFormat::Unknown:
248 m_format = GeneralMediaFormat::Speex;
249 m_mediaType = MediaType::Audio;
250 break;
251 case GeneralMediaFormat::Speex:
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) {
261 case GeneralMediaFormat::Unknown:
262 m_format = GeneralMediaFormat::UncompressedVideoFrames;
263 m_mediaType = MediaType::Video;
264 m_chromaFormat = "YUV";
265 break;
266 case GeneralMediaFormat::UncompressedVideoFrames:
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
TagParser::AbortableProgressFeedback
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
Definition progressfeedback.h:186
TagParser::AbstractTrack::m_chromaFormat
std::string_view m_chromaFormat
Definition abstracttrack.h:229
TagParser::AbstractTrack::m_sampleCount
std::uint64_t m_sampleCount
Definition abstracttrack.h:221
TagParser::AbstractTrack::startOffset
std::uint64_t startOffset() const
Returns the start offset of the track in the associated stream.
Definition abstracttrack.h:316
TagParser::AbstractTrack::m_version
double m_version
Definition abstracttrack.h:202
TagParser::AbstractTrack::m_size
std::uint64_t m_size
Definition abstracttrack.h:203
TagParser::AbstractTrack::m_bitsPerSample
std::uint16_t m_bitsPerSample
Definition abstracttrack.h:215
TagParser::AbstractTrack::inputStream
std::istream & inputStream()
Returns the associated input stream.
Definition abstracttrack.h:248
TagParser::AbstractTrack::AbstractTrack
AbstractTrack(std::istream &inputStream, std::ostream &outputStream, std::uint64_t startOffset)
Constructs a new track.
Definition abstracttrack.cpp:33
TagParser::AbstractTrack::m_channelCount
std::uint16_t m_channelCount
Definition abstracttrack.h:217
TagParser::AbstractTrack::reader
CppUtilities::BinaryReader & reader()
Returns a binary reader for the associated stream.
Definition abstracttrack.h:289
TagParser::AbstractTrack::m_duration
CppUtilities::TimeSpan m_duration
Definition abstracttrack.h:207
TagParser::AbstractTrack::m_format
MediaFormat m_format
Definition abstracttrack.h:198
TagParser::AbstractTrack::m_bitrate
double m_bitrate
Definition abstracttrack.h:208
TagParser::AbstractTrack::m_samplingFrequency
std::uint32_t m_samplingFrequency
Definition abstracttrack.h:213
TagParser::AbstractTrack::m_id
std::uint64_t m_id
Definition abstracttrack.h:205
TagParser::AbstractTrack::m_mediaType
MediaType m_mediaType
Definition abstracttrack.h:201
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition diagnostics.h:156
TagParser::FlacMetaDataBlockHeader
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
Definition flacmetadata.h:28
TagParser::FlacMetaDataBlockHeader::type
constexpr std::uint8_t type() const
Returns the block type.
Definition flacmetadata.h:79
TagParser::FlacMetaDataBlockHeader::isLast
constexpr std::uint8_t isLast() const
Returns whether this is the last metadata block before the audio blocks.
Definition flacmetadata.h:62
TagParser::FlacMetaDataBlockHeader::parseHeader
void parseHeader(std::string_view buffer)
Parses the FLAC "METADATA_BLOCK_HEADER" which is read using the specified iterator.
Definition flacmetadata.cpp:29
TagParser::FlacMetaDataBlockStreamInfo
The FlacMetaDataBlockStreamInfo class is a FLAC "METADATA_BLOCK_STREAMINFO" parser.
Definition flacmetadata.h:109
TagParser::FlacMetaDataBlockStreamInfo::totalSampleCount
constexpr std::uint64_t totalSampleCount() const
Returns the total samples in stream.
Definition flacmetadata.h:232
TagParser::FlacMetaDataBlockStreamInfo::samplingFrequency
constexpr std::uint32_t samplingFrequency() const
Returns the sampling frequency in Hz.
Definition flacmetadata.h:197
TagParser::FlacMetaDataBlockStreamInfo::bitsPerSample
constexpr std::uint8_t bitsPerSample() const
Returns the bits per sample.
Definition flacmetadata.h:219
TagParser::FlacMetaDataBlockStreamInfo::channelCount
constexpr std::uint8_t channelCount() const
Returns the number of channels.
Definition flacmetadata.h:207
TagParser::FlacToOggMappingHeader
The FlacToOggMappingHeader class is a FLAC-to-Ogg mapping header parser.
Definition flactooggmappingheader.h:10
TagParser::FlacToOggMappingHeader::streamInfo
constexpr const FlacMetaDataBlockStreamInfo & streamInfo() const
Returns the stream info.
Definition flactooggmappingheader.h:65
TagParser::FlacToOggMappingHeader::parseHeader
void parseHeader(OggIterator &iterator)
Parses the FLAC-to-Ogg mapping header which is read using the specified iterator.
Definition flactooggmappingheader.cpp:24
TagParser::OggIterator
The OggIterator class helps iterating through all segments of an Ogg bitstream.
Definition oggiterator.h:11
TagParser::OggIterator::read
void read(char *buffer, std::size_t count)
Reads count bytes from the Ogg stream and writes it to the specified buffer.
Definition oggiterator.cpp:134
TagParser::OggIterator::currentSegmentIndex
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:204
TagParser::OggIterator::currentSegmentOffset
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:213
TagParser::OggIterator::pages
const std::vector< OggPage > & pages() const
Returns a vector of containing the Ogg pages that have been fetched yet.
Definition oggiterator.h:125
TagParser::OggIterator::setPageIndex
void setPageIndex(std::vector< OggPage >::size_type index)
Sets the current page index.
Definition oggiterator.h:183
TagParser::OggIterator::currentPageIndex
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:174
TagParser::OggIterator::currentSegmentSize
std::uint32_t currentSegmentSize() const
Returns the size of the current segment.
Definition oggiterator.h:240
TagParser::OggIterator::setFilter
void setFilter(std::uint32_t streamSerialId)
Allows to filter pages by the specified streamSerialId.
Definition oggiterator.h:269
TagParser::OggPage
The OggPage class is used to parse Ogg pages.
Definition oggpage.h:13
TagParser::OggPage::streamSerialNumber
std::uint32_t streamSerialNumber() const
Returns the stream serial number.
Definition oggpage.h:166
TagParser::OggPage::matchesStreamSerialNumber
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
TagParser::OggPage::streamStructureVersion
std::uint8_t streamStructureVersion() const
Returns the stream structure version.
Definition oggpage.h:94
TagParser::OggStream::OggContainer
friend class OggContainer
Definition oggstream.h:14
TagParser::OggStream::~OggStream
~OggStream() override
Destroys the track.
Definition oggstream.cpp:45
TagParser::OggStream::OggStream
OggStream(OggContainer &container, std::vector< OggPage >::size_type startPage)
Constructs a new track for the stream at the specified startOffset.
Definition oggstream.cpp:34
TagParser::OggStream::internalParseHeader
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
This method is internally called to parse header information.
Definition oggstream.cpp:49
TagParser::OggStream::startPage
std::size_t startPage() const
Definition oggstream.h:34
TagParser::OpusIdentificationHeader
The OpusIdentificationHeader class is an Opus identification header parser.
Definition opusidentificationheader.h:12
TagParser::OpusIdentificationHeader::preSkip
constexpr std::uint16_t preSkip() const
Returns "pre-skip" value for the Opus stream.
Definition opusidentificationheader.h:70
TagParser::OpusIdentificationHeader::channels
constexpr std::uint8_t channels() const
Returns the number of channels for the Opus stream.
Definition opusidentificationheader.h:58
TagParser::OpusIdentificationHeader::version
constexpr std::uint8_t version() const
Returns the version (which should be 1 currently).
Definition opusidentificationheader.h:50
TagParser::OpusIdentificationHeader::sampleRate
constexpr std::uint32_t sampleRate() const
Returns the INPUT sample rate.
Definition opusidentificationheader.h:80
TagParser::OpusIdentificationHeader::parseHeader
void parseHeader(OggIterator &iterator)
Parses the Opus identification header which is read using the specified iterator.
Definition opusidentificationheader.cpp:24
TagParser::VorbisIdentificationHeader
The VorbisIdentificationHeader class is a Vorbis identification header parser.
Definition vorbisidentificationheader.h:12
TagParser::VorbisIdentificationHeader::sampleRate
constexpr std::uint32_t sampleRate() const
Definition vorbisidentificationheader.h:63
TagParser::VorbisIdentificationHeader::minBitrate
constexpr std::uint32_t minBitrate() const
Definition vorbisidentificationheader.h:78
TagParser::VorbisIdentificationHeader::channels
constexpr std::uint8_t channels() const
Definition vorbisidentificationheader.h:58
TagParser::VorbisIdentificationHeader::parseHeader
void parseHeader(OggIterator &iterator)
Parses the Vorbis identification header which is read using the specified iterator.
Definition vorbisidentificationheader.cpp:24
TagParser::VorbisIdentificationHeader::maxBitrate
constexpr std::uint32_t maxBitrate() const
Definition vorbisidentificationheader.h:68
TagParser::VorbisIdentificationHeader::version
constexpr std::uint32_t version() const
Definition vorbisidentificationheader.h:53
TagParser::VorbisIdentificationHeader::nominalBitrate
constexpr std::uint32_t nominalBitrate() const
Definition vorbisidentificationheader.h:73
exceptions.h
flactooggmappingheader.h
mediafileinfo.h
mediaformat.h
CppUtilities
Definition abstractcontainer.h:16
TagParser::VorbisPackageTypes::Identification
@ Identification
Definition vorbispackagetypes.h:12
TagParser::VorbisPackageTypes::Comments
@ Comments
Definition vorbispackagetypes.h:12
TagParser::VorbisPackageTypes::Setup
@ Setup
Definition vorbispackagetypes.h:12
TagParser
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TagParser::FlacMetaDataBlockType::VorbisComment
@ VorbisComment
Definition flacmetadata.h:16
TagParser::DiagLevel::Warning
@ Warning
Definition diagnostics.h:20
TagParser::DiagLevel::Critical
@ Critical
Definition diagnostics.h:21
TagParser::MediaType::Video
@ Video
Definition mediaformat.h:17
TagParser::MediaType::Audio
@ Audio
Definition mediaformat.h:16
TagParser::GeneralMediaFormat::Vorbis
@ Vorbis
Definition mediaformat.h:96
TagParser::GeneralMediaFormat::Speex
@ Speex
Definition mediaformat.h:83
TagParser::GeneralMediaFormat::Flac
@ Flac
Definition mediaformat.h:51
TagParser::GeneralMediaFormat::Theora
@ Theora
Definition mediaformat.h:88
TagParser::GeneralMediaFormat::Unknown
@ Unknown
Definition mediaformat.h:31
TagParser::GeneralMediaFormat::UncompressedVideoFrames
@ UncompressedVideoFrames
Definition mediaformat.h:92
TagParser::GeneralMediaFormat::Opus
@ Opus
Definition mediaformat.h:72
oggcontainer.h
oggstream.h
opusidentificationheader.h
vorbisidentificationheader.h
vorbispackagetypes.h