2021-11-04 06:13:00 +00:00
|
|
|
package media_test
|
|
|
|
|
|
|
|
import (
|
2021-12-07 19:58:11 +00:00
|
|
|
"bytes"
|
2021-12-05 19:05:58 +00:00
|
|
|
"context"
|
2021-12-07 19:58:11 +00:00
|
|
|
"database/sql"
|
2022-01-07 12:31:52 +00:00
|
|
|
"errors"
|
2021-11-04 06:13:00 +00:00
|
|
|
"io"
|
2021-12-05 19:05:58 +00:00
|
|
|
"os"
|
2021-11-04 06:13:00 +00:00
|
|
|
"testing"
|
|
|
|
|
2021-12-05 19:05:58 +00:00
|
|
|
"git.netflux.io/rob/clipper/config"
|
|
|
|
"git.netflux.io/rob/clipper/generated/mocks"
|
|
|
|
"git.netflux.io/rob/clipper/generated/store"
|
2021-11-04 06:13:00 +00:00
|
|
|
"git.netflux.io/rob/clipper/media"
|
2021-12-05 19:05:58 +00:00
|
|
|
"github.com/google/uuid"
|
2021-11-04 06:13:00 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2021-12-05 19:05:58 +00:00
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.uber.org/zap"
|
2021-11-04 06:13:00 +00:00
|
|
|
)
|
|
|
|
|
2022-01-07 12:31:52 +00:00
|
|
|
// segmentReader returns an error if provided after reading errBytes bytes.
|
|
|
|
type segmentReader struct {
|
|
|
|
r io.Reader
|
|
|
|
n, errBytes int64
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *segmentReader) Read(p []byte) (int, error) {
|
|
|
|
n, err := r.r.Read(p)
|
|
|
|
r.n += int64(n)
|
|
|
|
if r.n >= r.errBytes && r.err != nil {
|
|
|
|
return n, r.err
|
|
|
|
}
|
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *segmentReader) Close() error { return nil }
|
|
|
|
|
2021-12-17 16:30:53 +00:00
|
|
|
func TestPeaksForSegment(t *testing.T) {
|
2021-12-05 19:05:58 +00:00
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
fixturePath string
|
2022-01-07 12:31:52 +00:00
|
|
|
fixtureReadErrBytes int64
|
|
|
|
fixtureReadErr error
|
|
|
|
fixtureMaxRead int64
|
2021-12-05 19:05:58 +00:00
|
|
|
startFrame, endFrame int64
|
|
|
|
channels int32
|
|
|
|
numBins int
|
|
|
|
wantPeaks []int16
|
|
|
|
wantErr string
|
|
|
|
}{
|
2022-02-04 15:22:06 +00:00
|
|
|
{
|
|
|
|
name: "NOK, invalid arguments",
|
|
|
|
fixturePath: "testdata/tone-44100-stereo-int16.raw",
|
|
|
|
startFrame: 0,
|
|
|
|
endFrame: 0,
|
|
|
|
channels: 2,
|
|
|
|
numBins: 1,
|
|
|
|
wantErr: "invalid arguments",
|
|
|
|
},
|
2021-12-05 19:05:58 +00:00
|
|
|
{
|
2022-01-07 12:31:52 +00:00
|
|
|
name: "OK, entire fixture, stereo, 1 bin",
|
2021-12-05 19:05:58 +00:00
|
|
|
fixturePath: "testdata/tone-44100-stereo-int16.raw",
|
|
|
|
startFrame: 0,
|
|
|
|
endFrame: 44100,
|
|
|
|
channels: 2,
|
|
|
|
numBins: 1,
|
|
|
|
wantPeaks: []int16{32747, 32747},
|
|
|
|
},
|
|
|
|
{
|
2022-01-07 12:31:52 +00:00
|
|
|
name: "OK, entire fixture, stereo, 4 bins",
|
2021-12-05 19:05:58 +00:00
|
|
|
fixturePath: "testdata/tone-44100-stereo-int16.raw",
|
|
|
|
startFrame: 0,
|
|
|
|
endFrame: 44100,
|
|
|
|
channels: 2,
|
|
|
|
numBins: 4,
|
|
|
|
wantPeaks: []int16{8173, 8177, 16366, 16370, 24557, 24555, 32747, 32747},
|
|
|
|
},
|
|
|
|
{
|
2022-01-07 12:31:52 +00:00
|
|
|
name: "OK, entire fixture, stereo, 16 bins",
|
2021-12-05 19:05:58 +00:00
|
|
|
fixturePath: "testdata/tone-44100-stereo-int16.raw",
|
|
|
|
startFrame: 0,
|
|
|
|
endFrame: 44100,
|
|
|
|
channels: 2,
|
|
|
|
numBins: 16,
|
|
|
|
wantPeaks: []int16{2029, 2029, 4075, 4076, 6124, 6125, 8173, 8177, 10222, 10221, 12267, 12265, 14314, 14313, 16366, 16370, 18413, 18411, 20453, 20454, 22505, 22508, 24557, 24555, 26604, 26605, 28644, 28643, 30698, 30694, 32747, 32747},
|
|
|
|
},
|
|
|
|
{
|
2022-01-07 12:31:52 +00:00
|
|
|
name: "OK, entire fixture, mono, 1 bin",
|
2021-12-05 19:05:58 +00:00
|
|
|
fixturePath: "testdata/tone-44100-mono-int16.raw",
|
|
|
|
startFrame: 0,
|
|
|
|
endFrame: 44100,
|
|
|
|
channels: 1,
|
|
|
|
numBins: 1,
|
|
|
|
wantPeaks: []int16{32748},
|
|
|
|
},
|
|
|
|
{
|
2022-01-07 12:31:52 +00:00
|
|
|
name: "OK, entire fixture, mono, 32 bins",
|
2021-12-05 19:05:58 +00:00
|
|
|
fixturePath: "testdata/tone-44100-mono-int16.raw",
|
|
|
|
startFrame: 0,
|
|
|
|
endFrame: 44100,
|
|
|
|
channels: 1,
|
|
|
|
numBins: 32,
|
2022-01-07 12:31:52 +00:00
|
|
|
wantPeaks: []int16{1018, 2030, 3060, 4075, 5092, 6126, 7129, 8172, 9174, 10217, 11227, 12264, 13272, 14315, 15319, 16364, 17370, 18412, 19417, 20453, 21457, 22504, 23513, 24554, 25564, 26607, 27607, 28642, 29647, 30700, 31699, 32748},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "NOK, entire fixture, mono, 32 bins, read returns io.EOF after 50% complete",
|
|
|
|
fixturePath: "testdata/tone-44100-mono-int16.raw",
|
|
|
|
fixtureMaxRead: 44100,
|
|
|
|
startFrame: 0,
|
|
|
|
endFrame: 44100,
|
|
|
|
channels: 1,
|
|
|
|
numBins: 32,
|
|
|
|
wantPeaks: []int16{1018, 2030, 3060, 4075, 5092, 6126, 7129, 8172, 9174, 10217, 11227, 12264, 13272, 14315, 15319, 16364, 2053, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "NOK, entire fixture, mono, 32 bins, read error after 50% complete",
|
|
|
|
fixturePath: "testdata/tone-44100-mono-int16.raw",
|
|
|
|
fixtureReadErrBytes: 44100,
|
|
|
|
fixtureReadErr: errors.New("foo"),
|
|
|
|
startFrame: 0,
|
|
|
|
endFrame: 44100,
|
|
|
|
channels: 1,
|
|
|
|
numBins: 32,
|
|
|
|
wantErr: "read error: foo",
|
2021-12-05 19:05:58 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
2021-12-07 19:58:11 +00:00
|
|
|
startByte := tc.startFrame * int64(tc.channels) * media.SizeOfInt16
|
|
|
|
endByte := tc.endFrame * int64(tc.channels) * media.SizeOfInt16
|
|
|
|
expectedBytes := endByte - startByte
|
2022-01-07 12:31:52 +00:00
|
|
|
if tc.fixtureMaxRead != 0 {
|
|
|
|
expectedBytes = tc.fixtureMaxRead
|
|
|
|
}
|
2021-12-05 19:05:58 +00:00
|
|
|
|
2022-01-07 12:31:52 +00:00
|
|
|
fixture, err := os.Open(tc.fixturePath)
|
2021-12-05 19:05:58 +00:00
|
|
|
require.NoError(t, err)
|
2022-01-07 12:31:52 +00:00
|
|
|
defer fixture.Close()
|
|
|
|
sr := segmentReader{
|
|
|
|
r: io.LimitReader(fixture, int64(expectedBytes)),
|
|
|
|
err: tc.fixtureReadErr,
|
|
|
|
errBytes: tc.fixtureReadErrBytes,
|
|
|
|
}
|
2021-12-05 19:05:58 +00:00
|
|
|
|
2021-12-07 19:58:11 +00:00
|
|
|
mediaSet := store.MediaSet{
|
|
|
|
ID: uuid.New(),
|
|
|
|
AudioChannels: tc.channels,
|
|
|
|
AudioRawS3Key: sql.NullString{String: "foo", Valid: true},
|
|
|
|
}
|
2021-12-05 19:05:58 +00:00
|
|
|
|
|
|
|
// store is passed the mediaSetID and returns a mediaSet
|
|
|
|
store := &mocks.Store{}
|
2021-12-07 19:58:11 +00:00
|
|
|
store.On("GetMediaSet", mock.Anything, mediaSet.ID).Return(mediaSet, nil)
|
2021-12-05 19:05:58 +00:00
|
|
|
|
2021-12-07 19:58:11 +00:00
|
|
|
// fileStore is passed the expected byte range, and returns an io.Reader
|
|
|
|
fileStore := &mocks.FileStore{}
|
|
|
|
fileStore.
|
|
|
|
On("GetObjectWithRange", mock.Anything, "foo", startByte, endByte).
|
2022-01-07 12:31:52 +00:00
|
|
|
Return(&sr, nil)
|
2021-12-05 19:05:58 +00:00
|
|
|
|
2022-01-05 18:49:21 +00:00
|
|
|
service := media.NewMediaSetService(store, nil, fileStore, nil, media.NewTestWorkerPool(), config.Config{}, zap.NewNop().Sugar())
|
2021-12-17 16:30:53 +00:00
|
|
|
peaks, err := service.GetPeaksForSegment(context.Background(), mediaSet.ID, tc.startFrame, tc.endFrame, tc.numBins)
|
2021-12-05 19:05:58 +00:00
|
|
|
|
|
|
|
if tc.wantErr == "" {
|
2022-02-04 15:22:06 +00:00
|
|
|
defer store.AssertExpectations(t)
|
|
|
|
|
2022-01-07 12:31:52 +00:00
|
|
|
require.NoError(t, err)
|
2021-12-05 19:05:58 +00:00
|
|
|
assert.Equal(t, tc.wantPeaks, peaks)
|
|
|
|
} else {
|
|
|
|
assert.EqualError(t, err, tc.wantErr)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-17 16:30:53 +00:00
|
|
|
func BenchmarkGetPeaksForSegment(b *testing.B) {
|
2021-12-06 17:31:14 +00:00
|
|
|
const (
|
|
|
|
startFrame = 0
|
|
|
|
endFrame = 1323000
|
|
|
|
channels = 2
|
|
|
|
fixturePath = "testdata/tone-44100-stereo-int16-30000ms.raw"
|
|
|
|
numBins = 2000
|
|
|
|
)
|
|
|
|
|
2021-12-07 19:58:11 +00:00
|
|
|
audioFile, err := os.Open(fixturePath)
|
|
|
|
require.NoError(b, err)
|
|
|
|
audioData, err := io.ReadAll(audioFile)
|
|
|
|
require.NoError(b, err)
|
2021-12-06 17:31:14 +00:00
|
|
|
|
2021-12-07 19:58:11 +00:00
|
|
|
mediaSetID := uuid.New()
|
|
|
|
mediaSet := store.MediaSet{ID: mediaSetID, AudioChannels: channels}
|
2021-12-06 17:31:14 +00:00
|
|
|
|
2021-12-07 19:58:11 +00:00
|
|
|
store := &mocks.Store{}
|
|
|
|
store.On("GetMediaSet", mock.Anything, mediaSetID).Return(mediaSet, nil)
|
2021-12-06 17:31:14 +00:00
|
|
|
|
2021-12-07 19:58:11 +00:00
|
|
|
for n := 0; n < b.N; n++ {
|
|
|
|
// recreate the reader on each iteration
|
|
|
|
b.StopTimer()
|
|
|
|
readCloser := io.NopCloser(bytes.NewReader(audioData))
|
|
|
|
fileStore := &mocks.FileStore{}
|
|
|
|
fileStore.
|
|
|
|
On("GetObjectWithRange", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
|
|
|
Return(readCloser, nil)
|
2021-12-06 17:31:14 +00:00
|
|
|
|
2022-01-05 18:49:21 +00:00
|
|
|
service := media.NewMediaSetService(store, nil, fileStore, nil, media.NewTestWorkerPool(), config.Config{}, zap.NewNop().Sugar())
|
2021-12-06 17:31:14 +00:00
|
|
|
b.StartTimer()
|
|
|
|
|
2021-12-17 16:30:53 +00:00
|
|
|
_, err = service.GetPeaksForSegment(context.Background(), mediaSetID, startFrame, endFrame, numBins)
|
2021-12-06 17:31:14 +00:00
|
|
|
require.NoError(b, err)
|
|
|
|
}
|
|
|
|
}
|