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
|
||||
|
||||
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;
|
||||
/// Set to true to indicate that the stream is for hls only.
|
||||
bool hls_only = false;
|
||||
|
||||
/// Optional for DASH output. It defines the Label element in Adaptation Set.
|
||||
std::string dash_label;
|
||||
};
|
||||
|
||||
class SHAKA_EXPORT Packager {
|
||||
|
|
|
@ -39,6 +39,7 @@ enum FieldType {
|
|||
kDashRolesField,
|
||||
kDashOnlyField,
|
||||
kHlsOnlyField,
|
||||
kDashLabelField,
|
||||
};
|
||||
|
||||
struct FieldNameToTypeMapping {
|
||||
|
@ -86,6 +87,7 @@ const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
|
|||
{"role", kDashRolesField},
|
||||
{"dash_only", kDashOnlyField},
|
||||
{"hls_only", kHlsOnlyField},
|
||||
{"dash_label", kDashLabelField},
|
||||
};
|
||||
|
||||
FieldType GetFieldType(const std::string& field_name) {
|
||||
|
@ -250,6 +252,9 @@ std::optional<StreamDescriptor> ParseStreamDescriptor(
|
|||
}
|
||||
descriptor.hls_only = hls_only_value > 0;
|
||||
break;
|
||||
case kDashLabelField:
|
||||
descriptor.dash_label = pair.second;
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR) << "Unknown field in stream descriptor (\"" << pair.first
|
||||
<< "\").";
|
||||
|
|
|
@ -304,6 +304,7 @@ class PackagerAppTest(unittest.TestCase):
|
|||
dash_accessibilities=None,
|
||||
dash_roles=None,
|
||||
dash_only=None,
|
||||
dash_label=None,
|
||||
trick_play_factor=None,
|
||||
drm_label=None,
|
||||
skip_encryption=None,
|
||||
|
@ -334,6 +335,7 @@ class PackagerAppTest(unittest.TestCase):
|
|||
dash_accessibilities: Accessibility 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_label: Label element for the DASH stream.
|
||||
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
|
||||
same as not specifying a trick play factor.
|
||||
|
@ -400,6 +402,9 @@ class PackagerAppTest(unittest.TestCase):
|
|||
if dash_only:
|
||||
stream.Append('dash_only', 1)
|
||||
|
||||
if dash_label:
|
||||
stream.Append('dash_label', dash_label)
|
||||
|
||||
requires_init_segment = segmented and base_ext not in [
|
||||
'aac', 'ac3', 'ec3', 'ts', 'vtt', 'ttml',
|
||||
]
|
||||
|
@ -786,6 +791,14 @@ class PackagerFunctionalTest(PackagerAppTest):
|
|||
self._GetFlags(output_dash=True, output_hls=True))
|
||||
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):
|
||||
self.assertPackageSuccess(
|
||||
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())
|
||||
media_info->set_index(index_.value());
|
||||
|
||||
if (!dash_label_.empty())
|
||||
media_info->set_dash_label(dash_label_);
|
||||
|
||||
if (is_encrypted_) {
|
||||
internal::SetContentProtectionFields(protection_scheme_, default_key_id_,
|
||||
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_dash_label(std::string label) { dash_label_ = label; }
|
||||
|
||||
private:
|
||||
MpdNotifyMuxerListener(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> roles_;
|
||||
std::string dash_label_;
|
||||
|
||||
std::optional<uint32_t> index_ = 0;
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ std::unique_ptr<MuxerListener> CreateMpdListenerInternal(
|
|||
listener->set_accessibilities(stream.dash_accessiblities);
|
||||
listener->set_roles(stream.dash_roles);
|
||||
listener->set_index(stream.index);
|
||||
listener->set_dash_label(stream.dash_label);
|
||||
return listener;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ class MuxerListenerFactory {
|
|||
std::vector<std::string> dash_roles;
|
||||
bool dash_only = false;
|
||||
std::optional<uint32_t> index;
|
||||
std::string dash_label;
|
||||
};
|
||||
|
||||
/// 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_) {
|
||||
const auto& representation = representation_pair.second;
|
||||
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()) {
|
||||
content_type_ = "video";
|
||||
} else if (media_info.has_audio_info()) {
|
||||
|
|
|
@ -330,6 +330,9 @@ class AdaptationSet {
|
|||
|
||||
// the command-line index for this AdaptationSet
|
||||
std::optional<uint32_t> index_;
|
||||
|
||||
// The label of this AdaptationSet.
|
||||
std::string label_;
|
||||
};
|
||||
|
||||
} // namespace shaka
|
||||
|
|
|
@ -215,4 +215,7 @@ message MediaInfo {
|
|||
|
||||
// stream index for consistent ordering of streams
|
||||
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:");
|
||||
}
|
||||
|
||||
if (media_info.has_dash_label())
|
||||
key.append(media_info.dash_label() + ":");
|
||||
|
||||
key.append(MediaInfo_ContainerType_Name(media_info.container_type()));
|
||||
if (!ignore_codec) {
|
||||
key.append(":");
|
||||
|
|
|
@ -337,6 +337,12 @@ bool AdaptationSetXmlNode::AddRoleElement(const std::string& scheme_id_uri,
|
|||
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()
|
||||
: RepresentationBaseXmlNode("Representation") {}
|
||||
RepresentationXmlNode::~RepresentationXmlNode() {}
|
||||
|
|
|
@ -171,6 +171,9 @@ class AdaptationSetXmlNode : public RepresentationBaseXmlNode {
|
|||
[[nodiscard]] bool AddRoleElement(const std::string& scheme_id_uri,
|
||||
const std::string& value);
|
||||
|
||||
/// @param value is element's content.
|
||||
[[nodiscard]] bool AddLabelElement(const std::string& value);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(AdaptationSetXmlNode);
|
||||
};
|
||||
|
|
|
@ -75,6 +75,7 @@ MuxerListenerFactory::StreamData ToMuxerListenerData(
|
|||
data.dash_roles = stream.dash_roles;
|
||||
data.dash_only = stream.dash_only;
|
||||
data.index = stream.index;
|
||||
data.dash_label = stream.dash_label;
|
||||
return data;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue