Implement bitdepth conversion and upsampling

bitdepth package takes F32LE -> S16LE and handles upsampling
from 16Khz -> 48Khz
This commit is contained in:
Sean DuBois 2022-09-30 21:56:49 -06:00
parent 7941dd8c63
commit 733188ab17
6 changed files with 59 additions and 13 deletions

View file

@ -3,23 +3,26 @@ package opus
import ( import (
"fmt" "fmt"
"github.com/pion/opus/internal/bitdepth"
"github.com/pion/opus/internal/silk" "github.com/pion/opus/internal/silk"
) )
// Decoder decodes the Opus bitstream into PCM // Decoder decodes the Opus bitstream into PCM
type Decoder struct { type Decoder struct {
silkDecoder silk.Decoder silkDecoder silk.Decoder
silkBuffer []float32
} }
// NewDecoder creates a new Opus Decoder // NewDecoder creates a new Opus Decoder
func NewDecoder() Decoder { func NewDecoder() Decoder {
return Decoder{ return Decoder{
silkDecoder: silk.NewDecoder(), silkDecoder: silk.NewDecoder(),
silkBuffer: make([]float32, 320),
} }
} }
// Decode decodes the Opus bitstream into PCM // Decode decodes the Opus bitstream into PCM
func (d *Decoder) Decode(in []byte, out []float32) (bandwidth Bandwidth, isStereo bool, err error) { func (d *Decoder) Decode(in []byte, out []byte) (bandwidth Bandwidth, isStereo bool, err error) {
if len(in) < 1 { if len(in) < 1 {
return 0, false, errTooShortForTableOfContentsHeader return 0, false, errTooShortForTableOfContentsHeader
} }
@ -40,11 +43,15 @@ func (d *Decoder) Decode(in []byte, out []float32) (bandwidth Bandwidth, isStere
} }
for _, encodedFrame := range encodedFrames { for _, encodedFrame := range encodedFrames {
err := d.silkDecoder.Decode(encodedFrame, out, tocHeader.isStereo(), cfg.frameDuration().nanoseconds(), silk.Bandwidth(cfg.bandwidth())) err := d.silkDecoder.Decode(encodedFrame, d.silkBuffer, tocHeader.isStereo(), cfg.frameDuration().nanoseconds(), silk.Bandwidth(cfg.bandwidth()))
if err != nil { if err != nil {
return 0, false, err return 0, false, err
} }
} }
if err := bitdepth.ConvertFloat32LittleEndianToSigned16LittleEndian(d.silkBuffer, out, 3); err != nil {
return 0, false, err
}
return cfg.bandwidth(), tocHeader.isStereo(), nil return cfg.bandwidth(), tocHeader.isStereo(), nil
} }

View file

@ -28,9 +28,9 @@ decoder `pwd`/output.ogg `audio-samples.pcm`
Now play the audio with the tool of your choice. Now play the audio with the tool of your choice.
``` ```
gst-launch-1.0 filesrc location=audio-samples.pcm ! audio/x-raw, format=F32LE, rate=16000,channels=1 ! autoaudiosink -v gst-launch-1.0 filesrc location=audio-samples.pcm ! audio/x-raw, format=S16LE, rate=48000,channels=1 ! autoaudiosink -v
``` ```
``` ```
ffplay -f f32le -ar 16000 audio-samples.pcm ffplay -f s16le -ar 48000 audio-samples.pcm
``` ```

View file

@ -2,7 +2,6 @@ package main
import ( import (
"bytes" "bytes"
"encoding/binary"
"errors" "errors"
"io" "io"
"os" "os"
@ -11,12 +10,6 @@ import (
"github.com/pion/opus/pkg/oggreader" "github.com/pion/opus/pkg/oggreader"
) )
func convertFloatToByteSlice(i []float32) []byte {
buf := new(bytes.Buffer)
binary.Write(buf, binary.LittleEndian, i)
return buf.Bytes()
}
func main() { func main() {
if len(os.Args) != 3 { if len(os.Args) != 3 {
panic("Usage: <in-file> <out-file>") panic("Usage: <in-file> <out-file>")
@ -32,13 +25,14 @@ func main() {
panic(err) panic(err)
} }
out := make([]float32, 320) out := make([]byte, 1920)
f, err := os.Create(os.Args[2]) f, err := os.Create(os.Args[2])
if err != nil { if err != nil {
panic(err) panic(err)
} }
decoder := opus.NewDecoder() decoder := opus.NewDecoder()
x := 0
for { for {
segments, _, err := ogg.ParseNextPage() segments, _, err := ogg.ParseNextPage()
@ -57,7 +51,7 @@ func main() {
panic(err) panic(err)
} }
f.Write(convertFloatToByteSlice(out)) f.Write(out)
} }
} }
} }

View file

@ -0,0 +1,22 @@
package bitdepth
import (
"math"
)
func ConvertFloat32LittleEndianToSigned16LittleEndian(in []float32, out []byte, resampleCount int) error {
currIndex := 0
for i := range in {
res := int16(math.Floor(float64(in[i] * 32767)))
for j := resampleCount; j > 0; j-- {
out[currIndex] = byte(res & 0b11111111)
currIndex++
out[currIndex] = (byte(res >> 8))
currIndex++
}
}
return nil
}

View file

@ -0,0 +1,16 @@
package bitdepth
import (
"bytes"
"testing"
)
func TestConvertFloat32LittleEndianToSigned16LittleEndian(t *testing.T) {
in := []float32{0.3, 0, .55, .72, -.05}
out := make([]byte, len(in)*2)
ConvertFloat32LittleEndianToSigned16LittleEndian(in, out, 1)
if !bytes.Equal([]byte{0x66, 0x26, 0x00, 0x00, 0x65, 0x46, 0x28, 0x5c, 0x99, 0xf9}, out) {
t.Fatal("buffer mismatch")
}
}

View file

@ -0,0 +1,7 @@
package bitdepth
import "errors"
var (
errBufferLengthMismatch = errors.New("length of in and out buffer are not equal")
)