Added support for building using CGO_ENABLED=0 with limited codec and performance
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
aa67533e41
commit
a6134dfd3e
|
@ -19,8 +19,9 @@ steps:
|
|||
- git clone --depth 1 https://gitlab.xiph.org/xiph/libopusenc.git && cd libopusenc && ./autogen.sh && ./configure --prefix /usr && make && make install && cd ..
|
||||
- git clone --depth 1 https://git.gammaspectra.live/S.O.N.G/alac.git && cd alac && autoreconf -fi && ./configure --prefix /usr && make && make install && cd ..
|
||||
- go test -cover -v ./...
|
||||
- go test -cover -v -tags=disable_codec_libfdk-aac,disable_codec_lame,disable_codec_tta,disable_codec_libflac,enable_codec_libalac ./...
|
||||
- go test -cover -v -tags=disable_codec_libfdk-aac,disable_codec_lame,disable_codec_tta,disable_codec_libflac ./...
|
||||
- go test -cover -v -tags=disable_format_aac,disable_format_alac,disable_format_mp3,disable_format_opus,disable_format_tta,disable_format_vorbis ./...
|
||||
- CGO_ENABLED=0 GOOS=linux go test -cover -v ./...
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
|
@ -42,6 +43,7 @@ steps:
|
|||
- git clone --depth 1 https://github.com/mstorsjo/fdk-aac.git && cd fdk-aac && ./autogen.sh && ./configure --prefix /usr && make -j$(nproc) && make install && cd ..
|
||||
- git clone --depth 1 https://git.gammaspectra.live/S.O.N.G/alac.git && cd alac && autoreconf -fi && ./configure --prefix /usr && make && make install && cd ..
|
||||
- go test -cover -v ./...
|
||||
- go test -cover -v -tags=disable_codec_libfdk-aac,disable_codec_lame,disable_codec_tta,enable_codec_libalac ./...
|
||||
- go test -cover -v -tags=disable_codec_libfdk-aac,disable_codec_lame,disable_codec_tta,disable_codec_libflac ./...
|
||||
- go test -cover -v -tags=disable_format_aac,disable_format_alac,disable_format_mp3,disable_format_opus,disable_format_tta,disable_format_vorbis ./...
|
||||
- CGO_ENABLED=0 GOOS=linux go test -cover -v ./...
|
||||
...
|
||||
|
|
30
README.md
30
README.md
|
@ -102,6 +102,26 @@ sudo apt install libebur128-dev
|
|||
## Build tags
|
||||
Several Golang build tags exist to change which features are included in the project.
|
||||
|
||||
### CGO_ENABLED=0
|
||||
This tag disables CGO support, and disables any formats/codecs that require it. Some other features will degrade or be less performant, or alternative decoders be used.
|
||||
|
||||
Additionally, libebur128 (used for normalization) will be disabled.
|
||||
|
||||
Some tests cannot be completed in this mode.
|
||||
|
||||
#### Codecs supported, CGO_ENABLED=0
|
||||
|
||||
| Codec | Containers | Decoder | Analyzer | Encoder | Notes |
|
||||
|:----------:|:------------------------------------------------------------:|:-------:|:--------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **FLAC** | [FLAC](https://xiph.org/flac/format.html) | ✅ | ✅ | ❌ | Decoding by [mewkiz/flac](https://github.com/mewkiz/flac). |
|
||||
| **TTA** | [TTA](https://www.tausoft.org/en/true_audio_codec_format/) | ✅ | ✅ | ✅ | Decoding/encoding via [go-tta](https://git.gammaspectra.live/S.O.N.G/go-tta). |
|
||||
| **MP3** | [MP3](http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html) | ✅ | - | ❌ | Decoding via [go-mp3](https://github.com/hajimehoshi/go-mp3). |
|
||||
| **Opus** | - | ❌ | - | ❌ | |
|
||||
| **Vorbis** | [Ogg](https://www.xiph.org/ogg/doc/framing.html) | ✅ | - | ❌ | Decoding by [jfreymuth/vorbis](https://github.com/jfreymuth/vorbis) via [jfreymuth/oggvorbis](https://github.com/jfreymuth/oggvorbis). |
|
||||
| **AAC** | - | ❌ | - | ❌ | |
|
||||
| **ALAC** | - | ❌ | - | ❌ | |
|
||||
|
||||
|
||||
### disable_format_[format]
|
||||
This tag disables support for the specified format/codec. This does not affect packetizers.
|
||||
|
||||
|
@ -161,7 +181,7 @@ Subdependencies that are not cgo-based are denoted in cursive.
|
|||
| [S.O.N.G/alac](https://git.gammaspectra.live/S.O.N.G/alac) | C++/C | [Apache 2.0](https://git.gammaspectra.live/S.O.N.G/alac/src/branch/master/LICENSE) | Can be linked by cgo as a shared library. |
|
||||
| [S.O.N.G/go-ebur128](https://git.gammaspectra.live/S.O.N.G/go-ebur128) | Go | [BSD 2-Clause](https://git.gammaspectra.live/S.O.N.G/go-ebur128/src/branch/master/LICENSE) | |
|
||||
| [jiixyj/libebur128](https://github.com/jiixyj/libebur128) | C | [MIT](https://github.com/jiixyj/libebur128/blob/master/COPYING) | Can be linked by cgo as a shared library. |
|
||||
| [S.O.N.G/go-fdkaac](https://git.gammaspectra.live/S.O.N.G/go-fdkaac) | Go | | [MIT](https://git.gammaspectra.live/S.O.N.G/go-fdkaac/src/branch/master/LICENSE) | |
|
||||
| [S.O.N.G/go-fdkaac](https://git.gammaspectra.live/S.O.N.G/go-fdkaac) | Go | [MIT](https://git.gammaspectra.live/S.O.N.G/go-fdkaac/src/branch/master/LICENSE) | |
|
||||
| [mstorsjo/fdk-aac](https://github.com/mstorsjo/fdk-aac) | C++/C | [FDK License, BSD-like](https://github.com/mstorsjo/fdk-aac/blob/master/NOTICE) | Does not include patent grants.<br/>Considered free by [Red hat](https://bugzilla.redhat.com/show_bug.cgi?id=1501522), [Fedora](https://fedoraproject.org/wiki/Licensing/FDK-AAC), [GNU](https://www.gnu.org/licenses/license-list.html#fdk), [FSF](https://www.fsf.org/blogs/licensing/recent-licensing-updates#:~:text=maintain%20free%20versions.-,The%20Fraunhofer%20FDK%20AAC%20license,-We%20recently%20added); and non-free by [Debian](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=694257).<br/>Can be linked by cgo as a shared library. |
|
||||
| [S.O.N.G/go-pus](https://git.gammaspectra.live/S.O.N.G/go-pus) | Go | [MIT](https://git.gammaspectra.live/S.O.N.G/go-pus/src/branch/v2/LICENSE) | |
|
||||
| [xiph/opus](https://gitlab.xiph.org/xiph/opus) | C | [BSD 3-Clause](https://gitlab.xiph.org/xiph/opus/-/blob/master/COPYING) | Read extra license details on the [official site](https://opus-codec.org/license/).<br/>Can be linked by cgo as a shared library. |
|
||||
|
@ -176,11 +196,13 @@ Subdependencies that are not cgo-based are denoted in cursive.
|
|||
| [edgeware/mp4ff](https://github.com/edgeware/mp4ff) | Go | [MIT](https://github.com/edgeware/mp4ff/blob/master/LICENSE.md) | |
|
||||
| [gen2brain/aac-go](https://github.com/gen2brain/aac-go) | Go | [Apache 2.0](https://github.com/gen2brain/aac-go/blob/master/COPYING) | |
|
||||
| [mstorsjo/vo-aacenc](https://github.com/mstorsjo/vo-aacenc) | C | [Apache 2.0](https://github.com/mstorsjo/vo-aacenc/blob/master/COPYING) | Subdependency and included as part of _gen2brain/aac-go_. |
|
||||
| [jfreymuth/oggvorbis](https://github.com/jfreymuth/oggvorbis) | Go | [MIT](https://github.com/jfreymuth/oggvorbis/blob/master/LICENSE) | | |
|
||||
| _[jfreymuth/vorbis](https://github.com/jfreymuth/vorbis)_ | Go | [MIT](https://github.com/jfreymuth/vorbis/blob/master/LICENSE) | Subdependency of _jfreymuth/oggvorbis_. | |
|
||||
| [kvark128/minimp3](https://github.com/kvark128/minimp3) | Go | [MIT](https://github.com/kvark128/minimp3/blob/master/LICENSE.txt) | | |
|
||||
| [hajimehoshi/go-mp3](https://github.com/hajimehoshi/go-mp3) | Go | [Apache 2.0](https://github.com/hajimehoshi/go-mp3/blob/main/LICENSE) | |
|
||||
| [jfreymuth/oggvorbis](https://github.com/jfreymuth/oggvorbis) | Go | [MIT](https://github.com/jfreymuth/oggvorbis/blob/master/LICENSE) | |
|
||||
| _[jfreymuth/vorbis](https://github.com/jfreymuth/vorbis)_ | Go | [MIT](https://github.com/jfreymuth/vorbis/blob/master/LICENSE) | Subdependency of _jfreymuth/oggvorbis_. |
|
||||
| [kvark128/minimp3](https://github.com/kvark128/minimp3) | Go | [MIT](https://github.com/kvark128/minimp3/blob/master/LICENSE.txt) | |
|
||||
| [lieff/minimp3](https://github.com/lieff/minimp3) | C | [CC0 1.0](https://github.com/lieff/minimp3/blob/master/LICENSE) | Subdependency and included as part of _lieff/minimp3_. |
|
||||
| [mewkiz/flac](https://github.com/mewkiz/flac) | Go | [The Unlicense](https://github.com/mewkiz/flac/blob/master/LICENSE) | |
|
||||
| [oov/audio](https://github.com/oov/audio) | Go | [MIT](https://github.com/oov/audio/blob/master/LICENSE) | Used only for its audio resampler. |
|
||||
| _[go-audio/audio](https://github.com/go-audio/audio)_ | Go | [Apache 2.0](https://github.com/go-audio/audio/blob/master/LICENSE) | Subdependency of _mewkiz/flac_. Only used on tests there. |
|
||||
| _[go-audio/wav](https://github.com/go-audio/wav)_ | Go | [Apache 2.0](https://github.com/go-audio/wav/blob/master/LICENSE) | Subdependency of _mewkiz/flac_. Only used on tests there. |
|
||||
| _[go-audio/riff](https://github.com/go-audio/riff)_ | Go | [Apache 2.0](https://github.com/go-audio/riff/blob/master/LICENSE) | Subdependency of _go-audio/wav_. Only used on tests there. |
|
||||
|
|
|
@ -3,7 +3,6 @@ package filter
|
|||
import (
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/cgo"
|
||||
"github.com/dh1tw/gosamplerate"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
@ -162,87 +161,3 @@ func (f MonoFilter) Process(source audio.Source) audio.Source {
|
|||
Blocks: outBlocks,
|
||||
}
|
||||
}
|
||||
|
||||
type ResampleFilter struct {
|
||||
sampleRate int
|
||||
quality ResampleQuality
|
||||
blockSize int
|
||||
}
|
||||
|
||||
type ResampleQuality int
|
||||
|
||||
const (
|
||||
BandlimitedBest ResampleQuality = gosamplerate.SRC_SINC_BEST_QUALITY
|
||||
BandlimitedMedium ResampleQuality = gosamplerate.SRC_SINC_MEDIUM_QUALITY
|
||||
BandlimitedFastest ResampleQuality = gosamplerate.SRC_SINC_FASTEST
|
||||
ZeroOrderHold ResampleQuality = gosamplerate.SRC_ZERO_ORDER_HOLD
|
||||
Linear ResampleQuality = gosamplerate.SRC_LINEAR
|
||||
)
|
||||
|
||||
func NewResampleFilter(sampleRate int, quality ResampleQuality, blockSize int) ResampleFilter {
|
||||
if blockSize == 0 {
|
||||
blockSize = 1024 * 64
|
||||
}
|
||||
return ResampleFilter{
|
||||
sampleRate: sampleRate,
|
||||
quality: quality,
|
||||
blockSize: blockSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (f ResampleFilter) Process(source audio.Source) audio.Source {
|
||||
if source.SampleRate == f.sampleRate { //no change
|
||||
return source
|
||||
}
|
||||
|
||||
outBlocks := make(chan []float32)
|
||||
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
|
||||
blockSize := f.blockSize * source.Channels
|
||||
samplerateConverter, err := gosamplerate.New(int(f.quality), source.Channels, blockSize)
|
||||
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer gosamplerate.Delete(samplerateConverter)
|
||||
|
||||
ratio := float64(f.sampleRate) / float64(source.SampleRate)
|
||||
|
||||
for block := range source.Blocks {
|
||||
for len(block) >= blockSize {
|
||||
b, err := samplerateConverter.Process(block[0:blockSize], ratio, false)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
if len(b) > 0 {
|
||||
outBlocks <- b
|
||||
}
|
||||
block = block[blockSize:]
|
||||
}
|
||||
if len(block) > 0 {
|
||||
b, err := samplerateConverter.Process(block, ratio, false)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
if len(b) > 0 {
|
||||
outBlocks <- b
|
||||
}
|
||||
}
|
||||
}
|
||||
b, err := samplerateConverter.Process([]float32{}, ratio, true)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
if len(b) > 0 {
|
||||
outBlocks <- b
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: f.sampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestFilterChain(t *testing.T) {
|
||||
func TestFilterChainNoResample(t *testing.T) {
|
||||
t.Parallel()
|
||||
fp, err := os.Open(test.TestSingleSample24)
|
||||
if err != nil {
|
||||
|
@ -22,20 +22,15 @@ func TestFilterChain(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
const sampleRate = 16000
|
||||
|
||||
result := NewFilterChain(source, MonoFilter{}, NewResampleFilter(sampleRate, BandlimitedFastest, 0), StereoFilter{})
|
||||
result := NewFilterChain(source, MonoFilter{}, StereoFilter{})
|
||||
|
||||
sink := audio.NewForwardSink(audio.NewNullSink())
|
||||
sink.Process(result)
|
||||
|
||||
if result.SampleRate != sampleRate {
|
||||
t.Errorf("Wrong SampleRate %d != %d", result.SampleRate, sampleRate)
|
||||
}
|
||||
if result.Channels != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.SampleRate, 2)
|
||||
}
|
||||
if sink.SamplesRead != 6284999 {
|
||||
t.Errorf("Wrong Sample Count %d != %d", sink.SamplesRead, 6284999)
|
||||
if sink.SamplesRead != 17323031 {
|
||||
t.Errorf("Wrong Sample Count %d != %d", sink.SamplesRead, 17323031)
|
||||
}
|
||||
}
|
||||
|
|
20
audio/filter/resample_filter.go
Normal file
20
audio/filter/resample_filter.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package filter
|
||||
|
||||
type ResampleFilter struct {
|
||||
sampleRate int
|
||||
quality ResampleQuality
|
||||
blockSize int
|
||||
}
|
||||
|
||||
type ResampleQuality int
|
||||
|
||||
func NewResampleFilter(sampleRate int, quality ResampleQuality, blockSize int) ResampleFilter {
|
||||
if blockSize == 0 {
|
||||
blockSize = 1024 * 64
|
||||
}
|
||||
return ResampleFilter{
|
||||
sampleRate: sampleRate,
|
||||
quality: quality,
|
||||
blockSize: blockSize,
|
||||
}
|
||||
}
|
73
audio/filter/resample_filter_cgo.go
Normal file
73
audio/filter/resample_filter_cgo.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
//go:build cgo
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"github.com/dh1tw/gosamplerate"
|
||||
"log"
|
||||
)
|
||||
|
||||
const (
|
||||
QualityBest ResampleQuality = gosamplerate.SRC_SINC_BEST_QUALITY
|
||||
QualityGood ResampleQuality = gosamplerate.SRC_SINC_MEDIUM_QUALITY
|
||||
QualityFast ResampleQuality = gosamplerate.SRC_SINC_FASTEST
|
||||
QualityFastest ResampleQuality = gosamplerate.SRC_LINEAR
|
||||
)
|
||||
|
||||
func (f ResampleFilter) Process(source audio.Source) audio.Source {
|
||||
if source.SampleRate == f.sampleRate { //no change
|
||||
return source
|
||||
}
|
||||
|
||||
outBlocks := make(chan []float32)
|
||||
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
|
||||
blockSize := f.blockSize * source.Channels
|
||||
samplerateConverter, err := gosamplerate.New(int(f.quality), source.Channels, blockSize)
|
||||
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer gosamplerate.Delete(samplerateConverter)
|
||||
|
||||
ratio := float64(f.sampleRate) / float64(source.SampleRate)
|
||||
|
||||
for block := range source.Blocks {
|
||||
for len(block) >= blockSize {
|
||||
b, err := samplerateConverter.Process(block[0:blockSize], ratio, false)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
if len(b) > 0 {
|
||||
outBlocks <- b
|
||||
}
|
||||
block = block[blockSize:]
|
||||
}
|
||||
if len(block) > 0 {
|
||||
b, err := samplerateConverter.Process(block, ratio, false)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
if len(b) > 0 {
|
||||
outBlocks <- b
|
||||
}
|
||||
}
|
||||
}
|
||||
b, err := samplerateConverter.Process([]float32{}, ratio, true)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
if len(b) > 0 {
|
||||
outBlocks <- b
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: f.sampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
}
|
43
audio/filter/resample_filter_cgo_test.go
Normal file
43
audio/filter/resample_filter_cgo_test.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
//go:build cgo
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/test"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFilterChainResample(t *testing.T) {
|
||||
t.Parallel()
|
||||
fp, err := os.Open(test.TestSingleSample24)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
source, err := flac.NewFormat().Open(fp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
const sampleRate = 16000
|
||||
|
||||
result := NewFilterChain(source, MonoFilter{}, NewResampleFilter(sampleRate, QualityFast, 0), StereoFilter{})
|
||||
|
||||
sink := audio.NewForwardSink(audio.NewNullSink())
|
||||
sink.Process(result)
|
||||
|
||||
if result.SampleRate != sampleRate {
|
||||
t.Errorf("Wrong SampleRate %d != %d", result.SampleRate, sampleRate)
|
||||
}
|
||||
if result.Channels != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.SampleRate, 2)
|
||||
}
|
||||
if sink.SamplesRead != 6284999 {
|
||||
t.Errorf("Wrong Sample Count %d != %d", sink.SamplesRead, 6284999)
|
||||
}
|
||||
}
|
72
audio/filter/resample_filter_nocgo.go
Normal file
72
audio/filter/resample_filter_nocgo.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
//go:build !cgo
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"github.com/oov/audio/resampler"
|
||||
"log"
|
||||
)
|
||||
|
||||
const (
|
||||
QualityBest ResampleQuality = 10
|
||||
QualityGood ResampleQuality = 8
|
||||
QualityFast ResampleQuality = 5
|
||||
QualityFastest ResampleQuality = 0
|
||||
)
|
||||
|
||||
func (f ResampleFilter) Process(source audio.Source) audio.Source {
|
||||
if source.SampleRate == f.sampleRate { //no change
|
||||
return source
|
||||
}
|
||||
|
||||
outBlocks := make(chan []float32)
|
||||
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
|
||||
samplerateConverter := resampler.New(source.Channels, source.SampleRate, f.sampleRate, int(f.quality))
|
||||
|
||||
for block := range source.Blocks {
|
||||
output := make([][]float32, source.Channels)
|
||||
input := make([][]float32, source.Channels)
|
||||
|
||||
outputBufferLength := 0
|
||||
for i := 0; i < source.Channels; i++ {
|
||||
input[i] = make([]float32, len(block)/source.Channels)
|
||||
|
||||
ix := 0
|
||||
for j := i; j < len(block); j += source.Channels {
|
||||
input[i][ix] = block[j]
|
||||
ix++
|
||||
}
|
||||
|
||||
output[i] = make([]float32, int(float64(len(block)/source.Channels)*(float64(f.sampleRate)/float64(source.SampleRate))*1.1)) //resize to match expected output, with a fuzz factor of 10%
|
||||
|
||||
read, written := samplerateConverter.ProcessFloat32(i, input[i], output[i])
|
||||
if read != len(input[i]) {
|
||||
log.Panicf("could not read whole input: %d vs %d", len(input[i]), read)
|
||||
}
|
||||
output[i] = output[i][:written]
|
||||
|
||||
outputBufferLength += written
|
||||
}
|
||||
|
||||
buffer := make([]float32, outputBufferLength)
|
||||
for i, b := range output {
|
||||
for j := range b {
|
||||
buffer[i+j*len(output)] = b[j]
|
||||
}
|
||||
}
|
||||
|
||||
outBlocks <- buffer
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: f.sampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
}
|
43
audio/filter/resample_filter_nocgo_test.go
Normal file
43
audio/filter/resample_filter_nocgo_test.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
//go:build !cgo
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/test"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFilterChainResample(t *testing.T) {
|
||||
t.Parallel()
|
||||
fp, err := os.Open(test.TestSingleSample24)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
defer fp.Close()
|
||||
source, err := flac.NewFormat().Open(fp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
const sampleRate = 16000
|
||||
|
||||
result := NewFilterChain(source, MonoFilter{}, NewResampleFilter(sampleRate, QualityFast, 0), StereoFilter{})
|
||||
|
||||
sink := audio.NewForwardSink(audio.NewNullSink())
|
||||
sink.Process(result)
|
||||
|
||||
if result.SampleRate != sampleRate {
|
||||
t.Errorf("Wrong SampleRate %d != %d", result.SampleRate, sampleRate)
|
||||
}
|
||||
if result.Channels != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.SampleRate, 2)
|
||||
}
|
||||
if sink.SamplesRead != 6285000 {
|
||||
t.Errorf("Wrong Sample Count %d != %d", sink.SamplesRead, 6285000)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_aac && !disable_codec_libfdk_aac
|
||||
// +build !disable_format_aac,!disable_codec_libfdk_aac
|
||||
//go:build !disable_format_aac && !disable_codec_libfdk_aac && cgo
|
||||
|
||||
package aac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_aac && !disable_codec_libfdk_aac
|
||||
// +build !disable_format_aac,!disable_codec_libfdk_aac
|
||||
//go:build !disable_format_aac && !disable_codec_libfdk_aac && cgo
|
||||
|
||||
package aac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_mp4
|
||||
// +build !disable_format_mp4
|
||||
|
||||
package aac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_aac && disable_codec_libfdk_aac
|
||||
// +build !disable_format_aac,disable_codec_libfdk_aac
|
||||
//go:build !disable_format_aac && disable_codec_libfdk_aac && cgo
|
||||
|
||||
package aac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_aac && disable_codec_libfdk_aac
|
||||
// +build !disable_format_aac,disable_codec_libfdk_aac
|
||||
//go:build !disable_format_aac && disable_codec_libfdk_aac && cgo
|
||||
|
||||
package aac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_alac && enable_codec_libalac
|
||||
// +build !disable_format_alac,enable_codec_libalac
|
||||
//go:build !disable_format_alac && enable_codec_libalac && cgo
|
||||
|
||||
package alac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_alac && enable_codec_libalac
|
||||
// +build !disable_format_alac,enable_codec_libalac
|
||||
//go:build !disable_format_alac && enable_codec_libalac && cgo
|
||||
|
||||
package alac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_alac
|
||||
// +build !disable_format_alac
|
||||
|
||||
package alac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_flac && disable_codec_libflac
|
||||
// +build !disable_format_flac,disable_codec_libflac
|
||||
//go:build !disable_format_flac && (disable_codec_libflac || !cgo)
|
||||
|
||||
package flac
|
||||
|
||||
|
@ -24,7 +23,7 @@ func (f Format) Name() string {
|
|||
}
|
||||
|
||||
func (f Format) Description() string {
|
||||
return "github.com/mewkiz/flac"
|
||||
return "mewkiz/flac"
|
||||
}
|
||||
|
||||
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_flac && !disable_codec_libflac
|
||||
// +build !disable_format_flac,!disable_codec_libflac
|
||||
//go:build !disable_format_flac && !disable_codec_libflac && cgo
|
||||
|
||||
package flac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_flac && !disable_codec_libflac
|
||||
// +build !disable_format_flac,!disable_codec_libflac
|
||||
//go:build !disable_format_flac && !disable_codec_libflac && cgo
|
||||
|
||||
package flac
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_alac
|
||||
// +build !disable_format_alac
|
||||
//go:build !disable_format_flac
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_aac
|
||||
// +build !disable_format_aac
|
||||
//go:build !disable_format_aac && cgo
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_alac && enable_codec_libalac
|
||||
// +build !disable_format_alac,enable_codec_libalac
|
||||
//go:build !disable_format_alac && enable_codec_libalac && cgo
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_flac
|
||||
// +build !disable_format_flac
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_mp3
|
||||
// +build !disable_format_mp3
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_opus
|
||||
// +build !disable_format_opus
|
||||
//go:build !disable_format_opus && cgo
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_tta && !disable_codec_tta
|
||||
// +build !disable_format_tta,!disable_codec_tta
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_vorbis
|
||||
// +build !disable_format_vorbis
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_mp3
|
||||
// +build !disable_format_mp3
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_opus
|
||||
// +build !disable_format_opus
|
||||
//go:build !disable_format_opus && cgo
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_tta && !disable_codec_tta
|
||||
// +build !disable_format_tta,!disable_codec_tta
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_vorbis
|
||||
// +build !disable_format_vorbis
|
||||
|
||||
package guess
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_mp3
|
||||
// +build !disable_format_mp3
|
||||
//go:build !disable_format_mp3 && cgo
|
||||
|
||||
package mp3
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_mp3 && !disable_codec_lame
|
||||
// +build !disable_format_mp3,!disable_codec_lame
|
||||
//go:build !disable_format_mp3 && !disable_codec_lame && cgo
|
||||
|
||||
package mp3
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_mp3 && !disable_codec_lame
|
||||
// +build !disable_format_mp3,!disable_codec_lame
|
||||
//go:build !disable_format_mp3 && !disable_codec_lame && cgo
|
||||
|
||||
package mp3
|
||||
|
||||
|
|
65
audio/format/mp3/mp3_nocgo.go
Normal file
65
audio/format/mp3/mp3_nocgo.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
//go:build !disable_format_mp3 && !cgo
|
||||
|
||||
package mp3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/cgo"
|
||||
mp3Lib "github.com/hajimehoshi/go-mp3"
|
||||
"io"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const BlockSize = 1024 * 128
|
||||
|
||||
type Format struct {
|
||||
}
|
||||
|
||||
func NewFormat() Format {
|
||||
return Format{}
|
||||
}
|
||||
|
||||
func (f Format) Name() string {
|
||||
return "mp3"
|
||||
}
|
||||
func (f Format) Description() string {
|
||||
return "hajimehoshi/go-mp3"
|
||||
}
|
||||
|
||||
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
||||
decoder, err := mp3Lib.NewDecoder(r)
|
||||
if err != nil {
|
||||
return audio.Source{}, err
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
samples := make([]int16, BlockSize*2)
|
||||
const SizeofInt16 = int(unsafe.Sizeof(int16(0)))
|
||||
byteSlice := unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*SizeofInt16)
|
||||
for {
|
||||
n, err := decoder.Read(byteSlice)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n /= SizeofInt16
|
||||
|
||||
//convert 16-bit to f32 samples
|
||||
newChannel <- cgo.Int16ToFloat32(samples[:n], 16)
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: 2,
|
||||
SampleRate: decoder.SampleRate(),
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f Format) Identify(peek []byte, extension string) bool {
|
||||
//match ID3 / sync header
|
||||
return peek[0] == 0xff && (peek[1]>>5) == 0b111 || (bytes.Compare(peek[:3], []byte{'I', 'D', '3'}) == 0 && extension != "flac") || extension == "mp3"
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_mp3 && disable_codec_lame
|
||||
// +build !disable_format_mp3,disable_codec_lame
|
||||
//go:build !disable_format_mp3 && disable_codec_lame && cgo
|
||||
|
||||
package mp3
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_opus
|
||||
// +build !disable_format_opus
|
||||
//go:build !disable_format_opus && cgo
|
||||
|
||||
package opus
|
||||
|
||||
|
@ -68,7 +67,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error {
|
||||
|
||||
var bitrate = 0
|
||||
var resampleQuality = filter.Linear
|
||||
var resampleQuality = filter.QualityFastest
|
||||
|
||||
if options != nil {
|
||||
var val interface{}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_opus
|
||||
// +build !disable_format_opus
|
||||
//go:build !disable_format_opus && cgo
|
||||
|
||||
package opus
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_tta && !disable_codec_tta
|
||||
// +build !disable_format_tta,!disable_codec_tta
|
||||
|
||||
package tta
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_tta && !disable_codec_tta
|
||||
// +build !disable_format_tta,!disable_codec_tta
|
||||
|
||||
package tta
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_vorbis
|
||||
// +build !disable_format_vorbis
|
||||
|
||||
package vorbis
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_aac
|
||||
// +build !disable_format_aac
|
||||
//go:build !disable_format_aac && cgo
|
||||
|
||||
package packetizer
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_flac && !disable_codec_libflac
|
||||
// +build !disable_format_flac,!disable_codec_libflac
|
||||
//go:build !disable_format_flac && !disable_codec_libflac && cgo
|
||||
|
||||
package packetizer
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_mp3 && !disable_codec_lame
|
||||
// +build !disable_format_mp3,!disable_codec_lame
|
||||
//go:build !disable_format_mp3 && !disable_codec_lame && cgo
|
||||
|
||||
package packetizer
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !disable_format_opus
|
||||
// +build !disable_format_opus
|
||||
//go:build !disable_format_opus && cgo
|
||||
|
||||
package packetizer
|
||||
|
||||
|
|
|
@ -133,9 +133,9 @@ func (q *Queue) start() {
|
|||
}
|
||||
func (q *Queue) getFilterChain(source audio.Source) audio.Source {
|
||||
if q.GetChannels() == 1 {
|
||||
return filter.NewFilterChain(source, filter.MonoFilter{}, filter.NewResampleFilter(q.GetSampleRate(), filter.BandlimitedFastest, 0))
|
||||
return filter.NewFilterChain(source, filter.MonoFilter{}, filter.NewResampleFilter(q.GetSampleRate(), filter.QualityFastest, 0))
|
||||
} else {
|
||||
return filter.NewFilterChain(source, filter.StereoFilter{}, filter.NewResampleFilter(q.GetSampleRate(), filter.BandlimitedFastest, 0))
|
||||
return filter.NewFilterChain(source, filter.StereoFilter{}, filter.NewResampleFilter(q.GetSampleRate(), filter.QualityFastest, 0))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
package replaygain
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/filter"
|
||||
libebur128 "git.gammaspectra.live/S.O.N.G/go-ebur128"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
//NewReplayGainFilter Creates a VolumeFilter applying calculated ReplayGain values, pre amplifying by preAmp. Values are in dB
|
||||
|
@ -16,109 +13,3 @@ func NewReplayGainFilter(gain, peak, preAmp float64) filter.VolumeFilter {
|
|||
|
||||
return filter.NewVolumeFilter(float32(volume))
|
||||
}
|
||||
|
||||
// NormalizationFilter Normalizes running audio source
|
||||
type NormalizationFilter struct {
|
||||
delay int
|
||||
}
|
||||
|
||||
func NewNormalizationFilter(delayInSeconds int) NormalizationFilter {
|
||||
return NormalizationFilter{
|
||||
delay: delayInSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
func (f NormalizationFilter) Process(source audio.Source) audio.Source {
|
||||
outBlocks := make(chan []float32)
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
|
||||
state := libebur128.NewState(source.Channels, source.SampleRate, libebur128.LoudnessShortTerm|libebur128.SamplePeak)
|
||||
if state == nil {
|
||||
return
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
if state.SetMaxWindow(time.Second*time.Duration(f.delay)) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var sampleBuffer []float32
|
||||
var adjustment float32 = 1.0
|
||||
|
||||
for block := range source.Blocks {
|
||||
if state.AddFloat(block) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sampleBuffer = append(sampleBuffer, block...)
|
||||
|
||||
loudness, _ := state.GetLoudnessWindow(time.Second * time.Duration(f.delay))
|
||||
peakSlice, _ := state.GetPreviousSamplePeak()
|
||||
|
||||
var peak float64
|
||||
for _, p := range peakSlice {
|
||||
if p > peak {
|
||||
peak = p
|
||||
}
|
||||
}
|
||||
|
||||
gain := referenceLevel - loudness
|
||||
if gain > 52 {
|
||||
gain = 52
|
||||
} else if gain < -52 {
|
||||
gain = -52
|
||||
}
|
||||
|
||||
volume := math.Pow(10, (gain)/20)
|
||||
|
||||
nsamples := source.SampleRate * source.Channels * f.delay
|
||||
|
||||
ratio := float32(math.Min(1, float64(len(block))/float64(nsamples)))
|
||||
|
||||
adjustment = adjustment*(1-ratio) + float32(volume)*ratio
|
||||
|
||||
if adjustment > float32(1/peak) {
|
||||
adjustment = float32(1 / peak)
|
||||
}
|
||||
|
||||
if len(sampleBuffer) > nsamples {
|
||||
adjustment = adjustment*(1-ratio) + float32(volume)*ratio
|
||||
|
||||
if adjustment > float32(1/peak) {
|
||||
adjustment = float32(1 / peak)
|
||||
}
|
||||
|
||||
size := len(sampleBuffer) - nsamples
|
||||
out := make([]float32, size)
|
||||
for i, e := range sampleBuffer[:size] {
|
||||
out[i] = e * adjustment
|
||||
}
|
||||
outBlocks <- out
|
||||
sampleBuffer = sampleBuffer[size:]
|
||||
} else {
|
||||
adjustment = float32(volume)
|
||||
|
||||
if adjustment > float32(1/peak) {
|
||||
adjustment = float32(1 / peak)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//flush
|
||||
if len(sampleBuffer) > 0 {
|
||||
out := make([]float32, len(sampleBuffer))
|
||||
for i := range sampleBuffer {
|
||||
out[i] = sampleBuffer[i] * adjustment
|
||||
}
|
||||
outBlocks <- out
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: source.SampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
}
|
||||
|
|
116
audio/replaygain/filter_normalization.go
Normal file
116
audio/replaygain/filter_normalization.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
//go:build cgo
|
||||
|
||||
package replaygain
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
libebur128 "git.gammaspectra.live/S.O.N.G/go-ebur128"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NormalizationFilter Normalizes running audio source
|
||||
type NormalizationFilter struct {
|
||||
delay int
|
||||
}
|
||||
|
||||
func NewNormalizationFilter(delayInSeconds int) NormalizationFilter {
|
||||
return NormalizationFilter{
|
||||
delay: delayInSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
func (f NormalizationFilter) Process(source audio.Source) audio.Source {
|
||||
outBlocks := make(chan []float32)
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
|
||||
state := libebur128.NewState(source.Channels, source.SampleRate, libebur128.LoudnessShortTerm|libebur128.SamplePeak)
|
||||
if state == nil {
|
||||
return
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
if state.SetMaxWindow(time.Second*time.Duration(f.delay)) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var sampleBuffer []float32
|
||||
var adjustment float32 = 1.0
|
||||
|
||||
for block := range source.Blocks {
|
||||
if state.AddFloat(block) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sampleBuffer = append(sampleBuffer, block...)
|
||||
|
||||
loudness, _ := state.GetLoudnessWindow(time.Second * time.Duration(f.delay))
|
||||
peakSlice, _ := state.GetPreviousSamplePeak()
|
||||
|
||||
var peak float64
|
||||
for _, p := range peakSlice {
|
||||
if p > peak {
|
||||
peak = p
|
||||
}
|
||||
}
|
||||
|
||||
gain := referenceLevel - loudness
|
||||
if gain > 52 {
|
||||
gain = 52
|
||||
} else if gain < -52 {
|
||||
gain = -52
|
||||
}
|
||||
|
||||
volume := math.Pow(10, (gain)/20)
|
||||
|
||||
nsamples := source.SampleRate * source.Channels * f.delay
|
||||
|
||||
ratio := float32(math.Min(1, float64(len(block))/float64(nsamples)))
|
||||
|
||||
adjustment = adjustment*(1-ratio) + float32(volume)*ratio
|
||||
|
||||
if adjustment > float32(1/peak) {
|
||||
adjustment = float32(1 / peak)
|
||||
}
|
||||
|
||||
if len(sampleBuffer) > nsamples {
|
||||
adjustment = adjustment*(1-ratio) + float32(volume)*ratio
|
||||
|
||||
if adjustment > float32(1/peak) {
|
||||
adjustment = float32(1 / peak)
|
||||
}
|
||||
|
||||
size := len(sampleBuffer) - nsamples
|
||||
out := make([]float32, size)
|
||||
for i, e := range sampleBuffer[:size] {
|
||||
out[i] = e * adjustment
|
||||
}
|
||||
outBlocks <- out
|
||||
sampleBuffer = sampleBuffer[size:]
|
||||
} else {
|
||||
adjustment = float32(volume)
|
||||
|
||||
if adjustment > float32(1/peak) {
|
||||
adjustment = float32(1 / peak)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//flush
|
||||
if len(sampleBuffer) > 0 {
|
||||
out := make([]float32, len(sampleBuffer))
|
||||
for i := range sampleBuffer {
|
||||
out[i] = sampleBuffer[i] * adjustment
|
||||
}
|
||||
outBlocks <- out
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: source.SampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
//go:build cgo
|
||||
|
||||
package replaygain
|
||||
|
||||
import (
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:build cgo
|
||||
|
||||
package replaygain
|
||||
|
||||
import (
|
||||
|
|
11
cgo/audio.c
11
cgo/audio.c
|
@ -5,10 +5,11 @@ void audio_multiple_channels_to_mono(float* buffer, size_t buffer_len, float* ou
|
|||
float f;
|
||||
switch(channels) {
|
||||
case 1:
|
||||
memcpy(out, buffer, sizeof(float) * buffer_len);
|
||||
break;
|
||||
case 2:
|
||||
for (int i = 0; i < buffer_len; i += 2){
|
||||
out[i/2] = buffer[i] + buffer[i+1];
|
||||
out[i/2] = (buffer[i] + buffer[i+1]) / 2;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -52,8 +53,8 @@ void audio_multiple_channels_to_stereo(float* buffer, size_t buffer_len, float*
|
|||
int C = i+2;
|
||||
int RL = i+3;
|
||||
int RR = i+4;
|
||||
out[samples*2] = buffer[FL] + surroundMix * buffer[C] + surroundMix * RL;
|
||||
out[samples*2+1] = buffer[FR] + surroundMix * buffer[C] + surroundMix * RR;
|
||||
out[samples*2] = buffer[FL] + surroundMix * buffer[C] + surroundMix * buffer[RL];
|
||||
out[samples*2+1] = buffer[FR] + surroundMix * buffer[C] + surroundMix * buffer[RR];
|
||||
++samples;
|
||||
}
|
||||
case 6: //5.1, FL, FR, FC, LFE, RL, RR
|
||||
|
@ -64,8 +65,8 @@ void audio_multiple_channels_to_stereo(float* buffer, size_t buffer_len, float*
|
|||
int LFE = i+3;
|
||||
int RL = i+4;
|
||||
int RR = i+5;
|
||||
out[samples*2] = buffer[FL] + surroundMix * buffer[C] + surroundMix * RL;
|
||||
out[samples*2+1] = buffer[FR] + surroundMix * buffer[C] + surroundMix * RR;
|
||||
out[samples*2] = buffer[FL] + surroundMix * buffer[C] + surroundMix * buffer[RL];
|
||||
out[samples*2+1] = buffer[FR] + surroundMix * buffer[C] + surroundMix * buffer[RR];
|
||||
++samples;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//go:build cgo
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
|
|
236
cgo/audio_nocgo.go
Normal file
236
cgo/audio_nocgo.go
Normal file
|
@ -0,0 +1,236 @@
|
|||
//go:build !cgo
|
||||
|
||||
package cgo
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
)
|
||||
|
||||
func bitsToDiv(b int) float32 {
|
||||
return float32((int(1) << (b - 1)) - 1)
|
||||
}
|
||||
|
||||
//MultipleChannelsToMono bring any number of channels to mono, equally weighted, reusing buffer backwards
|
||||
func MultipleChannelsToMono(buffer []float32, channels int) (buf []float32) {
|
||||
buf = make([]float32, len(buffer)/channels)
|
||||
for i := 0; i < len(buffer); i += channels {
|
||||
f := buffer[i]
|
||||
for j := 1; j < channels; j++ {
|
||||
f += buffer[i+j]
|
||||
}
|
||||
buf[i/channels] = f / float32(channels)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//MultipleChannelsToStereo bring any number of channels to stereo, using downmix formulas when necessary
|
||||
func MultipleChannelsToStereo(buffer []float32, channels int) (buf []float32) {
|
||||
buf = make([]float32, (len(buffer)/channels)*2)
|
||||
|
||||
var samples int
|
||||
surroundMix := 1 / float32(math.Sqrt(2))
|
||||
|
||||
switch channels {
|
||||
case 1: //mono, duplicate channels
|
||||
for i := 0; i < len(buffer); i++ {
|
||||
buf[i*2] = buffer[i]
|
||||
buf[i*2+1] = buffer[i]
|
||||
}
|
||||
break
|
||||
case 2: //copy
|
||||
copy(buf, buffer)
|
||||
break
|
||||
case 3: //2.1, FL, FR, LFE
|
||||
for i := 0; i < len(buffer); i += 3 {
|
||||
FL := i
|
||||
FR := i + 1
|
||||
//LFE := i + 2
|
||||
buf[samples*2] = buffer[FL]
|
||||
buf[samples*2+1] = buffer[FR]
|
||||
samples++
|
||||
}
|
||||
case 5: //5.0, FL, FR, FC, RL, RR
|
||||
for i := 0; i < len(buffer); i += 5 {
|
||||
FL := i
|
||||
FR := i + 1
|
||||
C := i + 2
|
||||
RL := i + 3
|
||||
RR := i + 4
|
||||
buf[samples*2] = buffer[FL] + surroundMix*buffer[C] + surroundMix*buffer[RL]
|
||||
buf[samples*2+1] = buffer[FR] + surroundMix*buffer[C] + surroundMix*buffer[RR]
|
||||
samples++
|
||||
}
|
||||
case 6: //5.1, FL, FR, FC, LFE, RL, RR
|
||||
for i := 0; i < len(buffer); i += 6 {
|
||||
FL := i
|
||||
FR := i + 1
|
||||
C := i + 2
|
||||
//LFE := i + 3
|
||||
RL := i + 4
|
||||
RR := i + 5
|
||||
buf[samples*2] = buffer[FL] + surroundMix*buffer[C] + surroundMix*buffer[RL]
|
||||
buf[samples*2+1] = buffer[FR] + surroundMix*buffer[C] + surroundMix*buffer[RR]
|
||||
samples++
|
||||
}
|
||||
break
|
||||
default: //no known formula, just take stereo out of it
|
||||
for i := 0; i < len(buffer); i += channels {
|
||||
buf[samples*2] = buffer[i]
|
||||
buf[samples*2+1] = buffer[i+1]
|
||||
samples++
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func BytesToInt32(data []byte, bitDepth int) (buf []int32) {
|
||||
buf = make([]int32, len(data)/(bitDepth/8))
|
||||
|
||||
switch bitDepth {
|
||||
case 8:
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
buf[i] = int32(data[i])
|
||||
}
|
||||
break
|
||||
case 16:
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
buf[i/2] = int32(int16(binary.LittleEndian.Uint16(data[i : i+2])))
|
||||
}
|
||||
break
|
||||
case 24:
|
||||
{
|
||||
var sample uint32
|
||||
|
||||
for i := 0; i < len(data); i += 3 {
|
||||
sample = uint32(data[i])
|
||||
sample += uint32(data[i+1]) << 8
|
||||
sample += uint32(data[i+2]) << 16
|
||||
|
||||
/*if sample > (1 << 23) {
|
||||
sample = -((1 << 24) - sample)
|
||||
}*/
|
||||
//sign extend
|
||||
buf[i/3] = int32(sample<<8) >> 8
|
||||
}
|
||||
}
|
||||
break
|
||||
case 32:
|
||||
for i := 0; i < len(data)/4; i++ {
|
||||
buf[i] = int32(binary.LittleEndian.Uint32(data[i : i+4]))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Int32ToFloat32(data []int32, bitDepth int) (buf []float32) {
|
||||
buf = make([]float32, len(data))
|
||||
for i := 0; i < len(data); i++ {
|
||||
buf[i] = float32(data[i]) / bitsToDiv(bitDepth)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//Int24ToFloat32 special case
|
||||
func Int24ToFloat32(data []byte, bitDepth int) (buf []float32) {
|
||||
buf = make([]float32, len(data)/3)
|
||||
var sample uint32
|
||||
|
||||
for i := 0; i < len(data); i += 3 {
|
||||
sample = uint32(data[i])
|
||||
sample += uint32(data[i+1]) << 8
|
||||
sample += uint32(data[i+2]) << 16
|
||||
|
||||
//sign extend
|
||||
buf[i/3] = (float32(int32(sample<<8) >> 8)) / bitsToDiv(bitDepth)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Int16ToFloat32(data []int16, bitDepth int) (buf []float32) {
|
||||
buf = make([]float32, len(data))
|
||||
for i := 0; i < len(data); i++ {
|
||||
buf[i] = float32(data[i]) / bitsToDiv(bitDepth)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Int8ToFloat32(data []int8, bitDepth int) (buf []float32) {
|
||||
buf = make([]float32, len(data))
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
buf[i] = float32(data[i]) / bitsToDiv(bitDepth)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Float32ToInt32(data []float32, bitDepth int) (buf []int32) {
|
||||
buf = make([]int32, len(data))
|
||||
for i := 0; i < len(data); i++ {
|
||||
f := data[i]
|
||||
if f < -1.0 {
|
||||
f = -1.0
|
||||
}
|
||||
if f > 1.0 {
|
||||
f = 1.0
|
||||
}
|
||||
buf[i] = int32(f * bitsToDiv(bitDepth))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Float32ToInt24(data []float32) (buf []byte) {
|
||||
buf = make([]byte, len(data)*3)
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
f := data[i]
|
||||
if f < -1.0 {
|
||||
f = -1.0
|
||||
}
|
||||
if f > 1.0 {
|
||||
f = 1.0
|
||||
}
|
||||
|
||||
value := int32(f * bitsToDiv(24))
|
||||
buf[i*3] = byte(value & 0xFF)
|
||||
buf[i*3+1] = byte((value >> 8) & 0xFF)
|
||||
buf[i*3+2] = byte((value >> 16) & 0xFF)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Float32ToInt16(data []float32) (buf []int16) {
|
||||
buf = make([]int16, len(data))
|
||||
for i := 0; i < len(data); i++ {
|
||||
f := data[i]
|
||||
if f < -1.0 {
|
||||
f = -1.0
|
||||
}
|
||||
if f > 1.0 {
|
||||
f = 1.0
|
||||
}
|
||||
buf[i] = int16(f * bitsToDiv(16))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Float32ToInt8(data []float32) (buf []int8) {
|
||||
buf = make([]int8, len(data))
|
||||
for i := 0; i < len(data); i++ {
|
||||
f := data[i]
|
||||
if f < -1.0 {
|
||||
f = -1.0
|
||||
}
|
||||
if f > 1.0 {
|
||||
f = 1.0
|
||||
}
|
||||
buf[i] = int8(f * bitsToDiv(8))
|
||||
}
|
||||
return
|
||||
}
|
2
go.mod
2
go.mod
|
@ -12,9 +12,11 @@ require (
|
|||
github.com/dh1tw/gosamplerate v0.1.2
|
||||
github.com/edgeware/mp4ff v0.28.0
|
||||
github.com/gen2brain/aac-go v0.0.0-20180306134136-400c68157565
|
||||
github.com/hajimehoshi/go-mp3 v0.3.3
|
||||
github.com/jfreymuth/oggvorbis v1.0.3
|
||||
github.com/kvark128/minimp3 v0.0.0-20220408223524-dd428325fce7
|
||||
github.com/mewkiz/flac v1.0.7
|
||||
github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38
|
||||
github.com/sssgun/mp3 v0.0.0-20170810093403-85f2ec632081
|
||||
github.com/viert/go-lame v0.0.0-20201108052322-bb552596b11d
|
||||
)
|
||||
|
|
10
go.sum
10
go.sum
|
@ -25,6 +25,9 @@ github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5
|
|||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.3 h1:cWnfRdpye2m9ElSoVqneYRcpt/l3ijttgjMeQh+r+FE=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
github.com/icza/bitio v1.1.0 h1:ysX4vtldjdi3Ygai5m1cWy4oLkhWTAi+SyO6HC8L9T0=
|
||||
github.com/icza/bitio v1.1.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
|
@ -44,6 +47,8 @@ github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd
|
|||
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
||||
github.com/mewkiz/pkg v0.0.0-20211102230744-16a6ce8f1b77 h1:DDyKVkTkrFmd9lR84QW3EIfkkoHlurlpgW+DYuAUJn8=
|
||||
github.com/mewkiz/pkg v0.0.0-20211102230744-16a6ce8f1b77/go.mod h1:J/rDzvIiwiVpv72OEP8aJFxLXjGpUdviIIeqJPLIctA=
|
||||
github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38 h1:4Upfs5rLQdx7KwBct3bmPYAhWsDDJdx660gYb7Lv9TQ=
|
||||
github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38/go.mod h1:Xj06yMta9R1RSKiHmxL0Bo2TB8wiKVnMgA0KVopHHkk=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
@ -59,8 +64,13 @@ github.com/youpy/go-wav v0.3.2 h1:NLM8L/7yZ0Bntadw/0h95OyUsen+DQIVf9gay+SUsMU=
|
|||
github.com/youpy/go-wav v0.3.2/go.mod h1:0FCieAXAeSdcxFfwLpRuEo0PFmAoc+8NU34h7TUvk50=
|
||||
github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b h1:QqixIpc5WFIqTLxB3Hq8qs0qImAgBdq0p6rq2Qdl634=
|
||||
github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b/go.mod h1:T2h1zV50R/q0CVYnsQOQ6L7P4a2ZxH47ixWcMXFGyx8=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
Loading…
Reference in a new issue