Skip to content

Commit

Permalink
[AdaptiveTree] Reimplemented SortTree and adaptation set merge
Browse files Browse the repository at this point in the history
  • Loading branch information
CastagnaIT committed Aug 3, 2023
1 parent 0333bb0 commit 60fe359
Show file tree
Hide file tree
Showing 12 changed files with 410 additions and 84 deletions.
58 changes: 46 additions & 12 deletions src/common/AdaptationSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,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 @@ -86,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 @@ -114,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
13 changes: 13 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,6 +111,14 @@ 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);

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
2 changes: 0 additions & 2 deletions src/parser/HLSTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,6 @@ bool adaptive::CHLSTree::Open(std::string_view url,

m_currentPeriod = m_periods[0].get();

// SortTree();

return true;
}

Expand Down
1 change: 0 additions & 1 deletion src/parser/SmoothTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ bool adaptive::CSmoothTree::Open(std::string_view url,
m_currentPeriod = m_periods[0].get();

CreateSegmentTimeline();
SortTree();

return true;
}
Expand Down
30 changes: 26 additions & 4 deletions src/test/TestDASHTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class DASHTreeTest : public ::testing::Test
LOG::Log(LOGERROR, "Cannot open \"%s\" DASH manifest.", url.c_str());
exit(1);
}
tree->PostOpen(m_kodiProps);
}

DASHTestTree* tree;
Expand Down Expand Up @@ -611,19 +612,40 @@ TEST_F(DASHTreeTest, AdaptionSetSwitching)

auto& adpSets = tree->m_periods[0]->GetAdaptationSets();

EXPECT_EQ(adpSets.size(), 5);
EXPECT_EQ(adpSets.size(), 6);
EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetId()), "3");
EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[1]->GetId()), "1");
EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[2]->GetId()), "2");

// Below adaptation set (id 6) should be merged with previous one
// but since has a different codec will not be merged
// see note on related DASH parser code
EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetId()), "4");
EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[1]->GetId()), "5");

EXPECT_EQ(STR(adpSets[2]->GetRepresentations()[0]->GetId()), "6");
EXPECT_EQ(STR(adpSets[2]->GetRepresentations()[0]->GetId()), "5");
EXPECT_EQ(STR(adpSets[2]->GetRepresentations()[1]->GetId()), "6");

EXPECT_EQ(STR(adpSets[3]->GetRepresentations()[0]->GetId()), "7");

EXPECT_EQ(STR(adpSets[4]->GetRepresentations()[0]->GetId()), "8");

EXPECT_EQ(STR(adpSets[5]->GetRepresentations()[0]->GetId()), "9");
}

TEST_F(DASHTreeTest, AdaptionSetMerge)
{
OpenTestFile("mpd/adaptation_set_merge.mpd");

auto& adpSets = tree->m_periods[0]->GetAdaptationSets();

EXPECT_EQ(adpSets.size(), 6);
EXPECT_EQ(STR(adpSets[0]->GetRepresentations()[0]->GetId()), "video=100000");
EXPECT_EQ(STR(adpSets[1]->GetRepresentations()[0]->GetId()), "audio_ja-JP_3=128000");
EXPECT_EQ(STR(adpSets[2]->GetRepresentations()[0]->GetId()), "audio_es-419_3=128000");
EXPECT_EQ(STR(adpSets[3]->GetRepresentations()[0]->GetId()), "audio_en-GB_3=96000");
EXPECT_EQ(STR(adpSets[4]->GetRepresentations()[0]->GetId()), "audio_es-ES=20000");
// Below two adaptation sets merged
EXPECT_EQ(STR(adpSets[5]->GetRepresentations()[0]->GetId()), "audio_es-ES_1=64000");
EXPECT_EQ(STR(adpSets[5]->GetRepresentations()[1]->GetId()), "audio_es-ES_1=64000");
}

TEST_F(DASHTreeTest, SuggestedPresentationDelay)
Expand Down
2 changes: 1 addition & 1 deletion src/test/TestHLSTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class HLSTreeTest : public ::testing::Test
LOG::Log(LOGERROR, "Cannot open \"%s\" HLS manifest.", url.c_str());
exit(1);
}

tree->PostOpen(m_kodiProps);
tree->m_currentAdpSet = tree->m_periods[0]->GetAdaptationSets()[0].get();
tree->m_currentRepr = tree->m_currentAdpSet->GetRepresentations()[0].get();
}
Expand Down
1 change: 1 addition & 0 deletions src/test/TestSmoothTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class SmoothTreeTest : public ::testing::Test
LOG::Log(LOGERROR, "Cannot open \"%s\" Smooth Streaming manifest.", url.c_str());
exit(1);
}
tree->PostOpen(m_kodiProps);
}

SmoothTestTree* tree;
Expand Down
Loading

0 comments on commit 60fe359

Please sign in to comment.