Added MP3 encoding via LAME
This commit is contained in:
parent
9c27f76805
commit
41d0fdde5d
|
@ -13,7 +13,7 @@ steps:
|
|||
image: golang:1.18rc1-bullseye
|
||||
commands:
|
||||
- DEBIAN_FRONTEND=noninteractive apt update
|
||||
- DEBIAN_FRONTEND=noninteractive apt install -y libflac-dev libopus-dev libopusfile-dev libsamplerate0-dev
|
||||
- DEBIAN_FRONTEND=noninteractive apt install -y libflac-dev libopus-dev libopusfile-dev libsamplerate0-dev libmp3lame-dev
|
||||
- go test -cover -coverpkg=git.gammaspectra.live/S.O.N.G/Kirika/hasher,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/Kirika/audio/format/flac,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/mp3,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/opus,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/tta -v
|
||||
|
||||
...
|
||||
|
|
|
@ -262,6 +262,71 @@ func TestFilterChain(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEncodeFLAC(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_*.flac")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
name := target.Name()
|
||||
target.Close()
|
||||
os.Remove(name)
|
||||
}()
|
||||
|
||||
err = flac.NewFormat().Encode(source, target, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
func TestEncodeMP3(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_*.mp3")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
name := target.Name()
|
||||
target.Close()
|
||||
os.Remove(name)
|
||||
}()
|
||||
|
||||
err = mp3.NewFormat().Encode(source, target, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -24,6 +24,11 @@ sudo apt install libflac-dev
|
|||
sudo apt install libopus-dev libopusfile-dev
|
||||
```
|
||||
|
||||
### [LAME](https://lame.sourceforge.io/) (required by [go-lame](hhttps://github.com/viert/go-lame))
|
||||
```shell
|
||||
sudo apt install libmp3lame-dev
|
||||
```
|
||||
|
||||
### [libsamplerate](https://github.com/libsndfile/libsamplerate) (required by [gosamplerate](https://github.com/dh1tw/gosamplerate))
|
||||
```shell
|
||||
sudo apt install libsamplerate0-dev
|
||||
|
|
|
@ -6,9 +6,12 @@ package mp3
|
|||
*/
|
||||
import "C"
|
||||
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"
|
||||
"unsafe"
|
||||
)
|
||||
|
@ -17,6 +20,7 @@ const BlockSize = 1024 * 128
|
|||
|
||||
type Format struct {
|
||||
format.Format
|
||||
format.Encoder
|
||||
}
|
||||
|
||||
func NewFormat() Format {
|
||||
|
@ -61,6 +65,98 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error {
|
||||
var vbr = true
|
||||
var bitrate = 0
|
||||
|
||||
if options != nil {
|
||||
var val interface{}
|
||||
var ok bool
|
||||
var intVal int
|
||||
var strVal string
|
||||
if val, ok = options["bitrate"]; ok {
|
||||
if strVal, ok = val.(string); ok {
|
||||
switch strVal {
|
||||
case "v0":
|
||||
vbr = true
|
||||
bitrate = 0
|
||||
case "v1":
|
||||
vbr = true
|
||||
bitrate = 1
|
||||
case "v2":
|
||||
vbr = true
|
||||
bitrate = 2
|
||||
case "v3":
|
||||
vbr = true
|
||||
bitrate = 3
|
||||
case "320k":
|
||||
vbr = false
|
||||
bitrate = 320
|
||||
case "256k":
|
||||
vbr = false
|
||||
bitrate = 256
|
||||
case "192k":
|
||||
vbr = false
|
||||
bitrate = 192
|
||||
case "128k":
|
||||
vbr = false
|
||||
bitrate = 128
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown setting bitrate=%s", strVal)
|
||||
}
|
||||
} else if intVal, ok = val.(int); ok {
|
||||
vbr = false
|
||||
bitrate = intVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encoder := lame.NewEncoder(writer)
|
||||
|
||||
if encoder == nil {
|
||||
return errors.New("could not create encoder")
|
||||
}
|
||||
defer encoder.Close()
|
||||
if err := encoder.SetNumChannels(source.Channels); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := encoder.SetInSamplerate(source.SampleRate); err != nil {
|
||||
return err
|
||||
}
|
||||
encoder.SetWriteID3TagAutomatic(false)
|
||||
|
||||
if vbr {
|
||||
if err := encoder.SetVBR(lame.VBRMTRH); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := encoder.SetVBRQuality(float64(bitrate)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := encoder.SetVBR(lame.VBROff); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := encoder.SetBrate(bitrate); 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]))
|
||||
|
||||
_, err := encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*2))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Format) Identify(peek []byte, extension string) bool {
|
||||
return /*bytes.Compare(peek[:4], []byte{'f', 'L', 'a', 'C'}) == 0 || */ extension == "mp3"
|
||||
}
|
||||
|
|
13
cgo/audio.h
13
cgo/audio.h
|
@ -185,4 +185,17 @@ __attribute__((weak)) void audio_float32_to_int32(float* restrict data, size_t d
|
|||
}
|
||||
buffer[i] = (int32_t)(f * BITS_TO_DIV(bitDepth));
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((weak)) void audio_float32_to_int16(float* restrict data, size_t data_len, int16_t* restrict buffer){
|
||||
for (int i = 0; i < data_len; ++i){
|
||||
float f = data[i];
|
||||
if (f < -1.0) {
|
||||
f = -1.0;
|
||||
}
|
||||
if (f > 1.0) {
|
||||
f = 1.0;
|
||||
}
|
||||
buffer[i] = (int16_t)(f * BITS_TO_DIV(16));
|
||||
}
|
||||
}
|
1
go.mod
1
go.mod
|
@ -8,6 +8,7 @@ require (
|
|||
git.gammaspectra.live/S.O.N.G/goflac v0.0.0-20220223152921-827e6c3f729f
|
||||
github.com/dh1tw/gosamplerate v0.1.2
|
||||
github.com/kvark128/minimp3 v0.0.0-20211109174940-101188771a65
|
||||
github.com/viert/go-lame v0.0.0-20201108052322-bb552596b11d
|
||||
)
|
||||
|
||||
require github.com/klauspost/cpuid v1.3.1 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -11,3 +11,5 @@ github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s
|
|||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/kvark128/minimp3 v0.0.0-20211109174940-101188771a65 h1:8qfVQv7MSACDXadEwl1yjUKJ68yC9B7nR4cioEoCfH0=
|
||||
github.com/kvark128/minimp3 v0.0.0-20211109174940-101188771a65/go.mod h1:hIq9nAqNcwTySvnFhCe1C8xC/STIr2Fe5vJ52zk1jkE=
|
||||
github.com/viert/go-lame v0.0.0-20201108052322-bb552596b11d h1:LptdD7GTUZeklomtW5vZ1AHwBvDBUCZ2Ftpaz7uEI7g=
|
||||
github.com/viert/go-lame v0.0.0-20201108052322-bb552596b11d/go.mod h1:EqTcYM7y4JlSfeTI47pmNu3EZQuCuLQefsQyg1Imlz8=
|
||||
|
|
Loading…
Reference in a new issue