Kirika/audio/format/flac/goflac.go

231 lines
5 KiB
Go

//go:build !disable_format_flac && (disable_codec_libflac || !cgo)
package flac
import (
"bytes"
"errors"
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
libflac "git.gammaspectra.live/S.O.N.G/flacgo"
"git.gammaspectra.live/S.O.N.G/flacgo/frame"
"git.gammaspectra.live/S.O.N.G/flacgo/meta"
"io"
)
type Format struct {
}
func NewFormat() Format {
return Format{}
}
func (f Format) Name() string {
return "flac"
}
func (f Format) DecoderDescription() string {
return "S.O.N.G/flacgo"
}
func (f Format) EncoderDescription() string {
return f.DecoderDescription()
}
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
decoder, err := libflac.New(r)
if err != nil {
return nil, err
}
source := audio.NewSource[int32](int(decoder.Info.BitsPerSample), int(decoder.Info.SampleRate), int(decoder.Info.NChannels))
go func() {
defer source.Close()
defer decoder.Close()
frameNumber := 0
for {
currentFrame, err := decoder.ParseNext()
if err != nil {
return
}
bitDepth := int(decoder.Info.BitsPerSample)
channels := int(decoder.Info.NChannels)
if currentFrame.BitsPerSample != 0 {
bitDepth = int(currentFrame.BitsPerSample)
}
if len(currentFrame.Subframes) != 0 {
channels = len(currentFrame.Subframes)
}
buffer := make([]int32, len(currentFrame.Subframes[0].Samples)*channels)
for i := range buffer {
buffer[i] = currentFrame.Subframes[i%channels].Samples[i/channels]
}
source.IngestInt32(buffer, bitDepth)
frameNumber++
}
}()
return source, nil
}
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) {
return format.NewAnalyzerChannel(f.Open(r))
}
func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error {
var bitsPerSample uint8 = 16
var blockSize uint16 = 0
var ogg = false
if options != nil {
var val interface{}
var ok bool
var intVal int
var int64Val int64
var boolVal bool
if val, ok = options["bitdepth"]; ok {
if intVal, ok = val.(int); ok {
bitsPerSample = uint8(intVal)
} else if int64Val, ok = val.(int64); ok {
bitsPerSample = uint8(int(int64Val))
}
}
if val, ok = options["block_size"]; ok {
if intVal, ok = val.(int); ok {
blockSize = uint16(intVal)
} else if int64Val, ok = val.(int64); ok {
blockSize = uint16(int(int64Val))
}
}
if val, ok = options["ogg"]; ok {
if boolVal, ok = val.(bool); ok {
ogg = boolVal
}
}
}
if blockSize == 0 {
blockSize = 4096
}
sampleRate := uint32(source.GetSampleRate())
if source.GetChannels() > 8 {
return errors.New("inputs with more than 8 channels not supported")
}
channels := frame.Channels(source.GetChannels() - 1)
if ogg {
return errors.New("ogg not supported")
}
encoder, err := libflac.NewEncoder(writer, &meta.StreamInfo{
BlockSizeMin: blockSize,
BlockSizeMax: blockSize,
SampleRate: sampleRate,
NChannels: uint8(channels.Count()),
BitsPerSample: bitsPerSample,
})
if err != nil {
return err
}
defer encoder.Close()
ccount := channels.Count()
subframes := make([]*frame.Subframe, ccount)
for i := 0; i < ccount; i++ {
subframes[i] = &frame.Subframe{
Samples: make([]int32, blockSize),
NSamples: int(blockSize),
}
}
buf := make([]int32, 0, blockSize)
//Uses verbatim samples + constant. No savings will be had, but it's a proper FLAC
for block := range source.ToInt32(int(bitsPerSample)).GetBlocks() {
buf = append(buf, block...)
for len(buf) >= int(blockSize)*ccount {
for i := 0; i < ccount; i++ {
//De-interleave samples
for j := 0; j < int(blockSize); j++ {
subframes[i].Samples[j] = buf[j*ccount+i]
}
}
err = encoder.WriteFrame(&frame.Frame{
Header: frame.Header{
HasFixedBlockSize: true,
BlockSize: blockSize,
SampleRate: sampleRate,
Channels: channels,
BitsPerSample: bitsPerSample,
},
Subframes: subframes,
})
if err != nil {
return err
}
buf = buf[int(blockSize)*ccount:]
}
if len(buf) == 0 {
//free
buf = nil
}
}
if len(buf) > 0 {
for i := 0; i < ccount; i++ {
//De-interleave samples
for j := 0; j < len(buf)/ccount; j++ {
subframes[i].Samples[j] = buf[j*ccount+i]
}
subframes[i].Samples = subframes[i].Samples[0 : len(buf)/ccount]
subframes[i].NSamples = len(subframes[i].Samples)
}
err = encoder.WriteFrame(&frame.Frame{
Header: frame.Header{
HasFixedBlockSize: true,
BlockSize: uint16(subframes[0].NSamples),
SampleRate: sampleRate,
Channels: channels,
BitsPerSample: bitsPerSample,
},
Subframes: subframes,
})
if err != nil {
return err
}
}
return nil
}
func (f Format) Identify(peek [format.IdentifyPeekBytes]byte, extension string) bool {
//No Ogg supported
return bytes.Compare(peek[:4], []byte{'f', 'L', 'a', 'C'}) == 0 || (bytes.Compare(peek[:3], []byte{'I', 'D', '3'}) == 0 && extension != "mp3") || extension == "flac"
}