Text support for MpdBuilder
Change-Id: I75e1da391356e9edfcb520941029341941c31332
This commit is contained in:
parent
c577e6132f
commit
8c53995335
|
@ -77,11 +77,19 @@ bool DashIopMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
|
|||
std::string lang;
|
||||
if (media_info.has_audio_info()) {
|
||||
lang = media_info.audio_info().language();
|
||||
} else if (media_info.has_text_info()) {
|
||||
lang = media_info.text_info().language();
|
||||
}
|
||||
|
||||
AdaptationSet* adaptation_set =
|
||||
GetAdaptationSetForMediaInfo(media_info, content_type, lang);
|
||||
DCHECK(adaptation_set);
|
||||
if (media_info.has_text_info()) {
|
||||
// IOP requires all AdaptationSets to have (sub)segmentAlignment set to
|
||||
// true, so carelessly set it to true.
|
||||
// In practice it doesn't really make sense to adapt between text tracks.
|
||||
adaptation_set->ForceSetSegmentAlignment(true);
|
||||
}
|
||||
|
||||
MediaInfo adjusted_media_info(media_info);
|
||||
MpdBuilder::MakePathsRelativeToMpd(output_path_, &adjusted_media_info);
|
||||
|
|
|
@ -135,7 +135,9 @@ class DashIopMpdNotifierTest
|
|||
const MpdOptions empty_mpd_option_;
|
||||
const std::vector<std::string> empty_base_urls_;
|
||||
|
||||
// Default AdaptationSet mock.
|
||||
// Default mocks that can be used for the tests.
|
||||
// IOW, if a test only requires one instance of
|
||||
// Mock{AdaptationSet,Representation}, these can be used.
|
||||
scoped_ptr<MockAdaptationSet> default_mock_adaptation_set_;
|
||||
scoped_ptr<MockRepresentation> default_mock_representation_;
|
||||
|
||||
|
@ -181,6 +183,38 @@ TEST_P(DashIopMpdNotifierTest, NotifyNewContainer) {
|
|||
EXPECT_TRUE(notifier.Flush());
|
||||
}
|
||||
|
||||
// Verify that if the MediaInfo contains text information, then
|
||||
// MpdBuilder::ForceSetSegmentAlignment() is called.
|
||||
TEST_P(DashIopMpdNotifierTest, NotifyNewTextContainer) {
|
||||
const char kTextMediaInfo[] =
|
||||
"text_info {\n"
|
||||
" format: 'ttml'\n"
|
||||
" language: 'en'\n"
|
||||
"}\n"
|
||||
"container_type: CONTAINER_TEXT\n";
|
||||
DashIopMpdNotifier notifier(dash_profile(), empty_mpd_option_,
|
||||
empty_base_urls_, output_path_);
|
||||
|
||||
scoped_ptr<MockMpdBuilder> mock_mpd_builder(new MockMpdBuilder(mpd_type()));
|
||||
|
||||
EXPECT_CALL(*mock_mpd_builder, AddAdaptationSet(StrEq("en")))
|
||||
.WillOnce(Return(default_mock_adaptation_set_.get()));
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRole(_)).Times(0);
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, ForceSetSegmentAlignment(true));
|
||||
EXPECT_CALL(*default_mock_adaptation_set_, AddRepresentation(_))
|
||||
.WillOnce(Return(default_mock_representation_.get()));
|
||||
|
||||
// This is for the Flush() below but adding expectation here because the next
|
||||
// lines Pass() the pointer.
|
||||
EXPECT_CALL(*mock_mpd_builder, ToString(_)).WillOnce(Return(true));
|
||||
|
||||
uint32_t unused_container_id;
|
||||
SetMpdBuilder(¬ifier, mock_mpd_builder.Pass());
|
||||
EXPECT_TRUE(notifier.NotifyNewContainer(ConvertToMediaInfo(kTextMediaInfo),
|
||||
&unused_container_id));
|
||||
EXPECT_TRUE(notifier.Flush());
|
||||
}
|
||||
|
||||
// Verify VOD NotifyNewContainer() operation works with different
|
||||
// MediaInfo::ProtectedContent.
|
||||
// Two AdaptationSets should be created.
|
||||
|
|
|
@ -16,6 +16,7 @@ message MediaInfo {
|
|||
CONTAINER_MP4 = 1;
|
||||
CONTAINER_MPEG2_TS= 2;
|
||||
CONTAINER_WEBM = 3;
|
||||
CONTAINER_TEXT = 4;
|
||||
}
|
||||
|
||||
message VideoInfo {
|
||||
|
@ -51,8 +52,14 @@ message MediaInfo {
|
|||
}
|
||||
|
||||
message TextInfo {
|
||||
enum TextType {
|
||||
UNKNOWN = 0;
|
||||
CAPTION = 1;
|
||||
SUBTITLE = 2;
|
||||
}
|
||||
optional string format = 1;
|
||||
optional string language = 2;
|
||||
optional TextType type = 3;
|
||||
}
|
||||
|
||||
message ProtectedContent {
|
||||
|
|
|
@ -38,6 +38,7 @@ class MockAdaptationSet : public AdaptationSet {
|
|||
MOCK_METHOD2(UpdateContentProtectionPssh,
|
||||
void(const std::string& drm_uuid, const std::string& pssh));
|
||||
MOCK_METHOD1(AddRole, void(AdaptationSet::Role role));
|
||||
MOCK_METHOD1(ForceSetSegmentAlignment, void(bool segment_alignment));
|
||||
|
||||
MOCK_METHOD1(SetGroup, void(int group_number));
|
||||
MOCK_CONST_METHOD0(Group, int());
|
||||
|
|
|
@ -39,6 +39,22 @@ namespace {
|
|||
|
||||
const int kAdaptationSetGroupNotSet = -1;
|
||||
|
||||
AdaptationSet::Role MediaInfoTextTypeToRole(MediaInfo::TextInfo::TextType type) {
|
||||
switch (type) {
|
||||
case MediaInfo::TextInfo::UNKNOWN:
|
||||
LOG(WARNING) << "Unknown text type, assuming subtitle.";
|
||||
return AdaptationSet::kRoleSubtitle;
|
||||
case MediaInfo::TextInfo::CAPTION:
|
||||
return AdaptationSet::kRoleCaption;
|
||||
case MediaInfo::TextInfo::SUBTITLE:
|
||||
return AdaptationSet::kRoleSubtitle;
|
||||
default:
|
||||
NOTREACHED() << "Unknown MediaInfo TextType: " << type
|
||||
<< " assuming subtitle.";
|
||||
return AdaptationSet::kRoleSubtitle;
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetMimeType(const std::string& prefix,
|
||||
MediaInfo::ContainerType container_type) {
|
||||
switch (container_type) {
|
||||
|
@ -54,7 +70,7 @@ std::string GetMimeType(const std::string& prefix,
|
|||
}
|
||||
|
||||
// Unsupported container types should be rejected/handled by the caller.
|
||||
NOTREACHED() << "Unrecognized container type: " << container_type;
|
||||
LOG(ERROR) << "Unrecognized container type: " << container_type;
|
||||
return std::string();
|
||||
}
|
||||
|
||||
|
@ -684,6 +700,13 @@ Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) {
|
|||
content_type_ = "video";
|
||||
} else if (media_info.has_audio_info()) {
|
||||
content_type_ = "audio";
|
||||
} else if (media_info.has_text_info()) {
|
||||
content_type_ = "text";
|
||||
|
||||
if (media_info.text_info().has_type() &&
|
||||
(media_info.text_info().type() != MediaInfo::TextInfo::UNKNOWN)) {
|
||||
roles_.insert(MediaInfoTextTypeToRole(media_info.text_info().type()));
|
||||
}
|
||||
}
|
||||
|
||||
representations_.push_back(representation.get());
|
||||
|
@ -998,20 +1021,20 @@ Representation::Representation(
|
|||
Representation::~Representation() {}
|
||||
|
||||
bool Representation::Init() {
|
||||
codecs_ = GetCodecs(media_info_);
|
||||
if (codecs_.empty()) {
|
||||
LOG(ERROR) << "Missing codec info in MediaInfo.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool has_video_info = media_info_.has_video_info();
|
||||
const bool has_audio_info = media_info_.has_audio_info();
|
||||
|
||||
if (!has_video_info && !has_audio_info) {
|
||||
if (!AtLeastOneTrue(media_info_.has_video_info(),
|
||||
media_info_.has_audio_info(),
|
||||
media_info_.has_text_info())) {
|
||||
// This is an error. Segment information can be in AdaptationSet, Period, or
|
||||
// MPD but the interface does not provide a way to set them.
|
||||
// See 5.3.9.1 ISO 23009-1:2012 for segment info.
|
||||
LOG(ERROR) << "Representation needs video or audio.";
|
||||
LOG(ERROR) << "Representation needs one of video, audio, or text.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MoreThanOneTrue(media_info_.has_video_info(),
|
||||
media_info_.has_audio_info(),
|
||||
media_info_.has_text_info())) {
|
||||
LOG(ERROR) << "Only one of VideoInfo, AudioInfo, or TextInfo can be set.";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1020,18 +1043,22 @@ bool Representation::Init() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// For mimetypes, this checks the video and then audio. Usually when there is
|
||||
// audio + video, we take video/<type>.
|
||||
if (has_video_info) {
|
||||
if (media_info_.has_video_info()) {
|
||||
mime_type_ = GetVideoMimeType();
|
||||
if (!HasRequiredVideoFields(media_info_.video_info())) {
|
||||
LOG(ERROR) << "Missing required fields to create a video Representation.";
|
||||
return false;
|
||||
}
|
||||
} else if (has_audio_info) {
|
||||
} else if (media_info_.has_audio_info()) {
|
||||
mime_type_ = GetAudioMimeType();
|
||||
} else if (media_info_.has_text_info()) {
|
||||
mime_type_ = GetTextMimeType();
|
||||
}
|
||||
|
||||
if (mime_type_.empty())
|
||||
return false;
|
||||
|
||||
codecs_ = GetCodecs(media_info_);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1110,7 +1137,8 @@ xml::ScopedXmlPtr<xmlNode>::type Representation::GetXml() {
|
|||
// Mandatory fields for Representation.
|
||||
representation.SetId(id_);
|
||||
representation.SetIntegerAttribute("bandwidth", bandwidth);
|
||||
representation.SetStringAttribute("codecs", codecs_);
|
||||
if (!codecs_.empty())
|
||||
representation.SetStringAttribute("codecs", codecs_);
|
||||
representation.SetStringAttribute("mimeType", mime_type_);
|
||||
|
||||
const bool has_video_info = media_info_.has_video_info();
|
||||
|
@ -1285,6 +1313,35 @@ std::string Representation::GetAudioMimeType() const {
|
|||
return GetMimeType("audio", media_info_.container_type());
|
||||
}
|
||||
|
||||
std::string Representation::GetTextMimeType() const {
|
||||
CHECK(media_info_.has_text_info());
|
||||
if (media_info_.text_info().format() == "ttml") {
|
||||
switch (media_info_.container_type()) {
|
||||
case MediaInfo::CONTAINER_TEXT:
|
||||
return "application/ttml+xml";
|
||||
case MediaInfo::CONTAINER_MP4:
|
||||
return "application/mp4";
|
||||
default:
|
||||
LOG(ERROR) << "Failed to determine MIME type for TTML container: "
|
||||
<< media_info_.container_type();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (media_info_.text_info().format() == "vtt") {
|
||||
if (media_info_.container_type() == MediaInfo::CONTAINER_TEXT) {
|
||||
return "text/vtt";
|
||||
}
|
||||
LOG(ERROR) << "Failed to determine MIME type for VTT container: "
|
||||
<< media_info_.container_type();
|
||||
return "";
|
||||
}
|
||||
|
||||
LOG(ERROR) << "Cannot determine MIME type for format: "
|
||||
<< media_info_.text_info().format()
|
||||
<< " container: " << media_info_.container_type();
|
||||
return "";
|
||||
}
|
||||
|
||||
bool Representation::GetEarliestTimestamp(double* timestamp_seconds) {
|
||||
DCHECK(timestamp_seconds);
|
||||
|
||||
|
|
|
@ -172,7 +172,8 @@ class AdaptationSet {
|
|||
|
||||
/// Create a Representation instance using @a media_info.
|
||||
/// @param media_info is a MediaInfo object used to initialize the returned
|
||||
/// Representation instance.
|
||||
/// Representation instance. It may contain only one of VideoInfo,
|
||||
/// AudioInfo, or TextInfo, i.e. VideoInfo XOR AudioInfo XOR TextInfo.
|
||||
/// @return On success, returns a pointer to Representation. Otherwise returns
|
||||
/// NULL. The returned pointer is owned by the AdaptationSet instance.
|
||||
virtual Representation* AddRepresentation(const MediaInfo& media_info);
|
||||
|
@ -220,7 +221,7 @@ class AdaptationSet {
|
|||
/// for the AdaptationSet.
|
||||
/// @param segment_alignment is the value used for (sub)segmentAlignment
|
||||
/// attribute.
|
||||
void ForceSetSegmentAlignment(bool segment_alignment);
|
||||
virtual void ForceSetSegmentAlignment(bool segment_alignment);
|
||||
|
||||
/// Sets the AdaptationSet@group attribute.
|
||||
/// Passing a negative value to this method will unset the attribute.
|
||||
|
@ -517,11 +518,14 @@ class Representation {
|
|||
// strings.
|
||||
std::string GetVideoMimeType() const;
|
||||
std::string GetAudioMimeType() const;
|
||||
std::string GetTextMimeType() const;
|
||||
|
||||
// Gets the earliest, normalized segment timestamp. Returns true if
|
||||
// successful, false otherwise.
|
||||
bool GetEarliestTimestamp(double* timestamp_seconds);
|
||||
|
||||
// Init() checks that only one of VideoInfo, AudioInfo, or TextInfo is set. So
|
||||
// any logic using this can assume only one set.
|
||||
MediaInfo media_info_;
|
||||
std::list<ContentProtectionElement> content_protection_elements_;
|
||||
std::list<SegmentInfo> segment_infos_;
|
||||
|
|
|
@ -414,6 +414,42 @@ TEST_F(CommonMpdBuilderTest, ValidMediaInfo) {
|
|||
EXPECT_TRUE(representation->Init());
|
||||
}
|
||||
|
||||
// Verify that if VideoInfo, AudioInfo, or TextInfo is not set, Init() fails.
|
||||
TEST_F(CommonMpdBuilderTest, VideoAudioTextInfoNotSet) {
|
||||
const char kTestMediaInfo[] = "container_type: 1";
|
||||
|
||||
auto representation =
|
||||
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfo), MpdOptions(),
|
||||
kAnyRepresentationId, NoListener());
|
||||
EXPECT_FALSE(representation->Init());
|
||||
}
|
||||
|
||||
// Verify that if more than one of VideoInfo, AudioInfo, or TextInfo is set,
|
||||
// then Init() fails.
|
||||
TEST_F(CommonMpdBuilderTest, VideoAndAudioInfoSet) {
|
||||
const char kTestMediaInfo[] =
|
||||
"video_info {\n"
|
||||
" codec: 'avc1'\n"
|
||||
" height: 480\n"
|
||||
" time_scale: 10\n"
|
||||
" frame_duration: 10\n"
|
||||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"audio_info {\n"
|
||||
" codec: 'mp4a.40.2'\n"
|
||||
" sampling_frequency: 44100\n"
|
||||
" time_scale: 1200\n"
|
||||
" num_channels: 2\n"
|
||||
"}\n"
|
||||
"container_type: CONTAINER_MP4\n";
|
||||
|
||||
auto representation =
|
||||
CreateRepresentation(ConvertToMediaInfo(kTestMediaInfo), MpdOptions(),
|
||||
kAnyRepresentationId, NoListener());
|
||||
EXPECT_FALSE(representation->Init());
|
||||
}
|
||||
|
||||
// Verify that Representation::Init() fails if a required field is missing.
|
||||
TEST_F(CommonMpdBuilderTest, InvalidMediaInfo) {
|
||||
// Missing width.
|
||||
|
@ -538,16 +574,15 @@ TEST_F(CommonMpdBuilderTest, CheckAdaptationSetVideoContentType) {
|
|||
" pixel_width: 1\n"
|
||||
" pixel_height: 1\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
"container_type: CONTAINER_MP4\n";
|
||||
|
||||
auto adaptation_set =
|
||||
CreateAdaptationSet(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo));
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type node_xml(adaptation_set->GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeEqString("contentType", "video", node_xml.get()));
|
||||
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString(
|
||||
"contentType", "video", adaptation_set->GetXml().get()));
|
||||
}
|
||||
|
||||
// Verify that content type is set correctly if audio info is present in
|
||||
|
@ -561,40 +596,100 @@ TEST_F(CommonMpdBuilderTest, CheckAdaptationSetAudioContentType) {
|
|||
" time_scale: 1200\n"
|
||||
" num_channels: 2\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
"container_type: CONTAINER_MP4\n";
|
||||
|
||||
auto adaptation_set =
|
||||
CreateAdaptationSet(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo));
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type node_xml(adaptation_set->GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeEqString("contentType", "audio", node_xml.get()));
|
||||
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString(
|
||||
"contentType", "audio", adaptation_set->GetXml().get()));
|
||||
}
|
||||
|
||||
// Verify that content type is set correctly if text info is present in
|
||||
// MediaInfo.
|
||||
// TODO(rkuroiwa): Enable this once text support is implemented.
|
||||
// This fails because it fails to get the codec, therefore Representation
|
||||
// creation fails.
|
||||
TEST_F(CommonMpdBuilderTest, DISABLED_CheckAdaptationSetTextContentType) {
|
||||
TEST_F(CommonMpdBuilderTest, CheckAdaptationSetTextContentType) {
|
||||
base::AtomicSequenceNumber sequence_counter;
|
||||
const char kTextMediaInfo[] =
|
||||
"text_info {\n"
|
||||
" format: 'ttml'\n"
|
||||
" language: 'en'\n"
|
||||
"}\n"
|
||||
"container_type: 1\n";
|
||||
"container_type: CONTAINER_TEXT\n";
|
||||
|
||||
auto adaptation_set =
|
||||
CreateAdaptationSet(kAnyAdaptationSetId, "", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo));
|
||||
|
||||
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString(
|
||||
"contentType", "text", adaptation_set->GetXml().get()));
|
||||
}
|
||||
|
||||
TEST_F(CommonMpdBuilderTest, TtmlXmlMimeType) {
|
||||
const char kTtmlXmlMediaInfo[] =
|
||||
"text_info {\n"
|
||||
" format: 'ttml'\n"
|
||||
"}\n"
|
||||
"container_type: CONTAINER_TEXT\n";
|
||||
|
||||
auto representation =
|
||||
CreateRepresentation(ConvertToMediaInfo(kTtmlXmlMediaInfo), MpdOptions(),
|
||||
kAnyRepresentationId, NoListener());
|
||||
EXPECT_TRUE(representation->Init());
|
||||
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString(
|
||||
"mimeType", "application/ttml+xml", representation->GetXml().get()));
|
||||
}
|
||||
|
||||
TEST_F(CommonMpdBuilderTest, TtmlMp4MimeType) {
|
||||
const char kTtmlMp4MediaInfo[] =
|
||||
"text_info {\n"
|
||||
" format: 'ttml'\n"
|
||||
"}\n"
|
||||
"container_type: CONTAINER_MP4\n";
|
||||
|
||||
auto representation =
|
||||
CreateRepresentation(ConvertToMediaInfo(kTtmlMp4MediaInfo), MpdOptions(),
|
||||
kAnyRepresentationId, NoListener()).Pass();
|
||||
EXPECT_TRUE(representation->Init());
|
||||
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString(
|
||||
"mimeType", "application/mp4", representation->GetXml().get()));
|
||||
}
|
||||
|
||||
TEST_F(CommonMpdBuilderTest, WebVttMimeType) {
|
||||
const char kWebVttMediaInfo[] =
|
||||
"text_info {\n"
|
||||
" format: 'vtt'\n"
|
||||
"}\n"
|
||||
"container_type: CONTAINER_TEXT\n";
|
||||
|
||||
auto representation =
|
||||
CreateRepresentation(ConvertToMediaInfo(kWebVttMediaInfo), MpdOptions(),
|
||||
kAnyRepresentationId, NoListener()).Pass();
|
||||
EXPECT_TRUE(representation->Init());
|
||||
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString(
|
||||
"mimeType", "text/vtt", representation->GetXml().get()));
|
||||
}
|
||||
|
||||
// Verify that language passed to the constructor sets the @lang field is set.
|
||||
TEST_F(CommonMpdBuilderTest, CheckLanguageAttributeSet) {
|
||||
base::AtomicSequenceNumber sequence_counter;
|
||||
// The media info doesn't really matter as long as it is valid.
|
||||
const char kTextMediaInfo[] =
|
||||
"text_info {\n"
|
||||
" format: 'ttml'\n"
|
||||
"}\n"
|
||||
"container_type: CONTAINER_TEXT\n";
|
||||
|
||||
auto adaptation_set =
|
||||
CreateAdaptationSet(kAnyAdaptationSetId, "en", MpdOptions(),
|
||||
MpdBuilder::kStatic, &sequence_counter);
|
||||
adaptation_set->AddRepresentation(ConvertToMediaInfo(kTextMediaInfo));
|
||||
|
||||
xml::ScopedXmlPtr<xmlNode>::type node_xml(adaptation_set->GetXml());
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
ExpectAttributeEqString("contentType", "text", node_xml.get()));
|
||||
ExpectAttributeEqString("lang", "en", node_xml.get()));
|
||||
}
|
||||
|
||||
TEST_F(CommonMpdBuilderTest, CheckAdaptationSetId) {
|
||||
|
@ -1621,6 +1716,54 @@ TEST_F(StaticMpdBuilderTest, WriteToFile) {
|
|||
EXPECT_TRUE(DeleteFile(file_path, kNonRecursive));
|
||||
}
|
||||
|
||||
// Verify that a text path works.
|
||||
TEST_F(StaticMpdBuilderTest, Text) {
|
||||
const char kTextMediaInfo[] =
|
||||
"text_info {\n"
|
||||
" format: 'ttml'\n"
|
||||
" language: 'en'\n"
|
||||
" type: SUBTITLE\n"
|
||||
"}\n"
|
||||
"media_duration_seconds: 35\n"
|
||||
"bandwidth: 1000\n"
|
||||
"media_file_name: 'subtitle.xml'\n"
|
||||
"container_type: CONTAINER_TEXT\n";
|
||||
|
||||
const char kExpectedOutput[] =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
"<MPD xmlns=\"urn:mpeg:DASH:schema:MPD:2011\""
|
||||
" xmlns:cenc=\"urn:mpeg:cenc:2013\""
|
||||
" 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\""
|
||||
" minBufferTime=\"PT2S\" type=\"static\""
|
||||
" profiles=\"urn:mpeg:dash:profile:isoff-on-demand:2011\""
|
||||
" mediaPresentationDuration=\"PT35S\">"
|
||||
" <Period>"
|
||||
" <AdaptationSet id=\"0\" contentType=\"text\" lang=\"en\">"
|
||||
" <Role schemeIdUri=\"urn:mpeg:dash:role:2011\""
|
||||
" value=\"subtitle\"/>\n"
|
||||
" <Representation id=\"0\" bandwidth=\"1000\""
|
||||
" mimeType=\"application/ttml+xml\">"
|
||||
" <BaseURL>subtitle.xml</BaseURL>"
|
||||
" </Representation>"
|
||||
" </AdaptationSet>"
|
||||
" </Period>"
|
||||
"</MPD>";
|
||||
|
||||
AdaptationSet* text_adaptation_set = mpd_.AddAdaptationSet("en");
|
||||
ASSERT_TRUE(text_adaptation_set);
|
||||
|
||||
Representation* text_representation = text_adaptation_set->AddRepresentation(
|
||||
ConvertToMediaInfo(kTextMediaInfo));
|
||||
ASSERT_TRUE(text_representation);
|
||||
|
||||
std::string mpd_output;
|
||||
ASSERT_TRUE(mpd_.ToString(&mpd_output));
|
||||
ASSERT_TRUE(ValidateMpdSchema(mpd_output));
|
||||
EXPECT_TRUE(XmlEqual(kExpectedOutput, mpd_output));
|
||||
}
|
||||
|
||||
// Check whether the attributes are set correctly for dynamic <MPD> element.
|
||||
// This test must use ASSERT_EQ for comparison because XmlEqual() cannot
|
||||
// handle namespaces correctly yet.
|
||||
|
|
|
@ -13,6 +13,24 @@
|
|||
#include "packager/mpd/base/xml/scoped_xml_ptr.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace {
|
||||
|
||||
std::string TextCodecString(
|
||||
const edash_packager::MediaInfo& media_info) {
|
||||
CHECK(media_info.has_text_info());
|
||||
const std::string& format = media_info.text_info().format();
|
||||
// DASH IOP mentions that the codec for ttml in mp4 is stpp.
|
||||
if (format == "ttml" &&
|
||||
(media_info.container_type() == MediaInfo::CONTAINER_MP4)) {
|
||||
return "stpp";
|
||||
}
|
||||
|
||||
// Otherwise codec doesn't need to be specified, e.g. vtt and ttml+xml are
|
||||
// obvious from the mime type.
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool HasVODOnlyFields(const MediaInfo& media_info) {
|
||||
return media_info.has_init_range() || media_info.has_index_range() ||
|
||||
|
@ -40,22 +58,19 @@ void RemoveDuplicateAttributes(
|
|||
}
|
||||
|
||||
std::string GetCodecs(const MediaInfo& media_info) {
|
||||
std::string video_codec;
|
||||
CHECK(OnlyOneTrue(media_info.has_video_info(), media_info.has_audio_info(),
|
||||
media_info.has_text_info()));
|
||||
|
||||
if (media_info.has_video_info())
|
||||
video_codec = media_info.video_info().codec();
|
||||
return media_info.video_info().codec();
|
||||
|
||||
std::string audio_codec;
|
||||
if (media_info.has_audio_info())
|
||||
audio_codec = media_info.audio_info().codec();
|
||||
return media_info.audio_info().codec();
|
||||
|
||||
if (!video_codec.empty() && !audio_codec.empty()) {
|
||||
return video_codec + "," + audio_codec;
|
||||
} else if (!video_codec.empty()) {
|
||||
return video_codec;
|
||||
} else if (!audio_codec.empty()) {
|
||||
return audio_codec;
|
||||
}
|
||||
if (media_info.has_text_info())
|
||||
return TextCodecString(media_info);
|
||||
|
||||
NOTREACHED();
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,8 @@ bool SimpleMpdNotifier::NotifyNewContainer(const MediaInfo& media_info,
|
|||
std::string lang;
|
||||
if (media_info.has_audio_info()) {
|
||||
lang = media_info.audio_info().language();
|
||||
} else if (media_info.has_text_info()) {
|
||||
lang = media_info.text_info().language();
|
||||
}
|
||||
AdaptationSet** adaptation_set = &adaptation_set_map_[content_type][lang];
|
||||
if (*adaptation_set == NULL)
|
||||
|
|
Loading…
Reference in New Issue