From dc2cd4edc7ecd69664f29cdbfcf4d339e0c477f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Einarson?= Date: Tue, 22 Oct 2024 21:31:48 +0200 Subject: [PATCH] feat: DASH Ed 6 elements and new test content Added elements from current Ed 6 draft Changed Duration to round to milliseconds unless time is less than millisecond --- CHANGELOG.md | 15 ++ mpd/duration.go | 40 ++-- mpd/duration_test.go | 20 ++ mpd/mpd.go | 183 +++++++++++++----- mpd/testdata/go-dash-fixtures/truncate.mpd | 2 +- .../go-dash-fixtures/truncate_short.mpd | 2 +- mpd/testdata/schema-mpds/example_G28.1.mpd | 64 ++++++ mpd/testdata/schema-mpds/example_G28.2.mpd | 57 ++++++ mpd/testdata/schema-mpds/example_G29.mpd | 36 ++++ 9 files changed, 350 insertions(+), 69 deletions(-) create mode 100644 mpd/testdata/schema-mpds/example_G28.1.mpd create mode 100644 mpd/testdata/schema-mpds/example_G28.2.mpd create mode 100644 mpd/testdata/schema-mpds/example_G29.mpd diff --git a/CHANGELOG.md b/CHANGELOG.md index 9327cd5..c73fc5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Duration is now printed with millisecond accuracy unless value less than one millisecond +- PatchLocationType according to Ed. 6 +- Location according to Ed. 6 + ### Added - Ed5Amd1 element `` inside mixed XML type `` +- AlternativeMPD element according to Ed. 6 +- ContentSteering according to Ed. 6 +- ClientDataReporting according to Ed. 6 +- SegmentSequenceProperties according to Ed. 6 +- RunLengthType according to Ed. 6 +- Pattern and PatternType according to Ed. 6 +- SupVideoInfoType according to Ed. 6 xsd (what is it used for??) +- SapWithCadenceType according to Ed. 6 +- Example content G.23 to G28.1, G28.2, and G.29 ### Fixed diff --git a/mpd/duration.go b/mpd/duration.go index 9fbc8bd..03f453e 100644 --- a/mpd/duration.go +++ b/mpd/duration.go @@ -1,6 +1,7 @@ package mpd import ( + "math" "regexp" "strconv" "strings" @@ -64,33 +65,42 @@ func (d *Duration) UnmarshalXMLAttr(attr xml.Attr) error { // // It handles negative durations, although they should not occur. // The highest output unit is hours (H). +// There is never more than 3 decimals to the seconds. func (d *Duration) String() string { - // Largest time is 2540400h10m10.000000000s + // Largest time is 2540400h10m10.000s var buf [32]byte w := len(buf) u := uint64(*d) + if u == 0 { + return "PT0S" + } neg := *d < 0 if neg { u = -u } - if u < uint64(time.Second) { - var prec int - w-- - buf[w] = 'S' - w-- - if u == 0 { - return "PT0S" - } - w, u = fmtFrac(buf[:w], u, prec) - w = fmtInt(buf[:w], u) - } else { - w-- - buf[w] = 'S' + s := u / uint64(time.Second) + ns := u - s*uint64(time.Second) + ms := uint64(math.Round(float64(ns) * 1.0e-6)) - w, u = fmtFrac(buf[:w], u, 9) + w-- + buf[w] = 'S' // End with Seconds + switch { + case s == 0 && ms == 0: + // Time smaller than ms, return higher precision + w, u = fmtFrac(buf[:w], u, 9) + w = fmtInt(buf[:w], u) + case s == 0: + // Time smaller than 1s, return ms + w, _ = fmtFrac(buf[:w], ms, 3) + w-- + buf[w] = '0' + default: + // Time larger than 1s, return s and potentially ms + u = 1000*s + ms + w, u = fmtFrac(buf[:w], u, 3) // u is now integer seconds w = fmtInt(buf[:w], u%60) u /= 60 diff --git a/mpd/duration_test.go b/mpd/duration_test.go index 8215592..8f35aaf 100644 --- a/mpd/duration_test.go +++ b/mpd/duration_test.go @@ -63,3 +63,23 @@ func TestParseBadDurations(t *testing.T) { require.EqualError(t, err, msg, fmt.Sprintf("Expected an error for: %s", ins)) } } + +func TestUnMarshalReMarshalDuration(t *testing.T) { + cases := []string{ + "PT0.0002S", + "PT0.334S", + "PT2.002S", + "PT2S", + "PT1M", + "PT0S", + } + + for _, dur := range cases { + timeDur, err := ParseDuration(dur) + require.NoError(t, err) + + tDur := Duration(timeDur) + outDur := tDur.String() + require.Equal(t, dur, outDur) + } +} diff --git a/mpd/mpd.go b/mpd/mpd.go index 8e735f1..6a35357 100644 --- a/mpd/mpd.go +++ b/mpd/mpd.go @@ -57,7 +57,7 @@ type MPD struct { MaxSubsegmentDuration *Duration `xml:"maxSubsegmentDuration,attr"` ProgramInformation []*ProgramInformationType `xml:"ProgramInformation"` BaseURL []*BaseURLType `xml:"BaseURL"` - Location []AnyURI `xml:"Location"` + Location []*LocationType `xml:"Location"` PatchLocation []*PatchLocationType `xml:"PatchLocation"` ServiceDescription []*ServiceDescriptionType `xml:"ServiceDescription"` InitializationSet []*InitializationSetType `xml:"InitializationSet"` @@ -89,9 +89,16 @@ func Clone(mpd *MPD) *MPD { // PatchLocationType is Patch Location Type. type PatchLocationType struct { - XMLName xml.Name `xml:"PatchLocation"` - Ttl float64 `xml:"ttl,attr,omitempty"` - Value AnyURI `xml:",chardata"` + XMLName xml.Name `xml:"PatchLocation"` + ServiceLocation string `xml:"serviceLocation,attr,omitempty"` + Ttl float64 `xml:"ttl,attr,omitempty"` + Value AnyURI `xml:",chardata"` +} + +// LocationType is Location Type (extension with ServiceLocation in Ed 6) +type LocationType struct { + ServiceLocation string `xml:"serviceLocation,attr,omitempty"` + Value string `xml:",chardata"` } type Period struct { @@ -146,14 +153,15 @@ type EventStreamType struct { // EventType is Event. This has settings mixed="true" in the schema, so it can either // have charData or child elements or both. type EventType struct { - XMLName xml.Name `xml:"Event"` - PresentationTime uint64 `xml:"presentationTime,attr,omitempty"` // default is 0 - Duration uint64 `xml:"duration,attr,omitempty"` - Id uint32 `xml:"id,attr"` - ContentEncoding ContentEncodingType `xml:"contentEncoding,attr,omitempty"` - MessageData string `xml:"messageData,attr,omitempty"` - Value string `xml:",chardata"` - SelectionInfo *SelectionInfoType `xml:"SelectionInfo"` + XMLName xml.Name `xml:"Event"` + PresentationTime uint64 `xml:"presentationTime,attr,omitempty"` // default is 0 + Duration uint64 `xml:"duration,attr,omitempty"` + Id uint32 `xml:"id,attr"` + ContentEncoding ContentEncodingType `xml:"contentEncoding,attr,omitempty"` + MessageData string `xml:"messageData,attr,omitempty"` + Value string `xml:",chardata"` + SelectionInfo *SelectionInfoType `xml:"SelectionInfo"` + AlternativeMPD *AlternativeMPDEventType `xml:"AlternativeMPD"` } // SelectionInfoType is SelectionInfo @@ -170,6 +178,13 @@ type SelectionType struct { Data string `xml:"data,attr,omitempty"` } +// AlternativeMPDEventType is Alternative MPD event +type AlternativeMPDEventType struct { + Uri string `xml:"uri,attr"` + Mode string `xml:"mode,attr"` + EarliestResolutionTimeOffset float64 `xml:"earliestResolutionTimeOffset,attr"` +} + // InitializationSetType is Initialization Set. type InitializationSetType struct { XMLName xml.Name `xml:"InitializationSet"` @@ -193,13 +208,15 @@ type InitializationSetType struct { // ServiceDescriptionType is Service Description. type ServiceDescriptionType struct { - XMLName xml.Name `xml:"ServiceDescription"` - Id uint32 `xml:"id,attr"` - Scopes []*DescriptorType `xml:"Scope"` - Latencies []*LatencyType `xml:"Latency"` - PlaybackRates []*PlaybackRateType `xml:"PlaybackRate"` - OperatingQualities []*OperatingQualityType `xml:"OperatingQuality"` - OperatingBandwidths []*OperatingBandwidthType `xml:"OperatingBandwidth"` + XMLName xml.Name `xml:"ServiceDescription"` + Id uint32 `xml:"id,attr"` + Scopes []*DescriptorType `xml:"Scope"` + Latencies []*LatencyType `xml:"Latency"` + PlaybackRates []*PlaybackRateType `xml:"PlaybackRate"` + OperatingQualities []*OperatingQualityType `xml:"OperatingQuality"` + OperatingBandwidths []*OperatingBandwidthType `xml:"OperatingBandwidth"` + ContentSteering []*ContentSteeringType `xml:"ContentSteering"` + ClientDataReporting []*ClientDataReportingType `xml:"ClientDataReporting"` } // LatencyType is Service Description Latency (Annex K.4.2.2). @@ -423,37 +440,38 @@ func (s *SubRepresentationType) Parent() *RepresentationType { // RepresentationBaseType is Representation base (common attributes and elements). type RepresentationBaseType struct { - Profiles ListOfProfilesType `xml:"profiles,attr,omitempty"` - Width uint32 `xml:"width,attr,omitempty"` - Height uint32 `xml:"height,attr,omitempty"` - Sar RatioType `xml:"sar,attr,omitempty"` - FrameRate FrameRateType `xml:"frameRate,attr,omitempty"` - AudioSamplingRate *UIntVectorType `xml:"audioSamplingRate,attr,omitempty"` - MimeType string `xml:"mimeType,attr,omitempty"` - SegmentProfiles *ListOf4CCType `xml:"segmentProfiles,attr,omitempty"` - Codecs string `xml:"codecs,attr,omitempty"` - ContainerProfiles *ListOf4CCType `xml:"containerProfiles,attr,omitempty"` - MaximumSAPPeriod float64 `xml:"maximumSAPPeriod,attr,omitempty"` - StartWithSAP uint32 `xml:"startWithSAP,attr,omitempty"` - MaxPlayoutRate float64 `xml:"maxPlayoutRate,attr,omitempty"` - CodingDependency *bool `xml:"codingDependency,attr,omitempty"` - ScanType VideoScanType `xml:"scanType,attr,omitempty"` - SelectionPriority *uint32 `xml:"selectionPriority,attr"` // default = 1 - Tag string `xml:"tag,attr,omitempty"` - FramePackings []*DescriptorType `xml:"FramePacking"` - AudioChannelConfigurations []*DescriptorType `xml:"AudioChannelConfiguration"` - ContentProtections []*ContentProtectionType `xml:"ContentProtection"` - OutputProtection *DescriptorType `xml:"OutputProtection"` - EssentialProperties []*DescriptorType `xml:"EssentialProperty"` - SupplementalProperties []*DescriptorType `xml:"SupplementalProperty"` - InbandEventStreams []*EventStreamType `xml:"InbandEventStream"` - Switchings []*SwitchingType `xml:"Switching"` - RandomAccesses []*RandomAccessType `xml:"RandomAccess"` - GroupLabels []*LabelType `xml:"GroupLabel"` - Labels []*LabelType `xml:"Label"` - ProducerReferenceTimes []*ProducerReferenceTimeType `xml:"ProducerReferenceTime"` - ContentPopularityRates []*ContentPopularityRateType `xml:"ContentPopularityRate"` - Resyncs []*ResyncType `xml:"Resync"` + Profiles ListOfProfilesType `xml:"profiles,attr,omitempty"` + Width uint32 `xml:"width,attr,omitempty"` + Height uint32 `xml:"height,attr,omitempty"` + Sar RatioType `xml:"sar,attr,omitempty"` + FrameRate FrameRateType `xml:"frameRate,attr,omitempty"` + AudioSamplingRate *UIntVectorType `xml:"audioSamplingRate,attr,omitempty"` + MimeType string `xml:"mimeType,attr,omitempty"` + SegmentProfiles *ListOf4CCType `xml:"segmentProfiles,attr,omitempty"` + Codecs string `xml:"codecs,attr,omitempty"` + ContainerProfiles *ListOf4CCType `xml:"containerProfiles,attr,omitempty"` + MaximumSAPPeriod float64 `xml:"maximumSAPPeriod,attr,omitempty"` + StartWithSAP uint32 `xml:"startWithSAP,attr,omitempty"` + MaxPlayoutRate float64 `xml:"maxPlayoutRate,attr,omitempty"` + CodingDependency *bool `xml:"codingDependency,attr,omitempty"` + ScanType VideoScanType `xml:"scanType,attr,omitempty"` + SelectionPriority *uint32 `xml:"selectionPriority,attr"` // default = 1 + Tag string `xml:"tag,attr,omitempty"` + FramePackings []*DescriptorType `xml:"FramePacking"` + AudioChannelConfigurations []*DescriptorType `xml:"AudioChannelConfiguration"` + ContentProtections []*ContentProtectionType `xml:"ContentProtection"` + OutputProtection *DescriptorType `xml:"OutputProtection"` + EssentialProperties []*DescriptorType `xml:"EssentialProperty"` + SupplementalProperties []*DescriptorType `xml:"SupplementalProperty"` + InbandEventStreams []*EventStreamType `xml:"InbandEventStream"` + Switchings []*SwitchingType `xml:"Switching"` + RandomAccesses []*RandomAccessType `xml:"RandomAccess"` + GroupLabels []*LabelType `xml:"GroupLabel"` + Labels []*LabelType `xml:"Label"` + ProducerReferenceTimes []*ProducerReferenceTimeType `xml:"ProducerReferenceTime"` + ContentPopularityRates []*ContentPopularityRateType `xml:"ContentPopularityRate"` + Resyncs []*ResyncType `xml:"Resync"` + SegmentSequenceProperties *SegmentSequencePropertiesType `xml:"SegmentSequenceProperties"` } func (r *RepresentationType) GetSegmentTemplate() *SegmentTemplateType { @@ -756,6 +774,20 @@ type SegmentTemplateType struct { MultipleSegmentBaseType } +// RunLengthType is Run-length coded sequence of segments or segment +// sequences +type RunLengthType struct { + D uint64 `xml:"d,attr"` + R uint64 `xml:"r,attr,omitempty"` + K uint64 `xml:"k,attr,omitempty"` +} + +// PatternType is Duration pattern +type PatternType struct { + Id uint64 `xml:"id,attr"` + P []*RunLengthType `xml:"P"` +} + // S is the S element of SegmentTimeline. All time units in media timescale. // Defined in ISO/IEC 23009-1 Section 5.3.9.6 type S struct { @@ -768,12 +800,15 @@ type S struct { // R is repeat count (how many times to repeat. -1 is unlimited) R int `xml:"r,attr,omitempty"` // default = 0 // K is the number of Segments that are included in a Segment Sequence. - K *uint64 `xml:"k,attr"` // default = 1 + K *uint64 `xml:"k,attr"` // default = 1 + P uint64 `xml:"p,attr,omitempty"` // Ed6 + PE uint64 `xml:"pE,attr,omitempty"` // Ed6 } // SegmentTimelineType is Segment Timeline. type SegmentTimelineType struct { - S []*S `xml:"S"` + Pattern []*PatternType `xml:"Pattern"` // Ed6 + S []*S `xml:"S"` } // BaseURLType is Base URL. @@ -843,6 +878,50 @@ type LeapSecondInformationType struct { NextLeapChangeTime DateTime `xml:"nextLeapChangeTime,attr,omitempty"` } +// StringNoWhitespaceVectorType is Whitespace-separated list of no-whitespace strings +type StringNoWhitespaceVectorType []string + +// ContentSteeringType ... +type ContentSteeringType struct { + DefaultServiceLocation string `xml:"defaultServiceLocation,attr,omitempty"` + QueryBeforeStart bool `xml:"queryBeforeStart,attr,omitempty"` + ClientRequirement bool `xml:"clientRequirement,attr,omitempty"` + Value string `xml:",chardata"` +} + +// ClientDataReportingType is Client Data Reporting +type ClientDataReportingType struct { + ServiceLocations *StringVectorType `xml:"serviceLocations,attr,omitempty"` + AdaptationSets *UIntVectorType `xml:"adaptationSets,attr,omitempty"` + ReportingSystem []*DescriptorType `xml:"ReportingSystem"` +} + +// CMCDParameterType is CMCD Parameters +type CMCDParameterType struct { + Version uint32 `xml:"version,attr,omitempty"` + Mode string `xml:"mode,attr,omitempty"` + IncludeInRequests *StringNoWhitespaceVectorType `xml:"includeInRequests,attr,omitempty"` + Keys *StringNoWhitespaceVectorType `xml:"keys,attr,omitempty"` + ContentID string `xml:"contentID,attr,omitempty"` + SessionID string `xml:"sessionID,attr,omitempty"` +} + +// SupVideoInfoType is Picture-in-picture information +type SupVideoInfoType struct { + ProcessingInfo string `xml:"processingInfo,attr,omitempty"` +} + +// SegmentSequencePropertiesType is Segment Sequence properties +type SegmentSequencePropertiesType struct { + SAP []*SapWithCadenceType `xml:"SAP"` +} + +// SapWithCadenceType is Segment Sequence SAP properties +type SapWithCadenceType struct { + Type uint32 `xml:"type,attr"` + Cadence uint32 `xml:"cadence,attr,omitempty"` +} + // ListOfProfilesType is comma-separated list of profiles. type ListOfProfilesType string diff --git a/mpd/testdata/go-dash-fixtures/truncate.mpd b/mpd/testdata/go-dash-fixtures/truncate.mpd index 9381387..3655b0d 100644 --- a/mpd/testdata/go-dash-fixtures/truncate.mpd +++ b/mpd/testdata/go-dash-fixtures/truncate.mpd @@ -40,7 +40,7 @@ - + diff --git a/mpd/testdata/go-dash-fixtures/truncate_short.mpd b/mpd/testdata/go-dash-fixtures/truncate_short.mpd index 2c4c74a..cab73de 100644 --- a/mpd/testdata/go-dash-fixtures/truncate_short.mpd +++ b/mpd/testdata/go-dash-fixtures/truncate_short.mpd @@ -1,6 +1,6 @@ - + diff --git a/mpd/testdata/schema-mpds/example_G28.1.mpd b/mpd/testdata/schema-mpds/example_G28.1.mpd new file mode 100644 index 0000000..fdfd6cf --- /dev/null +++ b/mpd/testdata/schema-mpds/example_G28.1.mpd @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mpd/testdata/schema-mpds/example_G28.2.mpd b/mpd/testdata/schema-mpds/example_G28.2.mpd new file mode 100644 index 0000000..e30bfe4 --- /dev/null +++ b/mpd/testdata/schema-mpds/example_G28.2.mpd @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mpd/testdata/schema-mpds/example_G29.mpd b/mpd/testdata/schema-mpds/example_G29.mpd new file mode 100644 index 0000000..158b3ec --- /dev/null +++ b/mpd/testdata/schema-mpds/example_G29.mpd @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + +

+

+ + + + + + + + + + + + +