Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mpd/fixtures/hbbtv_profile.mpd
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<Representation audioSamplingRate="44100" bandwidth="67095" codecs="mp4a.40.2" id="800">
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"></AudioChannelConfiguration>
</Representation>
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" startWithSAP="1" scanType="progressive" id="7357" segmentAlignment="true">
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" xmlns:cenc="urn:mpeg:cenc:2013" cenc:default_KID="08e36702-8f33-436c-a5dd-60ffe5571e60" value="cenc"></ContentProtection>
Expand Down
1 change: 1 addition & 0 deletions mpd/fixtures/live_profile.mpd
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate duration="1968" initialization="$RepresentationID$/audio/en/init.mp4" media="$RepresentationID$/audio/en/seg-$Number$.m4f" startNumber="0" timescale="1000"></SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="67095" codecs="mp4a.40.2" id="800"></Representation>
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" startWithSAP="1" scanType="progressive" id="7357" segmentAlignment="true">
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" xmlns:cenc="urn:mpeg:cenc:2013" cenc:default_KID="08e36702-8f33-436c-a5dd-60ffe5571e60" value="cenc"></ContentProtection>
Expand Down
1 change: 1 addition & 0 deletions mpd/fixtures/live_profile_dynamic.mpd
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"></Role>
<SegmentTemplate duration="1968" initialization="$RepresentationID$/audio/en/init.mp4" media="$RepresentationID$/audio/en/seg-$Number$.m4f" startNumber="0" timescale="1000"></SegmentTemplate>
<Representation audioSamplingRate="44100" bandwidth="67095" codecs="mp4a.40.2" id="800"></Representation>
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" startWithSAP="1" scanType="progressive" id="7357" segmentAlignment="true">
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" xmlns:cenc="urn:mpeg:cenc:2013" cenc:default_KID="08e36702-8f33-436c-a5dd-60ffe5571e60" value="cenc"></ContentProtection>
Expand Down
1 change: 1 addition & 0 deletions mpd/fixtures/ondemand_profile.mpd
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<Initialization range="0-628"></Initialization>
</SegmentBase>
</Representation>
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
</AdaptationSet>
<AdaptationSet mimeType="video/mp4" startWithSAP="1" scanType="progressive" id="7357" segmentAlignment="true">
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" xmlns:cenc="urn:mpeg:cenc:2013" cenc:default_KID="08e36702-8f33-436c-a5dd-60ffe5571e60" value="cenc"></ContentProtection>
Expand Down
119 changes: 84 additions & 35 deletions mpd/mpd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"encoding/xml"
"errors"
"fmt"
"strings"
"time"

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

// AccessibilityElementScheme is the scheme definition for an Accessibility element
type AccessibilityElementScheme string

// Accessibility descriptor values for Audio Description
const ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO AccessibilityElementScheme = "urn:tva:metadata:cs:AudioPurposeCS:2007"

// Constants for some known MIME types, this is a limited list and others can be used.
const (
DASH_MIME_TYPE_VIDEO_MP4 string = "video/mp4"
Expand All @@ -50,6 +57,7 @@ var (
ErrSegmentTemplateLiveProfileOnly = errors.New("Segment template can only be used with Live Profile")
ErrSegmentTemplateNil = errors.New("Segment Template nil ")
ErrRepresentationNil = errors.New("Representation nil")
ErrAccessibilityNil = errors.New("Accessibility nil")
ErrBaseURLEmpty = errors.New("Base URL empty")
ErrSegmentBaseOnDemandProfileOnly = errors.New("Segment Base can only be used with On-Demand Profile")
ErrSegmentBaseNil = errors.New("Segment Base nil")
Expand Down Expand Up @@ -117,46 +125,48 @@ type CommonAttributesAndElements struct {

type AdaptationSet struct {
CommonAttributesAndElements
XMLName xml.Name `xml:"AdaptationSet"`
ID *string `xml:"id,attr"`
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
Lang *string `xml:"lang,attr"`
Group *string `xml:"group,attr"`
PAR *string `xml:"par,attr"`
MinBandwidth *string `xml:"minBandwidth,attr"`
MaxBandwidth *string `xml:"maxBandwidth,attr"`
MinWidth *string `xml:"minWidth,attr"`
MaxWidth *string `xml:"maxWidth,attr"`
ContentType *string `xml:"contentType,attr"`
ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here
Roles []*Role `xml:"Role,omitempty"`
SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"`
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
Representations []*Representation `xml:"Representation,omitempty"`
XMLName xml.Name `xml:"AdaptationSet"`
ID *string `xml:"id,attr"`
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
Lang *string `xml:"lang,attr"`
Group *string `xml:"group,attr"`
PAR *string `xml:"par,attr"`
MinBandwidth *string `xml:"minBandwidth,attr"`
MaxBandwidth *string `xml:"maxBandwidth,attr"`
MinWidth *string `xml:"minWidth,attr"`
MaxWidth *string `xml:"maxWidth,attr"`
ContentType *string `xml:"contentType,attr"`
ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here
Roles []*Role `xml:"Role,omitempty"`
SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"`
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
Representations []*Representation `xml:"Representation,omitempty"`
AccessibilityElems []*Accessibility `xml:"Accessibility,omitempty"`
}

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

adaptationSet := struct {
CommonAttributesAndElements
XMLName xml.Name `xml:"AdaptationSet"`
ID *string `xml:"id,attr"`
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
Lang *string `xml:"lang,attr"`
Group *string `xml:"group,attr"`
PAR *string `xml:"par,attr"`
MinBandwidth *string `xml:"minBandwidth,attr"`
MaxBandwidth *string `xml:"maxBandwidth,attr"`
MinWidth *string `xml:"minWidth,attr"`
MaxWidth *string `xml:"maxWidth,attr"`
ContentType *string `xml:"contentType,attr"`
ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here
Roles []*Role `xml:"Role,omitempty"`
SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"`
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
Representations []*Representation `xml:"Representation,omitempty"`
XMLName xml.Name `xml:"AdaptationSet"`
ID *string `xml:"id,attr"`
SegmentAlignment *bool `xml:"segmentAlignment,attr"`
Lang *string `xml:"lang,attr"`
Group *string `xml:"group,attr"`
PAR *string `xml:"par,attr"`
MinBandwidth *string `xml:"minBandwidth,attr"`
MaxBandwidth *string `xml:"maxBandwidth,attr"`
MinWidth *string `xml:"minWidth,attr"`
MaxWidth *string `xml:"maxWidth,attr"`
ContentType *string `xml:"contentType,attr"`
ContentProtection []ContentProtectioner `xml:"ContentProtection,omitempty"` // Common attribute, can be deprecated here
Roles []*Role `xml:"Role,omitempty"`
SegmentBase *SegmentBase `xml:"SegmentBase,omitempty"`
SegmentList *SegmentList `xml:"SegmentList,omitempty"`
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"` // Live Profile Only
Representations []*Representation `xml:"Representation,omitempty"`
AccessibilityElems []*Accessibility `xml:"Accessibility,omitempty"`
}{}

var (
Expand Down Expand Up @@ -240,8 +250,14 @@ func (as *AdaptationSet) UnmarshalXML(d *xml.Decoder, start xml.StartElement) er
return err
}
representations = append(representations, rp)
case "Accessibility":
ac := new(Accessibility)
err = d.DecodeElement(ac, &tt)
if err != nil {
return err
}
default:
return errors.New("Unrecognized element in AdaptationSet")
return fmt.Errorf("unrecognized element in AdaptationSet %q", tt.Name.Local)
}
case xml.EndElement:
if tt == start.End() {
Expand Down Expand Up @@ -435,6 +451,12 @@ type Representation struct {
SegmentTemplate *SegmentTemplate `xml:"SegmentTemplate,omitempty"`
}

type Accessibility struct {
AdaptationSet *AdaptationSet `xml:"-"`
SchemeIdUri *string `xml:"schemeIdUri,attr,omitempty"`
Value *string `xml:"value,attr,omitempty"`
}

type AudioChannelConfiguration struct {
SchemeIDURI *string `xml:"schemeIdUri,attr"`
// Value will be an int for non-Dolby Schemes, and a hexstring for Dolby Schemes, hence we make it a string
Expand Down Expand Up @@ -1019,6 +1041,16 @@ func (as *AdaptationSet) addRepresentation(r *Representation) error {
return nil
}

// Internal helper method for adding an Accessibility element to an AdaptationSet.
func (as *AdaptationSet) addAccessibility(a *Accessibility) error {
if a == nil {
return ErrAccessibilityNil
}
a.AdaptationSet = as
as.AccessibilityElems = append(as.AccessibilityElems, a)
return nil
}

// Adds a new Role to an AdaptationSet
// schemeIdUri - Scheme ID URI string (i.e. urn:mpeg:dash:role:2011)
// value - Value for this role, (i.e. caption, subtitle, main, alternate, supplementary, commentary, dub)
Expand All @@ -1032,6 +1064,23 @@ func (as *AdaptationSet) AddNewRole(schemeIDURI string, value string) (*Role, er
return r, nil
}

// AddNewAccessibilityElement adds a new accessibility element to an adaptation set
// schemeIdUri - Scheme ID URI for the Accessibility element (i.e. urn:tva:metadata:cs:AudioPurposeCS:2007)
// value - specified value based on scheme
func (as *AdaptationSet) AddNewAccessibilityElement(scheme AccessibilityElementScheme, val string) (*Accessibility, error) {
accessibility := &Accessibility{
SchemeIdUri: Strptr((string)(scheme)),
Value: Strptr(val),
}

err := as.addAccessibility(accessibility)
if err != nil {
return nil, err
}

return accessibility, nil
}

// Sets the BaseURL for a Representation.
// baseURL - Base URL as a string (i.e. 800k/output-audio-und.mp4)
func (r *Representation) SetNewBaseURL(baseURL string) error {
Expand Down
33 changes: 33 additions & 0 deletions mpd/mpd_read_write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,35 @@ func TestExampleAddNewPeriod(t *testing.T) {
testfixtures.CompareFixture(t, "fixtures/newperiod.mpd", xmlStr)
}

func TestAddNewAccessibilityElementWriteToString(t *testing.T) {
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
audioAS, err := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT,
VALID_START_WITH_SAP, VALID_LANG)
if err != nil {
t.Errorf("AddNewAccessibilityElement() error adding audio adaptation set: %v", err)
return
}

_, err = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
if err != nil {
t.Errorf("AddNewAccessibilityElement() error adding accessibility element: %v", err)
return
}

xmlStr, err := m.WriteToString()
require.NoError(t, err)
expectedXML := `<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="PT6M16S" minBufferTime="PT1.97S">
<Period>
<AdaptationSet mimeType="audio/mp4" startWithSAP="1" id="7357" segmentAlignment="true" lang="en">
<Accessibility schemeIdUri="urn:tva:metadata:cs:AudioPurposeCS:2007" value="1"></Accessibility>
</AdaptationSet>
</Period>
</MPD>
`
require.EqualString(t, expectedXML, xmlStr)
}

func LiveProfile() *MPD {
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME,
AttrAvailabilityStartTime(VALID_AVAILABILITY_START_TIME))
Expand All @@ -194,6 +223,7 @@ func LiveProfile() *MPD {

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

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

Expand Down Expand Up @@ -250,6 +280,7 @@ func LiveProfileDynamic() *MPD {

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

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

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

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

Expand Down Expand Up @@ -353,6 +385,7 @@ func OnDemandProfile() *MPD {
_, _ = audioAS.AddNewContentProtectionRoot("08e367028f33436ca5dd60ffe5571e60")
_, _ = audioAS.AddNewContentProtectionSchemeWidevineWithPSSH(getValidWVHeaderBytes())
_, _ = audioAS.AddNewContentProtectionSchemePlayreadyWithPSSH(VALID_PLAYREADY_PRO)
_, _ = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")

audioRep, _ := audioAS.AddNewRepresentationAudio(44100, 128558, "mp4a.40.5", "800k/audio-und")
_ = audioRep.SetNewBaseURL("800k/output-audio-und.mp4")
Expand Down
27 changes: 27 additions & 0 deletions mpd/mpd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,30 @@ func getValidWVHeaderBytes() []byte {
}
return wvHeader
}

func TestAddNewAccessibilityElement(t *testing.T) {
m := NewMPD(DASH_PROFILE_LIVE, VALID_MEDIA_PRESENTATION_DURATION, VALID_MIN_BUFFER_TIME)
audioAS, err := m.AddNewAdaptationSetAudioWithID("7357", DASH_MIME_TYPE_AUDIO_MP4, VALID_SEGMENT_ALIGNMENT,
VALID_START_WITH_SAP, VALID_LANG)
if err != nil {
t.Errorf("AddNewAccessibilityElement() error adding audio adaptation set: %v", err)
return
}

_, err = audioAS.AddNewAccessibilityElement(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO, "1")
if err != nil {
t.Errorf("AddNewAccessibilityElement() error adding accessibility element: %v", err)
return
}

if g, e := len(audioAS.AccessibilityElems), 1; g != e {
t.Errorf("AddNewAccessibilityElement() wrong number of accessibility elements, got: %d, expected: %d",
g, e)
return
}

elem := audioAS.AccessibilityElems[0]

require.EqualStringPtr(t, Strptr((string)(ACCESSIBILITY_ELEMENT_SCHEME_DESCRIPTIVE_AUDIO)), elem.SchemeIdUri)
require.EqualStringPtr(t, Strptr("1"), elem.Value)
}