mirror of
https://github.com/pion/mediadevices.git
synced 2025-10-30 03:21:55 +08:00
Define basic audio data interface
This commit is contained in:
committed by
Lukas Herman
parent
edc2ada757
commit
450f882c50
80
pkg/wave/float32.go
Normal file
80
pkg/wave/float32.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package wave
|
||||
|
||||
// Float32Sample is a 32-bits float audio sample.
|
||||
type Float32Sample float32
|
||||
|
||||
func (s Float32Sample) Int() int64 {
|
||||
return int64(s * 0x100000000)
|
||||
}
|
||||
|
||||
// Float32Interleaved multi-channel interlaced Audio.
|
||||
type Float32Interleaved struct {
|
||||
Data []float32
|
||||
Size ChunkInfo
|
||||
}
|
||||
|
||||
// ChunkInfo returns audio chunk size.
|
||||
func (a *Float32Interleaved) ChunkInfo() ChunkInfo {
|
||||
return a.Size
|
||||
}
|
||||
|
||||
func (a *Float32Interleaved) SampleFormat() SampleFormat {
|
||||
return Float32SampleFormat
|
||||
}
|
||||
|
||||
func (a *Float32Interleaved) At(i, ch int) Sample {
|
||||
return Float32Sample(a.Data[i*a.Size.Channels+ch])
|
||||
}
|
||||
|
||||
func (a *Float32Interleaved) Set(i, ch int, s Sample) {
|
||||
a.Data[i*a.Size.Channels+ch] = float32(Float32SampleFormat.Convert(s).(Float32Sample))
|
||||
}
|
||||
|
||||
func (a *Float32Interleaved) SetFloat32(i, ch int, s Float32Sample) {
|
||||
a.Data[i*a.Size.Channels+ch] = float32(s)
|
||||
}
|
||||
|
||||
func NewFloat32Interleaved(size ChunkInfo) *Float32Interleaved {
|
||||
return &Float32Interleaved{
|
||||
Data: make([]float32, size.Channels*size.Len),
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// Float32NonInterleaved multi-channel interlaced Audio.
|
||||
type Float32NonInterleaved struct {
|
||||
Data [][]float32
|
||||
Size ChunkInfo
|
||||
}
|
||||
|
||||
// ChunkInfo returns audio chunk size.
|
||||
func (a *Float32NonInterleaved) ChunkInfo() ChunkInfo {
|
||||
return a.Size
|
||||
}
|
||||
|
||||
func (a *Float32NonInterleaved) SampleFormat() SampleFormat {
|
||||
return Float32SampleFormat
|
||||
}
|
||||
|
||||
func (a *Float32NonInterleaved) At(i, ch int) Sample {
|
||||
return Float32Sample(a.Data[ch][i])
|
||||
}
|
||||
|
||||
func (a *Float32NonInterleaved) Set(i, ch int, s Sample) {
|
||||
a.Data[ch][i] = float32(Float32SampleFormat.Convert(s).(Float32Sample))
|
||||
}
|
||||
|
||||
func (a *Float32NonInterleaved) SetFloat32(i, ch int, s Float32Sample) {
|
||||
a.Data[ch][i] = float32(s)
|
||||
}
|
||||
|
||||
func NewFloat32NonInterleaved(size ChunkInfo) *Float32NonInterleaved {
|
||||
d := make([][]float32, size.Channels)
|
||||
for i := 0; i < size.Channels; i++ {
|
||||
d[i] = make([]float32, size.Len)
|
||||
}
|
||||
return &Float32NonInterleaved{
|
||||
Data: d,
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
53
pkg/wave/float32_test.go
Normal file
53
pkg/wave/float32_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package wave
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFloat32(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
in Audio
|
||||
expected [][]float32
|
||||
}{
|
||||
"Interleaved": {
|
||||
in: &Float32Interleaved{
|
||||
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},
|
||||
},
|
||||
},
|
||||
"NonInterleaved": {
|
||||
in: &Float32NonInterleaved{
|
||||
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{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},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, c := range cases {
|
||||
c := c
|
||||
t.Run(name, func(t *testing.T) {
|
||||
out := make([][]float32, c.in.ChunkInfo().Channels)
|
||||
for i := 0; i < c.in.ChunkInfo().Channels; i++ {
|
||||
for j := 0; j < c.in.ChunkInfo().Len; j++ {
|
||||
out[i] = append(out[i], float32(c.in.At(j, i).(Float32Sample)))
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(c.expected, out) {
|
||||
t.Errorf("Sample level differs, expected: %v, got: %v", c.expected, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
80
pkg/wave/int16.go
Normal file
80
pkg/wave/int16.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package wave
|
||||
|
||||
// Int16Sample is a 16-bits signed integer audio sample.
|
||||
type Int16Sample int16
|
||||
|
||||
func (s Int16Sample) Int() int64 {
|
||||
return int64(s) << 16
|
||||
}
|
||||
|
||||
// Int16Interleaved multi-channel interlaced Audio.
|
||||
type Int16Interleaved struct {
|
||||
Data []int16
|
||||
Size ChunkInfo
|
||||
}
|
||||
|
||||
// ChunkInfo returns audio chunk size.
|
||||
func (a *Int16Interleaved) ChunkInfo() ChunkInfo {
|
||||
return a.Size
|
||||
}
|
||||
|
||||
func (a *Int16Interleaved) SampleFormat() SampleFormat {
|
||||
return Int16SampleFormat
|
||||
}
|
||||
|
||||
func (a *Int16Interleaved) At(i, ch int) Sample {
|
||||
return Int16Sample(a.Data[i*a.Size.Channels+ch])
|
||||
}
|
||||
|
||||
func (a *Int16Interleaved) Set(i, ch int, s Sample) {
|
||||
a.Data[i*a.Size.Channels+ch] = int16(Int16SampleFormat.Convert(s).(Int16Sample))
|
||||
}
|
||||
|
||||
func (a *Int16Interleaved) SetInt16(i, ch int, s Int16Sample) {
|
||||
a.Data[i*a.Size.Channels+ch] = int16(s)
|
||||
}
|
||||
|
||||
func NewInt16Interleaved(size ChunkInfo) *Int16Interleaved {
|
||||
return &Int16Interleaved{
|
||||
Data: make([]int16, size.Channels*size.Len),
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// Int16NonInterleaved multi-channel interlaced Audio.
|
||||
type Int16NonInterleaved struct {
|
||||
Data [][]int16
|
||||
Size ChunkInfo
|
||||
}
|
||||
|
||||
// ChunkInfo returns audio chunk size.
|
||||
func (a *Int16NonInterleaved) ChunkInfo() ChunkInfo {
|
||||
return a.Size
|
||||
}
|
||||
|
||||
func (a *Int16NonInterleaved) SampleFormat() SampleFormat {
|
||||
return Int16SampleFormat
|
||||
}
|
||||
|
||||
func (a *Int16NonInterleaved) At(i, ch int) Sample {
|
||||
return Int16Sample(a.Data[ch][i])
|
||||
}
|
||||
|
||||
func (a *Int16NonInterleaved) Set(i, ch int, s Sample) {
|
||||
a.Data[ch][i] = int16(Int16SampleFormat.Convert(s).(Int16Sample))
|
||||
}
|
||||
|
||||
func (a *Int16NonInterleaved) SetInt16(i, ch int, s Int16Sample) {
|
||||
a.Data[ch][i] = int16(s)
|
||||
}
|
||||
|
||||
func NewInt16NonInterleaved(size ChunkInfo) *Int16NonInterleaved {
|
||||
d := make([][]int16, size.Channels)
|
||||
for i := 0; i < size.Channels; i++ {
|
||||
d[i] = make([]int16, size.Len)
|
||||
}
|
||||
return &Int16NonInterleaved{
|
||||
Data: d,
|
||||
Size: size,
|
||||
}
|
||||
}
|
||||
53
pkg/wave/int16_test.go
Normal file
53
pkg/wave/int16_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package wave
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInt16(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
in Audio
|
||||
expected [][]int16
|
||||
}{
|
||||
"Interleaved": {
|
||||
in: &Int16Interleaved{
|
||||
Data: []int16{
|
||||
1, -5, 2, -6, 3, -7, 4, -8, 5, -9, 6, -10, 7, -11, 8, -12,
|
||||
},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
},
|
||||
expected: [][]int16{
|
||||
{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
{-5, -6, -7, -8, -9, -10, -11, -12},
|
||||
},
|
||||
},
|
||||
"NonInterleaved": {
|
||||
in: &Int16NonInterleaved{
|
||||
Data: [][]int16{
|
||||
{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
{-5, -6, -7, -8, -9, -10, -11, -12},
|
||||
},
|
||||
Size: ChunkInfo{8, 2, 48000},
|
||||
},
|
||||
expected: [][]int16{
|
||||
{1, 2, 3, 4, 5, 6, 7, 8},
|
||||
{-5, -6, -7, -8, -9, -10, -11, -12},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, c := range cases {
|
||||
c := c
|
||||
t.Run(name, func(t *testing.T) {
|
||||
out := make([][]int16, c.in.ChunkInfo().Channels)
|
||||
for i := 0; i < c.in.ChunkInfo().Channels; i++ {
|
||||
for j := 0; j < c.in.ChunkInfo().Len; j++ {
|
||||
out[i] = append(out[i], int16(c.in.At(j, i).(Int16Sample)))
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(c.expected, out) {
|
||||
t.Errorf("Sample level differs, expected: %v, got: %v", c.expected, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
57
pkg/wave/wave.go
Normal file
57
pkg/wave/wave.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Package wave implements a basic audio data library.
|
||||
package wave
|
||||
|
||||
// Audio is a finite series of audio Sample values.
|
||||
type Audio interface {
|
||||
SampleFormat() SampleFormat
|
||||
ChunkInfo() ChunkInfo
|
||||
At(i, ch int) Sample
|
||||
}
|
||||
|
||||
// ChunkInfo contains size of the audio chunk.
|
||||
type ChunkInfo struct {
|
||||
Len int
|
||||
Channels int
|
||||
SamplingRate int
|
||||
}
|
||||
|
||||
// SampleFormat can convert any Sample to one from its own sample format.
|
||||
type SampleFormat interface {
|
||||
Convert(c Sample) Sample
|
||||
}
|
||||
|
||||
// SampleFormatFunc returns a SampleFormat that invokes f to implement the conversion.
|
||||
func SampleFormatFunc(f func(Sample) Sample) SampleFormat {
|
||||
return &sampleFormatFunc{f}
|
||||
}
|
||||
|
||||
type sampleFormatFunc struct {
|
||||
f func(Sample) Sample
|
||||
}
|
||||
|
||||
func (f *sampleFormatFunc) Convert(s Sample) Sample {
|
||||
return f.f(s)
|
||||
}
|
||||
|
||||
// SampleFormats for the standard formats.
|
||||
var (
|
||||
Int16SampleFormat = SampleFormatFunc(func(s Sample) Sample {
|
||||
if _, ok := s.(Int16Sample); ok {
|
||||
return s
|
||||
}
|
||||
return Int16Sample(s.Int() >> 16)
|
||||
})
|
||||
Float32SampleFormat = SampleFormatFunc(func(s Sample) Sample {
|
||||
if _, ok := s.(Float32Sample); ok {
|
||||
return s
|
||||
}
|
||||
return Float32Sample(float32(s.Int()) / 0x100000000)
|
||||
})
|
||||
)
|
||||
|
||||
// Sample can convert itself to 64-bits signed value.
|
||||
type Sample interface {
|
||||
// Int returns the audio level value for the sample.
|
||||
// A value ranges within [0, 0xffffffff], but is represented by a int64.
|
||||
Int() int64
|
||||
}
|
||||
61
pkg/wave/wave_test.go
Normal file
61
pkg/wave/wave_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package wave
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
in []Sample
|
||||
typ SampleFormat
|
||||
expected []Sample
|
||||
}{
|
||||
"Int16ToFloat32": {
|
||||
in: []Sample{
|
||||
Int16Sample(-0x1000),
|
||||
Int16Sample(-0x100),
|
||||
Int16Sample(0x0),
|
||||
Int16Sample(0x100),
|
||||
Int16Sample(0x1000),
|
||||
},
|
||||
typ: Float32SampleFormat,
|
||||
expected: []Sample{
|
||||
Float32Sample(-math.Pow(2, -4)),
|
||||
Float32Sample(-math.Pow(2, -8)),
|
||||
Float32Sample(0.0),
|
||||
Float32Sample(math.Pow(2, -8)),
|
||||
Float32Sample(math.Pow(2, -4)),
|
||||
},
|
||||
},
|
||||
"Float32ToInt16": {
|
||||
in: []Sample{
|
||||
Float32Sample(-math.Pow(2, -4)),
|
||||
Float32Sample(-math.Pow(2, -8)),
|
||||
Float32Sample(0.0),
|
||||
Float32Sample(math.Pow(2, -8)),
|
||||
Float32Sample(math.Pow(2, -4)),
|
||||
},
|
||||
typ: Int16SampleFormat,
|
||||
expected: []Sample{
|
||||
Int16Sample(-0x1000),
|
||||
Int16Sample(-0x100),
|
||||
Int16Sample(0x0),
|
||||
Int16Sample(0x100),
|
||||
Int16Sample(0x1000),
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, c := range cases {
|
||||
c := c
|
||||
t.Run(name, func(t *testing.T) {
|
||||
for i := range c.in {
|
||||
s := c.typ.Convert(c.in[i])
|
||||
if !reflect.DeepEqual(c.expected[i], s) {
|
||||
t.Errorf("Convert result differs, expected: %v, got: %v", c.expected[i], s)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user