vlc | branch: master | Francois Cartegnie <[email protected]> | Mon Dec 21 14:17:35 2020 +0100| [fc25e9b42b2833f015b4b753c7a74061c88299e2] | committer: Francois Cartegnie
demux: adaptive: add more unit tests > http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=fc25e9b42b2833f015b4b753c7a74061c88299e2 --- modules/demux/Makefile.am | 9 + .../demux/adaptive/test/logic/BufferingLogic.cpp | 171 ++++++++++ .../demux/adaptive/test/playlist/Inheritables.cpp | 126 ++++++++ modules/demux/adaptive/test/playlist/M3U8.cpp | 351 +++++++++++++++++++++ .../demux/adaptive/test/playlist/SegmentBase.cpp | 105 ++++++ .../demux/adaptive/test/playlist/SegmentList.cpp | 181 +++++++++++ .../adaptive/test/playlist/SegmentTemplate.cpp | 128 ++++++++ .../adaptive/test/playlist/SegmentTimeline.cpp | 184 +++++++++++ .../demux/adaptive/test/plumbing/CommandsQueue.cpp | 327 +++++++++++++++++++ modules/demux/adaptive/test/test.cpp | 18 +- modules/demux/adaptive/test/test.hpp | 10 + modules/demux/adaptive/test/tools/Conversions.cpp | 67 ++++ 12 files changed, 1676 insertions(+), 1 deletion(-) diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am index ca641e6670..6e2b5bfa00 100644 --- a/modules/demux/Makefile.am +++ b/modules/demux/Makefile.am @@ -505,7 +505,16 @@ libadaptive_plugin_la_LIBADD = libvlc_adaptive.la demux_LTLIBRARIES += libadaptive_plugin.la adaptive_test_SOURCES = \ + demux/adaptive/test/logic/BufferingLogic.cpp \ + demux/adaptive/test/tools/Conversions.cpp \ + demux/adaptive/test/playlist/Inheritables.cpp \ + demux/adaptive/test/playlist/M3U8.cpp \ + demux/adaptive/test/playlist/SegmentBase.cpp \ + demux/adaptive/test/playlist/SegmentList.cpp \ + demux/adaptive/test/playlist/SegmentTemplate.cpp \ + demux/adaptive/test/playlist/SegmentTimeline.cpp \ demux/adaptive/test/playlist/TemplatedUri.cpp \ + demux/adaptive/test/plumbing/CommandsQueue.cpp \ demux/adaptive/test/test.cpp \ demux/adaptive/test/test.hpp adaptive_test_LDADD = libvlc_adaptive.la diff --git a/modules/demux/adaptive/test/logic/BufferingLogic.cpp b/modules/demux/adaptive/test/logic/BufferingLogic.cpp new file mode 100644 index 0000000000..12bbdca744 --- /dev/null +++ b/modules/demux/adaptive/test/logic/BufferingLogic.cpp @@ -0,0 +1,171 @@ +/***************************************************************************** + * + ***************************************************************************** + * Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "../../playlist/SegmentBase.h" +#include "../../playlist/SegmentTimeline.h" +#include "../../playlist/SegmentTemplate.h" +#include "../../playlist/SegmentList.h" +#include "../../playlist/BasePlaylist.hpp" +#include "../../playlist/BasePeriod.h" +#include "../../playlist/BaseAdaptationSet.h" +#include "../../playlist/BaseRepresentation.h" +#include "../../playlist/Segment.h" +#include "../../logic/BufferingLogic.hpp" + +#include "../test.hpp" + +#include <limits> + +using namespace adaptive; +using namespace adaptive::playlist; +using namespace logic; + +class TestPlaylist : public BasePlaylist +{ + public: + TestPlaylist() : BasePlaylist(nullptr) + { + b_live = false; + b_lowlatency = false; + } + + virtual ~TestPlaylist() {} + + virtual bool isLive() const override + { + return b_live; + } + + virtual bool isLowLatency() const override + { + return b_lowlatency; + } + + bool b_live; + bool b_lowlatency; +}; + +int BufferingLogic_test() +{ + DefaultBufferingLogic bufferinglogic; + TestPlaylist *playlist = nullptr; + try + { + playlist = new TestPlaylist(); + BasePeriod *period = nullptr; + BaseAdaptationSet *set = nullptr; + BaseRepresentation *rep = nullptr; + try + { + period = new BasePeriod(playlist); + set = new BaseAdaptationSet(period); + rep = new BaseRepresentation(set); + } catch(...) { + delete period; + delete set; + std::rethrow_exception(std::current_exception()); + } + set->addRepresentation(rep); + period->addAdaptationSet(set); + playlist->addPeriod(period); + + Timescale timescale(100); + set->addAttribute(new TimescaleAttr(timescale)); + + SegmentList *segmentList = new SegmentList(rep); + rep->addAttribute(segmentList); + + Expect(bufferinglogic.getStartSegmentNumber(rep) == std::numeric_limits<uint64_t>::max()); + + stime_t segmentduration = timescale.ToScaled(DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT / 2); + uint64_t number = 22; + Segment *seg = new Segment(rep); + seg->setSequenceNumber(number); + seg->duration.Set(segmentduration); + segmentList->addSegment(seg); + + Expect(bufferinglogic.getStartSegmentNumber(rep) == number); + Expect(bufferinglogic.getMinBuffering(playlist) == DefaultBufferingLogic::DEFAULT_MIN_BUFFERING); + Expect(bufferinglogic.getMaxBuffering(playlist) == DefaultBufferingLogic::DEFAULT_MAX_BUFFERING); + + bufferinglogic.setUserMinBuffering(DefaultBufferingLogic::DEFAULT_MIN_BUFFERING / 2); + Expect(bufferinglogic.getMinBuffering(playlist) == std::max(DefaultBufferingLogic::DEFAULT_MIN_BUFFERING / 2, + DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT)); + + bufferinglogic.setUserMinBuffering(DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT / 2); + Expect(bufferinglogic.getMinBuffering(playlist) == DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT); + + bufferinglogic.setUserMinBuffering(DefaultBufferingLogic::DEFAULT_MIN_BUFFERING); + bufferinglogic.setUserMaxBuffering(DefaultBufferingLogic::DEFAULT_MIN_BUFFERING / 2); + Expect(bufferinglogic.getMaxBuffering(playlist) == DefaultBufferingLogic::DEFAULT_MIN_BUFFERING); + + bufferinglogic.setUserMinBuffering(DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT / 2); + bufferinglogic.setUserMaxBuffering(DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT / 2); + Expect(bufferinglogic.getMaxBuffering(playlist) == DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT); + + playlist->b_live = true; + bufferinglogic.setUserMinBuffering(0); + bufferinglogic.setUserMaxBuffering(0); + Expect(bufferinglogic.getMinBuffering(playlist) == DefaultBufferingLogic::DEFAULT_MIN_BUFFERING); + Expect(bufferinglogic.getMaxBuffering(playlist) <= DefaultBufferingLogic::DEFAULT_MAX_BUFFERING); + Expect(bufferinglogic.getLiveDelay(playlist) == DefaultBufferingLogic::DEFAULT_LIVE_BUFFERING); + + bufferinglogic.setUserLiveDelay(DefaultBufferingLogic::DEFAULT_MIN_BUFFERING / 2); + Expect(bufferinglogic.getLiveDelay(playlist) ==std::max(DefaultBufferingLogic::DEFAULT_MIN_BUFFERING, + DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT)); + + playlist->b_lowlatency = true; + bufferinglogic.setUserLiveDelay(0); + if(DefaultBufferingLogic::DEFAULT_MIN_BUFFERING > DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT) + Expect(bufferinglogic.getMinBuffering(playlist) < DefaultBufferingLogic::DEFAULT_MIN_BUFFERING); + Expect(bufferinglogic.getMaxBuffering(playlist) < DefaultBufferingLogic::DEFAULT_MAX_BUFFERING); + Expect(bufferinglogic.getMinBuffering(playlist) >= DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT); + Expect(bufferinglogic.getLiveDelay(playlist) >= DefaultBufferingLogic::BUFFERING_LOWEST_LIMIT); + + playlist->b_lowlatency = false; + Expect(bufferinglogic.getStartSegmentNumber(rep) == number); + + while(segmentList->getTotalLength() < + timescale.ToScaled(DefaultBufferingLogic::DEFAULT_MAX_BUFFERING)) + { + seg = new Segment(rep); + seg->setSequenceNumber(++number); + seg->duration.Set(segmentduration); + segmentList->addSegment(seg); + } + + Expect(bufferinglogic.getStartSegmentNumber(rep) > 22); + + Expect(bufferinglogic.getStartSegmentNumber(rep) <= + number - DefaultBufferingLogic::SAFETY_BUFFERING_EDGE_OFFSET); + Expect(bufferinglogic.getStartSegmentNumber(rep) >= + 22 + DefaultBufferingLogic::SAFETY_EXPURGING_OFFSET); + + delete playlist; + } catch(...) { + delete playlist; + return 1; + } + + return 0; +} diff --git a/modules/demux/adaptive/test/playlist/Inheritables.cpp b/modules/demux/adaptive/test/playlist/Inheritables.cpp new file mode 100644 index 0000000000..4276b22cbe --- /dev/null +++ b/modules/demux/adaptive/test/playlist/Inheritables.cpp @@ -0,0 +1,126 @@ +/***************************************************************************** + * + ***************************************************************************** + * Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "../../playlist/SegmentBase.h" +#include "../../playlist/SegmentTimeline.h" +#include "../../playlist/SegmentTemplate.h" +#include "../../playlist/SegmentList.h" +#include "../../playlist/BasePlaylist.hpp" +#include "../../playlist/BasePeriod.h" +#include "../../playlist/BaseAdaptationSet.h" +#include "../../playlist/BaseRepresentation.h" + +#include "../test.hpp" + +using namespace adaptive; +using namespace adaptive::playlist; + +int Inheritables_test() +{ + BasePeriod *period = nullptr; + try + { + period = new BasePeriod(nullptr); + BaseAdaptationSet *set = nullptr; + BaseRepresentation *rep = nullptr; + try + { + set = new BaseAdaptationSet(period); + rep = new BaseRepresentation(set); + } catch(...) { + delete set; + std::rethrow_exception(std::current_exception()); + } + set->addRepresentation(rep); + period->addAdaptationSet(set); + + Expect(rep->inheritAttribute(AbstractAttr::Type::SegmentBase) == nullptr); + period->addAttribute(new TimescaleAttr(Timescale(123))); + + const AbstractAttr *attr = rep->inheritAttribute(AbstractAttr::Type::Timescale); + Expect(attr != nullptr); + Expect(attr->isValid()); + Timescale timescale = rep->inheritTimescale(); + Expect(timescale == 123); + + set->addAttribute(new TimescaleAttr(Timescale(1230))); + timescale = rep->inheritTimescale(); + Expect(timescale == 1230); + + set->replaceAttribute(new TimescaleAttr(Timescale())); + timescale = rep->inheritTimescale(); + Expect(timescale == 123); + + delete period; + } catch(...) { + delete period; + return 1; + } + + try + { + /* Check inheritance from siblings */ + period = new BasePeriod(nullptr); + BaseAdaptationSet *set = nullptr; + BaseRepresentation *rep = nullptr; + try + { + set = new BaseAdaptationSet(period); + rep = new BaseRepresentation(set); + } catch(...) { + delete set; + std::rethrow_exception(std::current_exception()); + } + set->addRepresentation(rep); + period->addAdaptationSet(set); + + SegmentTemplate *templ = new SegmentTemplate(new SegmentTemplateSegment(), rep); + rep->addAttribute(templ); + SegmentTimeline *timeline = new SegmentTimeline(templ); + templ->addAttribute(timeline); + + templ = new SegmentTemplate(new SegmentTemplateSegment(), period); + period->addAttribute(templ); + templ->addAttribute(new TimescaleAttr(Timescale(123))); + timeline = new SegmentTimeline(templ); + timeline->addAttribute(new TimescaleAttr(Timescale(456))); + templ->addAttribute(timeline); + + /* check inheritance from matched sibling */ + const AbstractAttr *attr = rep->inheritAttribute(AbstractAttr::Type::Timescale); + Expect(attr == nullptr); + Timescale timescale = timeline->inheritTimescale(); + Expect(timescale == 456); + timeline->replaceAttribute(new TimescaleAttr(Timescale())); /* invalidate on timeline */ + /* should now inherit from sibling parent template */ + timescale = timeline->inheritTimescale(); + Expect(timescale == 123); + + delete period; + } catch(...) { + delete period; + return 1; + } + + return 0; +} diff --git a/modules/demux/adaptive/test/playlist/M3U8.cpp b/modules/demux/adaptive/test/playlist/M3U8.cpp new file mode 100644 index 0000000000..62e3f463a9 --- /dev/null +++ b/modules/demux/adaptive/test/playlist/M3U8.cpp @@ -0,0 +1,351 @@ +/***************************************************************************** + * + ***************************************************************************** + * Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "../../playlist/Segment.h" +#include "../../playlist/SegmentList.h" +#include "../../playlist/BasePeriod.h" +#include "../../playlist/BaseAdaptationSet.h" +#include "../../playlist/BaseRepresentation.h" +#include "../../logic/BufferingLogic.hpp" +#include "../../SegmentTracker.hpp" +#include "../../../hls/playlist/Parser.hpp" +#include "../../../hls/playlist/M3U8.hpp" +#include "../../../hls/playlist/HLSSegment.hpp" +#include "../../../hls/playlist/HLSRepresentation.hpp" + +#include "../test.hpp" + +#include <vlc_stream.h> + +#include <limits> +#include <algorithm> + +using namespace adaptive; +using namespace adaptive::playlist; +using namespace hls::playlist; + +static M3U8 * ParseM3U8(vlc_object_t *obj, const char *psz, size_t isz) +{ + M3U8Parser parser(nullptr); + stream_t *substream = vlc_stream_MemoryNew(obj, ((uint8_t *)psz), isz, true); + if(!substream) + return nullptr; + M3U8 *m3u = parser.parse(obj, substream, std::string("stdin://")); + vlc_stream_Delete(substream); + return m3u; +} + +int M3U8MasterPlaylist_test() +{ + vlc_object_t *obj = static_cast<vlc_object_t*>(nullptr); + + const char manifest0[] = + "#EXTM3U\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1280000\n" + "http://example.com/low.m3u8\n" + "#EXT-X-STREAM-INF:BANDWIDTH=2560000\n" + "http://example.com/mid.m3u8\n" + "#EXT-X-STREAM-INF:BANDWIDTH=7680000\n" + "http://example.com/hi.m3u8\n" + "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n" + "http://example.com/audio-only.m3u8\n"; + + M3U8 *m3u = ParseM3U8(obj, manifest0, sizeof(manifest0)); + try + { + Expect(m3u); + Expect(m3u->getUrlSegment().toString() == "stdin://"); + Expect(m3u->getPeriods().size() == 1); + Expect(m3u->getFirstPeriod()->getAdaptationSets().size() == 1); + Expect(m3u->getFirstPeriod()->getAdaptationSets().front()->getRepresentations().size() == 4); + BaseAdaptationSet *set = m3u->getFirstPeriod()->getAdaptationSets().front(); + std::vector<BaseRepresentation *> &reps = set->getRepresentations(); + std::vector<BaseRepresentation *>::iterator it; + it = std::find_if(reps.begin(), reps.end(), + [](BaseRepresentation *r) { return r->getBandwidth() == 1280000; }); + Expect(it != reps.end()); + Expect(static_cast<HLSRepresentation *>(*it)->getPlaylistUrl().toString() == "http://example.com/low.m3u8"); + it = std::find_if(reps.begin(), reps.end(), + [](BaseRepresentation *r) { return r->getBandwidth() == 65000; }); + Expect(it != reps.end()); + Expect(static_cast<HLSRepresentation *>(*it)->getPlaylistUrl().toString() == "http://example.com/audio-only.m3u8"); + Expect((*it)->getUrlSegment().toString() == "http://example.com/"); + const std::list<std::string> &codecs = (*it)->getCodecs(); + Expect(!codecs.empty()); + Expect(codecs.front() == "mp4a.40.5"); + Expect((*it)->needsUpdate(1)); + delete m3u; + } + catch(...) + { + delete m3u; + return 1; + } + + const char manifest1[] = + "#EXTM3U\n" + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"English\", " + " DEFAULT=YES,AUTOSELECT=YES,LANGUAGE=\"en\", " + " URI=\"main/english-audio.m3u8\"\n" + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"Deutsch\", " + " DEFAULT=NO,AUTOSELECT=YES,LANGUAGE=\"de\", " + " URI=\"main/german-audio.m3u8\"\n" + "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aac\",NAME=\"Commentary\", " + " DEFAULT=NO,AUTOSELECT=NO,LANGUAGE=\"en\", " + " URI=\"commentary/audio-only.m3u8\"\n" + "#EXT-X-STREAM-INF:BANDWIDTH=7680000,CODECS=\"avc1, mp4a.40.5\",AUDIO=\"aac\"\n" + "hi/video-only.m3u8\n" + "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\",AUDIO=\"aac\"\n" + "main/english-audio.m3u8\n"; + + m3u = ParseM3U8(obj, manifest1, sizeof(manifest1)); + try + { + Expect(m3u); + Expect(m3u->getPeriods().size() == 1); + Expect(m3u->getFirstPeriod()->getAdaptationSets().size() == 4); + BaseAdaptationSet *set = m3u->getFirstPeriod()->getAdaptationSetByID(ID("aac English")); + Expect(set); + Expect(set->getRepresentations().size() == 1); + Expect(set->getLang() == "en"); + Expect(set->getRole().autoSelectable()); + Expect(set->getRole().isDefault()); + delete m3u; + } + catch(...) + { + delete m3u; + return 1; + } + + return 0; +} + +int M3U8Playlist_test() +{ + vlc_object_t *obj = static_cast<vlc_object_t*>(nullptr); + DefaultBufferingLogic bufferingLogic; + + /* Manifest 0 */ + const char manifest0[] = + "#EXTM3U\n" + "#EXT-X-MEDIA-SEQUENCE:10\n" + "#EXTINF:8,\n" + "foobar.ts\n" + "#EXT-X-ENDLIST\n"; + + M3U8 *m3u = ParseM3U8(obj, manifest0, sizeof(manifest0)); + try + { + Expect(m3u); + Expect(m3u->isLive() == false); + Expect(m3u->isLowLatency() == false); + Expect(m3u->getPeriods().size() == 1); + Expect(m3u->getFirstPeriod()->getAdaptationSets().size() == 1); + Expect(m3u->getFirstPeriod()->getAdaptationSets().front()->getRepresentations().size() == 1); + BaseRepresentation *rep = m3u->getFirstPeriod()->getAdaptationSets().front()->getRepresentations().front(); + Expect(rep->getProfile()->getStartSegmentNumber() == 10); + + uint64_t number = bufferingLogic.getStartSegmentNumber(rep); + Expect(number == 10); + vlc_tick_t mediatime, duration; + Expect(rep->getPlaybackTimeDurationBySegmentNumber(std::numeric_limits<uint64_t>::max(), + &mediatime, &duration) == false); + Expect(rep->getPlaybackTimeDurationBySegmentNumber(number + 1, &mediatime, &duration) == false); + Expect(rep->getPlaybackTimeDurationBySegmentNumber(number - 1, &mediatime, &duration) == false); + Expect(rep->getPlaybackTimeDurationBySegmentNumber(number, &mediatime, &duration) == true); + Expect(mediatime == vlc_tick_from_sec(0)); + Expect(duration == vlc_tick_from_sec(8)); + Expect(rep->getSegmentNumberByTime(vlc_tick_from_sec(0), &number)); + Expect(number == 10); + Segment *seg = rep->getMediaSegment(number); + Expect(seg); + Expect(seg->getSequenceNumber() == 10); + Expect(seg->startTime.Get() == (stime_t) 0); + + vlc_tick_t begin, end; + Expect(rep->getMediaPlaybackRange(&begin, &end, &duration)); + Expect(begin == vlc_tick_from_sec(0)); + Expect(end == vlc_tick_from_sec(8)); + Expect(duration == vlc_tick_from_sec(8)); + delete m3u; + } + catch(...) + { + delete m3u; + return 1; + } + + /* Manifest 1 */ + const char manifest1[] = + "#EXTM3U\n" + "#EXT-X-MEDIA-SEQUENCE:10\n" + "#EXTINF:8,\n" + "foobar.ts\n" + "#EXTINF:12.0,\n" /* Broken format test: non integral notation */ + "foobar2.ts\n" + "#EXTINF:10\n" /* Broken format test: no mandatory comma */ + "foobar3.ts\n" + "#EXT-X-ENDLIST\n"; + + m3u = ParseM3U8(obj, manifest1, sizeof(manifest1)); + try + { + Expect(m3u); + BaseRepresentation *rep = m3u->getFirstPeriod()->getAdaptationSets().front()-> + getRepresentations().front(); + Expect(rep->getProfile()->getStartSegmentNumber() == 10); + Expect(m3u->duration.Get()); + + Timescale timescale = rep->inheritTimescale(); + Expect(timescale.isValid()); + + uint64_t number; + bool discont; + Segment *seg = rep->getNextMediaSegment(12, &number, &discont); + Expect(seg); + Expect(number == 12); + Expect(!discont); + Expect(seg->getSequenceNumber() == 12); + Expect(seg->startTime.Get() == timescale.ToScaled(vlc_tick_from_sec(20))); + + vlc_tick_t begin, end, duration; + Expect(rep->getMediaPlaybackRange(&begin, &end, &duration)); + Expect(begin == vlc_tick_from_sec(0)); + Expect(end == vlc_tick_from_sec(30)); + Expect(duration == vlc_tick_from_sec(30)); + + delete m3u; + } + catch (...) + { + delete m3u; + return 1; + } + + /* Manifest 2 */ + const char manifest2[] = + "#EXTM3U\n" + "#EXT-X-MEDIA-SEQUENCE:10\n" + "#EXTINF:8\n" + "foobar.ts\n" + "#EXTINF:5\n" + "foobar2.ts\n" + "#EXTINF:8\n" + "foobar3.ts\n" + "#EXTINF:5\n" + "foobar4.ts\n" + "#EXTINF:8\n" + "foobar5.ts\n"; + + m3u = ParseM3U8(obj, manifest2, sizeof(manifest2)); + try + { + Expect(m3u); + Expect(m3u->isLive() == true); + BaseRepresentation *rep = m3u->getFirstPeriod()->getAdaptationSets().front()-> + getRepresentations().front(); + uint64_t number; + Expect(rep->getSegmentNumberByTime(vlc_tick_from_sec(2), &number)); + Expect(number == 10); + Expect(rep->getSegmentNumberByTime(vlc_tick_from_sec(8), &number)); + Expect(number == 11); + Expect(rep->getSegmentNumberByTime(vlc_tick_from_sec(100), &number)); + Expect(number == 14); + Expect(bufferingLogic.getStartSegmentNumber(rep) < 13); + + delete m3u; + } + catch (...) + { + delete m3u; + return 1; + } + + /* Manifest 3 */ + const char manifest3[] = + "#EXTM3U\n" + "#EXT-X-MEDIA-SEQUENCE:10\n" + "#EXT-X-PROGRAM-DATE-TIME:1970-01-01T00:00:10.000+00:00\n" + "#EXTINF:8\n" + "foobar.ts\n" + "#EXTINF:5\n" + "foobar2.ts\n" + "#EXTINF:8\n" + "foobar3.ts\n" + "#EXT-X-DISCONTINUITY\n" + "#EXT-X-PROGRAM-DATE-TIME:1970-01-01T02:00:00.000+00:00\n" + "#EXT-X-MEDIA-SEQUENCE:20\n" + "#EXTINF:5\n" + "foobar4.ts\n" + "#EXTINF:8\n" + "foobar5.ts\n"; + + m3u = ParseM3U8(obj, manifest3, sizeof(manifest3)); + try + { + Expect(m3u); + BaseRepresentation *rep = m3u->getFirstPeriod()->getAdaptationSets().front()-> + getRepresentations().front(); + /* media sequence and discontinuity handling */ + uint64_t number; + bool discont; + Segment *seg = rep->getNextMediaSegment(13, &number, &discont); + Expect(seg); + Expect(number == 20); + Expect(discont); + + /* mapping past discontinuity */ + Expect(rep->getSegmentNumberByTime(vlc_tick_from_sec(23), &number)); + Expect(number == 20); + + /* date set and incremented */ + seg = rep->getMediaSegment(10); + Expect(seg); + Expect(static_cast<HLSSegment *>(seg)->getUTCTime() == VLC_TICK_0 + vlc_tick_from_sec(10)); + seg = rep->getMediaSegment(11); + Expect(seg); + Expect(static_cast<HLSSegment *>(seg)->getUTCTime() == VLC_TICK_0 + vlc_tick_from_sec(10 + 8)); + + /* date change after discontinuity */ + seg = rep->getMediaSegment(20); + Expect(seg); + Expect(static_cast<HLSSegment *>(seg)->getUTCTime() == VLC_TICK_0 + vlc_tick_from_sec(7200)); + + vlc_tick_t begin, end, duration; + Expect(rep->getMediaPlaybackRange(&begin, &end, &duration)); + Expect(begin == vlc_tick_from_sec(0)); + Expect(end == vlc_tick_from_sec(34)); + Expect(duration == vlc_tick_from_sec(34)); + + delete m3u; + } + catch (...) + { + delete m3u; + return 1; + } + + + return 0; +} diff --git a/modules/demux/adaptive/test/playlist/SegmentBase.cpp b/modules/demux/adaptive/test/playlist/SegmentBase.cpp new file mode 100644 index 0000000000..8b3083c19f --- /dev/null +++ b/modules/demux/adaptive/test/playlist/SegmentBase.cpp @@ -0,0 +1,105 @@ +/***************************************************************************** + * + ***************************************************************************** + * Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "../../playlist/Segment.h" +#include "../../playlist/SegmentBase.h" +#include "../../playlist/BasePeriod.h" +#include "../../playlist/BaseAdaptationSet.h" +#include "../../playlist/BaseRepresentation.h" +#include "../../playlist/SegmentInformation.hpp" + +#include "../test.hpp" + +#include <limits> + +using namespace adaptive; +using namespace adaptive::playlist; + +using SplitPoint = SegmentInformation::SplitPoint; + +int SegmentBase_test() +{ + BaseRepresentation *rep = new BaseRepresentation(nullptr); + try + { + SegmentBase *segmentBase = new SegmentBase(nullptr); + Timescale timescale(100); + segmentBase->addAttribute(new TimescaleAttr(timescale)); + rep->addAttribute(segmentBase); + /* Check failures */ + + Expect(segmentBase->getMediaSegment(0) == nullptr); + uint64_t number; bool discont; + Expect(segmentBase->getSegmentNumberByTime(1, &number) == false); + Expect(segmentBase->getNextMediaSegment(0, &number, &discont) == nullptr); + vlc_tick_t time, duration; + Expect(segmentBase->getPlaybackTimeDurationBySegmentNumber(0, &time, &duration) == false); + + /* Create a split range */ + std::vector<SplitPoint> splitlist; + for(int i=0; i<10; i++) + { + SplitPoint point; + point.time = i * 100; + point.duration = 100; + point.offset = 123 + i * 100; + splitlist.push_back(point); + } + rep->SplitUsingIndex(splitlist); + + /* For now we can't tell anything without main segment byte range */ + Expect(segmentBase->getMediaSegment(0) == nullptr); + + segmentBase->setByteRange(111, 2000); + + segmentBase->duration.Set(100 * 10); + rep->SplitUsingIndex(splitlist); + Expect(segmentBase->subSegments().size()); + Expect(segmentBase->getMediaSegment(0) != nullptr); + Expect(segmentBase->getMinAheadTime(0) == timescale.ToTime(9 * 100)); + Expect(segmentBase->getSegmentNumberByTime(timescale.ToTime(9 * 100 - 1), &number)); + Expect(number == 8); + Expect(segmentBase->getMinAheadTime(7) == timescale.ToTime(2 * 100)); + Expect(segmentBase->getPlaybackTimeDurationBySegmentNumber(7, &time, &duration) == true); + Expect(time == timescale.ToTime(7 * 100)); + Expect(duration == timescale.ToTime(100)); + Segment *seg = segmentBase->getMediaSegment(7); + Expect(seg); + Expect(seg->getSequenceNumber() == 7); + Expect(seg->getOffset() == 123 + 7*100); + + seg = segmentBase->getNextMediaSegment(7, &number, &discont); + Expect(seg); + Expect(seg->getSequenceNumber() == 7); + Expect(number == 7); + Expect(!discont); + + delete rep; + + } catch (...) { + delete rep; + return 1; + } + + return 0; +} diff --git a/modules/demux/adaptive/test/playlist/SegmentList.cpp b/modules/demux/adaptive/test/playlist/SegmentList.cpp new file mode 100644 index 0000000000..b0e2ff7044 --- /dev/null +++ b/modules/demux/adaptive/test/playlist/SegmentList.cpp @@ -0,0 +1,181 @@ +/***************************************************************************** + * + ***************************************************************************** + * Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "../../playlist/Segment.h" +#include "../../playlist/SegmentList.h" +#include "../../playlist/SegmentTimeline.h" +#include "../../playlist/BasePeriod.h" +#include "../../playlist/BaseAdaptationSet.h" +#include "../../playlist/BaseRepresentation.h" + +#include "../test.hpp" + +#include <limits> + +using namespace adaptive; +using namespace adaptive::playlist; + +int SegmentList_test() +{ + SegmentList *segmentList = nullptr; + SegmentList *segmentList2 = nullptr; + try + { + segmentList = new SegmentList(nullptr); + Timescale timescale(100); + segmentList->addAttribute(new TimescaleAttr(timescale)); + /* Check failures */ + Expect(segmentList->getStartSegmentNumber() == std::numeric_limits<uint64_t>::max()); + Expect(segmentList->getTotalLength() == 0); + uint64_t number; bool discont; + Expect(segmentList->getSegmentNumberByTime(1, &number) == false); + Expect(segmentList->getMediaSegment(0) == nullptr); + Expect(segmentList->getNextMediaSegment(0, &number, &discont) == nullptr); + Expect(segmentList->getMinAheadTime(0) == 0); + vlc_tick_t time, duration; + Expect(segmentList->getPlaybackTimeDurationBySegmentNumber(0, &time, &duration) == false); + + /* Simple elements list */ + const stime_t START = 1337; + Segment *seg = new Segment(nullptr); + seg->setSequenceNumber(123); + seg->startTime.Set(START); + seg->duration.Set(100); + segmentList->addSegment(seg); + + Expect(segmentList->getTotalLength() == 100); + Expect(segmentList->getSegmentNumberByTime(timescale.ToTime(0), &number) == false); + Expect(segmentList->getSegmentNumberByTime(timescale.ToTime(START), &number) == true); + Expect(number == 123); + Expect(segmentList->getPlaybackTimeDurationBySegmentNumber(123, &time, &duration) == true); + Expect(time == timescale.ToTime(START)); + Expect(duration == timescale.ToTime(100)); + seg = segmentList->getMediaSegment(123); + Expect(seg); + Expect(seg->getSequenceNumber() == 123); + Expect(seg->startTime.Get() == START); + seg = segmentList->getNextMediaSegment(123, &number, &discont); + Expect(seg); + Expect(number == 123); + Expect(!discont); + seg = segmentList->getNextMediaSegment(122, &number, &discont); + Expect(seg); + Expect(number == 123); + Expect(discont); + Expect(segmentList->getMinAheadTime(0) == timescale.ToTime(100)); + Expect(segmentList->getMinAheadTime(123) == timescale.ToTime(0)); + + for(int i=1; i<10; i++) + { + seg = new Segment(nullptr); + seg->setSequenceNumber(123 + i); + seg->startTime.Set(START + 100 * i); + seg->duration.Set(100); + segmentList->addSegment(seg); + } + + Expect(segmentList->getTotalLength() == 100 * 10); + Expect(segmentList->getMinAheadTime(123) == timescale.ToTime(100 * 9)); + Expect(segmentList->getSegmentNumberByTime(timescale.ToTime(START + 100*9 - 1), &number) == true); + Expect(number == 123 + 8); + Expect(segmentList->getPlaybackTimeDurationBySegmentNumber(123 + 8, &time, &duration) == true); + Expect(time == timescale.ToTime(START + 100 * 8)); + Expect(duration == timescale.ToTime(100)); + Expect(segmentList->getMinAheadTime(123+8) == timescale.ToTime(100)); + + /* merge */ + segmentList2 = new SegmentList(nullptr); + for(int i=5; i<20; i++) + { + seg = new Segment(nullptr); + seg->setSequenceNumber(123 + i); + seg->startTime.Set(START + 100 * i); + seg->duration.Set(100); + segmentList2->addSegment(seg); + } + segmentList->updateWith(segmentList2); + Expect(segmentList->getStartSegmentNumber() == 123 + 5); + Expect(segmentList->getTotalLength() == 100 * 15); + + for(int i=5; i<20; i++) + { + seg = segmentList->getMediaSegment(123 + i); + Expect(seg); + Expect(seg->getSequenceNumber() == (uint64_t) 123 + i); + Expect(seg->startTime.Get() == START + 100 * i); + Expect(seg->duration.Get() == 100); + } + + /* prune */ + segmentList->pruneByPlaybackTime(timescale.ToTime(START+100*6)); + Expect(segmentList->getStartSegmentNumber() == 123 + 6); + Expect(segmentList->getTotalLength() == 100 * 14); + + segmentList->pruneBySegmentNumber(123+10); + Expect(segmentList->getStartSegmentNumber() == 123 + 10); + Expect(segmentList->getTotalLength() == 100 * 10); + + delete segmentList; + delete segmentList2; + segmentList2 = nullptr; + + /* Tricky now, check timelined */ + segmentList = new SegmentList(nullptr); + segmentList->addAttribute(new TimescaleAttr(timescale)); + for(int i=0; i<10; i++) + { + seg = new Segment(nullptr); + seg->setSequenceNumber(123 + i); + seg->startTime.Set(START + 100 * i); + seg->duration.Set(100); + segmentList->addSegment(seg); + } + const std::vector<Segment*>&allsegments = segmentList->getSegments(); + + SegmentTimeline *timeline = new SegmentTimeline(nullptr); + segmentList->addAttribute(timeline); + timeline->addElement(44, 100, 4, START); + Expect(timeline->getTotalLength() == 5 * 100); + Expect(segmentList->getStartSegmentNumber() == 44); + Expect(segmentList->getTotalLength() == timeline->getTotalLength()); + seg = segmentList->getMediaSegment(44 + 2); + Expect(seg); + Expect(seg == allsegments.at(0)); + Expect(segmentList->getMediaSegment(44 + 6) == nullptr); /* restricted window */ + + timeline->addElement(44 + 5, 100, 1, START + 5*100); + Expect(timeline->getTotalLength() == 7 * 100); + seg = segmentList->getMediaSegment(44 + 6); + Expect(seg); + Expect(seg == allsegments.at(1)); + + delete segmentList; + + } catch (...) { + delete segmentList; + delete segmentList2; + return 1; + } + + return 0; +} diff --git a/modules/demux/adaptive/test/playlist/SegmentTemplate.cpp b/modules/demux/adaptive/test/playlist/SegmentTemplate.cpp new file mode 100644 index 0000000000..9f3d8b8c51 --- /dev/null +++ b/modules/demux/adaptive/test/playlist/SegmentTemplate.cpp @@ -0,0 +1,128 @@ +/***************************************************************************** + * + ***************************************************************************** + * Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "../../playlist/Segment.h" +#include "../../playlist/SegmentTemplate.h" +#include "../../playlist/SegmentTimeline.h" +#include "../../playlist/BasePlaylist.hpp" +#include "../../playlist/BasePeriod.h" +#include "../../playlist/Inheritables.hpp" +#include "../../../dash/mpd/AdaptationSet.h" +#include "../../../dash/mpd/Representation.h" + +#include "../test.hpp" + +#include <limits> + +using namespace adaptive; +using namespace adaptive::playlist; +using namespace dash::mpd; + +int SegmentTemplate_test() +{ + BasePlaylist *pl = nullptr; + try + { + pl = new BasePlaylist(nullptr); + BasePeriod *period = nullptr; + AdaptationSet *set = nullptr; + Representation *rep = nullptr; + try + { + period = new BasePeriod(pl); + set = new AdaptationSet(period); + rep = new Representation(set); + } catch(...) { + delete period; + delete set; + std::rethrow_exception(std::current_exception()); + } + set->addRepresentation(rep); + period->addAdaptationSet(set); + pl->addPeriod(period); + + Timescale timescale(100); + rep->addAttribute(new TimescaleAttr(timescale)); + SegmentTemplate *templ = new SegmentTemplate(new SegmentTemplateSegment(), rep); + rep->addAttribute(templ); + templ->addAttribute(new StartnumberAttr(11)); + std::string res = rep->contextualize(0, "$Number$.m4v", templ); + Expect(res == "0.m4v"); + + /* No timeline, no start/end, no segment duration known */ + Expect(templ->getStartSegmentNumber() == 11); + //Expect(templ->getMediaSegment(11) == nullptr); + uint64_t number; + Expect(templ->getSegmentNumberByTime(timescale.ToTime(0), &number) == false); + vlc_tick_t time, duration; + //Expect(templ->getPlaybackTimeDurationBySegmentNumber(11, &time, &duration) == false); + Expect(templ->getMinAheadTime(11) == 0); + + /* No timeline, no start/end, duration known */ + rep->addAttribute(new DurationAttr(100)); + Expect(templ->getSegmentNumberByTime(timescale.ToTime(500), &number) == true); + Expect(number == 11 + 5); + Expect(templ->getPlaybackTimeDurationBySegmentNumber(11 + 2, &time, &duration) == true); + Expect(time == timescale.ToTime(2 * 100)); + Expect(duration == timescale.ToTime(100)); + Expect(templ->getMediaSegment(11 + 2) != nullptr); + + /* start/end, duration known */ + vlc_tick_t now = timescale.ToTime(1000000); + pl->availabilityStartTime.Set(now); + pl->availabilityEndTime.Set(now + timescale.ToTime(100 * 20)); + Expect(templ->getLiveTemplateNumber(now, true) == templ->getStartSegmentNumber()); + //Expect(templ->getLiveTemplateNumber(now / 2, true) == std::numeric_limits<uint64_t>::max()); + Expect(templ->getLiveTemplateNumber(now + timescale.ToTime(100) * 2 + 1, true) == + templ->getStartSegmentNumber() + 1); + + /* reset */ + pl->availabilityStartTime.Set(0); + pl->availabilityEndTime.Set(0); + + /* timeline */ + const stime_t START = 1337; + SegmentTimeline *timeline = new SegmentTimeline(nullptr); + templ->addAttribute(timeline); + timeline->addElement(44, 100, 4, START); + timeline->addElement(44 + 5, 33, 4, START + 5 * 100); + Expect(templ->getMediaSegment(44 - 1) == nullptr); + Expect(templ->getMediaSegment(44 + 5 + 4) != nullptr); + Expect(templ->getMediaSegment(44 + 5 + 5) == nullptr); + Expect(templ->getStartSegmentNumber() == 44); + Expect(templ->getSegmentNumberByTime(timescale.ToTime(START + 5 * 100 + 2), &number) == true); + Expect(number == 44 + 5); + Expect(templ->getPlaybackTimeDurationBySegmentNumber(44 + 6, &time, &duration) == true); + Expect(time == timescale.ToTime(START + 5 * 100 + 33)); + Expect(duration == timescale.ToTime(33)); + Expect(templ->getMinAheadTime(44+6) == timescale.ToTime(3 * 33)); + + delete pl; + + } catch (...) { + delete pl; + return 1; + } + + return 0; +} diff --git a/modules/demux/adaptive/test/playlist/SegmentTimeline.cpp b/modules/demux/adaptive/test/playlist/SegmentTimeline.cpp new file mode 100644 index 0000000000..706bd20420 --- /dev/null +++ b/modules/demux/adaptive/test/playlist/SegmentTimeline.cpp @@ -0,0 +1,184 @@ +/***************************************************************************** + * + ***************************************************************************** + * Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "../../playlist/Segment.h" +#include "../../playlist/SegmentTimeline.h" +#include "../../playlist/BasePeriod.h" +#include "../../playlist/BaseAdaptationSet.h" +#include "../../playlist/BaseRepresentation.h" + +#include "../test.hpp" + +#include <limits> + +using namespace adaptive; +using namespace adaptive::playlist; + +int Timeline_test() +{ + SegmentTimeline *timeline = nullptr; + SegmentTimeline *timeline2 = nullptr; + try + { + timeline = new SegmentTimeline(nullptr); + /* Check failures */ + Expect(timeline->getTotalLength() == 0); + Expect(timeline->getElementIndexBySequence(123) == std::numeric_limits<uint64_t>::max()); + Expect(timeline->getElementNumberByScaledPlaybackTime(123) == 0); + Expect(timeline->getMinAheadScaledTime(0) == 0); + Expect(timeline->minElementNumber() == 0); + Expect(timeline->maxElementNumber() == 0); + + /* Simple elements list */ + const stime_t START = 1337; + timeline->addElement(11, 100, 0, START); + timeline->addElement(12, 50, 0, 0); + timeline->addElement(13, 25, 0, 0); + + Expect(timeline->minElementNumber() == 11); + Expect(timeline->maxElementNumber() == 13); + Expect(timeline->getTotalLength() == 175); + uint64_t idx = timeline->getElementIndexBySequence(0); + Expect(idx == std::numeric_limits<uint64_t>::max()); + idx = timeline->getElementIndexBySequence(100); + Expect(idx == std::numeric_limits<uint64_t>::max()); + idx = timeline->getElementIndexBySequence(11); + Expect(idx == 0); + idx = timeline->getElementIndexBySequence(13); + Expect(idx == 2); + + Expect(timeline->getMinAheadScaledTime(11) == 75); + Expect(timeline->getMinAheadScaledTime(12) == 25); + Expect(timeline->getMinAheadScaledTime(14) == 0); + Expect(timeline->getElementIndexBySequence(13) == 2); + Expect(timeline->getScaledPlaybackTimeByElementNumber(11) == START+0); + Expect(timeline->getScaledPlaybackTimeByElementNumber(13) == START+150); + stime_t time, duration; + Expect(timeline->getScaledPlaybackTimeDurationBySegmentNumber(12, &time, &duration)); + Expect(time == START+100); + Expect(duration == 50); + Expect(timeline->getElementNumberByScaledPlaybackTime(START-1) == 11); + Expect(timeline->getElementNumberByScaledPlaybackTime(START+9) == 11); + Expect(timeline->getElementNumberByScaledPlaybackTime(START+151) == 13); + + /* Check repeats */ + timeline->addElement(14, 100, 1, 0); + Expect(timeline->minElementNumber() == 11); + Expect(timeline->maxElementNumber() == 14 + 1); + Expect(timeline->getTotalLength() == 175 + 100*2); + Expect(timeline->getElementIndexBySequence(14) == 3); + Expect(timeline->getElementIndexBySequence(15) == 3); + timeline->addElement(16, 20, 9, 0); + Expect(timeline->maxElementNumber() == 16 + 9); + Expect(timeline->getTotalLength() == 175 + 100*2 + 20*10); + + Expect(timeline->getMinAheadScaledTime(14) == 100 + 20 * 10); + Expect(timeline->getMinAheadScaledTime(15) == 20 * 10); + Expect(timeline->getMinAheadScaledTime(20) == 20 * 5); + + Expect(timeline->getScaledPlaybackTimeByElementNumber(15) == START + 175 + 100); + Expect(timeline->getScaledPlaybackTimeByElementNumber(21) == START + 175 + 100*2 + 20*5); + + Expect(timeline->getElementNumberByScaledPlaybackTime(START + 175 + 100 + 10) == 15); + + /* Check discontinuity */ + timeline->addElement(40, 33, 1, START + 1000); + Expect(timeline->maxElementNumber() == 41); + Expect(timeline->getTotalLength() == 175 + 100*2 + 20*10 + 66); + Expect(timeline->getElementIndexBySequence(39) == std::numeric_limits<uint64_t>::max()); + Expect(timeline->getElementIndexBySequence(41) == 5); + + /* Pruning */ + Expect(timeline->pruneBySequenceNumber(24) == 5+8); + Expect(timeline->minElementNumber() == 24); + Expect(timeline->getTotalLength() == 20 * 2 + 33 * 2); + + Timescale timescale(100); + timeline->addAttribute(new TimescaleAttr(timescale)); + timeline->pruneByPlaybackTime(timescale.ToTime(START + 175 + 100*2 + 20*9 + 2)); + Expect(timeline->minElementNumber() == 25); + Expect(timeline->getTotalLength() == 20 + 33 * 2); + timeline->pruneByPlaybackTime(timescale.ToTime(START + 175 + 100*2 + 20*11)); + Expect(timeline->minElementNumber() == 25); /* tried to expurge before discontinuity time */ + timeline->pruneByPlaybackTime(timescale.ToTime(START + 1000 + 2)); + Expect(timeline->minElementNumber() == 40); + Expect(timeline->getTotalLength() == 33 * 2); + Expect(timeline->pruneBySequenceNumber(50) == 2); + Expect(timeline->getTotalLength() == 0); + + /* Merging */ + timeline->addElement(1, 1000, 0, START); + timeline->addElement(2, 2000, 1, 0); + Expect(timeline->minElementNumber() == 1); + Expect(timeline->maxElementNumber() == 2+1); + Expect(timeline->getTotalLength() == 1000 + 2000 * 2); + + timeline2 = new SegmentTimeline(nullptr); + timeline2->addAttribute(new TimescaleAttr(timescale)); + timeline2->addElement(1, 1000, 0, START); + timeline2->addElement(2, 2000, 1, 0); + Expect(timeline2->minElementNumber() == 1); + Expect(timeline->maxElementNumber() == 2+1); + + timeline->updateWith(*timeline2); /* should do no change */ + Expect(timeline->minElementNumber() == 1); + Expect(timeline->maxElementNumber() == 2+1); + Expect(timeline->getTotalLength() == 1000 + 2000 * 2); + + delete timeline2; + timeline2 = new SegmentTimeline(nullptr); + timeline2->addElement(1, 1000, 0, START); + timeline2->addElement(2, 2000, 1, 0); + timeline2->addElement(4, 2, 99, 0); + Expect(timeline2->maxElementNumber() == 4+99); + Expect(timeline2->getTotalLength() == 1000 + 2000 * 2 + 2 * 100); + + timeline->updateWith(*timeline2); /* should append missing content */ + Expect(timeline->maxElementNumber() == 4+99); + Expect(timeline->getTotalLength() == 1000 + 2000 * 2 + 2 * 100); + + delete timeline2; + timeline2 = new SegmentTimeline(nullptr); + /* add 0 more times at 1 inside offset, should not change anything */ + timeline2->addElement(4+1, 2, 99-1, START+1000 + 2000 * 2 + 2 * 1); + timeline->updateWith(*timeline2); + Expect(timeline->maxElementNumber() == 4+99); + + delete timeline2; + timeline2 = new SegmentTimeline(nullptr); + /* add 10 more times at 1 inside offset, should extend by 10 repeats */ + timeline2->addElement(4+1, 2, 99-1+10, START+1000 + 2000 * 2 + 2 * 1); + timeline->updateWith(*timeline2); + Expect(timeline->maxElementNumber() == 4+99+10); + + delete timeline; + delete timeline2; + + } catch (...) { + delete timeline; + delete timeline2; + return 1; + } + + return 0; +} diff --git a/modules/demux/adaptive/test/plumbing/CommandsQueue.cpp b/modules/demux/adaptive/test/plumbing/CommandsQueue.cpp new file mode 100644 index 0000000000..bba807e416 --- /dev/null +++ b/modules/demux/adaptive/test/plumbing/CommandsQueue.cpp @@ -0,0 +1,327 @@ +/***************************************************************************** + * + ***************************************************************************** + * Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "../../plumbing/FakeESOut.hpp" +#include "../../plumbing/FakeESOutID.hpp" +#include "../../plumbing/CommandsQueue.hpp" + +#include "../test.hpp" + +#include <vlc_block.h> + +#include <limits> +#include <list> +#include <algorithm> + +using namespace adaptive; + +using OutputVal = std::pair<const AbstractFakeESOutID *, block_t *>; + +class TestEsOut : public AbstractFakeEsOut +{ + public: + TestEsOut() {} + virtual ~TestEsOut() { cleanup(); } + virtual void recycle(AbstractFakeESOutID *) override {} + virtual void createOrRecycleRealEsID(AbstractFakeESOutID *) override {} + virtual void setPriority(int) override {} + virtual void sendData(AbstractFakeESOutID *id, block_t *b) override + { + output.push_back(OutputVal(id, b)); + } + virtual void sendMeta(int, const vlc_meta_t *) override {} + + std::list<OutputVal> output; + void cleanup() + { + while(!output.empty()) + { + block_Release(output.front().second); + output.pop_front(); + } + } + + private: + virtual es_out_id_t *esOutAdd(const es_format_t *) override { return nullptr; } + virtual int esOutSend(es_out_id_t *, block_t *) override { return VLC_SUCCESS; } + virtual void esOutDel(es_out_id_t *) override {} + virtual int esOutControl(int, va_list) override { return VLC_SUCCESS; } + virtual void esOutDestroy() override {} +}; + +class TestEsOutID : public AbstractFakeESOutID +{ + public: + TestEsOutID(TestEsOut *out) { this->out = out; } + virtual ~TestEsOutID() {} + virtual es_out_id_t * realESID() override { return nullptr; } + virtual void create() override {} + virtual void release() override {} + virtual void sendData(block_t *b) override + { + out->sendData(this, b); + } + + private: + TestEsOut *out; +}; + +int CommandsQueue_test() +{ + TestEsOut esout; + TestEsOutID *id0 = nullptr; + TestEsOutID *id1 = nullptr; + try + { + CommandsFactory *factory = new CommandsFactory(); + CommandsQueue queue(factory); + + id0 = new TestEsOutID(&esout); + AbstractCommand *cmd = nullptr; + + Expect(queue.isEOF() == false); + Expect(queue.isDraining() == false); + Expect(queue.isEmpty() == true); + Expect(queue.getDemuxedAmount(VLC_TICK_0) == 0); + Expect(queue.getBufferingLevel() == VLC_TICK_INVALID); + Expect(queue.getFirstDTS() == VLC_TICK_INVALID); + Expect(queue.getPCR() == VLC_TICK_INVALID); + cmd = queue.factory()->createEsOutAddCommand(id0); + queue.Schedule(cmd); + cmd = queue.factory()->createEsOutDelCommand(id0); + queue.Schedule(cmd); + for(size_t i=0; i<3; i++) /* Add / Del will return in between */ + queue.Process(std::numeric_limits<vlc_tick_t>::max()); + Expect(queue.isEmpty() == false); /* no PCR issued nor commit */ + queue.Commit(); + for(size_t i=0; i<3; i++) + queue.Process(std::numeric_limits<vlc_tick_t>::max()); + Expect(queue.isEOF() == false); + Expect(queue.isDraining() == false); + Expect(queue.isEmpty() == true); + Expect(queue.getDemuxedAmount(VLC_TICK_0) == 0); + Expect(queue.getBufferingLevel() == VLC_TICK_INVALID); + Expect(queue.getPCR() == std::numeric_limits<vlc_tick_t>::max()); + + queue.Abort(true); + esout.cleanup(); + + /* Feed data in */ + for(size_t i=0; i<10; i++) + { + block_t *data = block_Alloc(0); + Expect(data); + data->i_dts = VLC_TICK_0 + vlc_tick_from_sec(i); + cmd = queue.factory()->createEsOutSendCommand(id0, data); + queue.Schedule(cmd); + } + Expect(queue.getPCR() == VLC_TICK_INVALID); + Expect(queue.getFirstDTS() == VLC_TICK_INVALID); + Expect(queue.getDemuxedAmount(VLC_TICK_0) == 0); + Expect(queue.getBufferingLevel() == VLC_TICK_INVALID); + /* commit some */ + cmd = queue.factory()->createEsOutControlPCRCommand(0, VLC_TICK_0 + vlc_tick_from_sec(8)); + queue.Schedule(cmd); + Expect(queue.getDemuxedAmount(VLC_TICK_0) == vlc_tick_from_sec(8)); /* PCR committed data up to 8s */ + Expect(queue.getBufferingLevel() == VLC_TICK_0 + vlc_tick_from_sec(8)); + Expect(queue.getDemuxedAmount(VLC_TICK_0 + vlc_tick_from_sec(8)) == 0); + Expect(queue.getDemuxedAmount(VLC_TICK_0 + vlc_tick_from_sec(7)) == vlc_tick_from_sec(1)); + Expect(queue.getPCR() == VLC_TICK_INVALID); + /* extend through PCR */ + cmd = queue.factory()->createEsOutControlPCRCommand(0, VLC_TICK_0 + vlc_tick_from_sec(10)); + queue.Schedule(cmd); + Expect(queue.getBufferingLevel() == VLC_TICK_0 + vlc_tick_from_sec(10)); + Expect(queue.getDemuxedAmount(VLC_TICK_0) == vlc_tick_from_sec(10)); + + /* dequeue */ + queue.Process(VLC_TICK_0 + vlc_tick_from_sec(3)); + Expect(queue.getPCR() == VLC_TICK_0 + vlc_tick_from_sec(3)); + Expect(queue.getFirstDTS() == VLC_TICK_0 + vlc_tick_from_sec(3)); + Expect(queue.getDemuxedAmount(VLC_TICK_0 + vlc_tick_from_sec(3)) == vlc_tick_from_sec(7)); + Expect(queue.getDemuxedAmount(VLC_TICK_0 + vlc_tick_from_sec(4)) == vlc_tick_from_sec(6)); + + /* drop */ + queue.setDrop(true); + do + { + block_t *data = block_Alloc(0); + Expect(data); + data->i_dts = VLC_TICK_0 + vlc_tick_from_sec(11); + cmd = queue.factory()->createEsOutSendCommand(id0, data); + queue.Schedule(cmd); + } while(0); + cmd = queue.factory()->createEsOutControlPCRCommand(0, VLC_TICK_0 + vlc_tick_from_sec(11)); + queue.Schedule(cmd); + Expect(queue.getPCR() == VLC_TICK_0 + vlc_tick_from_sec(3)); /* should be unchanged */ + Expect(queue.getDemuxedAmount(VLC_TICK_0 + vlc_tick_from_sec(3)) == vlc_tick_from_sec(7)); + queue.setDrop(false); + + /* empty */ + Expect(queue.getPCR() == VLC_TICK_0 + vlc_tick_from_sec(3)); + queue.Process(VLC_TICK_0 + vlc_tick_from_sec(13)); + Expect(queue.isEmpty()); + Expect(queue.getPCR() == VLC_TICK_0 + vlc_tick_from_sec(9)); + + queue.Abort(true); + esout.cleanup(); + + /* reordering */ + id1 = new TestEsOutID(&esout); + const vlc_tick_t OFFSET = vlc_tick_from_sec(100); + for(size_t j=0; j<2; j++) + { + TestEsOutID *id = (j % 2) ? id1 : id0; + for(size_t i=0; i<5; i++) + { + block_t *data = block_Alloc(0); + Expect(data); + data->i_dts = VLC_TICK_0 + OFFSET + vlc_tick_from_sec(i); + cmd = queue.factory()->createEsOutSendCommand(id, data); + queue.Schedule(cmd); + } + } + + cmd = queue.factory()->createEsOutControlPCRCommand(0, VLC_TICK_0 + OFFSET + vlc_tick_from_sec(10)); + queue.Schedule(cmd); + Expect(esout.output.empty()); + queue.Process(VLC_TICK_0 + OFFSET - 1); + Expect(esout.output.empty()); + queue.Process(VLC_TICK_0 + OFFSET + vlc_tick_from_sec(10)); + Expect(esout.output.size() == 10); + for(size_t i=0; i<5; i++) + { + const vlc_tick_t now = VLC_TICK_0 + OFFSET + vlc_tick_from_sec(i); + OutputVal val = esout.output.front(); + Expect(val.first == id0); + Expect(val.second->i_dts == now); + block_Release(val.second); + esout.output.pop_front(); + val = esout.output.front(); + Expect(val.first == id1); + Expect(val.second->i_dts == now); + block_Release(val.second); + esout.output.pop_front(); + } + Expect(esout.output.empty()); + queue.Abort(true); + + /* reordering with PCR */ + for(size_t k=0; k<3; k++) + { + for(size_t j=0; j<2; j++) + { + TestEsOutID *id = (j % 2) ? id1 : id0; + for(size_t i=0; i<2; i++) + { + block_t *data = block_Alloc(0); + Expect(data); + data->i_dts = VLC_TICK_0 + OFFSET + vlc_tick_from_sec(k * 2 + i); + cmd = queue.factory()->createEsOutSendCommand(id, data); + queue.Schedule(cmd); + } + } + cmd = queue.factory()->createEsOutControlPCRCommand(0, + VLC_TICK_0 + OFFSET + vlc_tick_from_sec( (k*2)+1 )); + queue.Schedule(cmd); + } + queue.Process(std::numeric_limits<vlc_tick_t>::max()); + Expect(esout.output.size() == 12); + for(size_t i=0; i<12; i++) + { + TestEsOutID *id = (i % 2) ? id1 : id0; + const vlc_tick_t now = VLC_TICK_0 + OFFSET + vlc_tick_from_sec(i / 2); + OutputVal &val = esout.output.front(); + Expect(val.first == id); + Expect(val.second->i_dts == now); + block_Release(val.second); + esout.output.pop_front(); + } + Expect(esout.output.empty()); + queue.Abort(true); + + /* reordering with PCR & INVALID */ + for(size_t k=0; k<3; k++) + { + for(size_t j=0; j<2; j++) + { + TestEsOutID *id = (j % 2) ? id1 : id0; + for(size_t i=0; i<2; i++) + { + block_t *data = block_Alloc(0); + Expect(data); + if(i==0) + data->i_dts = VLC_TICK_0 + OFFSET + vlc_tick_from_sec(k); + cmd = queue.factory()->createEsOutSendCommand(id, data); + queue.Schedule(cmd); + } + } + cmd = queue.factory()->createEsOutControlPCRCommand(0, + VLC_TICK_0 + OFFSET + vlc_tick_from_sec(k)); + queue.Schedule(cmd); + } + queue.Process(std::numeric_limits<vlc_tick_t>::max()); + Expect(esout.output.size() == 12); + for(size_t i=0; i<6; i++) + { + TestEsOutID *id = (i % 2) ? id1 : id0; + const vlc_tick_t now = VLC_TICK_0 + OFFSET + vlc_tick_from_sec(i/2); + OutputVal val = esout.output.front(); + Expect(val.first == id); + Expect(val.second->i_dts == now); + block_Release(val.second); + esout.output.pop_front(); + val = esout.output.front(); + Expect(val.first == id); + Expect(val.second->i_dts == VLC_TICK_INVALID); + block_Release(val.second); + esout.output.pop_front(); + } + Expect(esout.output.empty()); + queue.Abort(true); + + /* reordering PCR before PTS */ + for(size_t i=0; i<2; i++) + { + const vlc_tick_t now = VLC_TICK_0 + OFFSET + vlc_tick_from_sec(i); + cmd = queue.factory()->createEsOutControlPCRCommand(0, now); + queue.Schedule(cmd); + block_t *data = block_Alloc(0); + Expect(data); + data->i_dts = now; + cmd = queue.factory()->createEsOutSendCommand(id0, data); + queue.Schedule(cmd); + } + queue.Process(VLC_TICK_0 + OFFSET + vlc_tick_from_sec(0)); + Expect(esout.output.size() == 1); + + } catch(...) { + delete id0; + delete id1; + return 1; + } + + delete id0; + delete id1; + + return 0; +} diff --git a/modules/demux/adaptive/test/test.cpp b/modules/demux/adaptive/test/test.cpp index 07ce55226a..68cf52c708 100644 --- a/modules/demux/adaptive/test/test.cpp +++ b/modules/demux/adaptive/test/test.cpp @@ -27,9 +27,25 @@ #include "test.hpp" +#include <iostream> + extern const char vlc_module_name[] = "foobar"; +#define TEST(func) []() { std::cerr << "Testing "#func << std::endl;\ + return func##_test(); }() + int main() { - return TemplatedUri_test(); + return + TEST(Inheritables) || + TEST(SegmentBase) || + TEST(SegmentList) || + TEST(SegmentTemplate) || + TEST(Timeline) || + TEST(Conversions) || + TEST(TemplatedUri) || + TEST(BufferingLogic) || + TEST(CommandsQueue) || + TEST(M3U8MasterPlaylist) || + TEST(M3U8Playlist); } diff --git a/modules/demux/adaptive/test/test.hpp b/modules/demux/adaptive/test/test.hpp index 3f39ae15dd..a2c27f57a7 100644 --- a/modules/demux/adaptive/test/test.hpp +++ b/modules/demux/adaptive/test/test.hpp @@ -36,6 +36,16 @@ #define Expect(testcond) DoExpect((testcond), __FUNCTION__, __LINE__) +int Inheritables_test(); int TemplatedUri_test(); +int SegmentBase_test(); +int SegmentList_test(); +int SegmentTemplate_test(); +int Timeline_test(); +int Conversions_test(); +int M3U8MasterPlaylist_test(); +int M3U8Playlist_test(); +int CommandsQueue_test(); +int BufferingLogic_test(); #endif diff --git a/modules/demux/adaptive/test/tools/Conversions.cpp b/modules/demux/adaptive/test/tools/Conversions.cpp new file mode 100644 index 0000000000..e54bf0d833 --- /dev/null +++ b/modules/demux/adaptive/test/tools/Conversions.cpp @@ -0,0 +1,67 @@ +/***************************************************************************** + * + ***************************************************************************** + * Copyright (C) 2020 VideoLabs, VideoLAN and VLC Authors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "../../tools/Conversions.hpp" + +#include "../test.hpp" + +#include <limits> + +int Conversions_test() +{ + UTCTime utc("1970-01-01T00:00:00.000+00:00"); + Expect(utc.mtime() == 0); + utc = UTCTime("1970-01-01T10:00:00.000+10:00"); + Expect(utc.mtime() == 0); + utc = UTCTime("1970-01-01T02:00:00.000+01:02"); + Expect(utc.mtime() == vlc_tick_from_sec(7200 - 3720)); + utc = UTCTime("1970-01-01T02:00:00.000+0102"); + Expect(utc.mtime() == vlc_tick_from_sec(7200 - 3720)); + utc = UTCTime("1970-01-01T01:10:00.000+1"); + Expect(utc.mtime() == vlc_tick_from_sec(10*60)); + utc = UTCTime("2019-06-11Z"); + Expect(utc.mtime() == vlc_tick_from_sec(1560211200)); + utc = UTCTime("2019-06-11T16:52:05.100Z"); + Expect(utc.mtime() == vlc_tick_from_sec(1560271925) + VLC_TICK_FROM_MS(100)); + utc = UTCTime("2019-06-11T16:52:05.012Z"); + Expect(utc.mtime() == vlc_tick_from_sec(1560271925) + VLC_TICK_FROM_MS(12)); + utc = UTCTime("T16:52:05.012Z"); + + + IsoTime isotime("PT0H9M56.46S"); + Expect(isotime == (vlc_tick_from_sec(9*60+56)+VLC_TICK_FROM_MS(460))); + isotime = IsoTime("HELLO"); + Expect(isotime == -1); + isotime = IsoTime("P1D"); + Expect(isotime == vlc_tick_from_sec(86400)); + isotime = IsoTime("PT2.5M"); + Expect(isotime == vlc_tick_from_sec(150)); + isotime = IsoTime("PT"); + Expect(isotime == 0); + isotime = IsoTime("PT.5S"); + Expect(isotime == VLC_TICK_FROM_MS(500)); + isotime = IsoTime("PT.010S"); + Expect(isotime == VLC_TICK_FROM_MS(10)); + + return 0; +} _______________________________________________ vlc-commits mailing list [email protected] https://mailman.videolan.org/listinfo/vlc-commits
