]> git.sesse.net Git - vlc/blobdiff - modules/stream_filter/dash/mpd/IsoffMainParser.cpp
demux: dash: simplify integer parsing using templates
[vlc] / modules / stream_filter / dash / mpd / IsoffMainParser.cpp
index c2bedd85297522f1d30623c54b94089a624f787a..8da7adf82112b89ce13317f617c82d42c18b908f 100644 (file)
@@ -7,19 +7,19 @@
  * Authors: Christopher Mueller <christopher.mueller@itec.uni-klu.ac.at>
  *          Christian Timmerer  <christian.timmerer@itec.uni-klu.ac.at>
  *
- * 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
+ * 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 General Public License for more details.
+ * 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.
+ * 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
 #endif
 
 #include "IsoffMainParser.h"
+#include "SegmentTemplate.h"
+#include "SegmentInfoDefault.h"
+#include "xml/DOMHelper.h"
+#include <vlc_strings.h>
+#include <vlc_stream.h>
+#include <cstdio>
+#include <sstream>
 
 using namespace dash::mpd;
 using namespace dash::xml;
 
 IsoffMainParser::IsoffMainParser    (Node *root, stream_t *p_stream) :
-                 root               (root),
-                 p_stream           (p_stream),
-                 mpd                (NULL)
+                 IMPDParser(root, NULL, p_stream, NULL)
 {
 }
 IsoffMainParser::~IsoffMainParser   ()
 {
 }
 
-bool    IsoffMainParser::parse              ()
+bool    IsoffMainParser::parse              (Profile profile)
 {
-    this->mpd = new MPD();
-
-    this->setMPDAttributes();
-    this->setMPDBaseUrl();
-    this->setPeriods();
+    mpd = new MPD(p_stream, profile);
+    setMPDAttributes();
+    setMPDBaseUrl(root);
+    parsePeriods(root);
 
+    print();
     return true;
 }
-MPD*    IsoffMainParser::getMPD             ()
-{
-    return this->mpd;
-}
+
 void    IsoffMainParser::setMPDAttributes   ()
 {
     const std::map<std::string, std::string> attr = this->root->getAttributes();
 
     std::map<std::string, std::string>::const_iterator it;
 
-    it = attr.find("profiles");
-    if(it != attr.end())
-        this->mpd->setProfile(it->second);
-
     it = attr.find("mediaPresentationDuration");
     if(it != attr.end())
-        this->mpd->setDuration(str_duration(it->second.c_str()));
+        this->mpd->setDuration(IsoTime(it->second));
 
     it = attr.find("minBufferTime");
     if(it != attr.end())
-        this->mpd->setMinBufferTime(str_duration( it->second.c_str()));
+        this->mpd->setMinBufferTime(IsoTime(it->second));
 
+    it = attr.find("type");
+    if(it != attr.end())
+        mpd->setType(it->second);
+
+    it = attr.find("availabilityStartTime");
+    if(it != attr.end())
+        mpd->setAvailabilityStartTime(UTCTime(it->second));
+
+    it = attr.find("timeShiftBufferDepth");
+        if(it != attr.end())
+            mpd->setTimeShiftBufferDepth(IsoTime(it->second));
 }
-void    IsoffMainParser::setMPDBaseUrl      ()
+
+void IsoffMainParser::parsePeriods(Node *root)
 {
-    std::vector<Node *> baseUrls = DOMHelper::getChildElementByTagName(this->root, "BaseURL");
+    std::vector<Node *> periods = DOMHelper::getElementByTagName(root, "Period", false);
+    std::vector<Node *>::const_iterator it;
 
-    for(size_t i = 0; i < baseUrls.size(); i++)
+    for(it = periods.begin(); it != periods.end(); it++)
     {
-        BaseUrl *url = new BaseUrl(baseUrls.at(i)->getText());
-        this->mpd->addBaseUrl(url);
+        Period *period = new (std::nothrow) Period(mpd);
+        if (!period)
+            continue;
+        parseSegmentInformation(*it, period);
+        if((*it)->hasAttribute("start"))
+            period->startTime.Set(IsoTime((*it)->getAttributeValue("start")));
+        setAdaptationSets(*it, period);
+        mpd->addPeriod(period);
     }
 }
-void    IsoffMainParser::setPeriods         ()
+
+size_t IsoffMainParser::parseSegmentTemplate(Node *templateNode, SegmentInformation *info)
 {
-    std::vector<Node *> periods = DOMHelper::getElementByTagName(this->root, "Period", false);
+    size_t total = 0;
+    if (templateNode == NULL || !templateNode->hasAttribute("media"))
+        return total;
+
+    std::string mediaurl = templateNode->getAttributeValue("media");
+    SegmentTemplate *mediaTemplate = NULL;
+    if(mediaurl.empty() || !(mediaTemplate = new (std::nothrow) SegmentTemplate(info)) )
+        return total;
+    mediaTemplate->setSourceUrl(mediaurl);
 
-    for(size_t i = 0; i < periods.size(); i++)
+    if(templateNode->hasAttribute("startNumber"))
+        mediaTemplate->setStartIndex(Integer<uint64_t>(templateNode->getAttributeValue("startNumber")));
+
+    if(templateNode->hasAttribute("duration"))
+        mediaTemplate->duration.Set(Integer<mtime_t>(templateNode->getAttributeValue("duration")));
+
+    if(templateNode->hasAttribute("timescale"))
+        mediaTemplate->timescale.Set(Integer<uint64_t>(templateNode->getAttributeValue("timescale")));
+
+    InitSegmentTemplate *initTemplate = NULL;
+
+    if(templateNode->hasAttribute("initialization"))
     {
-        Period *period = new Period();
-        this->setAdaptationSets(periods.at(i), period);
-        this->mpd->addPeriod(period);
+        std::string initurl = templateNode->getAttributeValue("initialization");
+        if(!initurl.empty() && (initTemplate = new (std::nothrow) InitSegmentTemplate(info)))
+            initTemplate->setSourceUrl(initurl);
     }
+
+    info->setSegmentTemplate(mediaTemplate, SegmentInformation::INFOTYPE_MEDIA);
+    info->setSegmentTemplate(initTemplate, SegmentInformation::INFOTYPE_INIT);
+
+    total += ( mediaTemplate != NULL );
+
+    return total;
 }
+
+size_t IsoffMainParser::parseSegmentInformation(Node *node, SegmentInformation *info)
+{
+    size_t total = 0;
+    parseSegmentBase(DOMHelper::getFirstChildElementByName(node, "SegmentBase"), info);
+    total += parseSegmentList(DOMHelper::getFirstChildElementByName(node, "SegmentList"), info);
+    total += parseSegmentTemplate(DOMHelper::getFirstChildElementByName(node, "SegmentTemplate" ), info);
+    if(node->hasAttribute("bitstreamSwitching"))
+        info->setBitstreamSwitching(node->getAttributeValue("bitstreamSwitching") == "true");
+    if(node->hasAttribute("timescale"))
+        info->timescale.Set(Integer<uint64_t>(node->getAttributeValue("timescale")));
+    return total;
+}
+
 void    IsoffMainParser::setAdaptationSets  (Node *periodNode, Period *period)
 {
     std::vector<Node *> adaptationSets = DOMHelper::getElementByTagName(periodNode, "AdaptationSet", false);
+    std::vector<Node *>::const_iterator it;
 
-    for(size_t i = 0; i < adaptationSets.size(); i++)
+    for(it = adaptationSets.begin(); it != adaptationSets.end(); it++)
     {
-        AdaptationSet *adaptationSet = new AdaptationSet();
-        this->setRepresentations(adaptationSets.at(i), adaptationSet);
+        AdaptationSet *adaptationSet = new AdaptationSet(period);
+        if(!adaptationSet)
+            continue;
+        if((*it)->hasAttribute("mimeType"))
+            adaptationSet->setMimeType((*it)->getAttributeValue("mimeType"));
+
+        parseSegmentInformation( *it, adaptationSet );
+
+        setRepresentations((*it), adaptationSet);
         period->addAdaptationSet(adaptationSet);
     }
 }
@@ -112,115 +178,259 @@ void    IsoffMainParser::setRepresentations (Node *adaptationSetNode, Adaptation
 
     for(size_t i = 0; i < representations.size(); i++)
     {
-        Representation *rep = new Representation;
-        this->setSegmentBase(representations.at(i), rep);
-        this->setSegmentList(representations.at(i), rep);
-        rep->setBandwidth(atoi(representations.at(i)->getAttributeValue("bandwidth").c_str()));
-        adaptationSet->addRepresentation(rep);
+        this->currentRepresentation = new Representation(adaptationSet, getMPD());
+        Node *repNode = representations.at(i);
+
+        std::vector<Node *> baseUrls = DOMHelper::getChildElementByTagName(repNode, "BaseURL");
+        if(!baseUrls.empty())
+            currentRepresentation->setBaseUrl( new BaseUrl( baseUrls.front()->getText() ) );
+
+        if(repNode->hasAttribute("id"))
+            currentRepresentation->setId(repNode->getAttributeValue("id"));
+
+        if(repNode->hasAttribute("width"))
+            this->currentRepresentation->setWidth(atoi(repNode->getAttributeValue("width").c_str()));
+
+        if(repNode->hasAttribute("height"))
+            this->currentRepresentation->setHeight(atoi(repNode->getAttributeValue("height").c_str()));
+
+        if(repNode->hasAttribute("bandwidth"))
+            this->currentRepresentation->setBandwidth(atoi(repNode->getAttributeValue("bandwidth").c_str()));
+
+        if(repNode->hasAttribute("mimeType"))
+            currentRepresentation->setMimeType(repNode->getAttributeValue("mimeType"));
+
+        size_t totalmediasegments = parseSegmentInformation(repNode, currentRepresentation);
+        if(!totalmediasegments)
+        {
+            /* unranged & segment less representation, add fake segment */
+            SegmentList *list = new SegmentList();
+            Segment *seg = new Segment(currentRepresentation);
+            if(list && seg)
+            {
+                list->addSegment(seg);
+                currentRepresentation->setSegmentList(list);
+            }
+            else
+            {
+                delete seg;
+                delete list;
+            }
+        }
+
+        adaptationSet->addRepresentation(this->currentRepresentation);
     }
 }
-void    IsoffMainParser::setSegmentBase     (dash::xml::Node *repNode, Representation *rep)
+
+void IsoffMainParser::parseSegmentBase(Node * segmentBaseNode, SegmentInformation *info)
 {
-    std::vector<Node *> segmentBase = DOMHelper::getElementByTagName(repNode, "SegmentBase", false);
+    if(!segmentBaseNode)
+        return;
 
-    if(segmentBase.size() > 0)
+    else if(segmentBaseNode->hasAttribute("indexRange"))
+    {
+        SegmentList *list = new SegmentList();
+        Segment *seg;
+
+        size_t start = 0, end = 0;
+        if (std::sscanf(segmentBaseNode->getAttributeValue("indexRange").c_str(), "%zu-%zu", &start, &end) == 2)
+        {
+            seg = new IndexSegment(info);
+            seg->setByteRange(start, end);
+            list->addSegment(seg);
+            /* index must be before data, so data starts at index end */
+            seg = new Segment(info);
+            seg->setByteRange(end + 1, 0);
+        }
+        else
+        {
+            seg = new Segment(info);
+        }
+
+        list->addSegment(seg);
+        info->setSegmentList(list);
+
+        std::vector<Node *> initSeg = DOMHelper::getElementByTagName(segmentBaseNode, "Initialization", false);
+        if(!initSeg.empty())
+        {
+            SegmentBase *base = new SegmentBase();
+            setInitSegment(segmentBaseNode, base);
+            info->setSegmentBase(base);
+        }
+    }
+    else
     {
         SegmentBase *base = new SegmentBase();
-        this->setInitSegment(segmentBase.at(0), base);
-        rep->setSegmentBase(base);
+        setInitSegment(segmentBaseNode, base);
+        info->setSegmentBase(base);
     }
 }
-void    IsoffMainParser::setSegmentList     (dash::xml::Node *repNode, Representation *rep)
-{
-    std::vector<Node *> segmentList = DOMHelper::getElementByTagName(repNode, "SegmentList", false);
 
-    if(segmentList.size() > 0)
+size_t IsoffMainParser::parseSegmentList(Node * segListNode, SegmentInformation *info)
+{
+    size_t total = 0;
+    if(segListNode)
     {
-        SegmentList *list = new SegmentList();
-        this->setSegments(segmentList.at(0), list);
-        rep->setSegmentList(list);
-    }
+        std::vector<Node *> segments = DOMHelper::getElementByTagName(segListNode, "SegmentURL", false);
+        SegmentList *list;
+        if(!segments.empty() && (list = new (std::nothrow) SegmentList()))
+        {
+            std::vector<Node *>::const_iterator it;
+            for(it = segments.begin(); it != segments.end(); it++)
+            {
+                Node *segmentURL = *it;
+                std::string mediaUrl = segmentURL->getAttributeValue("media");
+                if(mediaUrl.empty())
+                    continue;
+
+                Segment *seg = new (std::nothrow) Segment(info);
+                if(!seg)
+                    continue;
+
+                seg->setSourceUrl(segmentURL->getAttributeValue("media"));
+
+                if(segmentURL->hasAttribute("mediaRange"))
+                {
+                    std::string range = segmentURL->getAttributeValue("mediaRange");
+                    size_t pos = range.find("-");
+                    seg->setByteRange(atoi(range.substr(0, pos).c_str()), atoi(range.substr(pos + 1, range.size()).c_str()));
+                }
 
+                list->addSegment(seg);
+                total++;
+            }
+
+            info->setSegmentList(list);
+        }
+    }
+    return total;
 }
+
 void    IsoffMainParser::setInitSegment     (dash::xml::Node *segBaseNode, SegmentBase *base)
 {
     std::vector<Node *> initSeg = DOMHelper::getElementByTagName(segBaseNode, "Initialisation", false);
 
+    if(initSeg.size() == 0)
+        initSeg = DOMHelper::getElementByTagName(segBaseNode, "Initialization", false);
+
     if(initSeg.size() > 0)
     {
-        Segment *seg = new Segment();
+        Segment *seg = new InitSegment( currentRepresentation );
         seg->setSourceUrl(initSeg.at(0)->getAttributeValue("sourceURL"));
 
         if(initSeg.at(0)->hasAttribute("range"))
         {
             std::string range = initSeg.at(0)->getAttributeValue("range");
             size_t pos = range.find("-");
-            seg->setByteRange(atoi(range.substr(0, pos).c_str()), atoi(range.substr(pos, range.size()).c_str()));
+            seg->setByteRange(atoi(range.substr(0, pos).c_str()), atoi(range.substr(pos + 1, range.size()).c_str()));
         }
 
-        for(size_t i = 0; i < this->mpd->getBaseUrls().size(); i++)
-            seg->addBaseUrl(this->mpd->getBaseUrls().at(i));
-
         base->addInitSegment(seg);
     }
 }
-void    IsoffMainParser::setSegments        (dash::xml::Node *segListNode, SegmentList *list)
-{
-    std::vector<Node *> segments = DOMHelper::getElementByTagName(segListNode, "SegmentURL", false);
 
-    for(size_t i = 0; i < segments.size(); i++)
+void    IsoffMainParser::print              ()
+{
+    if(mpd)
     {
-        Segment *seg = new Segment();
-        seg->setSourceUrl(segments.at(i)->getAttributeValue("media"));
+        msg_Dbg(p_stream, "MPD profile=%s mediaPresentationDuration=%ld minBufferTime=%ld",
+                static_cast<std::string>(mpd->getProfile()).c_str(),
+                mpd->getDuration(),
+                mpd->getMinBufferTime());
+        msg_Dbg(p_stream, "BaseUrl=%s", mpd->getUrlSegment().toString().c_str());
 
-        if(segments.at(0)->hasAttribute("mediaRange"))
+        std::vector<Period *>::const_iterator i;
+        for(i = mpd->getPeriods().begin(); i != mpd->getPeriods().end(); i++)
         {
-            std::string range = segments.at(0)->getAttributeValue("mediaRange");
-            size_t pos = range.find("-");
-            seg->setByteRange(atoi(range.substr(0, pos).c_str()), atoi(range.substr(pos, range.size()).c_str()));
+            std::vector<std::string> debug = (*i)->toString();
+            std::vector<std::string>::const_iterator l;
+            for(l = debug.begin(); l < debug.end(); l++)
+            {
+                msg_Dbg(p_stream, "%s", (*l).c_str());
+            }
         }
+    }
+}
 
-        for(size_t i = 0; i < this->mpd->getBaseUrls().size(); i++)
-            seg->addBaseUrl(this->mpd->getBaseUrls().at(i));
+IsoTime::IsoTime(const std::string &str)
+{
+    time = str_duration(str.c_str());
+}
 
-        list->addSegment(seg);
-    }
+IsoTime::operator mtime_t () const
+{
+    return time;
 }
-void    IsoffMainParser::print              ()
+
+/* i_year: year - 1900  i_month: 0-11  i_mday: 1-31 i_hour: 0-23 i_minute: 0-59 i_second: 0-59 */
+static int64_t vlc_timegm( int i_year, int i_month, int i_mday, int i_hour, int i_minute, int i_second )
 {
-    if(this->mpd)
-    {
-        msg_Dbg(this->p_stream, "MPD profile=%d mediaPresentationDuration=%ld minBufferTime=%ld", this->mpd->getProfile(),
-                                                                                                  this->mpd->getDuration(),
-                                                                                                  this->mpd->getMinBufferTime());
-        const std::vector<BaseUrl *> baseurls = this->mpd->getBaseUrls();
+    static const int pn_day[12+1] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
+    int64_t i_day;
 
-        for(size_t i = 0; i < baseurls.size(); i++)
-            msg_Dbg(this->p_stream, "BaseUrl=%s", baseurls.at(i)->getUrl().c_str());
+    if( i_year < 70 ||
+        i_month < 0 || i_month > 11 || i_mday < 1 || i_mday > 31 ||
+        i_hour < 0 || i_hour > 23 || i_minute < 0 || i_minute > 59 || i_second < 0 || i_second > 59 )
+        return -1;
 
-        const std::vector<Period *> periods = this->mpd->getPeriods();
+    /* Count the number of days */
+    i_day = 365 * (i_year-70) + pn_day[i_month] + i_mday - 1;
+#define LEAP(y) ( ((y)%4) == 0 && (((y)%100) != 0 || ((y)%400) == 0) ? 1 : 0)
+    for( int i = 70; i < i_year; i++ )
+        i_day += LEAP(1900+i);
+    if( i_month > 1 )
+        i_day += LEAP(1900+i_year);
+#undef LEAP
+    /**/
+    return ((24*i_day + i_hour)*60 + i_minute)*60 + i_second;
+}
+
+UTCTime::UTCTime(const std::string &str)
+{
+    enum { YEAR = 0, MON, DAY, HOUR, MIN, SEC, TZ };
+    int values[7] = {0};
+    std::istringstream in(str);
 
-        for(size_t i = 0; i < periods.size(); i++)
+    try
+    {
+        /* Date */
+        for(int i=YEAR;i<=DAY && !in.eof();i++)
+        {
+            if(i!=YEAR)
+                in.ignore(1);
+            in >> values[i];
+        }
+        /* Time */
+        if (!in.eof() && in.peek() == 'T')
         {
-            Period *period = periods.at(i);
-            msg_Dbg(this->p_stream, " Period");
-            for(size_t j = 0; j < period->getAdaptationSets().size(); j++)
+            for(int i=HOUR;i<=SEC && !in.eof();i++)
             {
-                AdaptationSet *aset = period->getAdaptationSets().at(j);
-                msg_Dbg(this->p_stream, "  AdaptationSet");
-                for(size_t k = 0; k < aset->getRepresentations().size(); k++)
-                {
-                    Representation *rep = aset->getRepresentations().at(k);
-                    msg_Dbg(this->p_stream, "   Representation");
-                    Segment *initSeg = rep->getSegmentBase()->getInitSegment();
-                    msg_Dbg(this->p_stream, "    InitSeg url=%s", initSeg->getSourceUrl().c_str());
-                    for(size_t l = 0; l < rep->getSegmentList()->getSegments().size(); l++)
-                    {
-                        Segment *seg = rep->getSegmentList()->getSegments().at(l);
-                        msg_Dbg(this->p_stream, "    Segment url=%s", seg->getSourceUrl().c_str());
-                    }
-                }
+                in.ignore(1);
+                in >> values[i];
             }
         }
+        /* Timezone */
+        if (!in.eof() && in.peek() == 'Z')
+        {
+            in.ignore(1);
+            while(!in.eof())
+            {
+                if(in.peek() == '+')
+                    continue;
+                in >> values[TZ];
+                break;
+            }
+        }
+
+        time = vlc_timegm( values[YEAR] - 1900, values[MON] - 1, values[DAY],
+                           values[HOUR], values[MIN], values[SEC] );
+        time += values[TZ] * 3600;
+    } catch(int) {
+        time = 0;
     }
 }
+
+UTCTime::operator mtime_t () const
+{
+    return time;
+}