diff --git a/src/Session.cpp b/src/Session.cpp index 283ce39d3..2a1097ca1 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -1255,7 +1255,12 @@ bool CSession::GetNextSample(ISampleReader*& sampleReader) } else if (res) { - CheckFragmentDuration(*res); + if (res->m_hasSegmentChanged) + { + OnSegmentChangedRead(res); + res->m_hasSegmentChanged = false; + } + ISampleReader* sr{res->GetReader()}; if (sr->PTS() != STREAM_NOPTS_VALUE) @@ -1432,25 +1437,22 @@ void CSession::OnStreamChange(adaptive::AdaptiveStream* adStream) } } -void CSession::CheckFragmentDuration(CStream& stream) +void CSession::OnSegmentChangedRead(CStream* stream) { - uint64_t nextTs; - uint64_t nextDur; - ISampleReader* streamReader{stream.GetReader()}; - if (!streamReader) + if (m_adaptiveTree->IsLive()) { - LOG::LogF(LOGERROR, "Cannot get the stream sample reader"); - return; - } + ISampleReader* sr = stream->GetReader(); + uint64_t duration; - if (stream.m_hasSegmentChanged && streamReader->GetNextFragmentInfo(nextTs, nextDur)) - { - m_adaptiveTree->SetFragmentDuration( - stream.m_adStream.getPeriod(), stream.m_adStream.getAdaptationSet(), - stream.m_adStream.getRepresentation(), stream.m_adStream.getSegmentPos(), nextTs, - static_cast(nextDur), streamReader->GetTimeScale()); + if (sr->GetFragmentInfo(duration)) + { + adaptive::AdaptiveStream& adStream = stream->m_adStream; + + m_adaptiveTree->InsertLiveSegment(adStream.getPeriod(), adStream.getAdaptationSet(), + adStream.getRepresentation(), adStream.getSegmentPos(), + 0, duration, sr->GetTimeScale()); + } } - stream.m_hasSegmentChanged = false; } const AP4_UI08* CSession::GetDefaultKeyId(const uint16_t index) const diff --git a/src/Session.h b/src/Session.h index 46fe1a16c..78aec67f9 100644 --- a/src/Session.h +++ b/src/Session.h @@ -334,11 +334,12 @@ class ATTR_DLL_LOCAL CSession : public adaptive::AdaptiveStreamObserver void OnStreamChange(adaptive::AdaptiveStream* adStream) override; protected: - /*! \brief If available, read the duration and timestamp of the next fragment and - * set the related members - * \param adStream [OUT] The adaptive stream to check + /*! + * \brief Event raised when the current segment is changed and + * the data has already been read by the sample reader. + * \param stream The stream for which segment has changed. */ - void CheckFragmentDuration(CStream& stream); + void OnSegmentChangedRead(CStream* stream); /*! \brief Check for and load decrypter module matching the supplied key system * \param key_system [OUT] Will be assigned to if a decrypter is found matching diff --git a/src/Stream.cpp b/src/Stream.cpp index 04e0510c6..6b7832c90 100644 --- a/src/Stream.cpp +++ b/src/Stream.cpp @@ -42,3 +42,9 @@ void CStream::Reset() m_mainId = 0; } } + +void SESSION::CStream::SetReader(std::unique_ptr reader) +{ + m_streamReader = std::move(reader); + m_streamReader->SetObserver(&m_adStream); +} diff --git a/src/Stream.h b/src/Stream.h index 81fe38c5c..783669897 100644 --- a/src/Stream.h +++ b/src/Stream.h @@ -55,7 +55,7 @@ class ATTR_DLL_LOCAL CStream * \brief Set the stream sample reader * \param reader The reader */ - void SetReader(std::unique_ptr reader) { m_streamReader = std::move(reader); } + void SetReader(std::unique_ptr reader); /*! * \brief Get the stream file handler pointer diff --git a/src/common/AdaptiveStream.cpp b/src/common/AdaptiveStream.cpp index 1cb286b66..f4fba6822 100644 --- a/src/common/AdaptiveStream.cpp +++ b/src/common/AdaptiveStream.cpp @@ -434,6 +434,12 @@ int AdaptiveStream::SecondsSinceUpdate() const .count()); } +void AdaptiveStream::OnTFRFatom(uint64_t ts, uint64_t duration, uint32_t mediaTimescale) +{ + tree_.InsertLiveSegment(current_period_, current_adp_, current_rep_, getSegmentPos(), ts, + duration, mediaTimescale); +} + bool AdaptiveStream::parseIndexRange(PLAYLIST::CRepresentation* rep, const std::vector& buffer) { diff --git a/src/common/AdaptiveStream.h b/src/common/AdaptiveStream.h index 1347e3087..3a189a37b 100644 --- a/src/common/AdaptiveStream.h +++ b/src/common/AdaptiveStream.h @@ -10,6 +10,8 @@ #include "AdaptiveTree.h" +#include "../samplereader/SampleReader.h" + #include #include #include @@ -30,7 +32,7 @@ class AdaptiveStream; virtual void OnStreamChange(AdaptiveStream *stream) = 0; }; - class ATTR_DLL_LOCAL AdaptiveStream + class ATTR_DLL_LOCAL AdaptiveStream : public SampleReaderObserver { public: AdaptiveStream(AdaptiveTree& tree, @@ -88,6 +90,8 @@ class AdaptiveStream; void SetSegmentFileOffset(uint64_t offset) { m_segmentFileOffset = offset; }; bool StreamChanged() { return stream_changed_; } + void OnTFRFatom(uint64_t ts, uint64_t duration, uint32_t mediaTimescale) override; + protected: virtual bool parseIndexRange(PLAYLIST::CRepresentation* rep, const std::vector& buffer); diff --git a/src/common/AdaptiveTree.cpp b/src/common/AdaptiveTree.cpp index 50a6172ea..42f7cd948 100644 --- a/src/common/AdaptiveTree.cpp +++ b/src/common/AdaptiveTree.cpp @@ -96,81 +96,6 @@ namespace adaptive repr->current_segment_ = nullptr; } - void AdaptiveTree::SetFragmentDuration(PLAYLIST::CPeriod* period, - PLAYLIST::CAdaptationSet* adpSet, - PLAYLIST::CRepresentation* repr, - size_t pos, - uint64_t timestamp, - uint32_t fragmentDuration, - uint32_t movie_timescale) - { - if (!m_isLive || HasManifestUpdatesSegs()) - return; - - // Check if its the last frame we watch - if (!adpSet->SegmentTimelineDuration().IsEmpty()) - { - if (pos == adpSet->SegmentTimelineDuration().GetSize() - 1) - { - adpSet->SegmentTimelineDuration().Insert( - static_cast(static_cast(fragmentDuration) * - period->GetTimescale() / movie_timescale)); - } - else - { - repr->expired_segments_++; - return; - } - } - else if (pos != repr->SegmentTimeline().GetSize() - 1) - return; - - // Add new segment - // This may happen for example when a DASH live manifest - // has very long duration validity (set by minimumUpdatePeriod) and segments - // dont cover the entire duration until minimumUpdatePeriod interval time - // in this new segments must be added until the future manifest update - CSegment* segment = repr->SegmentTimeline().Get(pos); - - if (!segment) - { - LOG::LogF(LOGERROR, "Segment at position %zu not found from representation id: %s", pos, - repr->GetId().data()); - return; - } - - if (segment->HasByteRange()) - return; - - CSegment segCopy = *segment; - - if (!timestamp) - { - LOG::LogF(LOGDEBUG, "Scale fragment duration: fdur:%u, rep-scale:%u, mov-scale:%u", - fragmentDuration, repr->GetTimescale(), movie_timescale); - fragmentDuration = static_cast( - (static_cast(fragmentDuration) * repr->GetTimescale()) / movie_timescale); - } - else - { - LOG::LogF(LOGDEBUG, "Fragment duration from timestamp: ts:%llu, base:%llu, s-pts:%llu", - timestamp, base_time_, segCopy.startPTS_); - fragmentDuration = static_cast(timestamp - base_time_ - segCopy.startPTS_); - } - - segCopy.startPTS_ += fragmentDuration; - segCopy.m_time += fragmentDuration; - segCopy.m_number++; - - LOG::LogF(LOGDEBUG, "Insert live segment: pts: %llu number: %llu", segCopy.startPTS_, - segCopy.m_number); - - for (auto& repr : adpSet->GetRepresentations()) - { - repr->SegmentTimeline().Insert(segCopy); - } - } - void AdaptiveTree::OnDataArrived(uint64_t segNum, uint16_t psshSet, uint8_t iv[16], diff --git a/src/common/AdaptiveTree.h b/src/common/AdaptiveTree.h index 0d3e59f59..92a9f62bd 100644 --- a/src/common/AdaptiveTree.h +++ b/src/common/AdaptiveTree.h @@ -63,7 +63,6 @@ class ATTR_DLL_LOCAL AdaptiveTree uint64_t m_totalTimeSecs{0}; // Total playing time in seconds uint64_t stream_start_{0}; uint64_t available_time_{0}; - uint64_t base_time_{0}; // SmoothTree only, the lower start PTS time between all StreamIndex tags uint64_t m_liveDelay{0}; // Apply a delay in seconds from the live edge std::string m_supportedKeySystem; @@ -133,13 +132,27 @@ class ATTR_DLL_LOCAL AdaptiveTree void FreeSegments(PLAYLIST::CPeriod* period, PLAYLIST::CRepresentation* repr); - void SetFragmentDuration(PLAYLIST::CPeriod* period, - PLAYLIST::CAdaptationSet* adpSet, - PLAYLIST::CRepresentation* repr, - size_t pos, - uint64_t timestamp, - uint32_t fragmentDuration, - uint32_t movie_timescale); + /*! + * \brief Some adaptive streaming protocols allow the client to download the live playlist once and + * build future segments based on metadata contained in the fragments e.g. to avoid repeated + * manifest downloads or to cover the duration of a period not fully covered by the provided timeline. + * \param period Current period + * \param adpSet Current adaptation set + * \param repr Current representation + * \param pos Current segment position + * \param timestamp Fragment start timestamp + * \param fragmentDuration Fragment duration + * \param movieTimescale Fragment movie timescale + */ + virtual void InsertLiveSegment(PLAYLIST::CPeriod* period, + PLAYLIST::CAdaptationSet* adpSet, + PLAYLIST::CRepresentation* repr, + size_t pos, + uint64_t timestamp, + uint64_t fragmentDuration, + uint32_t movieTimescale) + { + } // Insert a PSSHSet to the specified Period and return the position uint16_t InsertPsshSet(PLAYLIST::StreamType streamType, diff --git a/src/common/SegTemplate.cpp b/src/common/SegTemplate.cpp index 1372d27b5..7a319092b 100644 --- a/src/common/SegTemplate.cpp +++ b/src/common/SegTemplate.cpp @@ -9,12 +9,14 @@ #include "SegTemplate.h" #include "Segment.h" #include "../utils/log.h" +#include "../utils/StringUtils.h" #include "kodi/tools/StringUtils.h" #include // sprintf using namespace PLAYLIST; +using namespace UTILS; using namespace kodi::tools; PLAYLIST::CSegmentTemplate::CSegmentTemplate(CSegmentTemplate* parent /* = nullptr */) @@ -90,43 +92,83 @@ CSegment PLAYLIST::CSegmentTemplate::MakeInitSegment() return seg; } -std::string PLAYLIST::CSegmentTemplate::FormatUrl(const std::string url, +std::string PLAYLIST::CSegmentTemplate::FormatUrl(std::string_view url, const std::string id, const uint32_t bandwidth, const uint64_t number, const uint64_t time) { - std::vector chunks = StringUtils::Split(url, '$'); + size_t curPos{0}; + std::string ret; - if (chunks.size() > 1) + do { - for (size_t i = 0; i < chunks.size(); i++) + size_t chPos = url.find('$', curPos); + if (chPos == std::string::npos) { - if (chunks.at(i) == "RepresentationID") - chunks.at(i) = id; - else if (chunks.at(i).find("Bandwidth") == 0) - FormatIdentifier(chunks.at(i), static_cast(bandwidth)); - else if (chunks.at(i).find("Number") == 0) - FormatIdentifier(chunks.at(i), number); - else if (chunks.at(i).find("Time") == 0) - FormatIdentifier(chunks.at(i), time); + // No other identifiers to substitute + ret += url.substr(curPos); + curPos = url.size(); + break; } - std::string replacedUrl = ""; - for (size_t i = 0; i < chunks.size(); i++) + ret += url.substr(curPos, chPos - curPos); + + size_t nextChPos = url.find('$', chPos + 1); + + if (nextChPos == std::string::npos) + nextChPos = url.size(); + + std::string_view identifier = url.substr(chPos, nextChPos - chPos + 1); + + if (identifier == "$$") // Escape sequence { - replacedUrl += chunks.at(i); + ret += "$"; + curPos = nextChPos + 1; } - return replacedUrl; - } + else if (identifier == "$RepresentationID$") + { + ret += id; + curPos = nextChPos + 1; + } + else if (STRING::StartsWith(identifier, "$Number")) + { + ret += FormatIdentifier(identifier, number); + curPos = nextChPos + 1; + } + else if (STRING::StartsWith(identifier, "$Time")) + { + ret += FormatIdentifier(identifier, time); + curPos = nextChPos + 1; + } + else if (STRING::StartsWith(identifier, "$Bandwidth")) + { + ret += FormatIdentifier(identifier, static_cast(bandwidth)); + curPos = nextChPos + 1; + } + else // Unknow indentifier, or $ char that isnt part of an identifier + { + if (nextChPos != url.size()) + identifier.remove_suffix(1); + ret += identifier; + curPos = nextChPos; + } + } while (curPos < url.size()); + + return ret; +} + +std::string PLAYLIST::CSegmentTemplate::FormatIdentifier(std::string_view identifier, + const uint64_t value) +{ + if (identifier.back() == '$') + identifier.remove_suffix(1); else { - return url; + LOG::LogF(LOGWARNING, "Cannot format template identifier because malformed"); + return std::string{identifier}; } -} -void PLAYLIST::CSegmentTemplate::FormatIdentifier(std::string& identifier, const uint64_t value) -{ size_t formatTagIndex = identifier.find("%0"); std::string formatTag = "%01d"; // default format tag @@ -143,7 +185,7 @@ void PLAYLIST::CSegmentTemplate::FormatIdentifier(std::string& identifier, const case 'o': break; // supported conversions as dash.js default: - return; // leave as is + return std::string{identifier}; // leave as is } } // sprintf expect the right length of data type @@ -155,8 +197,10 @@ void PLAYLIST::CSegmentTemplate::FormatIdentifier(std::string& identifier, const char substitution[128]; if (std::sprintf(substitution, formatTag.c_str(), value) > 0) - identifier = substitution; + return substitution; else LOG::LogF(LOGERROR, "Cannot convert value \"%llu\" with \"%s\" format tag", value, formatTag.c_str()); + + return std::string{identifier}; } diff --git a/src/common/SegTemplate.h b/src/common/SegTemplate.h index 7d0a29691..cdeb86301 100644 --- a/src/common/SegTemplate.h +++ b/src/common/SegTemplate.h @@ -52,14 +52,14 @@ class ATTR_DLL_LOCAL CSegmentTemplate CSegment MakeInitSegment(); - std::string FormatUrl(const std::string url, + std::string FormatUrl(std::string_view url, const std::string id, const uint32_t bandwidth, const uint64_t number, const uint64_t time); private: - void FormatIdentifier(std::string& identifier, const uint64_t value); + std::string FormatIdentifier(std::string_view identifier, const uint64_t value); std::string m_initialization; std::string m_media; diff --git a/src/parser/DASHTree.cpp b/src/parser/DASHTree.cpp index 2558cb99d..bb1045d67 100644 --- a/src/parser/DASHTree.cpp +++ b/src/parser/DASHTree.cpp @@ -1710,6 +1710,69 @@ void adaptive::CDashTree::RefreshLiveSegments() } } +void adaptive::CDashTree::InsertLiveSegment(PLAYLIST::CPeriod* period, + PLAYLIST::CAdaptationSet* adpSet, + PLAYLIST::CRepresentation* repr, + size_t pos, + uint64_t timestamp, + uint64_t fragmentDuration, + uint32_t movieTimescale) +{ + if (HasManifestUpdatesSegs()) + return; + + // Check if its the last frame we watch + if (!adpSet->SegmentTimelineDuration().IsEmpty()) + { + if (pos == adpSet->SegmentTimelineDuration().GetSize() - 1) + { + adpSet->SegmentTimelineDuration().Insert( + static_cast(fragmentDuration * period->GetTimescale() / movieTimescale)); + } + else + { + repr->expired_segments_++; + return; + } + } + else if (pos != repr->SegmentTimeline().GetSize() - 1) + return; + + // When a live manifest has very long duration validity (set by minimumUpdatePeriod) + // and segments dont cover the entire duration until minimumUpdatePeriod interval time + // we add new segments until the future manifest update + CSegment* segment = repr->SegmentTimeline().Get(pos); + + if (!segment) + { + LOG::LogF(LOGERROR, "Segment at position %zu not found from representation id: %s", pos, + repr->GetId().data()); + return; + } + + if (segment->HasByteRange()) + return; + + CSegment segCopy = *segment; + + LOG::LogF(LOGDEBUG, + "Scale fragment duration (duration: %llu, repr. timescale: %u, movie timescale: %u)", + fragmentDuration, repr->GetTimescale(), movieTimescale); + fragmentDuration = (fragmentDuration * repr->GetTimescale()) / movieTimescale; + + segCopy.startPTS_ += fragmentDuration; + segCopy.m_time += fragmentDuration; + segCopy.m_number++; + + LOG::LogF(LOGDEBUG, "Insert live segment to adptation set \"%s\" (PTS: %llu, number: %llu)", + adpSet->GetId().data(), segCopy.startPTS_, segCopy.m_number); + + for (auto& repr : adpSet->GetRepresentations()) + { + repr->SegmentTimeline().Insert(segCopy); + } +} + uint64_t adaptive::CDashTree::GetTimestamp() { return UTILS::GetTimestamp(); diff --git a/src/parser/DASHTree.h b/src/parser/DASHTree.h index 7be7a97fd..ffe8c6e4a 100644 --- a/src/parser/DASHTree.h +++ b/src/parser/DASHTree.h @@ -36,6 +36,14 @@ class ATTR_DLL_LOCAL CDashTree : public adaptive::AdaptiveTree const std::map& headers, const std::string& data) override; + virtual void InsertLiveSegment(PLAYLIST::CPeriod* period, + PLAYLIST::CAdaptationSet* adpSet, + PLAYLIST::CRepresentation* repr, + size_t pos, + uint64_t timestamp, + uint64_t fragmentDuration, + uint32_t movieTimescale); + protected: virtual CDashTree* Clone() const override { return new CDashTree{*this}; } diff --git a/src/parser/SmoothTree.cpp b/src/parser/SmoothTree.cpp index 46fd392cc..d42b2e0dd 100644 --- a/src/parser/SmoothTree.cpp +++ b/src/parser/SmoothTree.cpp @@ -46,6 +46,7 @@ bool adaptive::CSmoothTree::Open(std::string_view url, m_currentPeriod = m_periods[0].get(); + CreateSegmentTimeline(); SortTree(); return true; @@ -83,7 +84,6 @@ bool adaptive::CSmoothTree::ParseManifest(const std::string& data) } m_totalTimeSecs = period->GetDuration() / period->GetTimescale(); - base_time_ = NO_PTS_VALUE; // Parse tag PRProtectionParser protParser; @@ -96,8 +96,9 @@ bool adaptive::CSmoothTree::ParseManifest(const std::string& data) pugi::xml_node nodeProtHead = nodeProt.child("ProtectionHeader"); if (nodeProtHead) { - if (STRING::CompareNoCase(XML::GetAttrib(nodeProtHead, "SystemID"), - "9A04F079-9840-4286-AB92-E65BE0885F95")) + // SystemID can be wrapped by {} + if (STRING::Contains(XML::GetAttrib(nodeProtHead, "SystemID"), + "9A04F079-9840-4286-AB92-E65BE0885F95")) { if (protParser.ParseHeader(nodeProtHead.child_value())) { @@ -133,15 +134,52 @@ void adaptive::CSmoothTree::ParseTagStreamIndex(pugi::xml_node nodeSI, { std::unique_ptr adpSet = CAdaptationSet::MakeUniquePtr(period); + if (nodeSI.attribute("ParentStreamIndex")) + { + LOG::LogF(LOGDEBUG, "Skipped tag, \"ParentStreamIndex\" attribute is not supported."); + return; + } + adpSet->SetName(XML::GetAttrib(nodeSI, "Name")); + adpSet->SetId("SI:" + adpSet->GetName()); std::string_view type = XML::GetAttrib(nodeSI, "Type"); + std::string_view subtype = XML::GetAttrib(nodeSI, "Subtype"); + if (type == "video") + { + // Skip know unsupported subtypes + if (subtype == "ZOET" || // Trick mode + subtype == "CHAP") // Chapter headings + { + LOG::LogF(LOGDEBUG, "Skipped tag, Subtype \"%s\" not supported.", subtype.data()); + return; + } adpSet->SetStreamType(StreamType::VIDEO); + } else if (type == "audio") + { adpSet->SetStreamType(StreamType::AUDIO); + } else if (type == "text") + { + // Skip know unsupported subtypes + if (subtype == "SCMD" || // Script commands + subtype == "CHAP" || // Chapter headings + subtype == "CTRL" || // Control events (ADS) + subtype == "DATA" || // Application data + subtype == "ADI3") // ADS sparse tracks + { + LOG::LogF(LOGDEBUG, "Skipped tag, Subtype \"%s\" not supported.", + subtype.data()); + return; + } + else if (subtype == "CAPT" || subtype == "DESC") // Captions + { + adpSet->SetIsImpaired(true); + } adpSet->SetStreamType(StreamType::SUBTITLE); + } uint16_t psshSetPos = PSSHSET_POS_DEFAULT; @@ -237,8 +275,8 @@ void adaptive::CSmoothTree::ParseTagStreamIndex(pugi::xml_node nodeSI, return; } - if (adpSet->GetStartPTS() < base_time_) - base_time_ = adpSet->GetStartPTS(); + if (m_ptsBase == NO_PTS_VALUE || adpSet->GetStartPTS() < m_ptsBase) + m_ptsBase = adpSet->GetStartPTS(); period->AddAdaptationSet(adpSet); } @@ -253,7 +291,10 @@ void adaptive::CSmoothTree::ParseTagQualityLevel(pugi::xml_node nodeQI, repr->SetBaseUrl(adpSet->GetBaseUrl()); repr->SetTimescale(timescale); - repr->SetId(XML::GetAttrib(nodeQI, "Index")); + std::string id = "SI:" + adpSet->GetName() + " - QL:"; + id += XML::GetAttrib(nodeQI, "Index"); + repr->SetId(id); + repr->SetBandwidth(XML::GetAttribUint32(nodeQI, "Bitrate")); std::string fourCc; @@ -334,29 +375,99 @@ void adaptive::CSmoothTree::ParseTagQualityLevel(pugi::xml_node nodeQI, repr->SetSegmentTemplate(segTpl); - // Generate segment timeline - repr->SegmentTimeline().GetData().reserve(adpSet->SegmentTimelineDuration().GetSize()); + repr->assured_buffer_duration_ = m_settings.m_bufferAssuredDuration; + repr->max_buffer_duration_ = m_settings.m_bufferMaxDuration; + + repr->SetScaling(); + + adpSet->AddRepresentation(repr); +} + +void adaptive::CSmoothTree::CreateSegmentTimeline() +{ + for (auto& period : m_periods) + { + for (auto& adpSet : period->GetAdaptationSets()) + { + for (auto& repr : adpSet->GetRepresentations()) + { + repr->SegmentTimeline().GetData().reserve(adpSet->SegmentTimelineDuration().GetSize()); + + // Adjust PTS with the StreamIndex with lower PTS to sync streams during playback + uint64_t nextStartPts = adpSet->GetStartPTS() - m_ptsBase; + uint64_t index = 1; - uint64_t nextStartPts = adpSet->GetStartPTS() - base_time_; - uint64_t index = 1; + for (uint32_t segDuration : adpSet->SegmentTimelineDuration().GetData()) + { + CSegment seg; + seg.startPTS_ = nextStartPts; + seg.m_time = nextStartPts + m_ptsBase; + seg.m_number = index; + + repr->SegmentTimeline().GetData().emplace_back(seg); + + nextStartPts += segDuration; + index++; + } + } + } + } +} + +void adaptive::CSmoothTree::InsertLiveSegment(PLAYLIST::CPeriod* period, + PLAYLIST::CAdaptationSet* adpSet, + PLAYLIST::CRepresentation* repr, + size_t pos, + uint64_t timestamp, + uint64_t fragmentDuration, + uint32_t mediaTimescale) +{ + if (!m_isLive) + return; - for (uint32_t segDuration : adpSet->SegmentTimelineDuration().GetData()) + //! @todo: This old code is now wrong because InsertLiveSegment can be called many times + //! by the same segment, that will lead expired_segments_ to have a wrong value + //! expired_segments_ should be removed by implementing DVRWindowLength (and timeShiftBufferDepth on dash) + //! and then add a method to clear old segments from the timeline based on timeshift window + //! but this looks like that need to take care also of how works segment positions. + + // Check if its not the last frame we watch + //! @todo: this code prevent to add many fragments done by more callbacks + //! atm not found a good way to change this code by having smooth playback + if (pos != adpSet->SegmentTimelineDuration().GetSize() - 1) { - CSegment seg; - seg.startPTS_ = nextStartPts; - seg.m_time = nextStartPts + base_time_; - seg.m_number = index; + repr->expired_segments_++; + return; + } + + adpSet->SegmentTimelineDuration().Insert( + static_cast(fragmentDuration * period->GetTimescale() / mediaTimescale)); - repr->SegmentTimeline().GetData().emplace_back(seg); + CSegment* segment = repr->SegmentTimeline().Get(pos); - nextStartPts += segDuration; - index++; + if (!segment) + { + LOG::LogF(LOGERROR, "Segment at position %zu not found from representation id: %s", pos, + repr->GetId().data()); + return; } - repr->assured_buffer_duration_ = m_settings.m_bufferAssuredDuration; - repr->max_buffer_duration_ = m_settings.m_bufferMaxDuration; + CSegment segCopy = *segment; - repr->SetScaling(); + LOG::LogF(LOGDEBUG, + "Fragment duration from timestamp (timestamp: %llu, PTS base: %llu, start PTS: %llu)", + timestamp, m_ptsBase, segCopy.startPTS_); + fragmentDuration = timestamp - m_ptsBase - segCopy.startPTS_; - adpSet->AddRepresentation(repr); + segCopy.startPTS_ += fragmentDuration; + segCopy.m_time += fragmentDuration; + segCopy.m_number++; + + LOG::LogF(LOGDEBUG, "Insert live segment to adptation set \"%s\" (PTS: %llu, number: %llu)", + adpSet->GetId().data(), segCopy.startPTS_, segCopy.m_number); + + for (auto& repr : adpSet->GetRepresentations()) + { + repr->SegmentTimeline().Insert(segCopy); + } } diff --git a/src/parser/SmoothTree.h b/src/parser/SmoothTree.h index ee0ec095e..37652c7c7 100644 --- a/src/parser/SmoothTree.h +++ b/src/parser/SmoothTree.h @@ -32,6 +32,14 @@ class ATTR_DLL_LOCAL CSmoothTree : public AdaptiveTree virtual CSmoothTree* Clone() const override { return new CSmoothTree{*this}; } + virtual void InsertLiveSegment(PLAYLIST::CPeriod* period, + PLAYLIST::CAdaptationSet* adpSet, + PLAYLIST::CRepresentation* repr, + size_t pos, + uint64_t timestamp, + uint64_t fragmentDuration, + uint32_t mediaTimescale); + protected: virtual bool ParseManifest(const std::string& data); @@ -42,6 +50,9 @@ class ATTR_DLL_LOCAL CSmoothTree : public AdaptiveTree PLAYLIST::CAdaptationSet* adpSet, const uint32_t timescale, const uint16_t psshSetPos); + void CreateSegmentTimeline(); + + uint64_t m_ptsBase{PLAYLIST::NO_PTS_VALUE}; // The lower start PTS time between all StreamIndex tags }; } // namespace adaptive diff --git a/src/samplereader/ADTSSampleReader.h b/src/samplereader/ADTSSampleReader.h index 805539f01..7822a084e 100644 --- a/src/samplereader/ADTSSampleReader.h +++ b/src/samplereader/ADTSSampleReader.h @@ -32,7 +32,6 @@ class ATTR_DLL_LOCAL CADTSSampleReader : public ISampleReader, public ADTSReader uint64_t GetStartPTS() const override { return m_startPts; } void SetStartPTS(uint64_t pts) override { m_startPts = pts; } int64_t GetPTSDiff() const override { return m_ptsDiff; } - bool GetNextFragmentInfo(uint64_t& ts, uint64_t& dur) override { return false; } uint32_t GetTimeScale() const override { return 90000; } AP4_UI32 GetStreamId() const override { return m_streamId; } AP4_Size GetSampleDataSize() const override { return GetPacketSize(); } diff --git a/src/samplereader/FragmentedSampleReader.cpp b/src/samplereader/FragmentedSampleReader.cpp index e774554ce..3e8b0a320 100644 --- a/src/samplereader/FragmentedSampleReader.cpp +++ b/src/samplereader/FragmentedSampleReader.cpp @@ -17,6 +17,7 @@ #include "../codechandler/VP9CodecHandler.h" #include "../codechandler/WebVTTCodecHandler.h" #include "../utils/log.h" +#include "../utils/CharArrayParser.h" #include "../utils/Utils.h" using namespace UTILS; @@ -297,27 +298,16 @@ void CFragmentedSampleReader::SetPTSOffset(uint64_t offset) m_codecHandler->SetPTSOffset((offset * m_timeBaseInt) / m_timeBaseExt); } -bool CFragmentedSampleReader::GetNextFragmentInfo(uint64_t& ts, uint64_t& dur) +bool CFragmentedSampleReader::GetFragmentInfo(uint64_t& duration) { - if (m_nextDuration) - { - dur = m_nextDuration; - ts = m_nextTimestamp; - } + auto fragSampleTable = + dynamic_cast(FindTracker(m_track->GetId())->m_SampleTable); + if (fragSampleTable) + duration = fragSampleTable->GetDuration(); else { - auto fragSampleTable = - dynamic_cast(FindTracker(m_track->GetId())->m_SampleTable); - if (fragSampleTable) - { - dur = fragSampleTable->GetDuration(); - ts = 0; - } - else - { - LOG::LogF(LOGERROR, "Can't get FragmentSampleTable from track %u", m_track->GetId()); - return false; - } + LOG::LogF(LOGERROR, "Can't get FragmentSampleTable from track %u", m_track->GetId()); + return false; } return true; } @@ -355,25 +345,16 @@ AP4_Result CFragmentedSampleReader::ProcessMoof(AP4_ContainerAtom* moof, AP4_ContainerAtom* traf = AP4_DYNAMIC_CAST(AP4_ContainerAtom, moof->GetChild(AP4_ATOM_TYPE_TRAF, 0)); - //For ISM Livestreams we have an UUID atom with one / more following fragment durations - m_nextDuration = 0; - m_nextTimestamp = 0; AP4_Atom* atom{nullptr}; unsigned int atom_pos{0}; while ((atom = traf->GetChild(AP4_ATOM_TYPE_UUID, atom_pos++)) != nullptr) { - AP4_UuidAtom* uuid_atom{AP4_DYNAMIC_CAST(AP4_UuidAtom, atom)}; - if (memcmp(uuid_atom->GetUuid(), SMOOTHSTREAM_TFRFBOX_UUID, 16) == 0) + AP4_UuidAtom* uuidAtom{AP4_DYNAMIC_CAST(AP4_UuidAtom, atom)}; + // For smooth streaming live streams we have an TFRF atom with one / more following fragment durations + if (std::memcmp(uuidAtom->GetUuid(), SMOOTHSTREAM_TFRFBOX_UUID, 16) == 0) { - //verison(8) + flags(24) + numpairs(8) + pairs(ts(64)/dur(64))*numpairs - const AP4_DataBuffer& buf(AP4_DYNAMIC_CAST(AP4_UnknownUuidAtom, uuid_atom)->GetData()); - if (buf.GetDataSize() >= 21) - { - const uint8_t* data(buf.GetData()); - m_nextTimestamp = AP4_BytesToUInt64BE(data + 5); - m_nextDuration = AP4_BytesToUInt64BE(data + 13); - } + ParseTrafTfrf(uuidAtom); break; } } @@ -509,3 +490,42 @@ void CFragmentedSampleReader::UpdateSampleDescription() if ((m_decrypterCaps.flags & SSD::SSD_DECRYPTER::SSD_CAPS::SSD_ANNEXB_REQUIRED) != 0) m_codecHandler->ExtraDataToAnnexB(); } + +void CFragmentedSampleReader::ParseTrafTfrf(AP4_UuidAtom* uuidAtom) +{ + const AP4_DataBuffer& buf{AP4_DYNAMIC_CAST(AP4_UnknownUuidAtom, uuidAtom)->GetData()}; + CCharArrayParser parser; + parser.Reset(reinterpret_cast(buf.GetData()), static_cast(buf.GetDataSize())); + + if (parser.CharsLeft() < 5) + { + LOG::LogF(LOGERROR, "Wrong data length on TFRF atom."); + return; + } + uint8_t version = parser.ReadNextUnsignedChar(); + uint32_t flags = parser.ReadNextUnsignedInt24(); + uint8_t fragmentCount = parser.ReadNextUnsignedChar(); + + for (uint8_t index = 0; index < fragmentCount; index++) + { + uint64_t time; + uint64_t duration; + + if (version == 0) + { + time = static_cast(parser.ReadNextUnsignedInt()); + duration = static_cast(parser.ReadNextUnsignedInt()); + } + else if (version == 1) + { + time = parser.ReadNextUnsignedInt64(); + duration = parser.ReadNextUnsignedInt64(); + } + else + { + LOG::LogF(LOGWARNING, "Version %u of TFRF atom fragment is not supported.", version); + return; + } + m_observer->OnTFRFatom(time, duration, m_track->GetMediaTimeScale()); + } +} diff --git a/src/samplereader/FragmentedSampleReader.h b/src/samplereader/FragmentedSampleReader.h index 09ec365e3..668f13b84 100644 --- a/src/samplereader/FragmentedSampleReader.h +++ b/src/samplereader/FragmentedSampleReader.h @@ -45,7 +45,7 @@ class ATTR_DLL_LOCAL CFragmentedSampleReader : public ISampleReader, public AP4_ uint64_t GetStartPTS() const override { return m_startPts; } void SetStartPTS(uint64_t pts) override { m_startPts = pts; } int64_t GetPTSDiff() const override { return m_ptsDiff; } - bool GetNextFragmentInfo(uint64_t& ts, uint64_t& dur) override; + bool GetFragmentInfo(uint64_t& duration) override; uint32_t GetTimeScale() const override { return m_track->GetMediaTimeScale(); } CryptoInfo GetReaderCryptoInfo() const override { return m_readerCryptoInfo; } @@ -59,6 +59,7 @@ class ATTR_DLL_LOCAL CFragmentedSampleReader : public ISampleReader, public AP4_ private: void UpdateSampleDescription(); + void ParseTrafTfrf(AP4_UuidAtom* uuidAtom); AP4_Track* m_track; AP4_UI32 m_poolId{0}; @@ -84,7 +85,5 @@ class ATTR_DLL_LOCAL CFragmentedSampleReader : public ISampleReader, public AP4_ AP4_ProtectedSampleDescription* m_protectedDesc{nullptr}; Adaptive_CencSingleSampleDecrypter* m_singleSampleDecryptor; CAdaptiveCencSampleDecrypter* m_decrypter{nullptr}; - uint64_t m_nextDuration{0}; - uint64_t m_nextTimestamp{0}; CryptoInfo m_readerCryptoInfo{}; }; diff --git a/src/samplereader/SampleReader.h b/src/samplereader/SampleReader.h index 9455bdcee..578e44f95 100644 --- a/src/samplereader/SampleReader.h +++ b/src/samplereader/SampleReader.h @@ -22,11 +22,14 @@ #include -// Forward namespace/class -namespace SESSION +class ATTR_DLL_LOCAL SampleReaderObserver { -class CSession; -} +public: + /*! + * \brief Callback raised when each fragment contained in a (fMP4) TFRF atom is parsed + */ + virtual void OnTFRFatom(uint64_t ts, uint64_t duration, uint32_t mediaTimescale) = 0; +}; class ATTR_DLL_LOCAL ISampleReader { @@ -46,7 +49,14 @@ class ATTR_DLL_LOCAL ISampleReader virtual int64_t GetPTSDiff() const = 0; virtual void SetStartPTS(uint64_t pts) = 0; virtual uint64_t GetStartPTS() const = 0; - virtual bool GetNextFragmentInfo(uint64_t& ts, uint64_t& dur) = 0; + + /*! + * \brief Read info about fragment on current segment (fMP4) + * \param duration[OUT] Set the duration of current media sample + * \return True if the fragment info was successfully retrieved, otherwise false + */ + virtual bool GetFragmentInfo(uint64_t& duration) { return false; } + virtual uint32_t GetTimeScale() const = 0; virtual AP4_UI32 GetStreamId() const = 0; virtual AP4_Size GetSampleDataSize() const = 0; @@ -87,6 +97,11 @@ class ATTR_DLL_LOCAL ISampleReader std::future_status::ready; } + void SetObserver(SampleReaderObserver* observer) { m_observer = observer; } + +protected: + SampleReaderObserver* m_observer{nullptr}; + private: std::future m_readSampleAsyncState; }; diff --git a/src/samplereader/SubtitleSampleReader.h b/src/samplereader/SubtitleSampleReader.h index 45a576e55..df128f327 100644 --- a/src/samplereader/SubtitleSampleReader.h +++ b/src/samplereader/SubtitleSampleReader.h @@ -39,7 +39,6 @@ class ATTR_DLL_LOCAL CSubtitleSampleReader : public ISampleReader uint64_t GetStartPTS() const override { return m_startPts; } void SetStartPTS(uint64_t pts) override { m_startPts = pts; } int64_t GetPTSDiff() const override { return m_ptsDiff; } - bool GetNextFragmentInfo(uint64_t& ts, uint64_t& dur) override { return false; } uint32_t GetTimeScale() const override { return 1000; } AP4_UI32 GetStreamId() const override { return m_streamId; } AP4_Size GetSampleDataSize() const override { return m_sampleData.GetDataSize(); } diff --git a/src/samplereader/TSSampleReader.h b/src/samplereader/TSSampleReader.h index 29d38cc7b..60e2b29c4 100644 --- a/src/samplereader/TSSampleReader.h +++ b/src/samplereader/TSSampleReader.h @@ -38,7 +38,6 @@ class ATTR_DLL_LOCAL CTSSampleReader : public ISampleReader, public TSReader uint64_t GetStartPTS() const override { return m_startPts; } void SetStartPTS(uint64_t pts) override { m_startPts = pts; } int64_t GetPTSDiff() const override { return m_ptsDiff; } - bool GetNextFragmentInfo(uint64_t& ts, uint64_t& dur) override { return false; } uint32_t GetTimeScale() const override { return 90000; } AP4_UI32 GetStreamId() const override { return m_typeMap[GetStreamType()]; } AP4_Size GetSampleDataSize() const override { return GetPacketSize(); } diff --git a/src/samplereader/WebmSampleReader.h b/src/samplereader/WebmSampleReader.h index c24e1e995..ae121ec8d 100644 --- a/src/samplereader/WebmSampleReader.h +++ b/src/samplereader/WebmSampleReader.h @@ -32,7 +32,6 @@ class ATTR_DLL_LOCAL CWebmSampleReader : public ISampleReader, public WebmReader uint64_t GetStartPTS() const override { return m_startPts; } void SetStartPTS(uint64_t pts) override { m_startPts = pts; } int64_t GetPTSDiff() const override { return m_ptsDiff; } - bool GetNextFragmentInfo(uint64_t& ts, uint64_t& dur) override { return false; } uint32_t GetTimeScale() const override { return 1000; } AP4_UI32 GetStreamId() const override { return m_streamId; } AP4_Size GetSampleDataSize() const override { return GetPacketSize(); } diff --git a/src/test/KodiStubs.h b/src/test/KodiStubs.h index 004ec5ef6..ce5051114 100644 --- a/src/test/KodiStubs.h +++ b/src/test/KodiStubs.h @@ -69,10 +69,23 @@ enum AdjustRefreshRateStatus ADJUST_REFRESHRATE_STATUS_ON_START, }; +enum INPUTSTREAM_TYPE +{ + INPUTSTREAM_TYPE_NONE = 0, + INPUTSTREAM_TYPE_VIDEO, + INPUTSTREAM_TYPE_AUDIO, + INPUTSTREAM_TYPE_SUBTITLE, + INPUTSTREAM_TYPE_TELETEXT, + INPUTSTREAM_TYPE_RDS, + INPUTSTREAM_TYPE_ID3, +}; + namespace kodi { namespace addon { +class InputstreamInfo; + inline std::string GetLocalizedString(uint32_t labelId, const std::string& defaultStr = "") { return defaultStr; diff --git a/src/test/TestSmoothTree.cpp b/src/test/TestSmoothTree.cpp index 046314a7c..c3d0ae923 100644 --- a/src/test/TestSmoothTree.cpp +++ b/src/test/TestSmoothTree.cpp @@ -82,3 +82,32 @@ TEST_F(SmoothTreeTest, CalculateBaseURLWithNoExtension) OpenTestFile("ism/TearsOfSteel.ism", "http://amssamples.streaming.mediaservices.windows.net/bc57e088-27ec-44e0-ac20-a85ccbcd50da/TearsOfSteel.ism/manifest"); EXPECT_EQ(tree->base_url_, "http://amssamples.streaming.mediaservices.windows.net/bc57e088-27ec-44e0-ac20-a85ccbcd50da/TearsOfSteel.ism/"); } + +TEST_F(SmoothTreeTest, CheckAsyncTimelineStartPTS) +{ + OpenTestFile("ism/live_async_streams.ism", "http://amssamples.streaming.mediaservices.windows.net/bc57e088-27ec-44e0-ac20-a85ccbcd50da/live_async_streams.ism/manifest"); + + // Each start with different chunk timestamp + // so to sync streams we adjust PTS with which has the lowest timestamp (CSmoothTree::m_ptsBase) + auto& period = tree->m_periods[0]; + auto& segTL = period->GetAdaptationSets()[0]->GetRepresentations()[0]->SegmentTimeline(); + + EXPECT_EQ(segTL.GetSize(), 30); + EXPECT_EQ(segTL.Get(0)->startPTS_, 7058030); + EXPECT_EQ(segTL.Get(0)->m_time, 3903180167058030); + EXPECT_EQ(segTL.Get(0)->m_number, 1); + + segTL = period->GetAdaptationSets()[1]->GetRepresentations()[0]->SegmentTimeline(); + + EXPECT_EQ(segTL.GetSize(), 30); + EXPECT_EQ(segTL.Get(0)->startPTS_, 71363); + EXPECT_EQ(segTL.Get(0)->m_time, 3903180160071363); + EXPECT_EQ(segTL.Get(0)->m_number, 1); + + segTL = period->GetAdaptationSets()[3]->GetRepresentations()[0]->SegmentTimeline(); + + EXPECT_EQ(segTL.GetSize(), 29); + EXPECT_EQ(segTL.Get(0)->startPTS_, 0); + EXPECT_EQ(segTL.Get(0)->m_time, 3903180160000000); + EXPECT_EQ(segTL.Get(0)->m_number, 1); +} diff --git a/src/test/TestUtils.cpp b/src/test/TestUtils.cpp index 4e83d1382..9580e9770 100644 --- a/src/test/TestUtils.cpp +++ b/src/test/TestUtils.cpp @@ -9,11 +9,13 @@ #include "TestHelper.h" #include "../common/AdaptiveTreeFactory.h" +#include "../common/SegTemplate.h" #include "../utils/UrlUtils.h" #include using namespace adaptive; +using namespace PLAYLIST; using namespace PLAYLIST_FACTORY; using namespace UTILS; @@ -226,3 +228,22 @@ TEST_F(UtilsTest, AdaptiveTreeFactory_ISM) type = InferManifestType("http://www.someservice.com/cdm1/manifest.isml", "", ""); EXPECT_EQ(type, PROPERTIES::ManifestType::ISM); } + +TEST_F(UtilsTest, SegTemplateFormatUrlChecks) +{ + CSegmentTemplate segTpl; + + std::string url = "https://cdn.com/example/$$$Number$$RepresentationID$$Bandwidth$$Time$"; + std::string ret = segTpl.FormatUrl(url, "repID", 1500, 1, 0); + EXPECT_EQ(ret, "https://cdn.com/example/$1repID15000"); + + // Dash placeholders use special char "$", but an url can use single "$" char along the path that must be kept + url = "https://cdn.com/_$_example/QualityLevels($Bandwidth$)/Fragments(video=$Time$)"; + ret = segTpl.FormatUrl(url, "repID", 1500, 1, 0); + EXPECT_EQ(ret, "https://cdn.com/_$_example/QualityLevels(1500)/Fragments(video=0)"); + + // Malformed, do nothing + url = "https://cdn.com/_$_example/$Bandwidth"; + ret = segTpl.FormatUrl(url, "repID", 1500, 1, 0); + EXPECT_EQ(ret, "https://cdn.com/_$_example/$Bandwidth"); +} diff --git a/src/test/manifests/ism/live_async_streams.ism b/src/test/manifests/ism/live_async_streams.ism new file mode 100644 index 000000000..f5f0e6ae7 --- /dev/null +++ b/src/test/manifests/ism/live_async_streams.ism @@ -0,0 +1,141 @@ + + + MgMAAAEAAQAoAzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AdgBYAHAAMQA5AGgAVAAzAHAATABNAGoAZgAvAFIAMwBuAGkARgBEAFgAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgAyAEQAVwB1ADkAYgArAGwAcwBYADgAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AbABvAGMAYQBsAGgAbwBzAHQALwB2ADIALwBzAGUAcgB2AGkAYwBlAHMALwBWAG8AUABsAGEAeQBSAGUAYQBkAHkAQQBxAHUAaQByAGUATABpAGMAZQBuAHMAZQAuAGMAZgBtADwALwBMAEEAXwBVAFIATAA+ADwATABVAEkAXwBVAFIATAA+AGgAdAB0AHAAOgAvAC8AbABvAGMAYQBsAGgAbwBzAHQALwB2ADIALwBzAGUAcgB2AGkAYwBlAHMALwBWAG8AUABsAGEAeQBSAGUAYQBkAHkAQQBxAHUAaQByAGUATABpAGMAZQBuAHMAZQAuAGMAZgBtADwALwBMAFUASQBfAFUAUgBMAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/utils/CharArrayParser.cpp b/src/utils/CharArrayParser.cpp index 330536270..3d7f2940e 100644 --- a/src/utils/CharArrayParser.cpp +++ b/src/utils/CharArrayParser.cpp @@ -94,6 +94,21 @@ uint16_t UTILS::CCharArrayParser::ReadLENextUnsignedShort() (static_cast(m_data[m_position - 1]) & 0xFF) << 8; } +uint32_t UTILS::CCharArrayParser::ReadNextUnsignedInt24() +{ + if (!m_data) + { + LOG::LogF(LOGERROR, "{} - No data to read"); + return 0; + } + m_position += 3; + if (m_position > m_limit) + LOG::LogF(LOGERROR, "{} - Position out of range"); + return (static_cast(m_data[m_position - 3]) & 0xFF) << 16 | + (static_cast(m_data[m_position - 2]) & 0xFF) << 8 | + (static_cast(m_data[m_position - 1]) & 0xFF); +} + uint32_t UTILS::CCharArrayParser::ReadNextUnsignedInt() { if (!m_data) @@ -110,6 +125,26 @@ uint32_t UTILS::CCharArrayParser::ReadNextUnsignedInt() (static_cast(m_data[m_position - 1]) & 0xFF); } +uint64_t UTILS::CCharArrayParser::ReadNextUnsignedInt64() +{ + if (!m_data) + { + LOG::LogF(LOGERROR, "{} - No data to read"); + return 0; + } + m_position += 8; + if (m_position > m_limit) + LOG::LogF(LOGERROR, "{} - Position out of range"); + return (static_cast(m_data[m_position - 8]) & 0xFF) << 56 | + (static_cast(m_data[m_position - 7]) & 0xFF) << 48 | + (static_cast(m_data[m_position - 6]) & 0xFF) << 40 | + (static_cast(m_data[m_position - 5]) & 0xFF) << 32 | + (static_cast(m_data[m_position - 4]) & 0xFF) << 24 | + (static_cast(m_data[m_position - 3]) & 0xFF) << 16 | + (static_cast(m_data[m_position - 2]) & 0xFF) << 8 | + (static_cast(m_data[m_position - 1]) & 0xFF); +} + std::string UTILS::CCharArrayParser::ReadNextString(int length) { if (!m_data) diff --git a/src/utils/CharArrayParser.h b/src/utils/CharArrayParser.h index cc1cfb7b1..dbd79fac2 100644 --- a/src/utils/CharArrayParser.h +++ b/src/utils/CharArrayParser.h @@ -80,6 +80,13 @@ class CCharArrayParser */ uint16_t ReadLENextUnsignedShort(); + /*! + * \brief Reads the next three chars as unsigned int 24bit value (it is assumed + * that the caller has already checked the availability of the data for its length) + * \return The unsigned int24 converted to uint32_t + */ + uint32_t ReadNextUnsignedInt24(); + /*! * \brief Reads the next four chars as unsigned int value (it is assumed * that the caller has already checked the availability of the data for its length) @@ -87,6 +94,13 @@ class CCharArrayParser */ uint32_t ReadNextUnsignedInt(); + /*! + * \brief Reads the next eight chars as unsigned int64 value (it is assumed + * that the caller has already checked the availability of the data for its length) + * \return The unsigned int64 value + */ + uint64_t ReadNextUnsignedInt64(); + /*! * \brief Reads the next string of specified length (it is assumed that * the caller has already checked the availability of the data for its length)