mirror of
https://github.com/pion/mediadevices.git
synced 2025-10-04 08:16:33 +08:00
Compare commits
3 Commits
copy-audio
...
add-broadc
Author | SHA1 | Date | |
---|---|---|---|
![]() |
186ee09102 | ||
![]() |
20fadef555 | ||
![]() |
0734092a11 |
4
go.mod
4
go.mod
@@ -5,8 +5,8 @@ go 1.13
|
||||
require (
|
||||
github.com/blackjack/webcam v0.0.0-20200313125108-10ed912a8539
|
||||
github.com/jfreymuth/pulse v0.0.0-20200817093420-a82ccdb5e8aa
|
||||
github.com/lherman-cs/opus v0.0.0-20200925064139-8edf1852fd1f
|
||||
github.com/pion/webrtc/v2 v2.2.26
|
||||
github.com/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4
|
||||
github.com/pion/webrtc/v2 v2.2.24
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a
|
||||
|
16
go.sum
16
go.sum
@@ -22,8 +22,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lherman-cs/opus v0.0.0-20200925064139-8edf1852fd1f h1:xZKyjUoki95rRDQl3mDf20j2WZ+jZaFVzPZO72Jdi4A=
|
||||
github.com/lherman-cs/opus v0.0.0-20200925064139-8edf1852fd1f/go.mod h1:v9KQvlDYMuvlwniumBVMlrB0VHQvyTgxNvaXjPmTmps=
|
||||
github.com/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4 h1:2ydMA2KbxRkYmIw3R8Me8dn90bejxBR4MKYXJ5THK3I=
|
||||
github.com/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4/go.mod h1:v9KQvlDYMuvlwniumBVMlrB0VHQvyTgxNvaXjPmTmps=
|
||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9 h1:tbuodUh2vuhOVZAdW3NEUvosFHUMJwUNl7jk/VSEiwc=
|
||||
github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
|
||||
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
|
||||
@@ -33,8 +33,8 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
|
||||
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
|
||||
github.com/pion/datachannel v1.4.20 h1:+uYUrxbhGuEt+9En81Necda5ul8M2h7mMsvGWkYZ/yI=
|
||||
github.com/pion/datachannel v1.4.20/go.mod h1:hsjWYdTW5fMmtM4hVIxUNYqViRPv2A6ixzkQFd82wSc=
|
||||
github.com/pion/dtls/v2 v2.0.1 h1:ddE7+V0faYRbyh4uPsRZ2vLdRrjVZn+wmCfI7jlBfaA=
|
||||
github.com/pion/dtls/v2 v2.0.1/go.mod h1:uMQkz2W0cSqY00xav7WByQ4Hb+18xeQh2oH2fRezr5U=
|
||||
github.com/pion/dtls/v2 v2.0.2 h1:FHCHTiM182Y8e15aFTiORroiATUI16ryHiQh8AIOJ1E=
|
||||
@@ -54,8 +54,8 @@ github.com/pion/rtcp v1.2.3 h1:2wrhKnqgSz91Q5nzYTO07mQXztYPtxL8a0XOss4rJqA=
|
||||
github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I=
|
||||
github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk=
|
||||
github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI=
|
||||
github.com/pion/sctp v1.7.10 h1:o3p3/hZB5Cx12RMGyWmItevJtZ6o2cpuxaw6GOS4x+8=
|
||||
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sctp v1.7.9 h1:n+A37cTMU08xL3Oodkz39XjtPReQliKyk01q96mGB5M=
|
||||
github.com/pion/sctp v1.7.9/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
|
||||
github.com/pion/sdp/v2 v2.4.0 h1:luUtaETR5x2KNNpvEMv/r4Y+/kzImzbz4Lm1z8eQNQI=
|
||||
github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E=
|
||||
github.com/pion/srtp v1.5.1 h1:9Q3jAfslYZBt+C69SI/ZcONJh9049JUHZWYRRf5KEKw=
|
||||
@@ -73,8 +73,8 @@ github.com/pion/turn/v2 v2.0.4 h1:oDguhEv2L/4rxwbL9clGLgtzQPjtuZwCdoM7Te8vQVk=
|
||||
github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog=
|
||||
github.com/pion/udp v0.1.0 h1:uGxQsNyrqG3GLINv36Ff60covYmfrLoxzwnCsIYspXI=
|
||||
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
|
||||
github.com/pion/webrtc/v2 v2.2.26 h1:01hWE26pL3LgqfxvQ1fr6O4ZtyRFFJmQEZK39pHWfFc=
|
||||
github.com/pion/webrtc/v2 v2.2.26/go.mod h1:XMZbZRNHyPDe1gzTIHFcQu02283YO45CbiwFgKvXnmc=
|
||||
github.com/pion/webrtc/v2 v2.2.24 h1:l7q/iO96tMTElxuE2XGdNhCzklGcd9aVZ00XufASp0g=
|
||||
github.com/pion/webrtc/v2 v2.2.24/go.mod h1:U/m+nvG1t8gInf8PwiDyJEDcd7qfl+jmGQXqTX2zGvo=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
@@ -80,13 +80,13 @@ func (e *encoder) Read(p []byte) (int, error) {
|
||||
|
||||
switch b := buff.(type) {
|
||||
case *wave.Int16Interleaved:
|
||||
n, err := e.engine.EncodeBytes(b.Data, p, false)
|
||||
n, err := e.engine.Encode(b.Data, p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
case *wave.Float32Interleaved:
|
||||
n, err := e.engine.EncodeBytes(b.Data, p, true)
|
||||
n, err := e.engine.EncodeFloat32(b.Data, p)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
@@ -66,8 +66,8 @@ func NewBuffer(nSamples int) TransformFunc {
|
||||
case *wave.Int16Interleaved:
|
||||
ibCopy := *ib
|
||||
ibCopy.Size.Len = nSamples
|
||||
n := nSamples * ib.Size.Channels * 2
|
||||
ibCopy.Data = make([]uint8, n)
|
||||
n := nSamples * ib.Size.Channels
|
||||
ibCopy.Data = make([]int16, n)
|
||||
copy(ibCopy.Data, ib.Data)
|
||||
ib.Data = ib.Data[n:]
|
||||
ib.Size.Len -= nSamples
|
||||
@@ -76,8 +76,8 @@ func NewBuffer(nSamples int) TransformFunc {
|
||||
case *wave.Float32Interleaved:
|
||||
ibCopy := *ib
|
||||
ibCopy.Size.Len = nSamples
|
||||
n := nSamples * ib.Size.Channels * 4
|
||||
ibCopy.Data = make([]uint8, n)
|
||||
n := nSamples * ib.Size.Channels
|
||||
ibCopy.Data = make([]float32, n)
|
||||
copy(ibCopy.Data, ib.Data)
|
||||
ib.Data = ib.Data[n:]
|
||||
ib.Size.Len -= nSamples
|
||||
|
@@ -9,83 +9,41 @@ import (
|
||||
)
|
||||
|
||||
func TestBuffer(t *testing.T) {
|
||||
input1 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 1, Channels: 2, SamplingRate: 1234})
|
||||
input1.SetInt16(0, 0, 1)
|
||||
input1.SetInt16(0, 1, 2)
|
||||
|
||||
input2 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234})
|
||||
input2.SetInt16(0, 0, 3)
|
||||
input2.SetInt16(0, 1, 4)
|
||||
input2.SetInt16(1, 0, 5)
|
||||
input2.SetInt16(1, 1, 6)
|
||||
input2.SetInt16(2, 0, 7)
|
||||
input2.SetInt16(2, 1, 8)
|
||||
|
||||
input3 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 2, Channels: 2, SamplingRate: 1234})
|
||||
input3.SetInt16(0, 0, 9)
|
||||
input3.SetInt16(0, 1, 10)
|
||||
input3.SetInt16(1, 0, 11)
|
||||
input3.SetInt16(1, 1, 12)
|
||||
|
||||
input4 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 7, Channels: 2, SamplingRate: 1234})
|
||||
input4.SetInt16(0, 0, 13)
|
||||
input4.SetInt16(0, 1, 14)
|
||||
input4.SetInt16(1, 0, 15)
|
||||
input4.SetInt16(1, 1, 16)
|
||||
input4.SetInt16(2, 0, 17)
|
||||
input4.SetInt16(2, 1, 18)
|
||||
input4.SetInt16(3, 0, 19)
|
||||
input4.SetInt16(3, 1, 20)
|
||||
input4.SetInt16(4, 0, 21)
|
||||
input4.SetInt16(4, 1, 22)
|
||||
input4.SetInt16(5, 0, 23)
|
||||
input4.SetInt16(5, 1, 24)
|
||||
input4.SetInt16(6, 0, 25)
|
||||
input4.SetInt16(6, 1, 26)
|
||||
|
||||
expected1 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234})
|
||||
expected1.SetInt16(0, 0, 1)
|
||||
expected1.SetInt16(0, 1, 2)
|
||||
expected1.SetInt16(1, 0, 3)
|
||||
expected1.SetInt16(1, 1, 4)
|
||||
expected1.SetInt16(2, 0, 5)
|
||||
expected1.SetInt16(2, 1, 6)
|
||||
|
||||
expected2 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234})
|
||||
expected2.SetInt16(0, 0, 7)
|
||||
expected2.SetInt16(0, 1, 8)
|
||||
expected2.SetInt16(1, 0, 9)
|
||||
expected2.SetInt16(1, 1, 10)
|
||||
expected2.SetInt16(2, 0, 11)
|
||||
expected2.SetInt16(2, 1, 12)
|
||||
|
||||
expected3 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234})
|
||||
expected3.SetInt16(0, 0, 13)
|
||||
expected3.SetInt16(0, 1, 14)
|
||||
expected3.SetInt16(1, 0, 15)
|
||||
expected3.SetInt16(1, 1, 16)
|
||||
expected3.SetInt16(2, 0, 17)
|
||||
expected3.SetInt16(2, 1, 18)
|
||||
|
||||
expected4 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234})
|
||||
expected4.SetInt16(0, 0, 19)
|
||||
expected4.SetInt16(0, 1, 20)
|
||||
expected4.SetInt16(1, 0, 21)
|
||||
expected4.SetInt16(1, 1, 22)
|
||||
expected4.SetInt16(2, 0, 23)
|
||||
expected4.SetInt16(2, 1, 24)
|
||||
|
||||
input := []wave.Audio{
|
||||
input1,
|
||||
input2,
|
||||
input3,
|
||||
input4,
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 1, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{1, 2},
|
||||
},
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{3, 4, 5, 6, 7, 8},
|
||||
},
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 2, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{9, 10, 11, 12},
|
||||
},
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 7, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26},
|
||||
},
|
||||
}
|
||||
expected := []wave.Audio{
|
||||
expected1,
|
||||
expected2,
|
||||
expected3,
|
||||
expected4,
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{1, 2, 3, 4, 5, 6},
|
||||
},
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{7, 8, 9, 10, 11, 12},
|
||||
},
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{13, 14, 15, 16, 17, 18},
|
||||
},
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{19, 20, 21, 22, 23, 24},
|
||||
},
|
||||
}
|
||||
|
||||
trans := NewBuffer(3)
|
||||
|
@@ -10,33 +10,25 @@ import (
|
||||
)
|
||||
|
||||
func TestMixer(t *testing.T) {
|
||||
input1 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 1, Channels: 2, SamplingRate: 1234})
|
||||
input1.SetInt16(0, 0, 1)
|
||||
input1.SetInt16(0, 1, 3)
|
||||
|
||||
input2 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234})
|
||||
input2.SetInt16(0, 0, 2)
|
||||
input2.SetInt16(0, 1, 4)
|
||||
input2.SetInt16(1, 0, 3)
|
||||
input2.SetInt16(1, 1, 5)
|
||||
input2.SetInt16(2, 0, 4)
|
||||
input2.SetInt16(2, 1, 6)
|
||||
|
||||
expected1 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 1, Channels: 1, SamplingRate: 1234})
|
||||
expected1.SetInt16(0, 0, 2)
|
||||
|
||||
expected2 := wave.NewInt16Interleaved(wave.ChunkInfo{Len: 3, Channels: 1, SamplingRate: 1234})
|
||||
expected2.SetInt16(0, 0, 3)
|
||||
expected2.SetInt16(1, 0, 4)
|
||||
expected2.SetInt16(2, 0, 5)
|
||||
|
||||
input := []wave.Audio{
|
||||
input1,
|
||||
input2,
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 1, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{1, 3},
|
||||
},
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 3, Channels: 2, SamplingRate: 1234},
|
||||
Data: []int16{2, 4, 3, 5, 4, 6},
|
||||
},
|
||||
}
|
||||
expected := []wave.Audio{
|
||||
expected1,
|
||||
expected2,
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 1, Channels: 1, SamplingRate: 1234},
|
||||
Data: []int16{2},
|
||||
},
|
||||
&wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{Len: 3, Channels: 1, SamplingRate: 1234},
|
||||
Data: []int16{3, 4, 5},
|
||||
},
|
||||
}
|
||||
|
||||
trans := NewChannelMixer(1, &mixer.MonoMixer{})
|
||||
|
136
pkg/io/broadcast.go
Normal file
136
pkg/io/broadcast.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package io
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maskReading = 1 << 63
|
||||
broadcasterRingSize = 32
|
||||
// TODO: If the data source has fps greater than 30, they'll see some
|
||||
// fps fluctuation. But, 30 fps should be enough for general cases.
|
||||
broadcasterRingPollDuration = time.Millisecond * 33
|
||||
)
|
||||
|
||||
var errEmptySource = fmt.Errorf("Source can't be nil")
|
||||
|
||||
type broadcasterData struct {
|
||||
data interface{}
|
||||
count uint32
|
||||
err error
|
||||
}
|
||||
|
||||
type broadcasterRing struct {
|
||||
buffer []atomic.Value
|
||||
// reading (1 bit) + reserved (31 bits) + data count (32 bits)
|
||||
state uint64
|
||||
}
|
||||
|
||||
func newBroadcasterRing() *broadcasterRing {
|
||||
return &broadcasterRing{buffer: make([]atomic.Value, broadcasterRingSize)}
|
||||
}
|
||||
|
||||
func (ring *broadcasterRing) index(count uint32) int {
|
||||
return int(count) % len(ring.buffer)
|
||||
}
|
||||
|
||||
func (ring *broadcasterRing) acquire(count uint32) func(*broadcasterData) {
|
||||
// Reader has reached the latest data, should read from the source.
|
||||
// Only allow 1 reader to read from the source. When there are more than 1 readers,
|
||||
// the other readers will need to share the same data that the first reader gets from
|
||||
// the source.
|
||||
state := uint64(count)
|
||||
if atomic.CompareAndSwapUint64(&ring.state, state, state|maskReading) {
|
||||
return func(data *broadcasterData) {
|
||||
i := ring.index(count)
|
||||
ring.buffer[i].Store(data)
|
||||
atomic.StoreUint64(&ring.state, uint64(count+1))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ring *broadcasterRing) get(count uint32) *broadcasterData {
|
||||
for {
|
||||
reading := uint64(count) | maskReading
|
||||
// TODO: since it's lockless, it spends a lot of resources in the scheduling.
|
||||
for atomic.LoadUint64(&ring.state) == reading {
|
||||
// Yield current goroutine to let other goroutines to run instead
|
||||
time.Sleep(broadcasterRingPollDuration)
|
||||
}
|
||||
|
||||
i := ring.index(count)
|
||||
data := ring.buffer[i].Load().(*broadcasterData)
|
||||
if data.count == count {
|
||||
return data
|
||||
}
|
||||
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
func (ring *broadcasterRing) lastCount() uint32 {
|
||||
// ring.state always keeps track the next count, so we need to subtract it by 1 to get the
|
||||
// last count
|
||||
return uint32(atomic.LoadUint64(&ring.state)) - 1
|
||||
}
|
||||
|
||||
// Broadcaster is a generic pull-based broadcaster. Broadcaster is unique in a sense that
|
||||
// readers can come and go at anytime, and readers don't need to close or notify broadcaster.
|
||||
type Broadcaster struct {
|
||||
source atomic.Value
|
||||
buffer *broadcasterRing
|
||||
}
|
||||
|
||||
// NewNewBroadcaster creates a new broadcaster.
|
||||
func NewBroadcaster(source Reader) *Broadcaster {
|
||||
var broadcaster Broadcaster
|
||||
broadcaster.buffer = newBroadcasterRing()
|
||||
broadcaster.ReplaceSource(source)
|
||||
|
||||
return &broadcaster
|
||||
}
|
||||
|
||||
// NewReader creates a new reader. Each reader will retrieve the same data from the source.
|
||||
// copyFn is used to copy the data from the source to individual readers. Broadcaster uses a small ring
|
||||
// buffer, this means that slow readers might miss some data if they're really late and the data is no longer
|
||||
// in the ring buffer.
|
||||
func (broadcaster *Broadcaster) NewReader(copyFn func(interface{}) interface{}) Reader {
|
||||
currentCount := broadcaster.buffer.lastCount()
|
||||
|
||||
return ReaderFunc(func() (data interface{}, err error) {
|
||||
currentCount++
|
||||
if push := broadcaster.buffer.acquire(currentCount); push != nil {
|
||||
data, err = broadcaster.source.Load().(Reader).Read()
|
||||
push(&broadcasterData{
|
||||
data: data,
|
||||
err: err,
|
||||
count: currentCount,
|
||||
})
|
||||
} else {
|
||||
ringData := broadcaster.buffer.get(currentCount)
|
||||
data, err, currentCount = ringData.data, ringData.err, ringData.count
|
||||
}
|
||||
|
||||
data = copyFn(data)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// ReplaceSource replaces the underlying source. This operation is thread safe.
|
||||
func (broadcaster *Broadcaster) ReplaceSource(source Reader) error {
|
||||
if source == nil {
|
||||
return errEmptySource
|
||||
}
|
||||
|
||||
broadcaster.source.Store(source)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceSource retrieves the underlying source. This operation is thread safe.
|
||||
func (broadcaster *Broadcaster) Source() Reader {
|
||||
return broadcaster.source.Load().(Reader)
|
||||
}
|
14
pkg/io/reader.go
Normal file
14
pkg/io/reader.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package io
|
||||
|
||||
// Reader is a generic data reader. In the future, interface{} should be replaced by a generic type
|
||||
// to provide strong type.
|
||||
type Reader interface {
|
||||
Read() (interface{}, error)
|
||||
}
|
||||
|
||||
// ReaderFunc is a proxy type for Reader
|
||||
type ReaderFunc func() (interface{}, error)
|
||||
|
||||
func (f ReaderFunc) Read() (interface{}, error) {
|
||||
return f()
|
||||
}
|
65
pkg/io/video/broadcast.go
Normal file
65
pkg/io/video/broadcast.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/io"
|
||||
)
|
||||
|
||||
var errEmptySource = fmt.Errorf("Source can't be nil")
|
||||
|
||||
// Broadcaster is a specialized video broadcaster.
|
||||
type Broadcaster struct {
|
||||
ioBroadcaster *io.Broadcaster
|
||||
}
|
||||
|
||||
// NewNewBroadcaster creates a new broadcaster.
|
||||
func NewBroadcaster(source Reader) *Broadcaster {
|
||||
broadcaster := io.NewBroadcaster(io.ReaderFunc(func() (interface{}, error) {
|
||||
return source.Read()
|
||||
}))
|
||||
|
||||
return &Broadcaster{broadcaster}
|
||||
}
|
||||
|
||||
// NewReader creates a new reader. Each reader will retrieve the same data from the source.
|
||||
// copyFn is used to copy the data from the source to individual readers. Broadcaster uses a small ring
|
||||
// buffer, this means that slow readers might miss some data if they're really late and the data is no longer
|
||||
// in the ring buffer.
|
||||
func (broadcaster *Broadcaster) NewReader(copyFrame bool) Reader {
|
||||
copyFn := func(src interface{}) interface{} { return src }
|
||||
|
||||
if copyFrame {
|
||||
buffer := NewFrameBuffer(0)
|
||||
copyFn = func(src interface{}) interface{} {
|
||||
realSrc, _ := src.(image.Image)
|
||||
buffer.StoreCopy(realSrc)
|
||||
return buffer.Load()
|
||||
}
|
||||
}
|
||||
|
||||
reader := broadcaster.ioBroadcaster.NewReader(copyFn)
|
||||
return ReaderFunc(func() (image.Image, error) {
|
||||
data, err := reader.Read()
|
||||
img, _ := data.(image.Image)
|
||||
return img, err
|
||||
})
|
||||
}
|
||||
|
||||
// ReplaceSource replaces the underlying source. This operation is thread safe.
|
||||
func (broadcaster *Broadcaster) ReplaceSource(source Reader) error {
|
||||
return broadcaster.ioBroadcaster.ReplaceSource(io.ReaderFunc(func() (interface{}, error) {
|
||||
return source.Read()
|
||||
}))
|
||||
}
|
||||
|
||||
// ReplaceSource retrieves the underlying source. This operation is thread safe.
|
||||
func (broadcaster *Broadcaster) Source() Reader {
|
||||
source := broadcaster.ioBroadcaster.Source()
|
||||
return ReaderFunc(func() (image.Image, error) {
|
||||
data, err := source.Read()
|
||||
img, _ := data.(image.Image)
|
||||
return img, err
|
||||
})
|
||||
}
|
187
pkg/io/video/broadcast_test.go
Normal file
187
pkg/io/video/broadcast_test.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkBroadcast(b *testing.B) {
|
||||
var src Reader
|
||||
img := image.NewRGBA(image.Rect(0, 0, 1920, 1080))
|
||||
interval := time.NewTicker(time.Millisecond * 33) // 30 fps
|
||||
defer interval.Stop()
|
||||
src = ReaderFunc(func() (image.Image, error) {
|
||||
<-interval.C
|
||||
return img, nil
|
||||
})
|
||||
|
||||
for n := 1; n <= 4096; n *= 16 {
|
||||
n := n
|
||||
|
||||
b.Run(fmt.Sprintf("Readers-%d", n), func(b *testing.B) {
|
||||
b.SetParallelism(n)
|
||||
broadcaster := NewBroadcaster(src)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
reader := broadcaster.NewReader(false)
|
||||
for pb.Next() {
|
||||
reader.Read()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBroadcast(t *testing.T) {
|
||||
// https://github.com/pion/mediadevices/issues/198
|
||||
if runtime.GOOS == "darwin" {
|
||||
t.Skip("Skipping because Darwin CI is not reliable for timing related tests.")
|
||||
}
|
||||
frames := make([]image.Image, 5*30) // 5 seconds worth of frames
|
||||
resolution := image.Rect(0, 0, 1920, 1080)
|
||||
for i := range frames {
|
||||
rgba := image.NewRGBA(resolution)
|
||||
rgba.Pix[0] = uint8(i >> 24)
|
||||
rgba.Pix[1] = uint8(i >> 16)
|
||||
rgba.Pix[2] = uint8(i >> 8)
|
||||
rgba.Pix[3] = uint8(i)
|
||||
frames[i] = rgba
|
||||
}
|
||||
|
||||
routinePauseConds := []struct {
|
||||
src bool
|
||||
dst bool
|
||||
expectedFPS float64
|
||||
expectedDrop float64
|
||||
}{
|
||||
{
|
||||
src: false,
|
||||
dst: false,
|
||||
expectedFPS: 30,
|
||||
},
|
||||
{
|
||||
src: true,
|
||||
dst: false,
|
||||
expectedFPS: 20,
|
||||
expectedDrop: 10,
|
||||
},
|
||||
{
|
||||
src: false,
|
||||
dst: true,
|
||||
expectedFPS: 20,
|
||||
expectedDrop: 10,
|
||||
},
|
||||
}
|
||||
|
||||
for _, pauseCond := range routinePauseConds {
|
||||
pauseCond := pauseCond
|
||||
t.Run(fmt.Sprintf("SrcPause-%v/DstPause-%v", pauseCond.src, pauseCond.dst), func(t *testing.T) {
|
||||
for n := 1; n <= 256; n *= 16 {
|
||||
n := n
|
||||
|
||||
t.Run(fmt.Sprintf("Readers-%d", n), func(t *testing.T) {
|
||||
var src Reader
|
||||
interval := time.NewTicker(time.Millisecond * 33) // 30 fps
|
||||
defer interval.Stop()
|
||||
frameCount := 0
|
||||
frameSent := 0
|
||||
lastSend := time.Now()
|
||||
src = ReaderFunc(func() (image.Image, error) {
|
||||
if pauseCond.src && frameSent == 30 {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
<-interval.C
|
||||
|
||||
now := time.Now()
|
||||
if interval := now.Sub(lastSend); interval > time.Millisecond*33*3/2 {
|
||||
// Source reader should drop frames to catch up the latest frame.
|
||||
drop := int(interval/(time.Millisecond*33)) - 1
|
||||
frameCount += drop
|
||||
t.Logf("Skipped %d frames", drop)
|
||||
}
|
||||
lastSend = now
|
||||
frame := frames[frameCount]
|
||||
frameCount++
|
||||
frameSent++
|
||||
return frame, nil
|
||||
})
|
||||
broadcaster := NewBroadcaster(src)
|
||||
var done uint32
|
||||
duration := time.Second * 3
|
||||
fpsChan := make(chan []float64)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(n)
|
||||
for i := 0; i < n; i++ {
|
||||
go func() {
|
||||
reader := broadcaster.NewReader(false)
|
||||
count := 0
|
||||
lastFrameCount := -1
|
||||
droppedFrames := 0
|
||||
wg.Done()
|
||||
wg.Wait()
|
||||
for atomic.LoadUint32(&done) == 0 {
|
||||
if pauseCond.dst && count == 30 {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
frame, err := reader.Read()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
rgba := frame.(*image.RGBA)
|
||||
var frameCount int
|
||||
frameCount |= int(rgba.Pix[0]) << 24
|
||||
frameCount |= int(rgba.Pix[1]) << 16
|
||||
frameCount |= int(rgba.Pix[2]) << 8
|
||||
frameCount |= int(rgba.Pix[3])
|
||||
|
||||
droppedFrames += (frameCount - lastFrameCount - 1)
|
||||
lastFrameCount = frameCount
|
||||
count++
|
||||
}
|
||||
|
||||
fps := float64(count) / duration.Seconds()
|
||||
if fps < pauseCond.expectedFPS-2 || fps > pauseCond.expectedFPS+2 {
|
||||
t.Fatal("Unexpected average FPS")
|
||||
}
|
||||
|
||||
droppedFramesPerSecond := float64(droppedFrames) / duration.Seconds()
|
||||
if droppedFramesPerSecond < pauseCond.expectedDrop-2 || droppedFramesPerSecond > pauseCond.expectedDrop+2 {
|
||||
t.Fatal("Unexpected drop count")
|
||||
}
|
||||
|
||||
fpsChan <- []float64{fps, droppedFramesPerSecond, float64(lastFrameCount)}
|
||||
}()
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
atomic.StoreUint32(&done, 1)
|
||||
|
||||
var fpsAvg float64
|
||||
var droppedFramesPerSecondAvg float64
|
||||
var lastFrameCountAvg float64
|
||||
var count int
|
||||
for metric := range fpsChan {
|
||||
fps, droppedFramesPerSecond, lastFrameCount := metric[0], metric[1], metric[2]
|
||||
fpsAvg += fps
|
||||
droppedFramesPerSecondAvg += droppedFramesPerSecond
|
||||
lastFrameCountAvg += lastFrameCount
|
||||
count++
|
||||
if count == n {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("Average FPS :", fpsAvg/float64(n))
|
||||
t.Log("Average dropped frames per second:", droppedFramesPerSecondAvg/float64(n))
|
||||
t.Log("Last frame count (src) :", frameCount)
|
||||
t.Log("Average last frame count (dst) :", lastFrameCountAvg/float64(n))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -134,15 +134,18 @@ func TestDecodeInt16Interleaved(t *testing.T) {
|
||||
decoder, _ := newInt16InterleavedDecoder()
|
||||
|
||||
t.Run("BigEndian", func(t *testing.T) {
|
||||
expected := NewInt16Interleaved(ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
})
|
||||
|
||||
expected.SetInt16(0, 0, Int16Sample(binary.BigEndian.Uint16([]byte{0x01, 0x02})))
|
||||
expected.SetInt16(0, 1, Int16Sample(binary.BigEndian.Uint16([]byte{0x03, 0x04})))
|
||||
expected.SetInt16(1, 0, Int16Sample(binary.BigEndian.Uint16([]byte{0x05, 0x06})))
|
||||
expected.SetInt16(1, 1, Int16Sample(binary.BigEndian.Uint16([]byte{0x07, 0x08})))
|
||||
expected := &Int16Interleaved{
|
||||
Data: []int16{
|
||||
int16(binary.BigEndian.Uint16([]byte{0x01, 0x02})),
|
||||
int16(binary.BigEndian.Uint16([]byte{0x03, 0x04})),
|
||||
int16(binary.BigEndian.Uint16([]byte{0x05, 0x06})),
|
||||
int16(binary.BigEndian.Uint16([]byte{0x07, 0x08})),
|
||||
},
|
||||
Size: ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
},
|
||||
}
|
||||
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -154,15 +157,18 @@ func TestDecodeInt16Interleaved(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("LittleEndian", func(t *testing.T) {
|
||||
expected := NewInt16Interleaved(ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
})
|
||||
|
||||
expected.SetInt16(0, 0, Int16Sample(binary.LittleEndian.Uint16([]byte{0x02, 0x01})))
|
||||
expected.SetInt16(0, 1, Int16Sample(binary.LittleEndian.Uint16([]byte{0x04, 0x03})))
|
||||
expected.SetInt16(1, 0, Int16Sample(binary.LittleEndian.Uint16([]byte{0x06, 0x05})))
|
||||
expected.SetInt16(1, 1, Int16Sample(binary.LittleEndian.Uint16([]byte{0x08, 0x07})))
|
||||
expected := &Int16Interleaved{
|
||||
Data: []int16{
|
||||
int16(binary.LittleEndian.Uint16([]byte{0x01, 0x02})),
|
||||
int16(binary.LittleEndian.Uint16([]byte{0x03, 0x04})),
|
||||
int16(binary.LittleEndian.Uint16([]byte{0x05, 0x06})),
|
||||
int16(binary.LittleEndian.Uint16([]byte{0x07, 0x08})),
|
||||
},
|
||||
Size: ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
},
|
||||
}
|
||||
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -184,15 +190,16 @@ func TestDecodeInt16NonInterleaved(t *testing.T) {
|
||||
decoder, _ := newInt16NonInterleavedDecoder()
|
||||
|
||||
t.Run("BigEndian", func(t *testing.T) {
|
||||
expected := NewInt16NonInterleaved(ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
})
|
||||
|
||||
expected.SetInt16(0, 0, Int16Sample(binary.BigEndian.Uint16([]byte{0x01, 0x02})))
|
||||
expected.SetInt16(0, 1, Int16Sample(binary.BigEndian.Uint16([]byte{0x05, 0x06})))
|
||||
expected.SetInt16(1, 0, Int16Sample(binary.BigEndian.Uint16([]byte{0x03, 0x04})))
|
||||
expected.SetInt16(1, 1, Int16Sample(binary.BigEndian.Uint16([]byte{0x07, 0x08})))
|
||||
expected := &Int16NonInterleaved{
|
||||
Data: [][]int16{
|
||||
{int16(binary.BigEndian.Uint16([]byte{0x01, 0x02})), int16(binary.BigEndian.Uint16([]byte{0x03, 0x04}))},
|
||||
{int16(binary.BigEndian.Uint16([]byte{0x05, 0x06})), int16(binary.BigEndian.Uint16([]byte{0x07, 0x08}))},
|
||||
},
|
||||
Size: ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
},
|
||||
}
|
||||
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -204,15 +211,16 @@ func TestDecodeInt16NonInterleaved(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("LittleEndian", func(t *testing.T) {
|
||||
expected := NewInt16NonInterleaved(ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
})
|
||||
|
||||
expected.SetInt16(0, 0, Int16Sample(binary.LittleEndian.Uint16([]byte{0x02, 0x01})))
|
||||
expected.SetInt16(0, 1, Int16Sample(binary.LittleEndian.Uint16([]byte{0x06, 0x05})))
|
||||
expected.SetInt16(1, 0, Int16Sample(binary.LittleEndian.Uint16([]byte{0x04, 0x03})))
|
||||
expected.SetInt16(1, 1, Int16Sample(binary.LittleEndian.Uint16([]byte{0x08, 0x07})))
|
||||
expected := &Int16NonInterleaved{
|
||||
Data: [][]int16{
|
||||
{int16(binary.LittleEndian.Uint16([]byte{0x01, 0x02})), int16(binary.LittleEndian.Uint16([]byte{0x03, 0x04}))},
|
||||
{int16(binary.LittleEndian.Uint16([]byte{0x05, 0x06})), int16(binary.LittleEndian.Uint16([]byte{0x07, 0x08}))},
|
||||
},
|
||||
Size: ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
},
|
||||
}
|
||||
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -234,15 +242,18 @@ func TestDecodeFloat32Interleaved(t *testing.T) {
|
||||
decoder, _ := newFloat32InterleavedDecoder()
|
||||
|
||||
t.Run("BigEndian", func(t *testing.T) {
|
||||
expected := NewFloat32Interleaved(ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
})
|
||||
|
||||
expected.SetFloat32(0, 0, Float32Sample(math.Float32frombits(binary.BigEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04}))))
|
||||
expected.SetFloat32(0, 1, Float32Sample(math.Float32frombits(binary.BigEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08}))))
|
||||
expected.SetFloat32(1, 0, Float32Sample(math.Float32frombits(binary.BigEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c}))))
|
||||
expected.SetFloat32(1, 1, Float32Sample(math.Float32frombits(binary.BigEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10}))))
|
||||
expected := &Float32Interleaved{
|
||||
Data: []float32{
|
||||
math.Float32frombits(binary.BigEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})),
|
||||
math.Float32frombits(binary.BigEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})),
|
||||
math.Float32frombits(binary.BigEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})),
|
||||
math.Float32frombits(binary.BigEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})),
|
||||
},
|
||||
Size: ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
},
|
||||
}
|
||||
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -254,15 +265,18 @@ func TestDecodeFloat32Interleaved(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("LittleEndian", func(t *testing.T) {
|
||||
expected := NewFloat32Interleaved(ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
})
|
||||
|
||||
expected.SetFloat32(0, 0, Float32Sample(math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x04, 0x03, 0x02, 0x01}))))
|
||||
expected.SetFloat32(0, 1, Float32Sample(math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x08, 0x07, 0x06, 0x05}))))
|
||||
expected.SetFloat32(1, 0, Float32Sample(math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x0c, 0x0b, 0x0a, 0x09}))))
|
||||
expected.SetFloat32(1, 1, Float32Sample(math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x10, 0x0f, 0x0e, 0x0d}))))
|
||||
expected := &Float32Interleaved{
|
||||
Data: []float32{
|
||||
math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})),
|
||||
math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})),
|
||||
math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})),
|
||||
math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})),
|
||||
},
|
||||
Size: ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
},
|
||||
}
|
||||
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -284,15 +298,22 @@ func TestDecodeFloat32NonInterleaved(t *testing.T) {
|
||||
decoder, _ := newFloat32NonInterleavedDecoder()
|
||||
|
||||
t.Run("BigEndian", func(t *testing.T) {
|
||||
expected := NewFloat32NonInterleaved(ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
})
|
||||
|
||||
expected.SetFloat32(0, 0, Float32Sample(math.Float32frombits(binary.BigEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04}))))
|
||||
expected.SetFloat32(0, 1, Float32Sample(math.Float32frombits(binary.BigEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c}))))
|
||||
expected.SetFloat32(1, 0, Float32Sample(math.Float32frombits(binary.BigEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08}))))
|
||||
expected.SetFloat32(1, 1, Float32Sample(math.Float32frombits(binary.BigEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10}))))
|
||||
expected := &Float32NonInterleaved{
|
||||
Data: [][]float32{
|
||||
{
|
||||
math.Float32frombits(binary.BigEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})),
|
||||
math.Float32frombits(binary.BigEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})),
|
||||
},
|
||||
{
|
||||
math.Float32frombits(binary.BigEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})),
|
||||
math.Float32frombits(binary.BigEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})),
|
||||
},
|
||||
},
|
||||
Size: ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
},
|
||||
}
|
||||
actual, err := decoder.Decode(binary.BigEndian, raw, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -304,15 +325,22 @@ func TestDecodeFloat32NonInterleaved(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("LittleEndian", func(t *testing.T) {
|
||||
expected := NewFloat32NonInterleaved(ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
})
|
||||
|
||||
expected.SetFloat32(0, 0, Float32Sample(math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x04, 0x03, 0x02, 0x01}))))
|
||||
expected.SetFloat32(0, 1, Float32Sample(math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x0c, 0x0b, 0x0a, 0x09}))))
|
||||
expected.SetFloat32(1, 0, Float32Sample(math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x08, 0x07, 0x06, 0x05}))))
|
||||
expected.SetFloat32(1, 1, Float32Sample(math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x10, 0x0f, 0x0e, 0x0d}))))
|
||||
expected := &Float32NonInterleaved{
|
||||
Data: [][]float32{
|
||||
{
|
||||
math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})),
|
||||
math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})),
|
||||
},
|
||||
{
|
||||
math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})),
|
||||
math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})),
|
||||
},
|
||||
},
|
||||
Size: ChunkInfo{
|
||||
Len: 2,
|
||||
Channels: 2,
|
||||
},
|
||||
}
|
||||
actual, err := decoder.Decode(binary.LittleEndian, raw, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package wave
|
||||
|
||||
import "math"
|
||||
|
||||
// Float32Sample is a 32-bits float audio sample.
|
||||
type Float32Sample float32
|
||||
|
||||
@@ -11,7 +9,7 @@ func (s Float32Sample) Int() int64 {
|
||||
|
||||
// Float32Interleaved multi-channel interlaced Audio.
|
||||
type Float32Interleaved struct {
|
||||
Data []uint8
|
||||
Data []float32
|
||||
Size ChunkInfo
|
||||
}
|
||||
|
||||
@@ -25,36 +23,22 @@ func (a *Float32Interleaved) SampleFormat() SampleFormat {
|
||||
}
|
||||
|
||||
func (a *Float32Interleaved) At(i, ch int) Sample {
|
||||
loc := 4 * (a.Size.Channels*i + ch)
|
||||
|
||||
var v uint32
|
||||
v |= uint32(a.Data[loc]) << 24
|
||||
v |= uint32(a.Data[loc+1]) << 16
|
||||
v |= uint32(a.Data[loc+2]) << 8
|
||||
v |= uint32(a.Data[loc+3])
|
||||
|
||||
return Float32Sample(math.Float32frombits(v))
|
||||
return Float32Sample(a.Data[i*a.Size.Channels+ch])
|
||||
}
|
||||
|
||||
func (a *Float32Interleaved) Set(i, ch int, s Sample) {
|
||||
a.SetFloat32(i, ch, Float32SampleFormat.Convert(s).(Float32Sample))
|
||||
a.Data[i*a.Size.Channels+ch] = float32(Float32SampleFormat.Convert(s).(Float32Sample))
|
||||
}
|
||||
|
||||
func (a *Float32Interleaved) SetFloat32(i, ch int, s Float32Sample) {
|
||||
loc := 4 * (a.Size.Channels*i + ch)
|
||||
|
||||
v := math.Float32bits(float32(s))
|
||||
a.Data[loc] = uint8(v >> 24)
|
||||
a.Data[loc+1] = uint8(v >> 16)
|
||||
a.Data[loc+2] = uint8(v >> 8)
|
||||
a.Data[loc+3] = uint8(v)
|
||||
a.Data[i*a.Size.Channels+ch] = float32(s)
|
||||
}
|
||||
|
||||
// SubAudio returns part of the original audio sharing the buffer.
|
||||
func (a *Float32Interleaved) SubAudio(offsetSamples, nSamples int) *Float32Interleaved {
|
||||
ret := *a
|
||||
offset := 4 * offsetSamples * a.Size.Channels
|
||||
n := 4 * nSamples * a.Size.Channels
|
||||
offset := offsetSamples * a.Size.Channels
|
||||
n := nSamples * a.Size.Channels
|
||||
ret.Data = ret.Data[offset : offset+n]
|
||||
ret.Size.Len = nSamples
|
||||
return &ret
|
||||
@@ -62,14 +46,14 @@ func (a *Float32Interleaved) SubAudio(offsetSamples, nSamples int) *Float32Inter
|
||||
|
||||
func NewFloat32Interleaved(size ChunkInfo) *Float32Interleaved {
|
||||
return &Float32Interleaved{
|
||||
Data: make([]uint8, size.Channels*size.Len*4),
|
||||
Data: make([]float32, size.Channels*size.Len),
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// Float32NonInterleaved multi-channel interlaced Audio.
|
||||
type Float32NonInterleaved struct {
|
||||
Data [][]uint8
|
||||
Data [][]float32
|
||||
Size ChunkInfo
|
||||
}
|
||||
|
||||
@@ -83,48 +67,31 @@ func (a *Float32NonInterleaved) SampleFormat() SampleFormat {
|
||||
}
|
||||
|
||||
func (a *Float32NonInterleaved) At(i, ch int) Sample {
|
||||
loc := i * 4
|
||||
|
||||
var v uint32
|
||||
v |= uint32(a.Data[ch][loc]) << 24
|
||||
v |= uint32(a.Data[ch][loc+1]) << 16
|
||||
v |= uint32(a.Data[ch][loc+2]) << 8
|
||||
v |= uint32(a.Data[ch][loc+3])
|
||||
|
||||
return Float32Sample(math.Float32frombits(v))
|
||||
return Float32Sample(a.Data[ch][i])
|
||||
}
|
||||
|
||||
func (a *Float32NonInterleaved) Set(i, ch int, s Sample) {
|
||||
a.SetFloat32(i, ch, Float32SampleFormat.Convert(s).(Float32Sample))
|
||||
a.Data[ch][i] = float32(Float32SampleFormat.Convert(s).(Float32Sample))
|
||||
}
|
||||
|
||||
func (a *Float32NonInterleaved) SetFloat32(i, ch int, s Float32Sample) {
|
||||
loc := i * 4
|
||||
|
||||
v := math.Float32bits(float32(s))
|
||||
a.Data[ch][loc] = uint8(v >> 24)
|
||||
a.Data[ch][loc+1] = uint8(v >> 16)
|
||||
a.Data[ch][loc+2] = uint8(v >> 8)
|
||||
a.Data[ch][loc+3] = uint8(v)
|
||||
a.Data[ch][i] = float32(s)
|
||||
}
|
||||
|
||||
// SubAudio returns part of the original audio sharing the buffer.
|
||||
func (a *Float32NonInterleaved) SubAudio(offsetSamples, nSamples int) *Float32NonInterleaved {
|
||||
ret := *a
|
||||
ret.Size.Len = nSamples
|
||||
|
||||
offsetSamples *= 4
|
||||
nSamples *= 4
|
||||
for i := range a.Data {
|
||||
ret.Data[i] = ret.Data[i][offsetSamples : offsetSamples+nSamples]
|
||||
}
|
||||
ret.Size.Len = nSamples
|
||||
return &ret
|
||||
}
|
||||
|
||||
func NewFloat32NonInterleaved(size ChunkInfo) *Float32NonInterleaved {
|
||||
d := make([][]uint8, size.Channels)
|
||||
d := make([][]float32, size.Channels)
|
||||
for i := 0; i < size.Channels; i++ {
|
||||
d[i] = make([]uint8, size.Len*4)
|
||||
d[i] = make([]float32, size.Len)
|
||||
}
|
||||
return &Float32NonInterleaved{
|
||||
Data: d,
|
||||
|
@@ -1,51 +1,39 @@
|
||||
package wave
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func float32ToUint8(vs ...float32) []uint8 {
|
||||
var b []uint8
|
||||
|
||||
for _, v := range vs {
|
||||
s := math.Float32bits(v)
|
||||
b = append(b, uint8(s>>24), uint8(s>>16), uint8(s>>8), uint8(s))
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func TestFloat32(t *testing.T) {
|
||||
expected := [][]float32{
|
||||
{0.0, 1.0, 2.0, 3.0},
|
||||
{4.0, 5.0, 6.0, 7.0},
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
in Audio
|
||||
expected [][]float32
|
||||
}{
|
||||
"Interleaved": {
|
||||
in: &Float32Interleaved{
|
||||
Data: float32ToUint8(
|
||||
0.0, 4.0, 1.0, 5.0,
|
||||
2.0, 6.0, 3.0, 7.0,
|
||||
),
|
||||
Size: ChunkInfo{4, 2, 48000},
|
||||
Data: []float32{
|
||||
0.1, -0.5, 0.2, -0.6, 0.3, -0.7, 0.4, -0.8, 0.5, -0.9, 0.6, -1.0, 0.7, -1.1, 0.8, -1.2,
|
||||
},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
},
|
||||
expected: [][]float32{
|
||||
{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8},
|
||||
{-0.5, -0.6, -0.7, -0.8, -0.9, -1.0, -1.1, -1.2},
|
||||
},
|
||||
expected: expected,
|
||||
},
|
||||
"NonInterleaved": {
|
||||
in: &Float32NonInterleaved{
|
||||
Data: [][]uint8{
|
||||
float32ToUint8(expected[0]...),
|
||||
float32ToUint8(expected[1]...),
|
||||
Data: [][]float32{
|
||||
{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8},
|
||||
{-0.5, -0.6, -0.7, -0.8, -0.9, -1.0, -1.1, -1.2},
|
||||
},
|
||||
Size: ChunkInfo{4, 2, 48000},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
},
|
||||
expected: [][]float32{
|
||||
{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8},
|
||||
{-0.5, -0.6, -0.7, -0.8, -0.9, -1.0, -1.1, -1.2},
|
||||
},
|
||||
expected: expected,
|
||||
},
|
||||
}
|
||||
for name, c := range cases {
|
||||
@@ -67,43 +55,38 @@ func TestFloat32(t *testing.T) {
|
||||
func TestFloat32SubAudio(t *testing.T) {
|
||||
t.Run("Interleaved", func(t *testing.T) {
|
||||
in := &Float32Interleaved{
|
||||
// Data: []uint8{
|
||||
// 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
|
||||
// 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
// },
|
||||
Data: float32ToUint8(
|
||||
1.0, 2.0, 3.0, 4.0,
|
||||
5.0, 6.0, 7.0, 8.0,
|
||||
),
|
||||
Size: ChunkInfo{4, 2, 48000},
|
||||
Data: []float32{
|
||||
0.1, -0.5, 0.2, -0.6, 0.3, -0.7, 0.4, -0.8, 0.5, -0.9, 0.6, -1.0, 0.7, -1.1, 0.8, -1.2,
|
||||
},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
}
|
||||
expected := &Float32Interleaved{
|
||||
Data: float32ToUint8(
|
||||
5.0, 6.0, 7.0, 8.0,
|
||||
),
|
||||
Size: ChunkInfo{2, 2, 48000},
|
||||
Data: []float32{
|
||||
0.3, -0.7, 0.4, -0.8, 0.5, -0.9,
|
||||
},
|
||||
Size: ChunkInfo{3, 2, 48000},
|
||||
}
|
||||
out := in.SubAudio(2, 2)
|
||||
out := in.SubAudio(2, 3)
|
||||
if !reflect.DeepEqual(expected, out) {
|
||||
t.Errorf("SubAudio differs, expected: %v, got: %v", expected, out)
|
||||
}
|
||||
})
|
||||
t.Run("NonInterleaved", func(t *testing.T) {
|
||||
in := &Float32NonInterleaved{
|
||||
Data: [][]uint8{
|
||||
float32ToUint8(1.0, 2.0, 3.0, 4.0),
|
||||
float32ToUint8(5.0, 6.0, 7.0, 8.0),
|
||||
Data: [][]float32{
|
||||
{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8},
|
||||
{-0.5, -0.6, -0.7, -0.8, -0.9, -1.0, -1.1, -1.2},
|
||||
},
|
||||
Size: ChunkInfo{4, 2, 48000},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
}
|
||||
expected := &Float32NonInterleaved{
|
||||
Data: [][]uint8{
|
||||
float32ToUint8(3.0, 4.0),
|
||||
float32ToUint8(7.0, 8.0),
|
||||
Data: [][]float32{
|
||||
{0.3, 0.4, 0.5},
|
||||
{-0.7, -0.8, -0.9},
|
||||
},
|
||||
Size: ChunkInfo{2, 2, 48000},
|
||||
Size: ChunkInfo{3, 2, 48000},
|
||||
}
|
||||
out := in.SubAudio(2, 2)
|
||||
out := in.SubAudio(2, 3)
|
||||
if !reflect.DeepEqual(expected, out) {
|
||||
t.Errorf("SubAudio differs, expected: %v, got: %v", expected, out)
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ func (s Int16Sample) Int() int64 {
|
||||
|
||||
// Int16Interleaved multi-channel interlaced Audio.
|
||||
type Int16Interleaved struct {
|
||||
Data []uint8
|
||||
Data []int16
|
||||
Size ChunkInfo
|
||||
}
|
||||
|
||||
@@ -23,29 +23,22 @@ func (a *Int16Interleaved) SampleFormat() SampleFormat {
|
||||
}
|
||||
|
||||
func (a *Int16Interleaved) At(i, ch int) Sample {
|
||||
loc := 2 * (i*a.Size.Channels + ch)
|
||||
|
||||
var s Int16Sample
|
||||
s |= Int16Sample(a.Data[loc]) << 8
|
||||
s |= Int16Sample(a.Data[loc+1])
|
||||
return s
|
||||
return Int16Sample(a.Data[i*a.Size.Channels+ch])
|
||||
}
|
||||
|
||||
func (a *Int16Interleaved) Set(i, ch int, s Sample) {
|
||||
a.SetInt16(i, ch, Int16SampleFormat.Convert(s).(Int16Sample))
|
||||
a.Data[i*a.Size.Channels+ch] = int16(Int16SampleFormat.Convert(s).(Int16Sample))
|
||||
}
|
||||
|
||||
func (a *Int16Interleaved) SetInt16(i, ch int, s Int16Sample) {
|
||||
loc := 2 * (i*a.Size.Channels + ch)
|
||||
a.Data[loc] = uint8(s >> 8)
|
||||
a.Data[loc+1] = uint8(s)
|
||||
a.Data[i*a.Size.Channels+ch] = int16(s)
|
||||
}
|
||||
|
||||
// SubAudio returns part of the original audio sharing the buffer.
|
||||
func (a *Int16Interleaved) SubAudio(offsetSamples, nSamples int) *Int16Interleaved {
|
||||
ret := *a
|
||||
offset := 2 * offsetSamples * a.Size.Channels
|
||||
n := 2 * nSamples * a.Size.Channels
|
||||
offset := offsetSamples * a.Size.Channels
|
||||
n := nSamples * a.Size.Channels
|
||||
ret.Data = ret.Data[offset : offset+n]
|
||||
ret.Size.Len = nSamples
|
||||
return &ret
|
||||
@@ -53,14 +46,14 @@ func (a *Int16Interleaved) SubAudio(offsetSamples, nSamples int) *Int16Interleav
|
||||
|
||||
func NewInt16Interleaved(size ChunkInfo) *Int16Interleaved {
|
||||
return &Int16Interleaved{
|
||||
Data: make([]uint8, size.Channels*size.Len*2),
|
||||
Data: make([]int16, size.Channels*size.Len),
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// Int16NonInterleaved multi-channel interlaced Audio.
|
||||
type Int16NonInterleaved struct {
|
||||
Data [][]uint8
|
||||
Data [][]int16
|
||||
Size ChunkInfo
|
||||
}
|
||||
|
||||
@@ -74,41 +67,31 @@ func (a *Int16NonInterleaved) SampleFormat() SampleFormat {
|
||||
}
|
||||
|
||||
func (a *Int16NonInterleaved) At(i, ch int) Sample {
|
||||
loc := i * 2
|
||||
|
||||
var s Int16Sample
|
||||
s |= Int16Sample(a.Data[ch][loc]) << 8
|
||||
s |= Int16Sample(a.Data[ch][loc+1])
|
||||
return s
|
||||
return Int16Sample(a.Data[ch][i])
|
||||
}
|
||||
|
||||
func (a *Int16NonInterleaved) Set(i, ch int, s Sample) {
|
||||
a.SetInt16(i, ch, Int16SampleFormat.Convert(s).(Int16Sample))
|
||||
a.Data[ch][i] = int16(Int16SampleFormat.Convert(s).(Int16Sample))
|
||||
}
|
||||
|
||||
func (a *Int16NonInterleaved) SetInt16(i, ch int, s Int16Sample) {
|
||||
loc := i * 2
|
||||
a.Data[ch][loc] = uint8(s >> 8)
|
||||
a.Data[ch][loc+1] = uint8(s)
|
||||
a.Data[ch][i] = int16(s)
|
||||
}
|
||||
|
||||
// SubAudio returns part of the original audio sharing the buffer.
|
||||
func (a *Int16NonInterleaved) SubAudio(offsetSamples, nSamples int) *Int16NonInterleaved {
|
||||
ret := *a
|
||||
ret.Size.Len = nSamples
|
||||
|
||||
nSamples *= 2
|
||||
offsetSamples *= 2
|
||||
for i := range a.Data {
|
||||
ret.Data[i] = ret.Data[i][offsetSamples : offsetSamples+nSamples]
|
||||
}
|
||||
ret.Size.Len = nSamples
|
||||
return &ret
|
||||
}
|
||||
|
||||
func NewInt16NonInterleaved(size ChunkInfo) *Int16NonInterleaved {
|
||||
d := make([][]uint8, size.Channels)
|
||||
d := make([][]int16, size.Channels)
|
||||
for i := 0; i < size.Channels; i++ {
|
||||
d[i] = make([]uint8, size.Len*2)
|
||||
d[i] = make([]int16, size.Len)
|
||||
}
|
||||
return &Int16NonInterleaved{
|
||||
Data: d,
|
||||
|
@@ -12,28 +12,27 @@ func TestInt16(t *testing.T) {
|
||||
}{
|
||||
"Interleaved": {
|
||||
in: &Int16Interleaved{
|
||||
Data: []uint8{
|
||||
0, 1, 1, 2, 2, 3, 3, 4,
|
||||
4, 5, 5, 6, 6, 7, 7, 8,
|
||||
Data: []int16{
|
||||
1, -5, 2, -6, 3, -7, 4, -8, 5, -9, 6, -10, 7, -11, 8, -12,
|
||||
},
|
||||
Size: ChunkInfo{4, 2, 48000},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
},
|
||||
expected: [][]int16{
|
||||
{(0 << 8) | 1, (2 << 8) | 3, (4 << 8) | 5, (6 << 8) | 7},
|
||||
{(1 << 8) | 2, (3 << 8) | 4, (5 << 8) | 6, (7 << 8) | 8},
|
||||
{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
{-5, -6, -7, -8, -9, -10, -11, -12},
|
||||
},
|
||||
},
|
||||
"NonInterleaved": {
|
||||
in: &Int16NonInterleaved{
|
||||
Data: [][]uint8{
|
||||
{0, 1, 2, 3, 4, 5, 6, 7},
|
||||
Data: [][]int16{
|
||||
{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
{-5, -6, -7, -8, -9, -10, -11, -12},
|
||||
},
|
||||
Size: ChunkInfo{4, 2, 48000},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
},
|
||||
expected: [][]int16{
|
||||
{(0 << 8) | 1, (2 << 8) | 3, (4 << 8) | 5, (6 << 8) | 7},
|
||||
{(1 << 8) | 2, (3 << 8) | 4, (5 << 8) | 6, (7 << 8) | 8},
|
||||
{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
{-5, -6, -7, -8, -9, -10, -11, -12},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -56,39 +55,38 @@ func TestInt16(t *testing.T) {
|
||||
func TestInt32SubAudio(t *testing.T) {
|
||||
t.Run("Interleaved", func(t *testing.T) {
|
||||
in := &Int16Interleaved{
|
||||
Data: []uint8{
|
||||
1, 2, 3, 4, 5, 6, 7, 8,
|
||||
9, 10, 11, 12, 13, 14, 15, 16,
|
||||
Data: []int16{
|
||||
1, -5, 2, -6, 3, -7, 4, -8, 5, -9, 6, -10, 7, -11, 8, -12,
|
||||
},
|
||||
Size: ChunkInfo{4, 2, 48000},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
}
|
||||
expected := &Int16Interleaved{
|
||||
Data: []uint8{
|
||||
9, 10, 11, 12, 13, 14, 15, 16,
|
||||
Data: []int16{
|
||||
3, -7, 4, -8, 5, -9,
|
||||
},
|
||||
Size: ChunkInfo{2, 2, 48000},
|
||||
Size: ChunkInfo{3, 2, 48000},
|
||||
}
|
||||
out := in.SubAudio(2, 2)
|
||||
out := in.SubAudio(2, 3)
|
||||
if !reflect.DeepEqual(expected, out) {
|
||||
t.Errorf("SubAudio differs, expected: %v, got: %v", expected, out)
|
||||
}
|
||||
})
|
||||
t.Run("NonInterleaved", func(t *testing.T) {
|
||||
in := &Int16NonInterleaved{
|
||||
Data: [][]uint8{
|
||||
{1, 2, 5, 6, 9, 10, 13, 14},
|
||||
{3, 4, 7, 8, 11, 12, 15, 16},
|
||||
Data: [][]int16{
|
||||
{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
{-5, -6, -7, -8, -9, -10, -11, -12},
|
||||
},
|
||||
Size: ChunkInfo{4, 2, 48000},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
}
|
||||
expected := &Int16NonInterleaved{
|
||||
Data: [][]uint8{
|
||||
{9, 10, 13, 14},
|
||||
{11, 12, 15, 16},
|
||||
Data: [][]int16{
|
||||
{3, 4, 5},
|
||||
{-7, -8, -9},
|
||||
},
|
||||
Size: ChunkInfo{2, 2, 48000},
|
||||
Size: ChunkInfo{3, 2, 48000},
|
||||
}
|
||||
out := in.SubAudio(2, 2)
|
||||
out := in.SubAudio(2, 3)
|
||||
if !reflect.DeepEqual(expected, out) {
|
||||
t.Errorf("SubAudio differs, expected: %v, got: %v", expected, out)
|
||||
}
|
||||
|
@@ -19,10 +19,10 @@ func TestMonoMixer(t *testing.T) {
|
||||
Len: 3,
|
||||
Channels: 3,
|
||||
},
|
||||
Data: []uint8{
|
||||
0x00, 0x01, 0x00, 0x02, 0x00, 0x04,
|
||||
0x00, 0x01, 0x00, 0x02, 0x00, 0x01,
|
||||
0x00, 0x03, 0x00, 0x03, 0x00, 0x06,
|
||||
Data: []int16{
|
||||
0, 2, 4,
|
||||
1, -2, 1,
|
||||
3, 3, 6,
|
||||
},
|
||||
},
|
||||
dst: &wave.Int16Interleaved{
|
||||
@@ -30,14 +30,14 @@ func TestMonoMixer(t *testing.T) {
|
||||
Len: 3,
|
||||
Channels: 1,
|
||||
},
|
||||
Data: make([]uint8, 3*2),
|
||||
Data: make([]int16, 3),
|
||||
},
|
||||
expected: &wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{
|
||||
Len: 3,
|
||||
Channels: 1,
|
||||
},
|
||||
Data: []uint8{0x00, 0x02, 0x00, 0x01, 0x00, 0x04},
|
||||
Data: []int16{2, 0, 4},
|
||||
},
|
||||
},
|
||||
"MonoToStereo": {
|
||||
@@ -46,21 +46,21 @@ func TestMonoMixer(t *testing.T) {
|
||||
Len: 3,
|
||||
Channels: 1,
|
||||
},
|
||||
Data: []uint8{0x00, 0x00, 0x00, 0x02, 0x00, 0x04},
|
||||
Data: []int16{0, 2, 4},
|
||||
},
|
||||
dst: &wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{
|
||||
Len: 3,
|
||||
Channels: 2,
|
||||
},
|
||||
Data: make([]uint8, 6*2),
|
||||
Data: make([]int16, 6),
|
||||
},
|
||||
expected: &wave.Int16Interleaved{
|
||||
Size: wave.ChunkInfo{
|
||||
Len: 3,
|
||||
Channels: 2,
|
||||
},
|
||||
Data: []uint8{0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x04, 0x00, 0x04},
|
||||
Data: []int16{0, 0, 2, 2, 4, 4},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
Reference in New Issue
Block a user