Skip to content

Commit

Permalink
Merge pull request #1349 from CastagnaIT/hls_rework_multivariant
Browse files Browse the repository at this point in the history
[HLSTree] Reworked multivariant playlist parser
  • Loading branch information
CastagnaIT authored Aug 15, 2023
2 parents 9830591 + 0727846 commit a3782c1
Show file tree
Hide file tree
Showing 15 changed files with 1,003 additions and 380 deletions.
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

0 comments on commit a3782c1

Please sign in to comment.