tagparser/ogg/oggstream.cpp

318 lines
13 KiB
C++
Raw Normal View History

2015-09-06 19:57:33 +02:00
#include "./oggstream.h"
#include "./oggcontainer.h"
2015-04-22 19:22:01 +02:00
2015-09-06 19:57:33 +02:00
#include "../vorbis/vorbisidentificationheader.h"
2018-03-07 01:17:50 +01:00
#include "../vorbis/vorbispackagetypes.h"
2015-04-22 19:22:01 +02:00
2016-01-17 19:32:58 +01:00
#include "../opus/opusidentificationheader.h"
2016-05-14 00:24:01 +02:00
#include "../flac/flactooggmappingheader.h"
2015-09-06 19:57:33 +02:00
#include "../exceptions.h"
2018-03-07 01:17:50 +01:00
#include "../mediafileinfo.h"
2015-09-06 19:57:33 +02:00
#include "../mediaformat.h"
2015-04-22 19:22:01 +02:00
#include <c++utilities/chrono/timespan.h>
2016-05-14 00:24:01 +02:00
#include <functional>
2018-03-07 01:17:50 +01:00
#include <iostream>
2015-04-22 19:22:01 +02:00
using namespace std;
2016-05-14 00:24:01 +02:00
using namespace std::placeholders;
2015-04-22 19:22:01 +02:00
using namespace ChronoUtilities;
namespace TagParser {
2015-04-22 19:22:01 +02:00
/*!
* \class TagParser::OggStream
* \brief Implementation of TagParser::AbstractTrack for OGG streams.
2015-04-22 19:22:01 +02:00
*/
/*!
* \brief Constructs a new track for the \a stream at the specified \a startOffset.
*/
2018-03-07 01:17:50 +01:00
OggStream::OggStream(OggContainer &container, vector<OggPage>::size_type startPage)
: AbstractTrack(container.stream(), container.m_iterator.pages()[startPage].startOffset())
, m_startPage(startPage)
, m_container(container)
, m_currentSequenceNumber(0)
{
}
2015-04-22 19:22:01 +02:00
/*!
* \brief Destroys the track.
*/
OggStream::~OggStream()
2018-03-07 01:17:50 +01:00
{
}
2015-04-22 19:22:01 +02:00
void OggStream::internalParseHeader(Diagnostics &diag)
2015-04-22 19:22:01 +02:00
{
static const string context("parsing OGG page header");
2016-03-22 22:52:36 +01:00
2015-04-22 19:22:01 +02:00
// read basic information from first page
OggIterator &iterator = m_container.m_iterator;
const OggPage &firstPage = iterator.pages()[m_startPage];
m_version = firstPage.streamStructureVersion();
m_id = firstPage.streamSerialNumber();
2016-03-22 22:52:36 +01:00
2015-04-22 19:22:01 +02:00
// ensure iterator is setup properly
2016-05-14 00:24:01 +02:00
iterator.setFilter(firstPage.streamSerialNumber());
2015-04-22 19:22:01 +02:00
iterator.setPageIndex(m_startPage);
2016-03-22 22:52:36 +01:00
2015-04-22 19:22:01 +02:00
// iterate through segments using OggIterator
2018-03-07 01:17:50 +01:00
for (bool hasIdentificationHeader = false, hasCommentHeader = false; iterator && (!hasIdentificationHeader || !hasCommentHeader); ++iterator) {
2016-01-17 19:32:58 +01:00
const uint32 currentSize = iterator.currentSegmentSize();
2018-03-07 01:17:50 +01:00
if (currentSize >= 8) {
2015-04-22 19:22:01 +02:00
// determine stream format
inputStream().seekg(static_cast<streamoff>(iterator.currentSegmentOffset()));
2016-01-17 19:32:58 +01:00
const uint64 sig = reader().readUInt64BE();
2016-03-22 22:52:36 +01:00
2018-03-07 01:17:50 +01:00
if ((sig & 0x00ffffffffffff00u) == 0x00766F7262697300u) {
2015-04-22 19:22:01 +02:00
// Vorbis header detected
2018-03-07 01:17:50 +01:00
switch (m_format.general) {
case GeneralMediaFormat::Unknown:
m_format = GeneralMediaFormat::Vorbis;
m_mediaType = MediaType::Audio;
2015-04-22 19:22:01 +02:00
break;
case GeneralMediaFormat::Vorbis:
2015-04-22 19:22:01 +02:00
break;
default:
diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
2016-05-14 00:24:01 +02:00
continue;
2015-04-22 19:22:01 +02:00
}
2016-05-14 00:24:01 +02:00
2015-04-22 19:22:01 +02:00
// check header type
2018-03-07 01:17:50 +01:00
switch (sig >> 56) {
2015-04-22 19:22:01 +02:00
case VorbisPackageTypes::Identification:
2018-03-07 01:17:50 +01:00
if (!hasIdentificationHeader) {
2016-01-17 19:32:58 +01:00
// parse identification header
2015-04-22 19:22:01 +02:00
VorbisIdentificationHeader ind;
ind.parseHeader(iterator);
2016-01-17 19:32:58 +01:00
m_version = ind.version();
2015-04-22 19:22:01 +02:00
m_channelCount = ind.channels();
m_samplingFrequency = ind.sampleRate();
2018-03-07 01:17:50 +01:00
if (ind.nominalBitrate()) {
2015-04-22 19:22:01 +02:00
m_bitrate = ind.nominalBitrate();
2018-03-07 01:17:50 +01:00
} else if (ind.maxBitrate() == ind.minBitrate()) {
2015-04-22 19:22:01 +02:00
m_bitrate = ind.maxBitrate();
}
2018-03-07 01:17:50 +01:00
if (m_bitrate != 0.0) {
m_bitrate /= 1000.0;
2015-04-22 19:22:01 +02:00
}
calculateDurationViaSampleCount();
2015-04-22 19:22:01 +02:00
hasIdentificationHeader = true;
} else {
2018-03-07 01:17:50 +01:00
diag.emplace_back(DiagLevel::Critical,
"Vorbis identification header appears more than once. Oversupplied occurrence will be ignored.", context);
2015-04-22 19:22:01 +02:00
}
break;
case VorbisPackageTypes::Comments:
2016-01-17 19:32:58 +01:00
// Vorbis comment found -> notify container about comment
2018-03-07 01:17:50 +01:00
if (!hasCommentHeader) {
2016-05-14 00:24:01 +02:00
m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Vorbis);
2015-04-22 19:22:01 +02:00
hasCommentHeader = true;
} else {
2018-03-07 01:17:50 +01:00
diag.emplace_back(
DiagLevel::Critical, "Vorbis comment header appears more than once. Oversupplied occurrence will be ignored.", context);
2015-04-22 19:22:01 +02:00
}
break;
case VorbisPackageTypes::Setup:
break; // TODO
2018-03-07 01:17:50 +01:00
default:;
2015-04-22 19:22:01 +02:00
}
2016-03-22 22:52:36 +01:00
2018-03-07 01:17:50 +01:00
} else if (sig == 0x4F70757348656164u) {
2016-01-17 19:32:58 +01:00
// Opus header detected
2018-03-07 01:17:50 +01:00
switch (m_format.general) {
2016-01-17 19:32:58 +01:00
case GeneralMediaFormat::Unknown:
m_format = GeneralMediaFormat::Opus;
m_mediaType = MediaType::Audio;
break;
case GeneralMediaFormat::Opus:
break;
default:
diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
2016-05-14 00:24:01 +02:00
continue;
2016-01-17 19:32:58 +01:00
}
2018-03-07 01:17:50 +01:00
if (!hasIdentificationHeader) {
2016-01-17 19:32:58 +01:00
// parse identification header
OpusIdentificationHeader ind;
ind.parseHeader(iterator);
m_version = ind.version();
m_channelCount = ind.channels();
m_samplingFrequency = ind.sampleRate();
calculateDurationViaSampleCount(ind.preSkip());
2016-01-17 19:32:58 +01:00
hasIdentificationHeader = true;
} else {
2018-03-07 01:17:50 +01:00
diag.emplace_back(
DiagLevel::Critical, "Opus identification header appears more than once. Oversupplied occurrence will be ignored.", context);
2016-01-17 19:32:58 +01:00
}
2016-03-22 22:52:36 +01:00
2018-03-07 01:17:50 +01:00
} else if (sig == 0x4F70757354616773u) {
2016-01-17 19:32:58 +01:00
// Opus comment detected
2018-03-07 01:17:50 +01:00
switch (m_format.general) {
2016-01-17 19:32:58 +01:00
case GeneralMediaFormat::Unknown:
m_format = GeneralMediaFormat::Opus;
m_mediaType = MediaType::Audio;
break;
case GeneralMediaFormat::Opus:
break;
default:
diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
2016-05-14 00:24:01 +02:00
continue;
2016-01-17 19:32:58 +01:00
}
2016-05-14 00:24:01 +02:00
2016-01-17 19:32:58 +01:00
// notify container about comment
2018-03-07 01:17:50 +01:00
if (!hasCommentHeader) {
2016-05-14 00:24:01 +02:00
m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Opus);
2016-01-17 19:32:58 +01:00
hasCommentHeader = true;
} else {
2018-03-07 01:17:50 +01:00
diag.emplace_back(
DiagLevel::Critical, "Opus tags/comment header appears more than once. Oversupplied occurrence will be ignored.", context);
2016-01-17 19:32:58 +01:00
}
2016-03-22 22:52:36 +01:00
2018-03-07 01:17:50 +01:00
} else if ((sig & 0xFFFFFFFFFF000000u) == 0x7F464C4143000000u) {
2016-05-14 00:24:01 +02:00
// FLAC header detected
2018-03-07 01:17:50 +01:00
switch (m_format.general) {
2016-05-14 00:24:01 +02:00
case GeneralMediaFormat::Unknown:
m_format = GeneralMediaFormat::Flac;
m_mediaType = MediaType::Audio;
break;
case GeneralMediaFormat::Flac:
break;
default:
diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
2016-05-14 00:24:01 +02:00
continue;
}
2018-03-07 01:17:50 +01:00
if (!hasIdentificationHeader) {
2016-05-14 00:24:01 +02:00
// parse FLAC-to-Ogg mapping header
FlacToOggMappingHeader mapping;
const FlacMetaDataBlockStreamInfo &streamInfo = mapping.streamInfo();
mapping.parseHeader(iterator);
m_bitsPerSample = streamInfo.bitsPerSample();
m_channelCount = streamInfo.channelCount();
m_samplingFrequency = streamInfo.samplingFrequency();
m_sampleCount = streamInfo.totalSampleCount();
calculateDurationViaSampleCount();
2016-05-14 00:24:01 +02:00
hasIdentificationHeader = true;
} else {
2018-03-07 01:17:50 +01:00
diag.emplace_back(
DiagLevel::Critical, "FLAC-to-Ogg mapping header appears more than once. Oversupplied occurrence will be ignored.", context);
2016-05-14 00:24:01 +02:00
}
2018-03-07 01:17:50 +01:00
if (!hasCommentHeader) {
2016-05-14 00:24:01 +02:00
// a Vorbis comment should be following
2018-03-07 01:17:50 +01:00
if (++iterator) {
2016-05-14 00:24:01 +02:00
char buff[4];
iterator.read(buff, 4);
FlacMetaDataBlockHeader header;
header.parseHeader(buff);
2018-03-07 01:17:50 +01:00
if (header.type() == FlacMetaDataBlockType::VorbisComment) {
m_container.announceComment(
iterator.currentPageIndex(), iterator.currentSegmentIndex(), header.isLast(), GeneralMediaFormat::Flac);
2016-05-14 00:24:01 +02:00
hasCommentHeader = true;
} else {
2018-03-07 01:17:50 +01:00
diag.emplace_back(
DiagLevel::Critical, "OGG page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment.", context);
2016-05-14 00:24:01 +02:00
}
} else {
2018-03-07 01:17:50 +01:00
diag.emplace_back(
DiagLevel::Critical, "No more OGG pages after FLAC-to-Ogg mapping header (Vorbis comment expected).", context);
2016-05-14 00:24:01 +02:00
}
}
2018-03-07 01:17:50 +01:00
} else if ((sig & 0x00ffffffffffff00u) == 0x007468656F726100u) {
2016-01-17 19:32:58 +01:00
// Theora header detected
2018-03-07 01:17:50 +01:00
switch (m_format.general) {
2016-01-17 19:32:58 +01:00
case GeneralMediaFormat::Unknown:
m_format = GeneralMediaFormat::Theora;
m_mediaType = MediaType::Video;
break;
case GeneralMediaFormat::Theora:
break;
default:
diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
2016-05-14 00:24:01 +02:00
continue;
2016-01-17 19:32:58 +01:00
}
// TODO: read more information about Theora stream
2016-05-14 00:24:01 +02:00
2018-03-07 01:17:50 +01:00
} else if ((sig & 0xFFFFFFFFFFFF0000u) == 0x5370656578200000u) {
2017-09-03 18:34:33 +02:00
// Speex header detected
2018-03-07 01:17:50 +01:00
switch (m_format.general) {
2017-09-03 18:34:33 +02:00
case GeneralMediaFormat::Unknown:
m_format = GeneralMediaFormat::Speex;
m_mediaType = MediaType::Audio;
break;
case GeneralMediaFormat::Speex:
break;
default:
diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
2017-09-03 18:34:33 +02:00
continue;
}
// TODO: read more information about Speex stream
2018-03-07 01:17:50 +01:00
} else if (sig == 0x595556344D504547u) {
2017-09-03 18:26:32 +02:00
// YUV4MPEG header detected
2018-03-07 01:17:50 +01:00
switch (m_format.general) {
2017-09-03 18:26:32 +02:00
case GeneralMediaFormat::Unknown:
m_format = GeneralMediaFormat::UncompressedVideoFrames;
m_mediaType = MediaType::Video;
m_chromaFormat = "YUV";
break;
case GeneralMediaFormat::UncompressedVideoFrames:
break;
default:
diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context);
2017-09-03 18:26:32 +02:00
continue;
}
// TODO: read more information about YUV4MPEG stream
}
2017-09-03 18:34:33 +02:00
// currently only Vorbis, Opus, Theora, Speex and YUV4MPEG can be detected, TODO: detect more formats
2016-05-14 00:24:01 +02:00
} else {
// just ignore segments of only 8 byte or even less
// TODO: print warning?
}
// TODO: reduce code duplication
2015-04-22 19:22:01 +02:00
}
2016-03-22 22:52:36 +01:00
// estimate duration from size and bitrate if sample count and sample rate could not be determined
2018-03-07 01:17:50 +01:00
if (m_duration.isNull() && m_size && m_bitrate != 0.0) {
2015-04-22 19:22:01 +02:00
// calculate duration from stream size and bitrate, assuming 1 % overhead
m_duration = TimeSpan::fromSeconds(static_cast<double>(m_size) / (m_bitrate * 125.0) * 1.1);
}
m_headerValid = true;
}
void OggStream::calculateDurationViaSampleCount(uint16 preSkip)
{
// define predicate for finding pages of this stream by its stream serial number
const auto pred = bind(&OggPage::matchesStreamSerialNumber, _1, m_id);
// determine sample count
const auto &iterator = m_container.m_iterator;
2018-07-10 17:07:34 +02:00
if (!m_sampleCount && iterator.isLastPageFetched()) {
const auto &pages = iterator.pages();
const auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
const auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
2018-03-07 01:17:50 +01:00
if (firstPage != pages.cend() && lastPage != pages.crend()) {
m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
// must apply "pre-skip" here to calculate effective sample count and duration?
2018-03-07 01:17:50 +01:00
if (m_sampleCount > preSkip) {
m_sampleCount -= preSkip;
} else {
m_sampleCount = 0;
}
}
}
// actually calculate the duration
2018-03-07 01:17:50 +01:00
if (m_sampleCount && m_samplingFrequency != 0.0) {
m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
}
}
2018-03-07 01:17:50 +01:00
} // namespace TagParser