]> git.sesse.net Git - vlc/blob - modules/stream_filter/dash/mpd/BasicCMParser.cpp
stream_filter: dash: fix and add missing profiles
[vlc] / modules / stream_filter / dash / mpd / BasicCMParser.cpp
1 /*
2  * BasicCMParser.cpp
3  *****************************************************************************
4  * Copyright (C) 2010 - 2011 Klagenfurt University
5  *
6  * Created on: Aug 10, 2010
7  * Authors: Christopher Mueller <christopher.mueller@itec.uni-klu.ac.at>
8  *          Christian Timmerer  <christian.timmerer@itec.uni-klu.ac.at>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published
12  * by the Free Software Foundation; either version 2.1 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include "BasicCMParser.h"
29 #include "mpd/ContentDescription.h"
30 #include "mpd/SegmentInfoDefault.h"
31 #include "mpd/SegmentTemplate.h"
32 #include "mpd/SegmentTimeline.h"
33
34 #include <cstdlib>
35 #include <sstream>
36 #include <sstream>
37
38 #include <vlc_common.h>
39 #include <vlc_stream.h>
40 #include <vlc_strings.h>
41
42 using namespace dash::mpd;
43 using namespace dash::xml;
44
45 BasicCMParser::BasicCMParser( Node *root, stream_t *p_stream ) :
46     root( root ),
47     mpd( NULL ),
48     p_stream( p_stream ),
49     currentRepresentation( NULL )
50 {
51     this->url = p_stream->psz_access;
52     this->url += "://";
53     //Only append without the mpd file.
54     std::string path = p_stream->psz_path;
55     size_t      it = path.find_last_of( '/', path.length() - 1 );
56     if ( it != std::string::npos )
57         this->url.append( path, 0, it );
58     else
59         this->url += p_stream->psz_path;
60     this->url += '/';
61 }
62
63 BasicCMParser::~BasicCMParser   ()
64 {
65 }
66
67 bool    BasicCMParser::parse                ()
68 {
69     return this->setMPD();
70 }
71 bool    BasicCMParser::setMPD()
72 {
73     const std::map<std::string, std::string>    attr = this->root->getAttributes();
74     this->mpd = new MPD;
75
76     std::map<std::string, std::string>::const_iterator  it;
77     it = attr.find("mediaPresentationDuration");
78     /*
79         Standard specifies a default of "On-Demand",
80         so anything that is not "Live" is "On-Demand"
81     */
82     this->mpd->setLive( it != attr.end() && it->second == "Live" );
83     it = attr.find( "availabilityStartTime" );
84     if ( it == attr.end() && this->mpd->isLive() == true )
85     {
86         std::cerr << "An @availabilityStartTime attribute must be specified when"
87                      " the stream @type is Live" << std::endl;
88         return false;
89     }
90 #ifdef HAVE_STRPTIME
91     if ( it != attr.end() )
92     {
93         struct tm   t;
94         char        *res = strptime( it->second.c_str(), "%Y-%m-%dT%T", &t );
95         if ( res == NULL )
96         {
97             if ( this->mpd->isLive() == true )
98             {
99                 std::cerr << "An @availabilityStartTime attribute must be specified when"
100                              " the stream @type is Live" << std::endl;
101                 return false;
102             }
103         }
104         else
105             this->mpd->setAvailabilityStartTime( mktime( &t ) );
106     }
107     it = attr.find( "availabilityEndTime" );
108     if ( it != attr.end() )
109     {
110         struct tm   t;
111         char        *res = strptime( it->second.c_str(), "%Y-%m-%dT%T", &t );
112         if ( res != NULL )
113             this->mpd->setAvailabilityEndTime( mktime( &t ) );
114     }
115 #endif
116     it = attr.find( "mediaPresentationDuration" );
117     if ( it != attr.end() )
118         this->mpd->setDuration( str_duration( it->second.c_str() ) );
119     it = attr.find( "minimumUpdatePeriodMPD" );
120     if ( it != attr.end() )
121         this->mpd->setMinUpdatePeriod( str_duration( it->second.c_str() ) );
122     it = attr.find( "minBufferTime" );
123     if ( it != attr.end() )
124         this->mpd->setMinBufferTime( str_duration( it->second.c_str() ) );
125
126     if ( this->mpd->isLive() )
127     {
128         //This value is undefined when using type "On-Demand"
129         it = attr.find( "timeshiftBufferDepth" );
130         if ( it != attr.end() )
131             this->mpd->setTimeShiftBufferDepth( str_duration( it->second.c_str() ) );
132     }
133
134     this->setMPDBaseUrl(this->root);
135     this->setPeriods(this->root);
136     this->mpd->setProgramInformation( this->parseProgramInformation() );
137     return true;
138 }
139 void    BasicCMParser::setMPDBaseUrl        (Node *root)
140 {
141     std::vector<Node *> baseUrls = DOMHelper::getChildElementByTagName(root, "BaseURL");
142
143     for(size_t i = 0; i < baseUrls.size(); i++)
144     {
145         BaseUrl *url = new BaseUrl(baseUrls.at(i)->getText());
146         this->mpd->addBaseUrl(url);
147     }
148 }
149
150 void    BasicCMParser::setPeriods           (Node *root)
151 {
152     std::vector<Node *> periods = DOMHelper::getElementByTagName(root, "Period", false);
153
154     for(size_t i = 0; i < periods.size(); i++)
155     {
156         Period *period = new Period();
157         this->setAdaptationSet(periods.at(i), period);
158         this->mpd->addPeriod(period);
159     }
160 }
161
162 void BasicCMParser::parseSegmentTimeline(Node *node, SegmentInfoCommon *segmentInfo)
163 {
164     Node*   segmentTimelineNode = DOMHelper::getFirstChildElementByName( node, "SegmentTimeline" );
165     if ( segmentTimelineNode )
166     {
167         SegmentTimeline     *segmentTimeline = new SegmentTimeline;
168         std::vector<Node*>  sNodes = DOMHelper::getChildElementByTagName( segmentTimelineNode, "S" );
169         std::vector<Node*>::const_iterator  it = sNodes.begin();
170         std::vector<Node*>::const_iterator  end = sNodes.end();
171
172         while ( it != end )
173         {
174             SegmentTimeline::Element*    s = new SegmentTimeline::Element;
175             const std::map<std::string, std::string>    sAttr = (*it)->getAttributes();
176             std::map<std::string, std::string>::const_iterator  sIt;
177
178             sIt = sAttr.find( "t" );
179             if ( sIt == sAttr.end() )
180             {
181                 std::cerr << "'t' attribute is mandatory for every SegmentTimeline/S element" << std::endl;
182                 delete s;
183                 ++it;
184                 continue ;
185             }
186             s->t = atoll( sIt->second.c_str() );
187             sIt = sAttr.find( "d" );
188             if ( sIt == sAttr.end() )
189             {
190                 std::cerr << "'d' attribute is mandatory for every SegmentTimeline/S element" << std::endl;
191                 delete s;
192                 ++it;
193                 continue ;
194             }
195             s->d = atoll( sIt->second.c_str() );
196             sIt = sAttr.find( "r" );
197             if ( sIt != sAttr.end() )
198                 s->r = atoi( sIt->second.c_str() );
199             segmentTimeline->addElement( s );
200             ++it;
201         }
202         segmentInfo->setSegmentTimeline( segmentTimeline );
203     }
204 }
205
206 void BasicCMParser::parseSegmentInfoCommon(Node *node, SegmentInfoCommon *segmentInfo)
207 {
208     const std::map<std::string, std::string>            attr = node->getAttributes();
209
210     const std::vector<Node*>            baseUrls = DOMHelper::getChildElementByTagName( node, "BaseURL" );
211     if ( baseUrls.size() > 0 )
212     {
213         std::vector<Node*>::const_iterator  it = baseUrls.begin();
214         std::vector<Node*>::const_iterator  end = baseUrls.end();
215         while ( it != end )
216         {
217             segmentInfo->appendBaseURL( (*it)->getText() );
218             ++it;
219         }
220     }
221     std::map<std::string, std::string>::const_iterator  it = attr.begin();
222
223     this->setInitSegment( node, segmentInfo );
224     it = attr.find( "duration" );
225     if ( it != attr.end() )
226         segmentInfo->setDuration( str_duration( it->second.c_str() ) );
227     it = attr.find( "startIndex" );
228     if ( it != attr.end() )
229         segmentInfo->setStartIndex( atoi( it->second.c_str() ) );
230     this->parseSegmentTimeline( node, segmentInfo );
231 }
232
233 void BasicCMParser::parseSegmentInfoDefault(Node *node, AdaptationSet *group)
234 {
235     Node*   segmentInfoDefaultNode = DOMHelper::getFirstChildElementByName( node, "SegmentInfoDefault" );
236
237     if ( segmentInfoDefaultNode != NULL )
238     {
239         SegmentInfoDefault* segInfoDef = new SegmentInfoDefault;
240         this->parseSegmentInfoCommon( segmentInfoDefaultNode, segInfoDef );
241
242         group->setSegmentInfoDefault( segInfoDef );
243     }
244 }
245
246 void    BasicCMParser::setAdaptationSet            (Node *root, Period *period)
247 {
248     std::vector<Node *> adaptSets = DOMHelper::getElementByTagName(root, "AdaptationSet", false);
249     if ( adaptSets.size() == 0 ) //In some old file, AdaptationSet may still be called Group
250         adaptSets = DOMHelper::getElementByTagName(root, "Group", false);
251
252     for(size_t i = 0; i < adaptSets.size(); i++)
253     {
254         const std::map<std::string, std::string>    attr = adaptSets.at(i)->getAttributes();
255         AdaptationSet *adaptSet = new AdaptationSet;
256         if ( this->parseCommonAttributesElements( adaptSets.at( i ), adaptSet, NULL ) == false )
257         {
258             delete adaptSet;
259             continue ;
260         }
261         std::map<std::string, std::string>::const_iterator  it = attr.find( "subsegmentAlignmentFlag" );
262         if ( it != attr.end() && it->second == "true" )
263             adaptSet->setSubsegmentAlignmentFlag( true ); //Otherwise it is false by default.
264         this->parseSegmentInfoDefault( adaptSets.at( i ), adaptSet );
265         this->setRepresentations(adaptSets.at(i), adaptSet);
266         period->addAdaptationSet(adaptSet);
267     }
268 }
269
270 void BasicCMParser::parseTrickMode(Node *node, Representation *repr)
271 {
272     std::vector<Node *> trickModes = DOMHelper::getElementByTagName(node, "TrickMode", false);
273
274     if ( trickModes.size() == 0 )
275         return ;
276     if ( trickModes.size() > 1 )
277         std::cerr << "More than 1 TrickMode element. Only the first one will be used." << std::endl;
278
279     Node*                                       trickModeNode = trickModes[0];
280     TrickModeType                               *trickMode = new TrickModeType;
281     const std::map<std::string, std::string>    attr = trickModeNode->getAttributes();
282     std::map<std::string, std::string>::const_iterator    it = attr.find( "alternatePlayoutRate" );
283
284     if ( it != attr.end() )
285         trickMode->setAlternatePlayoutRate( atoi( it->second.c_str() ) );
286     repr->setTrickMode( trickMode );
287 }
288
289 void    BasicCMParser::setRepresentations   (Node *root, AdaptationSet *group)
290 {
291     std::vector<Node *> representations = DOMHelper::getElementByTagName(root, "Representation", false);
292
293     for(size_t i = 0; i < representations.size(); i++)
294     {
295         const std::map<std::string, std::string>    attributes = representations.at(i)->getAttributes();
296
297         Representation *rep = new Representation;
298         rep->setParentGroup( group );
299         this->currentRepresentation = rep;
300         if ( this->parseCommonAttributesElements( representations.at( i ), rep, group ) == false )
301         {
302             delete rep;
303             continue ;
304         }
305         std::map<std::string, std::string>::const_iterator  it;
306
307         it = attributes.find( "id" );
308         if ( it == attributes.end() )
309             std::cerr << "Missing mandatory attribute for Representation: @id" << std::endl;
310         else
311             rep->setId( it->second );
312
313         it = attributes.find( "bandwidth" );
314         if ( it == attributes.end() )
315         {
316             std::cerr << "Missing mandatory attribute for Representation: @bandwidth" << std::endl;
317             delete rep;
318             continue ;
319         }
320         rep->setBandwidth( atoi( it->second.c_str() ) );
321
322         it = attributes.find( "qualityRanking" );
323         if ( it != attributes.end() )
324             rep->setQualityRanking( atoi( it->second.c_str() ) );
325
326         it = attributes.find( "dependencyId" );
327         if ( it != attributes.end() )
328             this->handleDependencyId( rep, group, it->second );
329
330         if ( this->setSegmentInfo( representations.at(i), rep ) == false )
331         {
332             delete rep;
333             continue ;
334         }
335         group->addRepresentation(rep);
336     }
337 }
338
339 void    BasicCMParser::handleDependencyId(Representation *rep, const AdaptationSet *adaptationSet, const std::string &dependencyId )
340 {
341     if ( dependencyId.empty() == true )
342         return ;
343     std::istringstream  s( dependencyId );
344     while ( s )
345     {
346         std::string     id;
347         s >> id;
348         const Representation    *dep = adaptationSet->getRepresentationById( id );
349         if ( dep )
350             rep->addDependency( dep );
351     }
352 }
353
354 bool    BasicCMParser::setSegmentInfo       (Node *root, Representation *rep)
355 {
356     Node    *segmentInfo = DOMHelper::getFirstChildElementByName( root, "SegmentInfo");
357
358     if ( segmentInfo )
359     {
360         SegmentInfo *info = new SegmentInfo;
361         this->parseSegmentInfoCommon( segmentInfo, info );
362         //If we don't have any segment, there's no point keeping this SegmentInfo.
363         if ( this->setSegments( segmentInfo, info ) == false )
364         {
365             delete info;
366             return false;
367         }
368         rep->setSegmentInfo( info );
369         return true;
370     }
371     std::cerr << "Missing mandatory element: Representation/SegmentInfo" << std::endl;
372     return false;
373 }
374
375 Segment*    BasicCMParser::parseSegment( Node* node )
376 {
377     const std::map<std::string, std::string>    attr = node->getAttributes();
378     std::map<std::string, std::string>::const_iterator  it;
379
380     bool        isTemplate = false;
381     Segment*    seg = NULL;
382
383     if ( node->getName() == "UrlTemplate" )
384         isTemplate = true;
385     it = attr.find( "sourceURL" );
386     //FIXME: When not present, the sourceUrl attribute should be computed
387     //using BaseURL and the range attribute.
388     if ( it != attr.end() )
389     {
390         std::string     url = it->second;
391         bool            runtimeToken = false;
392         if ( isTemplate == true )
393         {
394             if ( this->resolveUrlTemplates( url, runtimeToken ) == false )
395             {
396                 std::cerr << "Failed to substitute URLTemplate identifier." << std::endl;
397                 return NULL;
398             }
399             seg = new SegmentTemplate( runtimeToken, this->currentRepresentation );
400         }
401         else
402             seg = new Segment( this->currentRepresentation );
403         if ( url.find( this->p_stream->psz_access ) != 0 ) //Relative url
404             url = this->url + url;
405         seg->setSourceUrl( url );
406     }
407     return seg;
408 }
409
410 ProgramInformation* BasicCMParser::parseProgramInformation()
411 {
412     Node*   pInfoNode = DOMHelper::getFirstChildElementByName( this->root, "ProgramInformation" );
413     if ( pInfoNode == NULL )
414         return NULL;
415     ProgramInformation  *pInfo = new ProgramInformation;
416     const std::map<std::string, std::string>    attr = pInfoNode->getAttributes();
417     std::map<std::string, std::string>::const_iterator  it;
418     it = attr.find( "moreInformationURL" );
419     if ( it != attr.end() )
420         pInfo->setMoreInformationUrl( it->second );
421     Node*   title = DOMHelper::getFirstChildElementByName( pInfoNode, "Title" );
422     if ( title )
423         pInfo->setTitle( title->getText() );
424     Node*   source = DOMHelper::getFirstChildElementByName( pInfoNode, "Source" );
425     if ( source )
426         pInfo->setSource( source->getText() );
427     Node*   copyright = DOMHelper::getFirstChildElementByName( pInfoNode, "copyright" );
428     if ( copyright )
429         pInfo->setCopyright( copyright->getText() );
430     return pInfo;
431 }
432
433 void    BasicCMParser::setInitSegment       (Node *root, SegmentInfoCommon *info)
434 {
435     const std::vector<Node *> initSeg = DOMHelper::getChildElementByTagName(root, "InitialisationSegmentURL");
436
437     if (  initSeg.size() > 1 )
438         std::cerr << "There could be at most one InitialisationSegmentURL per SegmentInfo"
439                      " other InitialisationSegmentURL will be dropped." << std::endl;
440     if ( initSeg.size() == 1 )
441     {
442         Segment     *seg = parseSegment( initSeg.at(0) );
443         if ( seg != NULL )
444             info->setInitialisationSegment( seg );
445     }
446 }
447
448 bool    BasicCMParser::setSegments          (Node *root, SegmentInfo *info)
449 {
450     std::vector<Node *> segments = DOMHelper::getElementByTagName( root, "Url", false );
451     std::vector<Node *> segmentsTemplates = DOMHelper::getElementByTagName( root, "UrlTemplate", false );
452
453     if ( segments.size() == 0 && segmentsTemplates.size() == 0 )
454         return false;
455     segments.insert( segments.end(), segmentsTemplates.begin(), segmentsTemplates.end() );
456     for(size_t i = 0; i < segments.size(); i++)
457     {
458         Segment*    seg = parseSegment( segments.at( i ) );
459         if ( seg == NULL )
460             continue ;
461         if ( seg->getSourceUrl().empty() == false )
462             info->addSegment(seg);
463     }
464     return true;
465 }
466
467 bool    BasicCMParser::resolveUrlTemplates( std::string &url, bool &containRuntimeToken )
468 {
469     size_t      it = url.find( '$' );
470     containRuntimeToken = false;
471
472     while ( it != std::string::npos )
473     {
474         size_t  closing = url.find( '$', it + 1 );
475         if ( closing == std::string::npos )
476         {
477             std::cerr << "Unmatched '$' in url template: " << url << std::endl;
478             return false;
479         }
480         std::string     token = std::string( url, it, closing - it + 1 );
481         if ( token == "$$" )
482         {
483             url.replace( it, token.length(), "$" );
484             it = closing + 1;
485         }
486         else if ( token == "$RepresentationID$" )
487         {
488             if ( this->currentRepresentation->getId().empty() == false )
489             {
490                 std::cerr << "Representation doesn't have an ID. Can't substitute"
491                              " identifier $RepresentationID$" << std::endl;
492                 return false;
493             }
494             url.replace( it, token.length(), this->currentRepresentation->getId() );
495             it = it + this->currentRepresentation->getId().length();
496         }
497         else if ( token == "$Bandwidth$" )
498         {
499             std::ostringstream  oss;
500             oss << this->currentRepresentation->getBandwidth();
501             url.replace( it, token.length(), oss.str() );
502             it = it + oss.str().length();
503         }
504         else
505         {
506             if ( token == "$Index$" || token == "$Time$" )
507             {
508                 containRuntimeToken = true;
509                 it = it + token.length();
510             }
511             else
512             {
513                 std::cerr << "Unhandled token " << token << std::endl;
514                 return false;
515             }
516         }
517         it = url.find( '$', it );
518     }
519     return true;
520 }
521
522 MPD*    BasicCMParser::getMPD()
523 {
524     return this->mpd;
525 }
526
527 void BasicCMParser::parseContentDescriptor(Node *node, const std::string &name, void (CommonAttributesElements::*addPtr)(ContentDescription *), CommonAttributesElements *self) const
528 {
529     std::vector<Node*>  descriptors = DOMHelper::getChildElementByTagName( node, name );
530     if ( descriptors.empty() == true )
531         return ;
532     std::vector<Node*>::const_iterator  it = descriptors.begin();
533     std::vector<Node*>::const_iterator  end = descriptors.end();
534
535     while ( it != end )
536     {
537         const std::map<std::string, std::string>    attr = (*it)->getAttributes();
538         std::map<std::string, std::string>::const_iterator  itAttr = attr.find( "schemeIdUri" );
539         if  ( itAttr == attr.end() )
540         {
541             ++it;
542             continue ;
543         }
544         ContentDescription  *desc = new ContentDescription;
545         desc->setSchemeIdUri( itAttr->second );
546         Node    *schemeInfo = DOMHelper::getFirstChildElementByName( node, "SchemeInformation" );
547         if ( schemeInfo != NULL )
548             desc->setSchemeInformation( schemeInfo->getText() );
549         (self->*addPtr)( desc );
550         ++it;
551     }
552 }
553
554 bool    BasicCMParser::parseCommonAttributesElements( Node *node, CommonAttributesElements *common, CommonAttributesElements *parent ) const
555 {
556     const std::map<std::string, std::string>                &attr = node->getAttributes();
557     std::map<std::string, std::string>::const_iterator      it;
558     //Parse mandatory elements first.
559     it = attr.find( "mimeType" );
560     if ( it == attr.end() )
561     {
562         if ( parent && parent->getMimeType().empty() == false )
563             common->setMimeType( parent->getMimeType() );
564         else if ( node->getName().find( "Representation" ) != std::string::npos )
565         {
566             std::cerr << "Missing mandatory attribute: @mimeType" << std::endl;
567             return false;
568         }
569     }
570     else
571         common->setMimeType( it->second );
572     //Everything else is optionnal.
573     it = attr.find( "width" );
574     if ( it != attr.end() )
575         common->setWidth( atoi( it->second.c_str() ) );
576     it = attr.find( "height" );
577     if ( it != attr.end() )
578         common->setHeight( atoi( it->second.c_str() ) );
579     it = attr.find( "parx" );
580     if ( it != attr.end() )
581         common->setParX( atoi( it->second.c_str() ) );
582     it = attr.find( "pary" );
583     if ( it != attr.end() )
584         common->setParY( atoi( it->second.c_str() ) );
585     it = attr.find( "frameRate" );
586     if ( it != attr.end() )
587         common->setFrameRate( atoi( it->second.c_str() ) );
588     it = attr.find( "lang" );
589
590     if ( it != attr.end() && it->second.empty() == false )
591     {
592         std::istringstream  s( it->second );
593         while ( s )
594         {
595             std::string     lang;
596             s >> lang;
597             common->addLang( lang );
598         }
599     }
600     it = attr.find( "numberOfChannels" );
601     if ( it != attr.end() )
602     {
603         std::istringstream  s( it->second );
604         while ( s )
605         {
606             std::string     channel;
607             s >> channel;
608             common->addChannel( channel );
609         }
610     }
611     it = attr.find( "samplingRate" );
612     if ( it != attr.end() )
613     {
614         std::istringstream  s( it->second );
615         while ( s )
616         {
617             int         rate;
618             s >> rate;
619             common->addSampleRate( rate );
620         }
621     }
622     this->parseContentDescriptor( node, "ContentProtection",
623                                   &CommonAttributesElements::addContentProtection,
624                                   common );
625     this->parseContentDescriptor( node, "Accessibility",
626                                   &CommonAttributesElements::addAccessibility,
627                                   common );
628     this->parseContentDescriptor( node, "Rating",
629                                   &CommonAttributesElements::addRating, common );
630     this->parseContentDescriptor( node, "Viewpoint",
631                                   &CommonAttributesElements::addViewpoint, common );
632     //FIXME: Handle : group, maximumRAPPeriod startWithRAP attributes
633     //FIXME: Handle : ContentProtection Accessibility Rating Viewpoing MultipleViews elements
634     return true;
635 }