Added TTA encoder, updated README.md
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
bad2534ca1
commit
4a7b6f3bea
|
@ -296,6 +296,39 @@ func TestEncodeFLAC(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEncodeTTA(t *testing.T) {
|
||||
t.Parallel()
|
||||
fp, err := os.Open(TestSingleSample24)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
source, err := flac.NewFormat().Open(fp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
target, err := os.CreateTemp("/tmp", "encode_test_*.tta")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
name := target.Name()
|
||||
target.Close()
|
||||
os.Remove(name)
|
||||
}()
|
||||
|
||||
err = tta.NewFormat().Encode(source, target, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeMP3(t *testing.T) {
|
||||
t.Parallel()
|
||||
fp, err := os.Open(TestSingleSample24)
|
||||
|
@ -358,7 +391,7 @@ func TestEncodeOpus(t *testing.T) {
|
|||
options := make(map[string]interface{})
|
||||
options["bitrate"] = "256k"
|
||||
|
||||
err = opus.NewFormat().Encode(audio.NewResampleFilter(opus.FixedSampleRate, audio.Linear, 0).Process(source), target, nil)
|
||||
err = opus.NewFormat().Encode(audio.NewResampleFilter(opus.FixedSampleRate, audio.Linear, 0).Process(source), target, options)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -394,7 +427,44 @@ func TestEncodeAAC(t *testing.T) {
|
|||
options := make(map[string]interface{})
|
||||
options["bitrate"] = "256k"
|
||||
|
||||
err = aac.NewFormat().Encode(source, target, nil)
|
||||
err = aac.NewFormat().Encode(source, target, options)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeAACHE(t *testing.T) {
|
||||
t.Parallel()
|
||||
fp, err := os.Open(TestSingleSample24)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
source, err := flac.NewFormat().Open(fp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
target, err := os.CreateTemp("/tmp", "encode_test_*.aac")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
name := target.Name()
|
||||
target.Close()
|
||||
os.Remove(name)
|
||||
}()
|
||||
|
||||
options := make(map[string]interface{})
|
||||
options["bitrate"] = "256k"
|
||||
options["mode"] = "hev2"
|
||||
|
||||
err = aac.NewFormat().Encode(source, target, options)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
|
16
README.md
16
README.md
|
@ -6,11 +6,17 @@ Collection of audio utilities for decoding/encoding files and streams.
|
|||
* AnalyzerChannel channels / mergers / splitters / trimmers
|
||||
* Audio resampler
|
||||
* Audio downmixing to stereo/mono
|
||||
* FLAC stream decoder and encoder
|
||||
* TTA stream decoder
|
||||
* MP3 stream decoder and encoder
|
||||
* Opus stream decoder and encoder
|
||||
* AAC LC ADTS stream encoder
|
||||
* Multi-format decoder and encoder
|
||||
|
||||
## Formats and codecs supported
|
||||
|
||||
| Codec | Container | Decoder | Analyzer | Encoder | Notes |
|
||||
|:--------:|:---------:|:-------:|:--------:|:-------:|:------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **FLAC** | FLAC | ✅ | ✅ | ✅ | Adjustable encoding compression level and block size. |
|
||||
| **TTA** | TTA | ✅ | ✅ | ✅ | Only 16bit encoding |
|
||||
| **MP3** | MP3 | ✅ | - | ✅ | Adjustable encoding bitrate and mode. Decoding via [minimp3](https://github.com/kvark128/minimp3), encoding via [LAME](https://lame.sourceforge.io/). |
|
||||
| **Opus** | Ogg | ✅ | - | ✅ | Adjustable encoding bitrate. |
|
||||
| **AAC** | ADTS | ❌ | - | ✅ | Adjustable encoding bitrate and mode (LC, HEv2). |
|
||||
|
||||
## Dependencies
|
||||
### Go >= 1.18
|
||||
|
|
|
@ -8,14 +8,12 @@ import "C"
|
|||
import (
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
|
||||
"git.gammaspectra.live/S.O.N.G/go-fdkaac/fdkaac"
|
||||
"io"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type Format struct {
|
||||
format.Encoder
|
||||
}
|
||||
|
||||
func NewFormat() Format {
|
||||
|
@ -24,6 +22,7 @@ func NewFormat() Format {
|
|||
|
||||
func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error {
|
||||
var bitrate = 128
|
||||
var isHEv2 bool
|
||||
|
||||
if options != nil {
|
||||
var val interface{}
|
||||
|
@ -49,12 +48,32 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
bitrate = intVal
|
||||
}
|
||||
}
|
||||
if val, ok = options["mode"]; ok {
|
||||
if strVal, ok = val.(string); ok {
|
||||
switch strVal {
|
||||
case "lc":
|
||||
isHEv2 = false
|
||||
case "hev2":
|
||||
isHEv2 = true
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown setting mode=%s", strVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encoder := fdkaac.NewAacEncoder()
|
||||
err := encoder.InitLc(source.Channels, source.SampleRate, bitrate*1024)
|
||||
if err != nil {
|
||||
return err
|
||||
if isHEv2 {
|
||||
err := encoder.InitHEv2(source.Channels, source.SampleRate, bitrate*1024)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := encoder.InitLc(source.Channels, source.SampleRate, bitrate*1024)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer encoder.Close()
|
||||
|
||||
|
|
|
@ -14,8 +14,6 @@ import (
|
|||
)
|
||||
|
||||
type Format struct {
|
||||
format.AnalyzerDecoder
|
||||
format.Encoder
|
||||
}
|
||||
|
||||
func NewFormat() Format {
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
|
||||
mp3Lib "github.com/kvark128/minimp3"
|
||||
"github.com/viert/go-lame"
|
||||
"io"
|
||||
|
@ -19,8 +18,6 @@ import (
|
|||
const BlockSize = 1024 * 128
|
||||
|
||||
type Format struct {
|
||||
format.Decoder
|
||||
format.Encoder
|
||||
}
|
||||
|
||||
func NewFormat() Format {
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
|
||||
libopus "git.gammaspectra.live/S.O.N.G/go-pus"
|
||||
"io"
|
||||
)
|
||||
|
@ -17,8 +16,6 @@ import (
|
|||
const FixedSampleRate int = 48000
|
||||
|
||||
type Format struct {
|
||||
format.Decoder
|
||||
format.Encoder
|
||||
}
|
||||
|
||||
func NewFormat() Format {
|
||||
|
|
|
@ -12,13 +12,13 @@ import (
|
|||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
|
||||
libtta "git.gammaspectra.live/S.O.N.G/go-tta"
|
||||
"io"
|
||||
"math"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const BlockSize = 1024 * 128
|
||||
|
||||
type Format struct {
|
||||
format.AnalyzerDecoder
|
||||
}
|
||||
|
||||
func NewFormat() Format {
|
||||
|
@ -43,14 +43,14 @@ func (i fakeReadWriteSeeker) Write(p []byte) (n int, err error) {
|
|||
}
|
||||
|
||||
type fakeReadWriteSeeker2 struct {
|
||||
w format.WriteSeekCloser
|
||||
w io.WriteCloser
|
||||
}
|
||||
|
||||
func (i fakeReadWriteSeeker2) Read(p []byte) (n int, err error) {
|
||||
return 0, io.ErrNoProgress
|
||||
}
|
||||
func (i fakeReadWriteSeeker2) Seek(offset int64, whence int) (int64, error) {
|
||||
return i.w.Seek(offset, whence)
|
||||
return 0, io.ErrNoProgress
|
||||
}
|
||||
func (i fakeReadWriteSeeker2) Close() error {
|
||||
return i.w.Close()
|
||||
|
@ -167,6 +167,64 @@ func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.Analyzer
|
|||
}, analyzerChannel, nil
|
||||
}
|
||||
|
||||
func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error {
|
||||
|
||||
var bitsPerSample = 16
|
||||
|
||||
if options != nil {
|
||||
var val interface{}
|
||||
var ok bool
|
||||
var intVal int
|
||||
if val, ok = options["bitdepth"]; ok {
|
||||
if intVal, ok = val.(int); ok {
|
||||
bitsPerSample = intVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bitsPerSample != 16 { //TODO
|
||||
return errors.New("not supported bits")
|
||||
}
|
||||
|
||||
var encoder *libtta.Encoder
|
||||
|
||||
if writeSeeker, ok := writer.(io.ReadWriteSeeker); ok {
|
||||
encoder = libtta.MakeEncoder(writeSeeker)
|
||||
} else {
|
||||
encoder = libtta.MakeEncoder(fakeReadWriteSeeker2{w: writer})
|
||||
}
|
||||
|
||||
if encoder == nil {
|
||||
return errors.New("could not create encoder")
|
||||
}
|
||||
|
||||
defer encoder.Close()
|
||||
|
||||
err := encoder.SetInfo(&libtta.Info{
|
||||
Format: 1,
|
||||
Nch: uint32(source.Channels),
|
||||
Bps: uint32(bitsPerSample),
|
||||
Sps: uint32(source.SampleRate),
|
||||
Samples: math.MaxUint32,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for block := range source.Blocks {
|
||||
samples := make([]int16, len(block))
|
||||
|
||||
C.audio_float32_to_int16((*C.float)(&block[0]), C.size_t(len(block)), (*C.int16_t)(&samples[0]))
|
||||
|
||||
encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*2), nil)
|
||||
}
|
||||
|
||||
encoder.Finalize()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Format) Identify(peek []byte, extension string) bool {
|
||||
return bytes.Compare(peek[:4], []byte{'T', 'T', 'A', '1'}) == 0 || extension == "tta"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue