#include "./oggstream.h" #include "./oggcontainer.h" #include "../vorbis/vorbispackagetypes.h" #include "../vorbis/vorbisidentificationheader.h" #include "../opus/opusidentificationheader.h" #include "../flac/flactooggmappingheader.h" #include "../mediafileinfo.h" #include "../exceptions.h" #include "../mediaformat.h" #include #include #include using namespace std; using namespace std::placeholders; using namespace ChronoUtilities; namespace TagParser { /*! * \class Media::OggStream * \brief Implementation of Media::AbstractTrack for OGG streams. */ /*! * \brief Constructs a new track for the \a stream at the specified \a startOffset. */ OggStream::OggStream(OggContainer &container, vector::size_type startPage) : AbstractTrack(container.stream(), container.m_iterator.pages()[startPage].startOffset()), m_startPage(startPage), m_container(container), m_currentSequenceNumber(0) {} /*! * \brief Destroys the track. */ OggStream::~OggStream() {} void OggStream::internalParseHeader(Diagnostics &diag) { static const string context("parsing OGG page header"); // 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(); // ensure iterator is setup properly iterator.setFilter(firstPage.streamSerialNumber()); iterator.setPageIndex(m_startPage); // iterate through segments using OggIterator for(bool hasIdentificationHeader = false, hasCommentHeader = false; iterator && (!hasIdentificationHeader || !hasCommentHeader); ++iterator) { const uint32 currentSize = iterator.currentSegmentSize(); if(currentSize >= 8) { // determine stream format inputStream().seekg(iterator.currentSegmentOffset()); const uint64 sig = reader().readUInt64BE(); if((sig & 0x00ffffffffffff00u) == 0x00766F7262697300u) { // Vorbis header detected switch(m_format.general) { case GeneralMediaFormat::Unknown: m_format = GeneralMediaFormat::Vorbis; m_mediaType = MediaType::Audio; break; case GeneralMediaFormat::Vorbis: break; default: diag.emplace_back(DiagLevel::Warning, "Stream format is inconsistent.", context); continue; } // check header type switch(sig >> 56) { case VorbisPackageTypes::Identification: if(!hasIdentificationHeader) { // parse identification header VorbisIdentificationHeader ind; ind.parseHeader(iterator); m_version = ind.version(); m_channelCount = ind.channels(); m_samplingFrequency = ind.sampleRate(); if(ind.nominalBitrate()) { m_bitrate = ind.nominalBitrate(); } else if(ind.maxBitrate() == ind.minBitrate()) { m_bitrate = ind.maxBitrate(); } if(m_bitrate != 0.0) { m_bitrate /= 1000.0; } calculateDurationViaSampleCount(); hasIdentificationHeader = true; } else { diag.emplace_back(DiagLevel::Critical, "Vorbis identification header appears more than once. Oversupplied occurrence will be ignored.", context); } break; case VorbisPackageTypes::Comments: // Vorbis comment found -> notify container about comment if(!hasCommentHeader) { m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Vorbis); hasCommentHeader = true; } else { diag.emplace_back(DiagLevel::Critical, "Vorbis comment header appears more than once. Oversupplied occurrence will be ignored.", context); } break; case VorbisPackageTypes::Setup: break; // TODO default: ; } } else if(sig == 0x4F70757348656164u) { // Opus header detected switch(m_format.general) { 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); continue; } if(!hasIdentificationHeader) { // parse identification header OpusIdentificationHeader ind; ind.parseHeader(iterator); m_version = ind.version(); m_channelCount = ind.channels(); m_samplingFrequency = ind.sampleRate(); calculateDurationViaSampleCount(ind.preSkip()); hasIdentificationHeader = true; } else { diag.emplace_back(DiagLevel::Critical, "Opus identification header appears more than once. Oversupplied occurrence will be ignored.", context); } } else if(sig == 0x4F70757354616773u) { // Opus comment detected switch(m_format.general) { 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); continue; } // notify container about comment if(!hasCommentHeader) { m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Opus); hasCommentHeader = true; } else { diag.emplace_back(DiagLevel::Critical, "Opus tags/comment header appears more than once. Oversupplied occurrence will be ignored.", context); } } else if((sig & 0xFFFFFFFFFF000000u) == 0x7F464C4143000000u) { // FLAC header detected switch(m_format.general) { 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); continue; } if(!hasIdentificationHeader) { // 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(); hasIdentificationHeader = true; } else { diag.emplace_back(DiagLevel::Critical, "FLAC-to-Ogg mapping header appears more than once. Oversupplied occurrence will be ignored.", context); } if(!hasCommentHeader) { // a Vorbis comment should be following if(++iterator) { char buff[4]; iterator.read(buff, 4); FlacMetaDataBlockHeader header; header.parseHeader(buff); if(header.type() == FlacMetaDataBlockType::VorbisComment) { m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), header.isLast(), GeneralMediaFormat::Flac); hasCommentHeader = true; } else { diag.emplace_back(DiagLevel::Critical, "OGG page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment.", context); } } else { diag.emplace_back(DiagLevel::Critical, "No more OGG pages after FLAC-to-Ogg mapping header (Vorbis comment expected).", context); } } } else if((sig & 0x00ffffffffffff00u) == 0x007468656F726100u) { // Theora header detected switch(m_format.general) { 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); continue; } // TODO: read more information about Theora stream } else if((sig & 0xFFFFFFFFFFFF0000u) == 0x5370656578200000u) { // Speex header detected switch(m_format.general) { 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); continue; } // TODO: read more information about Speex stream } else if(sig == 0x595556344D504547u) { // YUV4MPEG header detected switch(m_format.general) { 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); continue; } // TODO: read more information about YUV4MPEG stream } // currently only Vorbis, Opus, Theora, Speex and YUV4MPEG can be detected, TODO: detect more formats } else { // just ignore segments of only 8 byte or even less // TODO: print warning? } // TODO: reduce code duplication } // estimate duration from size and bitrate if sample count and sample rate could not be determined if(m_duration.isNull() && m_size && m_bitrate != 0.0) { // calculate duration from stream size and bitrate, assuming 1 % overhead m_duration = TimeSpan::fromSeconds(static_cast(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; if(!m_sampleCount && iterator.areAllPagesFetched()) { 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); 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? if(m_sampleCount > preSkip) { m_sampleCount -= preSkip; } else { m_sampleCount = 0; } } } // actually calculate the duration if(m_sampleCount && m_samplingFrequency != 0.0) { m_duration = TimeSpan::fromSeconds(static_cast(m_sampleCount) / m_samplingFrequency); } } }