mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-27 04:46:10 +08:00
Add generic wave's Buffer
This commit is contained in:
145
pkg/wave/buffer.go
Normal file
145
pkg/wave/buffer.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package wave
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errUnsupportedFormat = fmt.Errorf("Unsupported format")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Buffer is a buffer that can store any audio format.
|
||||||
|
type Buffer struct {
|
||||||
|
// TODO: Probably standardize the audio formats so that we don't need to have the following different types
|
||||||
|
// and duplicated codes for each type
|
||||||
|
bufferFloat32Interleaved []float32
|
||||||
|
bufferFloat32NonInterleaved [][]float32
|
||||||
|
bufferInt16Interleaved []int16
|
||||||
|
bufferInt16NonInterleaved [][]int16
|
||||||
|
tmp Audio
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuffer creates a new Buffer instance
|
||||||
|
func NewBuffer() *Buffer {
|
||||||
|
return &Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads the current owned Audio
|
||||||
|
func (buff *Buffer) Load() Audio {
|
||||||
|
return buff.tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreCopy makes a copy of src and store its copy. StoreCopy will reuse as much memory as it can
|
||||||
|
// from the previous copies. For example, if StoreCopy is given an audio that has the format from the previous call,
|
||||||
|
// StoreCopy will not allocate extra memory and only copy the content from src to the previous buffer.
|
||||||
|
func (buff *Buffer) StoreCopy(src Audio) {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case *Float32Interleaved:
|
||||||
|
clone, ok := buff.tmp.(*Float32Interleaved)
|
||||||
|
if ok {
|
||||||
|
*clone = *src
|
||||||
|
} else {
|
||||||
|
copied := *src
|
||||||
|
clone = &copied
|
||||||
|
}
|
||||||
|
|
||||||
|
neededSize := len(src.Data)
|
||||||
|
if len(buff.bufferFloat32Interleaved) < neededSize {
|
||||||
|
if cap(buff.bufferFloat32Interleaved) >= neededSize {
|
||||||
|
buff.bufferFloat32Interleaved = buff.bufferFloat32Interleaved[:neededSize]
|
||||||
|
} else {
|
||||||
|
buff.bufferFloat32Interleaved = make([]float32, neededSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(buff.bufferFloat32Interleaved, src.Data)
|
||||||
|
buff.tmp = clone
|
||||||
|
|
||||||
|
case *Float32NonInterleaved:
|
||||||
|
clone, ok := buff.tmp.(*Float32NonInterleaved)
|
||||||
|
if ok {
|
||||||
|
*clone = *src
|
||||||
|
} else {
|
||||||
|
copied := *src
|
||||||
|
clone = &copied
|
||||||
|
}
|
||||||
|
|
||||||
|
neededSize := len(src.Data)
|
||||||
|
if len(buff.bufferFloat32NonInterleaved) < neededSize {
|
||||||
|
if cap(buff.bufferFloat32NonInterleaved) >= neededSize {
|
||||||
|
buff.bufferFloat32NonInterleaved = buff.bufferFloat32NonInterleaved[:neededSize]
|
||||||
|
} else {
|
||||||
|
buff.bufferFloat32NonInterleaved = make([][]float32, neededSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range src.Data {
|
||||||
|
neededSize := len(src.Data[i])
|
||||||
|
if len(buff.bufferFloat32NonInterleaved[i]) < neededSize {
|
||||||
|
if cap(buff.bufferFloat32NonInterleaved[i]) >= neededSize {
|
||||||
|
buff.bufferFloat32NonInterleaved[i] = buff.bufferFloat32NonInterleaved[i][:neededSize]
|
||||||
|
} else {
|
||||||
|
buff.bufferFloat32NonInterleaved[i] = make([]float32, neededSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(buff.bufferFloat32NonInterleaved[i], src.Data[i])
|
||||||
|
}
|
||||||
|
buff.tmp = clone
|
||||||
|
|
||||||
|
case *Int16Interleaved:
|
||||||
|
clone, ok := buff.tmp.(*Int16Interleaved)
|
||||||
|
if ok {
|
||||||
|
*clone = *src
|
||||||
|
} else {
|
||||||
|
copied := *src
|
||||||
|
clone = &copied
|
||||||
|
}
|
||||||
|
|
||||||
|
neededSize := len(src.Data)
|
||||||
|
if len(buff.bufferInt16Interleaved) < neededSize {
|
||||||
|
if cap(buff.bufferInt16Interleaved) >= neededSize {
|
||||||
|
buff.bufferInt16Interleaved = buff.bufferInt16Interleaved[:neededSize]
|
||||||
|
} else {
|
||||||
|
buff.bufferInt16Interleaved = make([]int16, neededSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(buff.bufferInt16Interleaved, src.Data)
|
||||||
|
buff.tmp = clone
|
||||||
|
|
||||||
|
case *Int16NonInterleaved:
|
||||||
|
clone, ok := buff.tmp.(*Int16NonInterleaved)
|
||||||
|
if ok {
|
||||||
|
*clone = *src
|
||||||
|
} else {
|
||||||
|
copied := *src
|
||||||
|
clone = &copied
|
||||||
|
}
|
||||||
|
|
||||||
|
neededSize := len(src.Data)
|
||||||
|
if len(buff.bufferInt16NonInterleaved) < neededSize {
|
||||||
|
if cap(buff.bufferInt16NonInterleaved) >= neededSize {
|
||||||
|
buff.bufferInt16NonInterleaved = buff.bufferInt16NonInterleaved[:neededSize]
|
||||||
|
} else {
|
||||||
|
buff.bufferInt16NonInterleaved = make([][]int16, neededSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range src.Data {
|
||||||
|
neededSize := len(src.Data[i])
|
||||||
|
if len(buff.bufferInt16NonInterleaved[i]) < neededSize {
|
||||||
|
if cap(buff.bufferInt16NonInterleaved[i]) >= neededSize {
|
||||||
|
buff.bufferInt16NonInterleaved[i] = buff.bufferInt16NonInterleaved[i][:neededSize]
|
||||||
|
} else {
|
||||||
|
buff.bufferInt16NonInterleaved[i] = make([]int16, neededSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(buff.bufferInt16NonInterleaved[i], src.Data[i])
|
||||||
|
}
|
||||||
|
buff.tmp = clone
|
||||||
|
|
||||||
|
default:
|
||||||
|
// TODO: Should have a routine to convert any format to one of the supported formats above
|
||||||
|
panic(errUnsupportedFormat)
|
||||||
|
}
|
||||||
|
}
|
71
pkg/wave/buffer_test.go
Normal file
71
pkg/wave/buffer_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package wave
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBufferStoreCopyAndLoad(t *testing.T) {
|
||||||
|
chunkInfo := ChunkInfo{
|
||||||
|
Len: 4,
|
||||||
|
Channels: 2,
|
||||||
|
SamplingRate: 48000,
|
||||||
|
}
|
||||||
|
testCases := map[string]struct {
|
||||||
|
New func() EditableAudio
|
||||||
|
Update func(EditableAudio)
|
||||||
|
}{
|
||||||
|
"Float32Interleaved": {
|
||||||
|
New: func() EditableAudio {
|
||||||
|
return NewFloat32Interleaved(chunkInfo)
|
||||||
|
},
|
||||||
|
Update: func(src EditableAudio) {
|
||||||
|
src.Set(0, 0, Float32Sample(1))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Float32NonInterleaved": {
|
||||||
|
New: func() EditableAudio {
|
||||||
|
return NewFloat32NonInterleaved(chunkInfo)
|
||||||
|
},
|
||||||
|
Update: func(src EditableAudio) {
|
||||||
|
src.Set(0, 0, Float32Sample(1))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Int16Interleaved": {
|
||||||
|
New: func() EditableAudio {
|
||||||
|
return NewInt16Interleaved(chunkInfo)
|
||||||
|
},
|
||||||
|
Update: func(src EditableAudio) {
|
||||||
|
src.Set(1, 1, Int16Sample(2))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Int16NonInterleaved": {
|
||||||
|
New: func() EditableAudio {
|
||||||
|
return NewInt16NonInterleaved(chunkInfo)
|
||||||
|
},
|
||||||
|
Update: func(src EditableAudio) {
|
||||||
|
src.Set(1, 1, Int16Sample(2))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := NewBuffer()
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
// Since the test also wants to make sure that Copier can convert from 1 type to another,
|
||||||
|
// t.Run is not ideal since it'll run the tests separately
|
||||||
|
t.Log("Testing", name)
|
||||||
|
|
||||||
|
src := testCase.New()
|
||||||
|
buffer.StoreCopy(src)
|
||||||
|
if !reflect.DeepEqual(buffer.Load(), src) {
|
||||||
|
t.Fatal("Expected the copied audio chunk to be identical with the source")
|
||||||
|
}
|
||||||
|
|
||||||
|
testCase.Update(src)
|
||||||
|
buffer.StoreCopy(src)
|
||||||
|
if !reflect.DeepEqual(buffer.Load(), src) {
|
||||||
|
t.Fatal("Expected the copied audio chunk to be identical with the source after an update in source")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user