Skip to content

Commit

Permalink
Merge pull request #223 from Dash-Industry-Forum/test-endnumber
Browse files Browse the repository at this point in the history
test: add test that endNumber shortens representation used
  • Loading branch information
tobbee authored Nov 1, 2024
2 parents 21bce56 + 64a405f commit 405b7fb
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Better logging when loading asset representation data
- Check that pre-encrypted content has the same duration for all representations
- Test that endNumber in SegmentTemplate will limit segments used

### Fixed

Expand Down
11 changes: 6 additions & 5 deletions cmd/livesim2/app/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,13 @@ func (am *assetMgr) loadRep(logger *slog.Logger, assetPath string, as *m.Adaptat
MpdTimescale: 1,
}
if !am.writeRepData {
ok, err := rp.readFromJSON(logger, am.vodFS, am.repDataDir, assetPath)
ok, err := rp.loadFromJSON(logger, am.vodFS, am.repDataDir, assetPath)
if ok {
logger.Debug("Loaded representation data from JSON", "rep", rp.ID, "asset", assetPath)
return &rp, err
}
}
logger.Debug("Loading full representation", "rep", rp.ID, "asset", assetPath)
logger.Debug("Loading full representation by reading all segments")
st := as.SegmentTemplate
if rep.SegmentTemplate != nil {
st = rep.SegmentTemplate
Expand Down Expand Up @@ -298,8 +299,8 @@ segLoop:
return &rp, err
}

// readFromJSON reads the representation data from a gzipped or plain JSON file.
func (rp *RepData) readFromJSON(logger *slog.Logger, vodFS fs.FS, repDataDir, assetPath string) (bool, error) {
// loadFromJSON reads the representation data from a gzipped or plain JSON file.
func (rp *RepData) loadFromJSON(logger *slog.Logger, vodFS fs.FS, repDataDir, assetPath string) (bool, error) {
logger = logger.With("rep", rp.ID, "assetPath", assetPath)
if repDataDir == "" {
return false, nil
Expand Down Expand Up @@ -398,7 +399,7 @@ func (rp *RepData) writeToJSON(logger *slog.Logger, repDataDir, assetPath string
if err != nil {
return err
}
logger.Debug("Wrote repData", "path", gzipPath)
logger.Info("Wrote repData", "path", gzipPath)
return nil
}

Expand Down
201 changes: 170 additions & 31 deletions cmd/livesim2/app/asset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,130 @@ package app
import (
"log/slog"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"testing"

"github.com/stretchr/testify/assert"
m "github.com/Eyevinn/dash-mpd/mpd"
"github.com/stretchr/testify/require"
)

type wantedAssetData struct {
nrReps int
loopDurationMS int
}

type wantedRepData struct {
nrSegs int
initURI string
mpdTimescale int // SegmentTemplate timescale
mediaTimescale int
duration int
}

func TestLoadAsset(t *testing.T) {
vodFS := os.DirFS("testdata")
tmpDir := t.TempDir()
am := newAssetMgr(vodFS, tmpDir, true)
logger := slog.Default()
err := am.discoverAssets(logger)
require.NoError(t, err)
// This was first time
asset, ok := am.findAsset("assets/testpic_2s")
require.True(t, ok)
require.Equal(t, 5, len(asset.Reps))
rep := asset.Reps["V300"]
assert.Equal(t, "V300/init.mp4", rep.InitURI)
assert.Equal(t, 4, len(rep.Segments))
assert.Equal(t, 90000, rep.MediaTimescale)
assert.Equal(t, 1, rep.MpdTimescale)
assert.Equal(t, 720_000, rep.duration())
assert.Equal(t, 8000, asset.LoopDurMS)
// Second time we load using gzipped repData files
am = newAssetMgr(vodFS, tmpDir, true)
err = am.discoverAssets(logger)
require.NoError(t, err)
asset, ok = am.findAsset("assets/testpic_2s")
require.True(t, ok)
require.Equal(t, 5, len(asset.Reps))
rep = asset.Reps["V300"]
assert.Equal(t, "V300/init.mp4", rep.InitURI)
assert.Equal(t, 4, len(rep.Segments))
assert.Equal(t, 90000, rep.MediaTimescale)
assert.Equal(t, 1, rep.MpdTimescale)
assert.Equal(t, 720_000, rep.duration())
assert.Equal(t, 8000, asset.LoopDurMS)
testCases := []struct {
desc string
assetPath string
segmentEndNr uint32
ad wantedAssetData
rds map[string]wantedRepData
}{
{
desc: "testpic_2s",
assetPath: "assets/testpic_2s",
segmentEndNr: 0, // Will not be used
ad: wantedAssetData{
nrReps: 5,
loopDurationMS: 8000,
},
rds: map[string]wantedRepData{
"V300": {
nrSegs: 4,
initURI: "V300/init.mp4",
mpdTimescale: 1,
mediaTimescale: 90_000,
duration: 720_000,
},
"A48": {
nrSegs: 4,
initURI: "A48/init.mp4",
mpdTimescale: 1,
mediaTimescale: 48_000,
duration: 384_000,
},
},
},
{
desc: "testpic_2s with endNumber == 2",
assetPath: "assets/testpic_2s",
segmentEndNr: 2, // Shorten representations to 2 segments via SegmentTemplate,
ad: wantedAssetData{
nrReps: 5,
loopDurationMS: 4000,
},
rds: map[string]wantedRepData{
"V300": {
nrSegs: 2,
initURI: "V300/init.mp4",
mpdTimescale: 1,
mediaTimescale: 90_000,
duration: 360_000,
},
"A48": {
nrSegs: 2,
initURI: "A48/init.mp4",
mpdTimescale: 1,
mediaTimescale: 48_000,
duration: 192_512,
},
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
vodRoot := "testdata"
tmpDir := t.TempDir()
if tc.segmentEndNr > 0 {
if runtime.GOOS == "windows" {
return // Skip test on Windows since the tree copy does not work properly
}
// Copy the the asset part of testdata to a temporary directory and shorten the representations
src := path.Join(vodRoot, tc.assetPath)
dst := path.Join(tmpDir, tc.assetPath)
err := copyDir(src, dst)
require.NoError(t, err)
vodRoot = tmpDir
err = setSegmentEndNr(path.Join(vodRoot, tc.assetPath), tc.segmentEndNr)
require.NoError(t, err)
}
vodFS := os.DirFS(vodRoot)
for _, writeRepData := range []bool{true, false} {
// Write repData files the first time, and read them the second
am := newAssetMgr(vodFS, tmpDir, writeRepData)
err := am.discoverAssets(logger)
require.NoError(t, err)
asset, ok := am.findAsset(tc.assetPath)
require.True(t, ok)
require.NotNil(t, asset)
require.Equal(t, tc.ad.nrReps, len(asset.Reps))
require.Equal(t, tc.ad.loopDurationMS, asset.LoopDurMS)
for repID, wrd := range tc.rds {
rep, ok := asset.Reps[repID]
require.True(t, ok)
require.NotNil(t, rep)
require.Equal(t, wrd.nrSegs, len(rep.Segments))
require.Equal(t, wrd.initURI, rep.InitURI)
require.Equal(t, wrd.mpdTimescale, rep.MpdTimescale)
require.Equal(t, wrd.mediaTimescale, rep.MediaTimescale)
require.Equal(t, wrd.duration, rep.duration())
}
}
})
}
}

func TestAssetLookupForNameOverlap(t *testing.T) {
Expand All @@ -57,3 +143,56 @@ func TestAssetLookupForNameOverlap(t *testing.T) {
require.True(t, ok)
require.Equal(t, "assets/testpic_2s_1", a.AssetPath)
}

func copyDir(srcDir, dstDir string) error {
if err := os.MkdirAll(dstDir, 0755); err != nil {
return err
}
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
var relPath string = strings.Replace(path, srcDir, "", 1)
if relPath == "" {
return nil
}
if info.IsDir() {
return os.Mkdir(filepath.Join(dstDir, relPath), 0755)
} else {
var data, err = os.ReadFile(filepath.Join(srcDir, relPath))
if err != nil {
return err
}
return os.WriteFile(filepath.Join(dstDir, relPath), data, 0644)
}
})
}

// Set the endNumber attribute in all MPDs SegmentTemplate elements
func setSegmentEndNr(assetDir string, endNumber uint32) error {
files, err := os.ReadDir(assetDir)
if err != nil {
return err
}
for _, file := range files {
if filepath.Ext(file.Name()) == ".mpd" {
mpdPath := filepath.Join(assetDir, file.Name())

mpd, err := m.ReadFromFile(mpdPath)
if err != nil {
return err
}
p := mpd.Periods[0]
for _, a := range p.AdaptationSets {
stl := a.SegmentTemplate
stl.EndNumber = &endNumber
}
mpdRaw, err := mpd.WriteToString("", false)
if err != nil {
return err
}
err = os.WriteFile(mpdPath, []byte(mpdRaw), 0644)
if err != nil {
return err
}
}
}
return nil
}

0 comments on commit 405b7fb

Please sign in to comment.