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:
sr90 2024-02-14 10:36:08 -08:00 committed by GitHub
parent aad2a12a9d
commit b1c5a7433e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 86 additions and 1 deletions

View File

@ -105,3 +105,9 @@ DASH options
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.

View File

@ -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 {

View File

@ -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
<< "\")."; << "\").";

View File

@ -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.

View File

@ -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>

View File

@ -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());

View File

@ -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;

View File

@ -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;
} }

View File

@ -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.

View File

@ -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()) {

View File

@ -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

View File

@ -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;
} }

View File

@ -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(":");

View File

@ -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() {}

View File

@ -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);
}; };

View File

@ -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;
}; };