DataHoarder
439ad19af8
All checks were successful
continuous-integration/drone/push Build is passing
185 lines
4 KiB
Go
185 lines
4 KiB
Go
//go:build !disable_format_opus && cgo
|
|
|
|
package opus
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"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/audio/format"
|
|
libopus "git.gammaspectra.live/S.O.N.G/go-pus"
|
|
"golang.org/x/exp/slices"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
const FixedSampleRate int = 48000
|
|
|
|
type Format struct {
|
|
}
|
|
|
|
func NewFormat() Format {
|
|
return Format{}
|
|
}
|
|
|
|
func (f Format) Name() string {
|
|
return "opus"
|
|
}
|
|
|
|
func (f Format) DecoderDescription() string {
|
|
return fmt.Sprintf("%s (S.O.N.G/go-pus)", libopus.Version())
|
|
}
|
|
|
|
func (f Format) EncoderDescription() string {
|
|
return f.DecoderDescription()
|
|
}
|
|
|
|
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|
stream, err := libopus.NewStream(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
info := stream.Info()
|
|
if info == nil {
|
|
return nil, errors.New("could not get stream information")
|
|
}
|
|
|
|
channelCount := info.ChannelCount
|
|
source := audio.NewSource[float32](16, FixedSampleRate, channelCount)
|
|
|
|
go func() {
|
|
defer stream.Close()
|
|
defer source.Close()
|
|
|
|
buf := make([]float32, 120*FixedSampleRate*channelCount)
|
|
|
|
for {
|
|
//Check channels each try to see if new links have different channel count
|
|
if stream.Channels() != channelCount {
|
|
//Channel count changed!
|
|
return
|
|
}
|
|
|
|
n, err := stream.ReadFloat32(buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if n > 0 {
|
|
source.IngestFloat32(slices.Clone(buf[:n*channelCount]))
|
|
}
|
|
}
|
|
}()
|
|
|
|
//We always get two channels via stereo api
|
|
return source, nil
|
|
}
|
|
|
|
func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error {
|
|
|
|
var bitrate = 0
|
|
var complexity = 10
|
|
var delay = time.Millisecond * 1000
|
|
var resampleQuality = filter.QualityFastest
|
|
|
|
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)
|
|
}
|
|
}
|
|
if val, ok = options["complexity"]; ok {
|
|
if intVal, ok = val.(int); ok {
|
|
complexity = intVal
|
|
} else if int64Val, ok = val.(int64); ok {
|
|
complexity = int(int64Val)
|
|
}
|
|
}
|
|
if val, ok = options["delay"]; ok {
|
|
if intVal, ok = val.(int); ok {
|
|
delay = time.Millisecond * time.Duration(intVal)
|
|
} else if int64Val, ok = val.(int64); ok {
|
|
delay = time.Millisecond * time.Duration(int64Val)
|
|
}
|
|
}
|
|
}
|
|
|
|
source = filter.NewResampleFilter(FixedSampleRate, resampleQuality, 0).Process(source)
|
|
|
|
encoder, err := libopus.NewEncoder(FixedSampleRate, source.GetChannels(), 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
|
|
}
|
|
}
|
|
|
|
if err = encoder.SetComplexity(complexity); err != nil {
|
|
return err
|
|
}
|
|
if err = encoder.SetMuxingDelay(delay); err != nil {
|
|
return err
|
|
}
|
|
|
|
for block := range source.ToFloat32().GetBlocks() {
|
|
err = encoder.EncodeFloat32(block)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func (f Format) Identify(peek [format.IdentifyPeekBytes]byte, extension string) bool {
|
|
return bytes.Compare(peek[:4], []byte{'O', 'g', 'g', 'S'}) == 0 || extension == "opus" || extension == "ogg"
|
|
}
|