Added TTA encoder, updated README.md
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
DataHoarder 2022-02-28 15:47:50 +01:00
parent bad2534ca1
commit 4a7b6f3bea
7 changed files with 168 additions and 23 deletions

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -14,8 +14,6 @@ import (
)
type Format struct {
format.AnalyzerDecoder
format.Encoder
}
func NewFormat() Format {

View file

@ -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 {

View file

@ -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 {

View file

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