Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HLSTree] Reworked multivariant playlist parser #1349

Merged
merged 3 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 71 additions & 12 deletions src/common/AdaptationSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "Representation.h"
#include "../utils/StringUtils.h"
#include "../utils/Utils.h"

#include <algorithm> // any_of

Expand All @@ -22,6 +23,11 @@ void PLAYLIST::CAdaptationSet::AddCodecs(std::string_view codecs)
m_codecs.insert(list.begin(), list.end());
}

void PLAYLIST::CAdaptationSet::AddCodecs(const std::set<std::string>& codecs)
{
m_codecs.insert(codecs.begin(), codecs.end());
}

bool PLAYLIST::CAdaptationSet::ContainsCodec(std::string_view codec)
{
if (std::any_of(m_codecs.begin(), m_codecs.end(),
Expand Down Expand Up @@ -85,18 +91,7 @@ bool PLAYLIST::CAdaptationSet::IsMergeable(const CAdaptationSet* other) const
if (m_streamType != other->m_streamType)
return false;

if (m_streamType == StreamType::VIDEO)
{
if (m_group == other->m_group &&
std::find(m_switchingIds.begin(), m_switchingIds.end(), other->m_id) !=
m_switchingIds.end() &&
std::find(other->m_switchingIds.begin(), other->m_switchingIds.end(), m_id) !=
other->m_switchingIds.end())
{
return true;
}
}
else if (m_streamType == StreamType::AUDIO)
if (m_streamType == StreamType::AUDIO)
{
if (m_id == other->m_id && m_startPts == other->m_startPts &&
m_startNumber == other->m_startNumber && m_duration == other->m_duration &&
Expand All @@ -113,6 +108,46 @@ bool PLAYLIST::CAdaptationSet::IsMergeable(const CAdaptationSet* other) const
return false;
}

bool PLAYLIST::CAdaptationSet::CompareSwitchingId(const CAdaptationSet* other) const
{
if (m_streamType != other->m_streamType || m_switchingIds.empty())
return false;

if (m_streamType == StreamType::VIDEO)
{
if (m_group == other->m_group &&
std::find(m_switchingIds.cbegin(), m_switchingIds.cend(), other->m_id) !=
m_switchingIds.cend() &&
std::find(other->m_switchingIds.cbegin(), other->m_switchingIds.cend(), m_id) !=
other->m_switchingIds.cend())
{
//! @todo: we have no way to determine supported codecs by hardware in use
//! and can broken playback, we allow same codec only
for (std::string codec : m_codecs)
{
codec = codec.substr(0, codec.find('.')); // Get fourcc only
if (CODEC::IsVideo(codec) && CODEC::Contains(other->m_codecs, codec))
{
return true;
}
}
}
}
else if (m_streamType == StreamType::AUDIO)
{
if (m_language == other->m_language && m_group == other->m_group &&
std::find(m_switchingIds.cbegin(), m_switchingIds.cend(), other->m_id) !=
m_switchingIds.cend() &&
std::find(other->m_switchingIds.cbegin(), other->m_switchingIds.cend(), m_id) !=
other->m_switchingIds.cend())
{
return true;
}
}

return false;
}

bool PLAYLIST::CAdaptationSet::Compare(const std::unique_ptr<CAdaptationSet>& left,
const std::unique_ptr<CAdaptationSet>& right)
{
Expand Down Expand Up @@ -156,3 +191,27 @@ bool PLAYLIST::CAdaptationSet::Compare(const std::unique_ptr<CAdaptationSet>& le

return false;
}

PLAYLIST::CAdaptationSet* PLAYLIST::CAdaptationSet::FindByCodec(
std::vector<std::unique_ptr<CAdaptationSet>>& adpSets, std::string codec)
{
auto itAdpSet = std::find_if(adpSets.cbegin(), adpSets.cend(),
[&codec](const std::unique_ptr<CAdaptationSet>& item)
{ return CODEC::Contains(item->GetCodecs(), codec); });
if (itAdpSet != adpSets.cend())
return (*itAdpSet).get();

return nullptr;
}

CAdaptationSet* PLAYLIST::CAdaptationSet::FindMergeable(
std::vector<std::unique_ptr<CAdaptationSet>>& adpSets, CAdaptationSet* adpSet)
{
auto itAdpSet = std::find_if(adpSets.cbegin(), adpSets.cend(),
[&adpSet](const std::unique_ptr<CAdaptationSet>& item)
{ return item->IsMergeable(adpSet); });
if (itAdpSet != adpSets.cend())
return (*itAdpSet).get();

return nullptr;
}
31 changes: 31 additions & 0 deletions src/common/AdaptationSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ class ATTR_DLL_LOCAL CAdaptationSet : public CCommonSegAttribs, public CCommonAt
void AddCodecs(std::string_view codecs);
const std::set<std::string>& GetCodecs() { return m_codecs; }

/*!
* \brief Add codec strings
*/
void AddCodecs(const std::set<std::string>& codecs);

StreamType GetStreamType() const { return m_streamType; }
void SetStreamType(StreamType streamType) { m_streamType = streamType; }

Expand Down Expand Up @@ -106,9 +111,35 @@ class ATTR_DLL_LOCAL CAdaptationSet : public CCommonSegAttribs, public CCommonAt

bool IsMergeable(const CAdaptationSet* other) const;

/*!
* \brief Determine if an adaptation set is switchable with another one,
* as urn:mpeg:dash:adaptation-set-switching:2016 scheme
* \param adpSets The adaptation set to compare
* \return True if switchable, otherwise false
*/
bool CompareSwitchingId(const CAdaptationSet* other) const;

static bool Compare(const std::unique_ptr<CAdaptationSet>& left,
const std::unique_ptr<CAdaptationSet>& right);

/*!
* \brief Find an adaptation set by codec string.
* \param adpSets The adaptation set list where to search
* \param codec The codec string
* \return The adaptation set if found, otherwise nullptr
*/
static CAdaptationSet* FindByCodec(std::vector<std::unique_ptr<CAdaptationSet>>& adpSets,
std::string codec);

/*!
* \brief Find a mergeable adaptation set by comparing properties.
* \param adpSets The adaptation set list where to search
* \param adpSet The adaptation set to be compared
* \return The adaptation set if found, otherwise nullptr
*/
static CAdaptationSet* FindMergeable(std::vector<std::unique_ptr<CAdaptationSet>>& adpSets,
CAdaptationSet* adpSet);

protected:
std::vector<std::unique_ptr<CRepresentation>> m_representations;

Expand Down
58 changes: 6 additions & 52 deletions src/common/AdaptiveTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ namespace adaptive

void AdaptiveTree::PostOpen(const UTILS::PROPERTIES::KodiProperties& kodiProps)
{
SortTree();

// A manifest can provide live delay value, if not so we use our default
// value of 16 secs, this is needed to ensure an appropriate playback,
// an add-on can override the delay to try fix edge use cases
Expand Down Expand Up @@ -141,61 +143,13 @@ namespace adaptive

void AdaptiveTree::SortTree()
{
for (auto itPeriod = m_periods.begin(); itPeriod != m_periods.end(); itPeriod++)
for (auto& period : m_periods)
{
CPeriod* period = (*itPeriod).get();
auto& periodAdpSets = period->GetAdaptationSets();

// Merge VIDEO & AUDIO adaptation sets
//! @todo: seem that merge adpsets is this not safe thing to do, adpsets may have different encryptions
//! and relative different child data (e.g. dash xml child tags)
//! it is needed to investigate if we really need do this,
//! if so maybe limit for some use cases or improve it in some way.
//! audio merging has been impl years ago without give any details of the reasons or for what manifest types
//! video merging has been impl by https://github.com/xbmc/inputstream.adaptive/pull/694
//! second thing, merge should be decoupled from sort behaviour with different methods
for (auto itAdpSet = periodAdpSets.begin(); itAdpSet != periodAdpSets.end();)
{
auto adpSet = (*itAdpSet).get();
auto itNextAdpSet = itAdpSet + 1;

if (itNextAdpSet != periodAdpSets.end() &&
(adpSet->GetStreamType() == StreamType::AUDIO ||
adpSet->GetStreamType() == StreamType::VIDEO))
{
auto nextAdpSet = (*itNextAdpSet).get();

if (adpSet->IsMergeable(nextAdpSet))
{
std::vector<CPeriod::PSSHSet>& psshSets = period->GetPSSHSets();
for (size_t index = 1; index < psshSets.size(); index++)
{
if (psshSets[index].adaptation_set_ == adpSet)
{
psshSets[index].adaptation_set_ = nextAdpSet;
}
}

// Move representations unique_ptr from adpSet repr vector to nextAdpSet repr vector
for (auto itRepr = adpSet->GetRepresentations().begin();
itRepr != adpSet->GetRepresentations().end(); itRepr++)
{
nextAdpSet->GetRepresentations().push_back(std::move(*itRepr));
// We need to change the parent adaptation set in the representation itself
nextAdpSet->GetRepresentations().back()->SetParent(nextAdpSet);
}

itAdpSet = periodAdpSets.erase(itAdpSet);
continue;
}
}
itAdpSet++;
}
auto& adpSets = period->GetAdaptationSets();

std::stable_sort(periodAdpSets.begin(), periodAdpSets.end(),
CAdaptationSet::Compare);
std::stable_sort(adpSets.begin(), adpSets.end(), CAdaptationSet::Compare);

for (auto& adpSet : periodAdpSets)
for (auto& adpSet : adpSets)
{
std::sort(adpSet->GetRepresentations().begin(), adpSet->GetRepresentations().end(),
CRepresentation::CompareBandwidth);
Expand Down
59 changes: 56 additions & 3 deletions src/parser/DASHTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ bool adaptive::CDashTree::Open(std::string_view url,
return false;
}

m_currentPeriod = m_periods[0].get();
MergeAdpSets();

SortTree();
m_currentPeriod = m_periods[0].get();

return true;
}
Expand Down Expand Up @@ -348,7 +348,7 @@ void adaptive::CDashTree::ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST

std::string id;
// "audioTrackId" tag is amazon VOD specific, since dont use the standard "id" tag
// this helps to avoid merging adpSets (done with AdaptiveTree::SortTree) for some limit cases
// this to to make MergeAdpSets more effective for some limit case
if (XML::QueryAttrib(nodeAdp, "id", id) || XML::QueryAttrib(nodeAdp, "audioTrackId", id))
adpSet->SetId(id);

Expand Down Expand Up @@ -626,6 +626,12 @@ void adaptive::CDashTree::ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST
}
}

// Copy codecs in the adaptation set to make MergeAdpSets more effective
if (adpSet->GetCodecs().empty())
{
adpSet->AddCodecs(adpSet->GetRepresentations().front()->GetCodecs());
}

period->AddAdaptationSet(adpSet);
}

Expand Down Expand Up @@ -1380,6 +1386,53 @@ size_t adaptive::CDashTree::EstimateSegmentsCount(uint64_t duration,
return static_cast<size_t>(totalTimeSecs / lengthSecs);
}

void adaptive::CDashTree::MergeAdpSets()
{
// NOTE: This method wipe out all properties of merged adaptation set
for (auto itPeriod = m_periods.begin(); itPeriod != m_periods.end(); ++itPeriod)
{
auto period = itPeriod->get();
auto& periodAdpSets = period->GetAdaptationSets();
for (auto itAdpSet = periodAdpSets.begin(); itAdpSet != periodAdpSets.end(); ++itAdpSet)
{
CAdaptationSet* adpSet = itAdpSet->get();
for (auto itNextAdpSet = itAdpSet + 1; itNextAdpSet != periodAdpSets.end();)
{
CAdaptationSet* nextAdpSet = itNextAdpSet->get();
// IsMergeable:
// Some services (e.g. amazon) may have several AdaptationSets of the exact same audio track
// the only difference is in the ContentProtection kid/pssh and the base url,
// in order not to show several identical audio tracks in the Kodi GUI, we must merge adaptation sets
// CompareSwitchingId:
// Some services can provide switchable video adp sets, these could havedifferent codecs, and could be
// used to split HD resolutions from SD, so to allow Chooser's to autoselect the video quality
// we need to merge them all
// CODEC NOTE: since we cannot know in advance the supported video codecs by the hardware in use
// we cannot merge adp sets with different codecs otherwise playback will not work
if (adpSet->CompareSwitchingId(nextAdpSet) || adpSet->IsMergeable(nextAdpSet))
{
// Sanitize adaptation set references to pssh sets
for (CPeriod::PSSHSet& psshSet : period->GetPSSHSets())
{
if (psshSet.adaptation_set_ == nextAdpSet)
psshSet.adaptation_set_ = adpSet;
}
// Move representations to the first switchable adaptation set
for (auto itRepr = nextAdpSet->GetRepresentations().begin();
itRepr < nextAdpSet->GetRepresentations().end(); ++itRepr)
{
itRepr->get()->SetParent(adpSet);
adpSet->GetRepresentations().push_back(std::move(*itRepr));
}
itNextAdpSet = periodAdpSets.erase(itNextAdpSet);
}
else
++itNextAdpSet;
}
}
}
}

bool adaptive::CDashTree::DownloadManifestUpd(std::string_view url,
const std::map<std::string, std::string>& reqHeaders,
const std::vector<std::string>& respHeaders,
Expand Down
2 changes: 2 additions & 0 deletions src/parser/DASHTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class ATTR_DLL_LOCAL CDashTree : public adaptive::AdaptiveTree
*/
size_t EstimateSegmentsCount(uint64_t duration, uint32_t timescale, uint64_t totalTimeSecs = 0);

void MergeAdpSets();

/*!
* \brief Download manifest update, overridable method for test project
*/
Expand Down
Loading
Loading