//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" }