Kirika/audio/format/opus/opus.go
2022-04-23 20:41:14 +02:00

149 lines
2.9 KiB
Go

//go:build !disable_format_opus
// +build !disable_format_opus
package opus
import (
"bytes"
"fmt"
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/filter"
"git.gammaspectra.live/S.O.N.G/Kirika/cgo"
libopus "git.gammaspectra.live/S.O.N.G/go-pus"
"io"
)
const FixedSampleRate int = 48000
type Format struct {
}
func NewFormat() Format {
return Format{}
}
func (f Format) Name() string {
return "opus"
}
func (f Format) Description() string {
return "libopus (S.O.N.G/go-pus)"
}
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
stream, err := libopus.NewStream(r)
if err != nil {
return audio.Source{}, err
}
newChannel := make(chan []float32)
go func() {
defer stream.Close()
defer close(newChannel)
buf := make([]int16, 120*FixedSampleRate*2)
for {
n, err := stream.ReadStereo(buf)
if err != nil {
return
}
if n > 0 {
newChannel <- cgo.Int16ToFloat32(buf[:n*2], 16)
}
}
}()
//We always get two channels via stereo api
return audio.Source{
Channels: 2,
SampleRate: FixedSampleRate,
Blocks: newChannel,
}, nil
}
func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error {
var bitrate = 0
var resampleQuality = filter.Linear
if options != nil {
var val interface{}
var ok bool
var intVal int
var int64Val int64
var strVal string
if val, ok = options["bitrate"]; ok {
if strVal, ok = val.(string); ok {
switch strVal {
case "auto":
bitrate = 0
case "256k":
bitrate = 256
case "192k":
bitrate = 192
case "128k":
bitrate = 128
default:
return fmt.Errorf("unknown setting bitrate=%s", strVal)
}
} else if intVal, ok = val.(int); ok {
bitrate = intVal
} else if int64Val, ok = val.(int64); ok {
bitrate = int(int64Val)
}
}
if val, ok = options["resampleQuality"]; ok {
if typeVal, ok := val.(filter.ResampleQuality); ok {
resampleQuality = typeVal
} else if intVal, ok = val.(int); ok {
resampleQuality = filter.ResampleQuality(intVal)
} else if int64Val, ok = val.(int64); ok {
resampleQuality = filter.ResampleQuality(int64Val)
}
}
}
source = filter.NewResampleFilter(FixedSampleRate, resampleQuality, 0).Process(source)
encoder, err := libopus.NewEncoder(FixedSampleRate, source.Channels, libopus.AppAudio, writer)
if err != nil {
return err
}
defer encoder.Close()
if bitrate == 0 {
err = encoder.SetBitrateToAuto()
if err != nil {
return err
}
} else {
//kbits to bits
err = encoder.SetBitrate(bitrate * 1024)
if err != nil {
return err
}
}
for block := range source.Blocks {
err = encoder.Encode(cgo.Float32ToInt16(block))
if err != nil {
return err
}
}
return nil
}
func (f Format) Identify(peek []byte, extension string) bool {
return bytes.Compare(peek[:4], []byte{'O', 'g', 'g', 'S'}) == 0 || extension == "opus" || extension == "ogg"
}