diff --git a/pkg/io/audio/detect.go b/pkg/io/audio/detect.go new file mode 100644 index 0000000..454e5d6 --- /dev/null +++ b/pkg/io/audio/detect.go @@ -0,0 +1,46 @@ +package audio + +import ( + "time" + + "github.com/pion/mediadevices/pkg/prop" + "github.com/pion/mediadevices/pkg/wave" +) + +// DetectChanges will detect chunk and audio property changes. For audio property detection, +// since it's time related, interval will be used to determine the sample rate. +func DetectChanges(interval time.Duration, onChange func(prop.Media)) TransformFunc { + return func(r Reader) Reader { + var currentProp prop.Media + var chunkCount uint + return ReaderFunc(func() (wave.Audio, error) { + var dirty bool + + chunk, err := r.Read() + if err != nil { + return nil, err + } + + info := chunk.ChunkInfo() + if currentProp.ChannelCount != info.Channels { + currentProp.ChannelCount = info.Channels + dirty = true + } + + if currentProp.SampleRate != info.SamplingRate { + currentProp.SampleRate = info.SamplingRate + dirty = true + } + + // TODO: Also detect sample format changes? + // TODO: Add audio detect changes. As of now, there's no useful property to track. + + if dirty { + onChange(currentProp) + } + + chunkCount++ + return chunk, nil + }) + } +} diff --git a/pkg/io/audio/detect_test.go b/pkg/io/audio/detect_test.go new file mode 100644 index 0000000..450e402 --- /dev/null +++ b/pkg/io/audio/detect_test.go @@ -0,0 +1,77 @@ +package audio + +import ( + "reflect" + "testing" + "time" + + "github.com/pion/mediadevices/pkg/prop" + "github.com/pion/mediadevices/pkg/wave" +) + +func TestDetectChanges(t *testing.T) { + buildSource := func(p prop.Media) (Reader, func(prop.Media)) { + return ReaderFunc(func() (wave.Audio, error) { + return wave.NewFloat32Interleaved(wave.ChunkInfo{ + Len: 0, + Channels: p.ChannelCount, + SamplingRate: p.SampleRate, + }), nil + }), func(newProp prop.Media) { + p = newProp + } + } + + t.Run("OnChangeCalledBeforeFirstFrame", func(t *testing.T) { + var detectBeforeFirstChunk bool + var expected prop.Media + var actual prop.Media + expected.ChannelCount = 2 + expected.SampleRate = 48000 + src, _ := buildSource(expected) + src = DetectChanges(time.Second, func(p prop.Media) { + actual = p + detectBeforeFirstChunk = true + })(src) + + _, err := src.Read() + if err != nil { + t.Fatal(err) + } + + if !detectBeforeFirstChunk { + t.Fatal("on change callback should have called before first chunk") + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Received an unexpected prop\nExpected:\n%v\nActual:\n%v\n", expected, actual) + } + }) + + t.Run("DetectChangesOnEveryUpdate", func(t *testing.T) { + var expected prop.Media + var actual prop.Media + expected.ChannelCount = 2 + expected.SampleRate = 48000 + src, update := buildSource(expected) + src = DetectChanges(time.Second, func(p prop.Media) { + actual = p + })(src) + + for channelCount := 1; channelCount < 8; channelCount++ { + for sampleRate := 12000; sampleRate <= 48000; sampleRate += 4000 { + expected.ChannelCount = channelCount + expected.SampleRate = sampleRate + update(expected) + _, err := src.Read() + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Received an unexpected prop\nExpected:\n%v\nActual:\n%v\n", expected, actual) + } + } + } + }) +}