Skip to content

Commit

Permalink
feat(DASH): Add Label element. (#1175)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
sr1990 and cosmin committed Feb 14, 2024
1 parent aad2a12 commit b1c5a74
Show file tree
Hide file tree
Showing 18 changed files with 86 additions and 1 deletion.
8 changes: 7 additions & 1 deletion docs/source/options/dash_options.rst
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions include/packager/packager.h
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions packager/app/stream_descriptor.cc
Expand Up @@ -39,6 +39,7 @@ enum FieldType {
kDashRolesField,
kDashOnlyField,
kHlsOnlyField,
kDashLabelField,
};

struct FieldNameToTypeMapping {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
<< "\").";
Expand Down
13 changes: 13 additions & 0 deletions packager/app/test/packager_test.py
Expand Up @@ -304,6 +304,7 @@ def _GetStream(self,
dash_accessibilities=None,
dash_roles=None,
dash_only=None,
dash_label=None,
trick_play_factor=None,
drm_label=None,
skip_encryption=None,
Expand Down Expand Up @@ -334,6 +335,7 @@ def _GetStream(self,
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.
Expand Down Expand Up @@ -400,6 +402,9 @@ def _GetStream(self,
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',
]
Expand Down Expand Up @@ -786,6 +791,14 @@ def testDashOnlyAndHlsOnly(self):
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),
Expand Down
Binary file not shown.
Binary file not shown.
25 changes: 25 additions & 0 deletions packager/app/test/testdata/dash-label/output.mpd
@@ -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>
3 changes: 3 additions & 0 deletions packager/media/event/mpd_notify_muxer_listener.cc
Expand Up @@ -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());
Expand Down
3 changes: 3 additions & 0 deletions packager/media/event/mpd_notify_muxer_listener.h
Expand Up @@ -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;
Expand All @@ -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;

Expand Down
1 change: 1 addition & 0 deletions packager/media/event/muxer_listener_factory.cc
Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions packager/media/event/muxer_listener_factory.h
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions packager/mpd/base/adaptation_set.cc
Expand Up @@ -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)
Expand Down Expand Up @@ -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()) {
Expand Down
3 changes: 3 additions & 0 deletions packager/mpd/base/adaptation_set.h
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions packager/mpd/base/media_info.proto
Expand Up @@ -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;
}
3 changes: 3 additions & 0 deletions packager/mpd/base/mpd_utils.cc
Expand Up @@ -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(":");
Expand Down
6 changes: 6 additions & 0 deletions packager/mpd/base/xml/xml_node.cc
Expand Up @@ -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() {}
Expand Down
3 changes: 3 additions & 0 deletions packager/mpd/base/xml/xml_node.h
Expand Up @@ -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);
};
Expand Down
1 change: 1 addition & 0 deletions packager/packager.cc
Expand Up @@ -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;
};

Expand Down

0 comments on commit b1c5a74

Please sign in to comment.