Hooked MPD flags up to MpdOptions.

Added calculation of availabilityStartTime MPD attribute.

Change-Id: I00876005c71f28ea83fb5d9ba0ad1f19f1d08e69
This commit is contained in:
Thomas Inskip 2014-06-25 18:33:09 -07:00 committed by Gerrit Code Review
parent 6651ae1c3d
commit 79d3c4f4ec
9 changed files with 202 additions and 95 deletions

View File

@ -29,3 +29,25 @@ DEFINE_string(base_urls,
"",
"Comma separated BaseURLs for the MPD. The values will be added "
"as <BaseURL> element(s) immediately under the <MPD> element.");
DEFINE_double(min_buffer_time,
2.0,
"Specifies, in seconds, a common duration used in the definition "
"of the MPD Representation data rate.");
DEFINE_double(availability_time_offset,
10.0,
"Offset with respect to the wall clock time for MPD "
"availabilityStartTime and availabilityEndTime values, in "
" seconds. This value is used for live profile only.");
DEFINE_double(minimum_update_period,
5.0,
"Indicates to the player how often to refresh the media "
"presentation description in seconds. This value is used for "
"live profile only.");
DEFINE_double(time_shift_buffer_depth,
1800.0,
"Guaranteed duration of the time shifting buffer for dynamic "
"media presentations, in seconds.");
DEFINE_double(suggested_presentation_delay,
0.0,
"Specifies a delay, in seconds, to be added to the media "
"presentation time. This value is used for live profile only.");

View File

@ -15,5 +15,10 @@ DECLARE_bool(output_media_info);
DECLARE_string(mpd_output);
DECLARE_string(scheme_id_uri);
DECLARE_string(base_urls);
DECLARE_double(availability_time_offset);
DECLARE_double(minimum_update_period);
DECLARE_double(min_buffer_time);
DECLARE_double(time_shift_buffer_depth);
DECLARE_double(suggested_presentation_delay);
#endif // APP_MPD_FLAGS_H_

View File

@ -234,6 +234,10 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
if (!GetMuxerOptions(&muxer_options))
return false;
MpdOptions mpd_options;
if (!GetMpdOptions(&mpd_options))
return false;
// Create encryption key source if needed.
scoped_ptr<EncryptionKeySource> encryption_key_source;
if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption) {
@ -248,8 +252,7 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
FLAGS_single_segment ? kOnDemandProfile : kLiveProfile;
std::vector<std::string> base_urls;
base::SplitString(FLAGS_base_urls, ',', &base_urls);
// TODO(rkuroiwa,kqyang): Get mpd options from command line.
mpd_notifier.reset(new SimpleMpdNotifier(profile, MpdOptions(), base_urls,
mpd_notifier.reset(new SimpleMpdNotifier(profile, mpd_options, base_urls,
FLAGS_mpd_output));
if (!mpd_notifier->Init()) {
LOG(ERROR) << "MpdNotifier failed to initialize.";

View File

@ -8,6 +8,7 @@
#include <iostream>
#include "app/fixed_key_encryption_flags.h"
#include "app/mpd_flags.h"
#include "app/muxer_flags.h"
#include "app/widevine_encryption_flags.h"
#include "base/logging.h"
@ -19,9 +20,12 @@
#include "media/base/stream_info.h"
#include "media/base/widevine_encryption_key_source.h"
#include "media/file/file.h"
#include "mpd/base/mpd_builder.h"
DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info.");
using dash_packager::MpdOptions;
namespace media {
void DumpStreamInfo(const std::vector<MediaStream*>& streams) {
@ -128,6 +132,18 @@ bool GetMuxerOptions(MuxerOptions* muxer_options) {
return true;
}
bool GetMpdOptions(MpdOptions* mpd_options) {
DCHECK(mpd_options);
mpd_options->availability_time_offset = FLAGS_availability_time_offset;
mpd_options->minimum_update_period = FLAGS_minimum_update_period;
mpd_options->min_buffer_time = FLAGS_min_buffer_time;
mpd_options->time_shift_buffer_depth = FLAGS_time_shift_buffer_depth;
mpd_options->suggested_presentation_delay =
FLAGS_suggested_presentation_delay;
return true;
}
MediaStream* FindFirstStreamOfType(const std::vector<MediaStream*>& streams,
StreamType stream_type) {
typedef std::vector<MediaStream*>::const_iterator StreamIterator;

View File

@ -17,6 +17,10 @@
DECLARE_bool(dump_stream_info);
namespace dash_packager {
struct MpdOptions;
}
namespace media {
class EncryptionKeySource;
@ -39,6 +43,9 @@ bool AssignFlagsFromProfile();
/// Fill MuxerOptions members using provided command line options.
bool GetMuxerOptions(MuxerOptions* muxer_options);
/// Fill MpdOptions members using provided command line options.
bool GetMpdOptions(dash_packager::MpdOptions* mpd_options);
/// Select and add a stream from a provided set to a muxer.
/// @param streams contains the set of MediaStreams from which to select.
/// @param stream_selector is a string containing one of the following values:

View File

@ -7,6 +7,8 @@
#ifndef MEDIA_BASE_MUXER_OPTIONS_H_
#define MEDIA_BASE_MUXER_OPTIONS_H_
#include "base/basictypes.h"
#include <string>
#include "base/basictypes.h"
@ -65,7 +67,8 @@ struct MuxerOptions {
/// Specify temporary directory for intermediate files.
std::string temp_dir;
/// User-specified bandwidth for the stream. zero means "unspecified".
/// User-specified bit rate for the media stream. If zero, the muxer will
/// attempt to estimate.
uint32 bandwidth;
};

View File

@ -6,6 +6,7 @@
#include "mpd/base/mpd_builder.h"
#include <cmath>
#include <list>
#include <string>
@ -90,18 +91,19 @@ bool Positive(double d) {
}
// Return current time in XML DateTime format.
std::string XmlDateTimeNow() {
base::Time now = base::Time::Now();
base::Time::Exploded now_exploded;
now.UTCExplode(&now_exploded);
std::string XmlDateTimeNowWithOffset(int32 offset_seconds) {
base::Time time = base::Time::Now();
time += base::TimeDelta::FromSeconds(offset_seconds);
base::Time::Exploded time_exploded;
time.UTCExplode(&time_exploded);
return base::StringPrintf("%4d-%02d-%02dT%02d:%02d:%02d",
now_exploded.year,
now_exploded.month,
now_exploded.day_of_month,
now_exploded.hour,
now_exploded.minute,
now_exploded.second);
time_exploded.year,
time_exploded.month,
time_exploded.day_of_month,
time_exploded.hour,
time_exploded.minute,
time_exploded.second);
}
void SetIfPositive(const char* attr_name, double value, XmlNode* mpd) {
@ -157,13 +159,12 @@ int SearchTimedOutRepeatIndex(uint64 timeshift_limit,
} // namespace
MpdOptions::MpdOptions()
: minimum_update_period(),
min_buffer_time(),
time_shift_buffer_depth(),
suggested_presentation_delay(),
max_segment_duration(),
max_subsegment_duration(),
number_of_blocks_for_bandwidth_estimation() {}
: availability_time_offset(0),
minimum_update_period(0),
// TODO(tinskip): Set min_buffer_time in unit tests rather than here.
min_buffer_time(2.0),
time_shift_buffer_depth(0),
suggested_presentation_delay(0) {}
MpdOptions::~MpdOptions() {}
@ -222,9 +223,6 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
static const char kXmlVersion[] = "1.0";
xml::ScopedXmlPtr<xmlDoc>::type doc(xmlNewDoc(BAD_CAST kXmlVersion));
XmlNode mpd("MPD");
AddMpdNameSpaceInfo(&mpd);
SetMpdOptionsValues(&mpd);
// Iterate thru AdaptationSets and add them to one big Period element.
XmlNode period("Period");
@ -254,6 +252,8 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
if (!mpd.AddChild(period.PassScopedPtr()))
return NULL;
AddMpdNameSpaceInfo(&mpd);
AddCommonMpdInfo(&mpd);
switch (type_) {
case kStatic:
AddStaticMpdInfo(&mpd);
@ -271,6 +271,17 @@ xmlDocPtr MpdBuilder::GenerateMpd() {
return doc.release();
}
void MpdBuilder::AddCommonMpdInfo(XmlNode* mpd_node) {
if (Positive(mpd_options_.min_buffer_time)) {
mpd_node->SetStringAttribute(
"minBufferTime",
SecondsToXmlDuration(mpd_options_.min_buffer_time));
} else {
LOG(ERROR) << "minBufferTime value not specified.";
// TODO(tinskip): Propagate error.
}
}
void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) {
DCHECK(mpd_node);
DCHECK_EQ(MpdBuilder::kStatic, type_);
@ -294,6 +305,38 @@ void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) {
"urn:mpeg:dash:profile:isoff-live:2011";
mpd_node->SetStringAttribute("type", kDynamicMpdType);
mpd_node->SetStringAttribute("profiles", kDynamicMpdProfile);
// 'availabilityStartTime' is required for dynamic profile. Calculate if
// not already calculated.
if (availability_start_time_.empty()) {
double earliest_presentation_time;
if (GetEarliestTimestamp(&earliest_presentation_time)) {
availability_start_time_ =
XmlDateTimeNowWithOffset(mpd_options_.availability_time_offset
- std::ceil(earliest_presentation_time));
} else {
LOG(ERROR) << "Could not determine the earliest segment presentation "
"time for availabilityStartTime calculation.";
// TODO(tinskip). Propagate an error.
}
}
if (!availability_start_time_.empty())
mpd_node->SetStringAttribute("availabilityStartTime", availability_start_time_);
if (Positive(mpd_options_.minimum_update_period)) {
mpd_node->SetStringAttribute(
"minimumUpdatePeriod",
SecondsToXmlDuration(mpd_options_.minimum_update_period));
} else {
LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
"specified.";
}
SetIfPositive(
"timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth, mpd_node);
SetIfPositive("suggestedPresentationDelay",
mpd_options_.suggested_presentation_delay,
mpd_node);
}
float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
@ -328,62 +371,25 @@ float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
return max_duration;
}
void MpdBuilder::SetMpdOptionsValues(XmlNode* mpd) {
if (type_ == kStatic) {
if (!mpd_options_.availability_start_time.empty()) {
mpd->SetStringAttribute("availabilityStartTime",
mpd_options_.availability_start_time);
bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
DCHECK(timestamp_seconds);
double earliest_timestamp(-1);
for (std::list<AdaptationSet*>::const_iterator iter =
adaptation_sets_.begin();
iter != adaptation_sets_.end();
++iter) {
double timestamp;
if ((*iter)->GetEarliestTimestamp(&timestamp) &&
((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
earliest_timestamp = timestamp;
}
LOG_IF(WARNING, Positive(mpd_options_.minimum_update_period))
<< "minimumUpdatePeriod should not be present in 'static' profile. "
"Ignoring.";
LOG_IF(WARNING, Positive(mpd_options_.time_shift_buffer_depth))
<< "timeShiftBufferDepth will not be used for 'static' profile. "
"Ignoring.";
LOG_IF(WARNING, Positive(mpd_options_.suggested_presentation_delay))
<< "suggestedPresentationDelay will not be used for 'static' profile. "
"Ignoring.";
} else if (type_ == kDynamic) {
// 'availabilityStartTime' is required for dynamic profile, so use current
// time if not specified.
const std::string avail_start =
!mpd_options_.availability_start_time.empty()
? mpd_options_.availability_start_time
: XmlDateTimeNow();
mpd->SetStringAttribute("availabilityStartTime", avail_start);
if (Positive(mpd_options_.minimum_update_period)) {
mpd->SetStringAttribute(
"minimumUpdatePeriod",
SecondsToXmlDuration(mpd_options_.minimum_update_period));
} else {
// TODO(rkuroiwa): Set minimumUpdatePeriod to some default value.
LOG(WARNING) << "The profile is dynamic but no minimumUpdatePeriod "
"specified. Setting minimumUpdatePeriod to 0.";
}
SetIfPositive(
"timeShiftBufferDepth", mpd_options_.time_shift_buffer_depth, mpd);
SetIfPositive("suggestedPresentationDelay",
mpd_options_.suggested_presentation_delay,
mpd);
}
if (earliest_timestamp < 0)
return false;
const double kDefaultMinBufferTime = 2.0;
const double min_buffer_time = Positive(mpd_options_.min_buffer_time)
? mpd_options_.min_buffer_time
: kDefaultMinBufferTime;
mpd->SetStringAttribute("minBufferTime",
SecondsToXmlDuration(min_buffer_time));
if (!mpd_options_.availability_end_time.empty()) {
mpd->SetStringAttribute("availabilityEndTime",
mpd_options_.availability_end_time);
}
SetIfPositive("maxSegmentDuration", mpd_options_.max_segment_duration, mpd);
SetIfPositive(
"maxSubsegmentDuration", mpd_options_.max_subsegment_duration, mpd);
*timestamp_seconds = earliest_timestamp;
return true;
}
AdaptationSet::AdaptationSet(uint32 adaptation_set_id,
@ -441,6 +447,29 @@ xml::ScopedXmlPtr<xmlNode>::type AdaptationSet::GetXml() {
return adaptation_set.PassScopedPtr();
}
bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) {
DCHECK(timestamp_seconds);
base::AutoLock scoped_lock(lock_);
double earliest_timestamp(-1);
for (std::list<Representation*>::const_iterator iter =
representations_.begin();
iter != representations_.end();
++iter) {
double timestamp;
if ((*iter)->GetEarliestTimestamp(&timestamp) &&
((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
earliest_timestamp = timestamp;
}
}
if (earliest_timestamp < 0)
return false;
*timestamp_seconds = earliest_timestamp;
return true;
}
Representation::Representation(const MediaInfo& media_info,
const MpdOptions& mpd_options,
uint32 id)
@ -715,4 +744,17 @@ std::string Representation::GetAudioMimeType() const {
return GetMimeType("audio", media_info_.container_type());
}
bool Representation::GetEarliestTimestamp(double* timestamp_seconds) {
DCHECK(timestamp_seconds);
base::AutoLock scoped_lock(lock_);
if (segment_infos_.empty())
return false;
*timestamp_seconds =
static_cast<double>(segment_infos_.begin()->start_time) /
GetTimeScale(media_info_);
return true;
}
} // namespace dash_packager

View File

@ -41,18 +41,11 @@ struct MpdOptions {
MpdOptions();
~MpdOptions();
std::string availability_start_time;
std::string availability_end_time;
double availability_time_offset;
double minimum_update_period;
double min_buffer_time;
double time_shift_buffer_depth;
double suggested_presentation_delay;
double max_segment_duration;
double max_subsegment_duration;
/// Value passed to BandwidthEstimator's contructor. See BandwidthEstimator
/// for more.
int number_of_blocks_for_bandwidth_estimation;
};
/// This class generates DASH MPDs (Media Presentation Descriptions).
@ -86,8 +79,8 @@ class MpdBuilder {
MpdType type() { return type_; }
private:
// DynamicMpdBuilderTest uses SetMpdOptionsValues to set availabilityStartTime
// so that the test doesn't need to depend on current time.
// DynamicMpdBuilderTest needs to set availabilityStartTime so that the test
// doesn't need to depend on current time.
friend class DynamicMpdBuilderTest;
bool ToStringImpl(std::string* output);
@ -97,6 +90,10 @@ class MpdBuilder {
// On failure, this returns NULL.
xmlDocPtr GenerateMpd();
// Set MPD attributes common to all profiles. Uses non-zero |mpd_options_| to
// set attributes for the MPD.
void AddCommonMpdInfo(xml::XmlNode* mpd_node);
// Adds 'static' MPD attributes and elements to |mpd_node|. This assumes that
// the first child element is a Period element.
void AddStaticMpdInfo(xml::XmlNode* mpd_node);
@ -106,10 +103,13 @@ class MpdBuilder {
float GetStaticMpdDuration(xml::XmlNode* mpd_node);
// Use |mpd_options_| to set attributes for MPD. Only values that are set will be
// used, i.e. if a string field is not empty and numeric field is not 0.
// Required fields will be set with some reasonable values.
void SetMpdOptionsValues(xml::XmlNode* mpd_node);
// Set MPD attributes for dynamic profile MPD. Uses non-zero |mpd_options_| as
// well as various calculations to set attributes for the MPD.
void SetDynamicMpdAttributes(xml::XmlNode* mpd_node);
// Gets the earliest, normalized segment timestamp. Returns true if
// successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
MpdType type_;
MpdOptions mpd_options_;
@ -117,6 +117,7 @@ class MpdBuilder {
::STLElementDeleter<std::list<AdaptationSet*> > adaptation_sets_deleter_;
std::list<std::string> base_urls_;
std::string availability_start_time_;
base::Lock lock_;
base::AtomicSequenceNumber adaptation_set_counter_;
@ -167,6 +168,10 @@ class AdaptationSet {
const MpdOptions& mpd_options_,
base::AtomicSequenceNumber* representation_counter);
// Gets the earliest, normalized segment timestamp. Returns true if
// successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
std::list<ContentProtectionElement> content_protection_elements_;
std::list<Representation*> representations_;
::STLElementDeleter<std::list<Representation*> > representations_deleter_;
@ -248,6 +253,10 @@ class Representation {
std::string GetVideoMimeType() const;
std::string GetAudioMimeType() const;
// Gets the earliest, normalized segment timestamp. Returns true if
// successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
MediaInfo media_info_;
std::list<ContentProtectionElement> content_protection_elements_;
std::list<SegmentInfo> segment_infos_;
@ -258,7 +267,7 @@ class Representation {
std::string mime_type_;
std::string codecs_;
BandwidthEstimator bandwidth_estimator_;
const MpdOptions& mpd_options_;;
const MpdOptions& mpd_options_;
// startNumber attribute for SegmentTemplate.
// Starts from 1.

View File

@ -99,7 +99,7 @@ class DynamicMpdBuilderTest : public MpdBuilderTest<MpdBuilder::kDynamic> {
// Anchors availabilityStartTime so that the test result doesn't depend on the
// current time.
virtual void SetUp() {
mpd_.mpd_options_.availability_start_time = "2011-12-25T12:30:00";
mpd_.availability_start_time_ = "2011-12-25T12:30:00";
}
MpdOptions* mutable_mpd_options() { return &mpd_.mpd_options_; }
@ -370,9 +370,9 @@ TEST_F(DynamicMpdBuilderTest, CheckMpdAttributes) {
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
"xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
"xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 "
"DASH-MPD.xsd\" availabilityStartTime=\"2011-12-25T12:30:00\" "
"minBufferTime=\"PT2S\" type=\"dynamic\" "
"profiles=\"urn:mpeg:dash:profile:isoff-live:2011\">\n"
"DASH-MPD.xsd\" minBufferTime=\"PT2S\" type=\"dynamic\" "
"profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
"availabilityStartTime=\"2011-12-25T12:30:00\">\n"
" <Period start=\"PT0S\"/>\n"
"</MPD>\n";