feat(DASH): Add Label element. (#1175)
Add ability to set `Label` tag in MPD, see page 35 of DASH-IF IOP 4.3 https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf Implements #881 --------- Co-authored-by: Cosmin Stejerean <cstejerean@meta.com>
This commit is contained in:
parent
aad2a12a9d
commit
b1c5a7433e
|
@ -104,4 +104,10 @@ DASH options
|
||||||
--force_cl_index
|
--force_cl_index
|
||||||
|
|
||||||
True forces the muxer to order streams in the order given
|
True forces the muxer to order streams in the order given
|
||||||
on the command-line. False uses the previous unordered behavior.
|
on the command-line. False uses the previous unordered behavior.
|
||||||
|
|
||||||
|
--dash_label <label_name>
|
||||||
|
|
||||||
|
Optional. Will add Label tag to adapation set and will be taken into
|
||||||
|
consideration along with codecs, language, media type (audio, video etc)
|
||||||
|
and container type to create different adaptation sets.
|
||||||
|
|
|
@ -151,6 +151,9 @@ struct StreamDescriptor {
|
||||||
bool dash_only = false;
|
bool dash_only = false;
|
||||||
/// Set to true to indicate that the stream is for hls only.
|
/// Set to true to indicate that the stream is for hls only.
|
||||||
bool hls_only = false;
|
bool hls_only = false;
|
||||||
|
|
||||||
|
/// Optional for DASH output. It defines the Label element in Adaptation Set.
|
||||||
|
std::string dash_label;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SHAKA_EXPORT Packager {
|
class SHAKA_EXPORT Packager {
|
||||||
|
|
|
@ -39,6 +39,7 @@ enum FieldType {
|
||||||
kDashRolesField,
|
kDashRolesField,
|
||||||
kDashOnlyField,
|
kDashOnlyField,
|
||||||
kHlsOnlyField,
|
kHlsOnlyField,
|
||||||
|
kDashLabelField,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FieldNameToTypeMapping {
|
struct FieldNameToTypeMapping {
|
||||||
|
@ -86,6 +87,7 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
|
||||||
{"role", kDashRolesField},
|
{"role", kDashRolesField},
|
||||||
{"dash_only", kDashOnlyField},
|
{"dash_only", kDashOnlyField},
|
||||||
{"hls_only", kHlsOnlyField},
|
{"hls_only", kHlsOnlyField},
|
||||||
|
{"dash_label", kDashLabelField},
|
||||||
};
|
};
|
||||||
|
|
||||||
FieldType GetFieldType(const std::string& field_name) {
|
FieldType GetFieldType(const std::string& field_name) {
|
||||||
|
@ -250,6 +252,9 @@ std::optional<StreamDescriptor> ParseStreamDescriptor(
|
||||||
}
|
}
|
||||||
descriptor.hls_only = hls_only_value > 0;
|
descriptor.hls_only = hls_only_value > 0;
|
||||||
break;
|
break;
|
||||||
|
case kDashLabelField:
|
||||||
|
descriptor.dash_label = pair.second;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG(ERROR) << "Unknown field in stream descriptor (\"" << pair.first
|
LOG(ERROR) << "Unknown field in stream descriptor (\"" << pair.first
|
||||||
<< "\").";
|
<< "\").";
|
||||||
|
|
|
@ -304,6 +304,7 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
dash_accessibilities=None,
|
dash_accessibilities=None,
|
||||||
dash_roles=None,
|
dash_roles=None,
|
||||||
dash_only=None,
|
dash_only=None,
|
||||||
|
dash_label=None,
|
||||||
trick_play_factor=None,
|
trick_play_factor=None,
|
||||||
drm_label=None,
|
drm_label=None,
|
||||||
skip_encryption=None,
|
skip_encryption=None,
|
||||||
|
@ -334,6 +335,7 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
dash_accessibilities: Accessibility element for the DASH stream.
|
dash_accessibilities: Accessibility element for the DASH stream.
|
||||||
dash_roles: Role element for the DASH stream.
|
dash_roles: Role element for the DASH stream.
|
||||||
dash_only: If set to true, will indicate that the stream is for DASH only.
|
dash_only: If set to true, will indicate that the stream is for DASH only.
|
||||||
|
dash_label: Label element for the DASH stream.
|
||||||
trick_play_factor: Signals the stream is to be used for a trick play
|
trick_play_factor: Signals the stream is to be used for a trick play
|
||||||
stream and which key frames to use. A trick play factor of 0 is the
|
stream and which key frames to use. A trick play factor of 0 is the
|
||||||
same as not specifying a trick play factor.
|
same as not specifying a trick play factor.
|
||||||
|
@ -400,6 +402,9 @@ class PackagerAppTest(unittest.TestCase):
|
||||||
if dash_only:
|
if dash_only:
|
||||||
stream.Append('dash_only', 1)
|
stream.Append('dash_only', 1)
|
||||||
|
|
||||||
|
if dash_label:
|
||||||
|
stream.Append('dash_label', dash_label)
|
||||||
|
|
||||||
requires_init_segment = segmented and base_ext not in [
|
requires_init_segment = segmented and base_ext not in [
|
||||||
'aac', 'ac3', 'ec3', 'ts', 'vtt', 'ttml',
|
'aac', 'ac3', 'ec3', 'ts', 'vtt', 'ttml',
|
||||||
]
|
]
|
||||||
|
@ -786,6 +791,14 @@ class PackagerFunctionalTest(PackagerAppTest):
|
||||||
self._GetFlags(output_dash=True, output_hls=True))
|
self._GetFlags(output_dash=True, output_hls=True))
|
||||||
self._CheckTestResults('hls-only-dash-only')
|
self._CheckTestResults('hls-only-dash-only')
|
||||||
|
|
||||||
|
def testDashLabel(self):
|
||||||
|
streams = [
|
||||||
|
self._GetStream('video', dash_label='Main'),
|
||||||
|
self._GetStream('audio', dash_label='English'),
|
||||||
|
]
|
||||||
|
self.assertPackageSuccess(streams, self._GetFlags(output_dash=True))
|
||||||
|
self._CheckTestResults('dash-label')
|
||||||
|
|
||||||
def testAudioVideoWithLanguageOverride(self):
|
def testAudioVideoWithLanguageOverride(self):
|
||||||
self.assertPackageSuccess(
|
self.assertPackageSuccess(
|
||||||
self._GetStreams(['audio', 'video'], language='por', hls=True),
|
self._GetStreams(['audio', 'video'], language='por', hls=True),
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--Generated with https://github.com/shaka-project/shaka-packager version <tag>-<hash>-<test>-->
|
||||||
|
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.736067S">
|
||||||
|
<Period id="0">
|
||||||
|
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subsegmentAlignment="true" par="16:9">
|
||||||
|
<Label>Main</Label>
|
||||||
|
<Representation id="0" bandwidth="973483" codecs="avc1.64001e" mimeType="video/mp4" sar="1:1">
|
||||||
|
<BaseURL>bear-640x360-video.mp4</BaseURL>
|
||||||
|
<SegmentBase indexRange="870-937" timescale="30000">
|
||||||
|
<Initialization range="0-869"/>
|
||||||
|
</SegmentBase>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
<AdaptationSet id="1" contentType="audio" subsegmentAlignment="true">
|
||||||
|
<Label>English</Label>
|
||||||
|
<Representation id="1" bandwidth="133334" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="44100">
|
||||||
|
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||||
|
<BaseURL>bear-640x360-audio.mp4</BaseURL>
|
||||||
|
<SegmentBase indexRange="804-871" timescale="44100">
|
||||||
|
<Initialization range="0-803"/>
|
||||||
|
</SegmentBase>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
|
@ -88,6 +88,9 @@ void MpdNotifyMuxerListener::OnMediaStart(const MuxerOptions& muxer_options,
|
||||||
if (index_.has_value())
|
if (index_.has_value())
|
||||||
media_info->set_index(index_.value());
|
media_info->set_index(index_.value());
|
||||||
|
|
||||||
|
if (!dash_label_.empty())
|
||||||
|
media_info->set_dash_label(dash_label_);
|
||||||
|
|
||||||
if (is_encrypted_) {
|
if (is_encrypted_) {
|
||||||
internal::SetContentProtectionFields(protection_scheme_, default_key_id_,
|
internal::SetContentProtectionFields(protection_scheme_, default_key_id_,
|
||||||
key_system_info_, media_info.get());
|
key_system_info_, media_info.get());
|
||||||
|
|
|
@ -69,6 +69,8 @@ class MpdNotifyMuxerListener : public MuxerListener {
|
||||||
|
|
||||||
void set_index(std::optional<uint32_t> idx) { index_ = idx; }
|
void set_index(std::optional<uint32_t> idx) { index_ = idx; }
|
||||||
|
|
||||||
|
void set_dash_label(std::string label) { dash_label_ = label; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MpdNotifyMuxerListener(const MpdNotifyMuxerListener&) = delete;
|
MpdNotifyMuxerListener(const MpdNotifyMuxerListener&) = delete;
|
||||||
MpdNotifyMuxerListener& operator=(const MpdNotifyMuxerListener&) = delete;
|
MpdNotifyMuxerListener& operator=(const MpdNotifyMuxerListener&) = delete;
|
||||||
|
@ -81,6 +83,7 @@ class MpdNotifyMuxerListener : public MuxerListener {
|
||||||
|
|
||||||
std::vector<std::string> accessibilities_;
|
std::vector<std::string> accessibilities_;
|
||||||
std::vector<std::string> roles_;
|
std::vector<std::string> roles_;
|
||||||
|
std::string dash_label_;
|
||||||
|
|
||||||
std::optional<uint32_t> index_ = 0;
|
std::optional<uint32_t> index_ = 0;
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ std::unique_ptr<MuxerListener> CreateMpdListenerInternal(
|
||||||
listener->set_accessibilities(stream.dash_accessiblities);
|
listener->set_accessibilities(stream.dash_accessiblities);
|
||||||
listener->set_roles(stream.dash_roles);
|
listener->set_roles(stream.dash_roles);
|
||||||
listener->set_index(stream.index);
|
listener->set_index(stream.index);
|
||||||
|
listener->set_dash_label(stream.dash_label);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ class MuxerListenerFactory {
|
||||||
std::vector<std::string> dash_roles;
|
std::vector<std::string> dash_roles;
|
||||||
bool dash_only = false;
|
bool dash_only = false;
|
||||||
std::optional<uint32_t> index;
|
std::optional<uint32_t> index;
|
||||||
|
std::string dash_label;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a new muxer listener.
|
/// Create a new muxer listener.
|
||||||
|
|
|
@ -382,6 +382,9 @@ std::optional<xml::XmlNode> AdaptationSet::GetXml() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!label_.empty() && !adaptation_set.AddLabelElement(label_))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
for (const auto& representation_pair : representation_map_) {
|
for (const auto& representation_pair : representation_map_) {
|
||||||
const auto& representation = representation_pair.second;
|
const auto& representation = representation_pair.second;
|
||||||
if (suppress_representation_width)
|
if (suppress_representation_width)
|
||||||
|
@ -474,6 +477,9 @@ void AdaptationSet::UpdateFromMediaInfo(const MediaInfo& media_info) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media_info.has_dash_label())
|
||||||
|
label_ = media_info.dash_label();
|
||||||
|
|
||||||
if (media_info.has_video_info()) {
|
if (media_info.has_video_info()) {
|
||||||
content_type_ = "video";
|
content_type_ = "video";
|
||||||
} else if (media_info.has_audio_info()) {
|
} else if (media_info.has_audio_info()) {
|
||||||
|
|
|
@ -330,6 +330,9 @@ class AdaptationSet {
|
||||||
|
|
||||||
// the command-line index for this AdaptationSet
|
// the command-line index for this AdaptationSet
|
||||||
std::optional<uint32_t> index_;
|
std::optional<uint32_t> index_;
|
||||||
|
|
||||||
|
// The label of this AdaptationSet.
|
||||||
|
std::string label_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -215,4 +215,7 @@ message MediaInfo {
|
||||||
|
|
||||||
// stream index for consistent ordering of streams
|
// stream index for consistent ordering of streams
|
||||||
optional uint32 index = 28;
|
optional uint32 index = 28;
|
||||||
|
|
||||||
|
// DASH only. Label element.
|
||||||
|
optional string dash_label = 29;
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,6 +161,9 @@ std::string GetAdaptationSetKey(const MediaInfo& media_info,
|
||||||
key.append("unknown:");
|
key.append("unknown:");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (media_info.has_dash_label())
|
||||||
|
key.append(media_info.dash_label() + ":");
|
||||||
|
|
||||||
key.append(MediaInfo_ContainerType_Name(media_info.container_type()));
|
key.append(MediaInfo_ContainerType_Name(media_info.container_type()));
|
||||||
if (!ignore_codec) {
|
if (!ignore_codec) {
|
||||||
key.append(":");
|
key.append(":");
|
||||||
|
|
|
@ -337,6 +337,12 @@ bool AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
|
||||||
return AddDescriptor("Role", scheme_id_uri, value);
|
return AddDescriptor("Role", scheme_id_uri, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AdaptationSetXmlNode::AddLabelElement(const std::string& value) {
|
||||||
|
XmlNode descriptor("Label");
|
||||||
|
descriptor.SetContent(value);
|
||||||
|
return AddChild(std::move(descriptor));
|
||||||
|
}
|
||||||
|
|
||||||
RepresentationXmlNode::RepresentationXmlNode()
|
RepresentationXmlNode::RepresentationXmlNode()
|
||||||
: RepresentationBaseXmlNode("Representation") {}
|
: RepresentationBaseXmlNode("Representation") {}
|
||||||
RepresentationXmlNode::~RepresentationXmlNode() {}
|
RepresentationXmlNode::~RepresentationXmlNode() {}
|
||||||
|
|
|
@ -171,6 +171,9 @@ class AdaptationSetXmlNode : public RepresentationBaseXmlNode {
|
||||||
[[nodiscard]] bool AddRoleElement(const std::string& scheme_id_uri,
|
[[nodiscard]] bool AddRoleElement(const std::string& scheme_id_uri,
|
||||||
const std::string& value);
|
const std::string& value);
|
||||||
|
|
||||||
|
/// @param value is element's content.
|
||||||
|
[[nodiscard]] bool AddLabelElement(const std::string& value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DISALLOW_COPY_AND_ASSIGN(AdaptationSetXmlNode);
|
DISALLOW_COPY_AND_ASSIGN(AdaptationSetXmlNode);
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,6 +75,7 @@ MuxerListenerFactory::StreamData ToMuxerListenerData(
|
||||||
data.dash_roles = stream.dash_roles;
|
data.dash_roles = stream.dash_roles;
|
||||||
data.dash_only = stream.dash_only;
|
data.dash_only = stream.dash_only;
|
||||||
data.index = stream.index;
|
data.index = stream.index;
|
||||||
|
data.dash_label = stream.dash_label;
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue