mirror of
				https://github.com/pion/mediadevices.git
				synced 2025-10-31 11:56:28 +08:00 
			
		
		
		
	Define basic audio data interface
This commit is contained in:
		 Atsushi Watanabe
					Atsushi Watanabe
				
			
				
					committed by
					
						 Lukas Herman
						Lukas Herman
					
				
			
			
				
	
			
			
			 Lukas Herman
						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