Skip to content

Commit 2358130

Browse files
zsiecflavioribeiro
authored andcommitted
mpd: support for Accessibility element in AdaptationSet (#69)
* return element name unrecognized in adaptation set * extend elements in adaptation set to cover accessibility elements * testing: add tests for the Accessibility element * testing: assert accessibility value in test Co-authored-by: Flavio Ribeiro <email@flavioribeiro.com>
1 parent 34ac3a6 commit 2358130

File tree

7 files changed

+148
-35
lines changed

7 files changed

+148
-35
lines changed

mpd/fixtures/hbbtv_profile.mpd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<Representation audioSamplingRate="44100" bandwidth="67095" codecs="mp4a.40.2" id="800">
1616
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"></AudioChannelConfiguration>
1717
</Representation>
18+
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
1819
</AdaptationSet>
1920
<AdaptationSet mimeType="video/mp4" startWithSAP="1" scanType="progressive" id="7357" segmentAlignment="true">
2021
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" xmlns:cenc="urn:mpeg:cenc:2013" cenc:default_KID="08e36702-8f33-436c-a5dd-60ffe5571e60" value="cenc"></ContentProtection>

mpd/fixtures/live_profile.mpd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
1414
<SegmentTemplate duration="1968" initialization="$RepresentationID$/audio/en/init.mp4" media="$RepresentationID$/audio/en/seg-$Number$.m4f" startNumber="0" timescale="1000"></SegmentTemplate>
1515
<Representation audioSamplingRate="44100" bandwidth="67095" codecs="mp4a.40.2" id="800"></Representation>
16+
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
1617
</AdaptationSet>
1718
<AdaptationSet mimeType="video/mp4" startWithSAP="1" scanType="progressive" id="7357" segmentAlignment="true">
1819
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" xmlns:cenc="urn:mpeg:cenc:2013" cenc:default_KID="08e36702-8f33-436c-a5dd-60ffe5571e60" value="cenc"></ContentProtection>

mpd/fixtures/live_profile_dynamic.mpd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
1414
<SegmentTemplate duration="1968" initialization="$RepresentationID$/audio/en/init.mp4" media="$RepresentationID$/audio/en/seg-$Number$.m4f" startNumber="0" timescale="1000"></SegmentTemplate>
1515
<Representation audioSamplingRate="44100" bandwidth="67095" codecs="mp4a.40.2" id="800"></Representation>
16+
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
1617
</AdaptationSet>
1718
<AdaptationSet mimeType="video/mp4" startWithSAP="1" scanType="progressive" id="7357" segmentAlignment="true">
1819
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" xmlns:cenc="urn:mpeg:cenc:2013" cenc:default_KID="08e36702-8f33-436c-a5dd-60ffe5571e60" value="cenc"></ContentProtection>

mpd/fixtures/ondemand_profile.mpd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<Initialization range="0-628"></Initialization>
1717
</SegmentBase>
1818
</Representation>
19+
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
1920
</AdaptationSet>
2021
<AdaptationSet mimeType="video/mp4" startWithSAP="1" scanType="progressive" id="7357" segmentAlignment="true">
2122
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" xmlns:cenc="urn:mpeg:cenc:2013" cenc:default_KID="08e36702-8f33-436c-a5dd-60ffe5571e60" value="cenc"></ContentProtection>

mpd/mpd.go

Lines changed: 84 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/hex"
66
"encoding/xml"
77
"errors"
8+
"fmt"
89
"strings"
910
"time"
1011

@@ -33,6 +34,12 @@ const (
3334
AUDIO_CHANNEL_CONFIGURATION_MPEG_DOLBY AudioChannelConfigurationScheme = "tag:dolby.com,2014:dash:audio_channel_configuration:2011"
3435
)
3536

37+
// AccessibilityElementScheme is the scheme definition for an Accessibility element
38+
type AccessibilityElementScheme string
39+
40+
// Accessibility descriptor values for Audio Description
41+
const ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO AccessibilityElementScheme = "urn:tva:metadata:cs:AudioPurposeCS:2007"
42+
3643
// Constants for some known MIME types, this is a limited list and others can be used.
3744
const (
3845
DASH_MIME_TYPE_VIDEO_MP4 string = "video/mp4"
@@ -50,6 +57,7 @@ var (
5057
ErrSegmentTemplateLiveProfileOnly = errors.New("Segment template can only be used with Live Profile")
5158
ErrSegmentTemplateNil = errors.New("Segment Template nil ")
5259
ErrRepresentationNil = errors.New("Representation nil")
60+
ErrAccessibilityNil = errors.New("Accessibility nil")
5361
ErrBaseURLEmpty = errors.New("Base URL empty")
5462
ErrSegmentBaseOnDemandProfileOnly = errors.New("Segment Base can only be used with On-Demand Profile")
5563
ErrSegmentBaseNil = errors.New("Segment Base nil")
@@ -117,46 +125,48 @@ type CommonAttributesAndElements struct {
117125

118126
type AdaptationSet struct {
119127
CommonAttributesAndElements
120-
XMLName xml.Name `xml:"AdaptationSet"`
121-
ID *string `xml:"id,attr"`
122-
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
123-
Lang *string `xml:"lang,attr"`
124-
Group *string `xml:"group,attr"`
125-
PAR *string `xml:"par,attr"`
126-
MinBandwidth *string `xml:"minBandwidth,attr"`
127-
MaxBandwidth *string `xml:"maxBandwidth,attr"`
128-
MinWidth *string `xml:"minWidth,attr"`
129-
MaxWidth *string `xml:"maxWidth,attr"`
130-
ContentType *string `xml:"contentType,attr"`
131-
ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here
132-
Roles []*Role `xml:"Role,omitempty"`
133-
SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"`
134-
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
135-
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
136-
Representations []*Representation `xml:"Representation,omitempty"`
128+
XMLName xml.Name `xml:"AdaptationSet"`
129+
ID *string `xml:"id,attr"`
130+
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
131+
Lang *string `xml:"lang,attr"`
132+
Group *string `xml:"group,attr"`
133+
PAR *string `xml:"par,attr"`
134+
MinBandwidth *string `xml:"minBandwidth,attr"`
135+
MaxBandwidth *string `xml:"maxBandwidth,attr"`
136+
MinWidth *string `xml:"minWidth,attr"`
137+
MaxWidth *string `xml:"maxWidth,attr"`
138+
ContentType *string `xml:"contentType,attr"`
139+
ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here
140+
Roles []*Role `xml:"Role,omitempty"`
141+
SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"`
142+
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
143+
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
144+
Representations []*Representation `xml:"Representation,omitempty"`
145+
AccessibilityElems []*Accessibility `xml:"Accessibility,omitempty"`
137146
}
138147

139148
func (as *AdaptationSet) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
140149

141150
adaptationSet := struct {
142151
CommonAttributesAndElements
143-
XMLName xml.Name `xml:"AdaptationSet"`
144-
ID *string `xml:"id,attr"`
145-
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
146-
Lang *string `xml:"lang,attr"`
147-
Group *string `xml:"group,attr"`
148-
PAR *string `xml:"par,attr"`
149-
MinBandwidth *string `xml:"minBandwidth,attr"`
150-
MaxBandwidth *string `xml:"maxBandwidth,attr"`
151-
MinWidth *string `xml:"minWidth,attr"`
152-
MaxWidth *string `xml:"maxWidth,attr"`
153-
ContentType *string `xml:"contentType,attr"`
154-
ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here
155-
Roles []*Role `xml:"Role,omitempty"`
156-
SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"`
157-
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
158-
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
159-
Representations []*Representation `xml:"Representation,omitempty"`
152+
XMLName xml.Name `xml:"AdaptationSet"`
153+
ID *string `xml:"id,attr"`
154+
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
155+
Lang *string `xml:"lang,attr"`
156+
Group *string `xml:"group,attr"`
157+
PAR *string `xml:"par,attr"`
158+
MinBandwidth *string `xml:"minBandwidth,attr"`
159+
MaxBandwidth *string `xml:"maxBandwidth,attr"`
160+
MinWidth *string `xml:"minWidth,attr"`
161+
MaxWidth *string `xml:"maxWidth,attr"`
162+
ContentType *string `xml:"contentType,attr"`
163+
ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here
164+
Roles []*Role `xml:"Role,omitempty"`
165+
SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"`
166+
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
167+
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
168+
Representations []*Representation `xml:"Representation,omitempty"`
169+
AccessibilityElems []*Accessibility `xml:"Accessibility,omitempty"`
160170
}{}
161171

162172
var (
@@ -240,8 +250,14 @@ func (as *AdaptationSet) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er
240250
return err
241251
}
242252
representations = append(representations, rp)
253+
case "Accessibility":
254+
ac := new(Accessibility)
255+
err = d.DecodeElement(ac, &tt)
256+
if err != nil {
257+
return err
258+
}
243259
default:
244-
return errors.New("Unrecognized element in AdaptationSet")
260+
return fmt.Errorf("unrecognized element in AdaptationSet %q", tt.Name.Local)
245261
}
246262
case xml.EndElement:
247263
if tt == start.End() {
@@ -435,6 +451,12 @@ type Representation struct {
435451
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"`
436452
}
437453

454+
type Accessibility struct {
455+
AdaptationSet *AdaptationSet `xml:"-"`
456+
SchemeIdUri *string `xml:"schemeIdUri,attr,omitempty"`
457+
Value *string `xml:"value,attr,omitempty"`
458+
}
459+
438460
type AudioChannelConfiguration struct {
439461
SchemeIDURI *string `xml:"schemeIdUri,attr"`
440462
// Value will be an int for non-Dolby Schemes, and a hexstring for Dolby Schemes, hence we make it a string
@@ -1019,6 +1041,16 @@ func (as *AdaptationSet) addRepresentation(r *Representation) error {
10191041
return nil
10201042
}
10211043

1044+
// Internal helper method for adding an Accessibility element to an AdaptationSet.
1045+
func (as *AdaptationSet) addAccessibility(a *Accessibility) error {
1046+
if a == nil {
1047+
return ErrAccessibilityNil
1048+
}
1049+
a.AdaptationSet = as
1050+
as.AccessibilityElems = append(as.AccessibilityElems, a)
1051+
return nil
1052+
}
1053+
10221054
// Adds a new Role to an AdaptationSet
10231055
// schemeIdUri - Scheme ID URI string (i.e. urn:mpeg:dash:role:2011)
10241056
// value - Value for this role, (i.e. caption, subtitle, main, alternate, supplementary, commentary, dub)
@@ -1032,6 +1064,23 @@ func (as *AdaptationSet) AddNewRole(schemeIDURI string, value string) (*Role, er
10321064
return r, nil
10331065
}
10341066

1067+
// AddNewAccessibilityElement adds a new accessibility element to an adaptation set
1068+
// schemeIdUri - Scheme ID URI for the Accessibility element (i.e. urn:tva:metadata:cs:AudioPurposeCS:2007)
1069+
// value - specified value based on scheme
1070+
func (as *AdaptationSet) AddNewAccessibilityElement(scheme AccessibilityElementScheme, val string) (*Accessibility, error) {
1071+
accessibility := &Accessibility{
1072+
SchemeIdUri: Strptr((string)(scheme)),
1073+
Value: Strptr(val),
1074+
}
1075+
1076+
err := as.addAccessibility(accessibility)
1077+
if err != nil {
1078+
return nil, err
1079+
}
1080+
1081+
return accessibility, nil
1082+
}
1083+
10351084
// Sets the BaseURL for a Representation.
10361085
// baseURL - Base URL as a string (i.e. 800k/output-audio-und.mp4)
10371086
func (r *Representation) SetNewBaseURL(baseURL string) error {

mpd/mpd_read_write_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,35 @@ func TestExampleAddNewPeriod(t *testing.T) {
180180
testfixtures.CompareFixture(t, "fixtures/newperiod.mpd", xmlStr)
181181
}
182182

183+
func TestAddNewAccessibilityElementWriteToString(t *testing.T) {
184+
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
185+
audioAS, err := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT,
186+
VALID_START_WITH_SAP, VALID_LANG)
187+
if err != nil {
188+
t.Errorf("AddNewAccessibilityElement() error adding audio adaptation set: %v", err)
189+
return
190+
}
191+
192+
_, err = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
193+
if err != nil {
194+
t.Errorf("AddNewAccessibilityElement() error adding accessibility element: %v", err)
195+
return
196+
}
197+
198+
xmlStr, err := m.WriteToString()
199+
require.NoError(t, err)
200+
expectedXML := `<?xml version="1.0" encoding="UTF-8"?>
201+
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6M16S" minBufferTime="PT1.97S">
202+
<Period>
203+
<AdaptationSet mimeType="audio/mp4" startWithSAP="1" id="7357" segmentAlignment="true" lang="en">
204+
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
205+
</AdaptationSet>
206+
</Period>
207+
</MPD>
208+
`
209+
require.EqualString(t, expectedXML, xmlStr)
210+
}
211+
183212
func LiveProfile() *MPD {
184213
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME,
185214
AttrAvailabilityStartTime(VALID_AVAILABILITY_START_TIME))
@@ -194,6 +223,7 @@ func LiveProfile() *MPD {
194223

195224
_, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000)
196225
_, _ = audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800")
226+
_, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
197227

198228
videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
199229

@@ -250,6 +280,7 @@ func LiveProfileDynamic() *MPD {
250280

251281
_, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000)
252282
_, _ = audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800")
283+
_, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
253284

254285
videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
255286

@@ -305,6 +336,7 @@ func HbbTVProfile() *MPD {
305336
_, _ = audioAS.SetNewSegmentTemplate(1968, "$RepresentationID$/audio/en/init.mp4", "$RepresentationID$/audio/en/seg-$Number$.m4f", 0, 1000)
306337
r, _ := audioAS.AddNewRepresentationAudio(44100, 67095, "mp4a.40.2", "800")
307338
_, _ = r.AddNewAudioChannelConfiguration(AUDIO_CHANNEL_CONFIGURATION_MPEG_DASH, "2")
339+
_, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
308340

309341
videoAS, _ := m.AddNewAdaptationSetVideoWithID("7357", DASH_MIME_TYPE_VIDEO_MP4, VALID_SCAN_TYPE, VALID_SEGMENT_ALIGNMENT, VALID_START_WITH_SAP)
310342

@@ -353,6 +385,7 @@ func OnDemandProfile() *MPD {
353385
_, _ = audioAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
354386
_, _ = audioAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
355387
_, _ = audioAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
388+
_, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
356389

357390
audioRep, _ := audioAS.AddNewRepresentationAudio(44100, 128558, "mp4a.40.5", "800k/audio-und")
358391
_ = audioRep.SetNewBaseURL("800k/output-audio-und.mp4")

mpd/mpd_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,3 +457,30 @@ func getValidWVHeaderBytes() []byte {
457457
}
458458
return wvHeader
459459
}
460+
461+
func TestAddNewAccessibilityElement(t *testing.T) {
462+
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
463+
audioAS, err := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT,
464+
VALID_START_WITH_SAP, VALID_LANG)
465+
if err != nil {
466+
t.Errorf("AddNewAccessibilityElement() error adding audio adaptation set: %v", err)
467+
return
468+
}
469+
470+
_, err = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
471+
if err != nil {
472+
t.Errorf("AddNewAccessibilityElement() error adding accessibility element: %v", err)
473+
return
474+
}
475+
476+
if g, e := len(audioAS.AccessibilityElems), 1; g != e {
477+
t.Errorf("AddNewAccessibilityElement() wrong number of accessibility elements, got: %d, expected: %d",
478+
g, e)
479+
return
480+
}
481+
482+
elem := audioAS.AccessibilityElems[0]
483+
484+
require.EqualStringPtr(t, Strptr((string)(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO)), elem.SchemeIdUri)
485+
require.EqualStringPtr(t, Strptr("1"), elem.Value)
486+
}

0 commit comments

Comments
 (0)