* 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),
- currentRepresentation( 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);
}
}
for(size_t i = 0; i < representations.size(); i++)
{
- Representation *rep = new Representation;
- this->currentRepresentation = rep;
- 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( this->currentRepresentation );
+ 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( this->currentRepresentation );
- 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;
+}