From a6134dfd3e9d40f31866f3649563fb59851d5afc Mon Sep 17 00:00:00 2001
From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com>
Date: Fri, 20 May 2022 17:23:50 +0200
Subject: [PATCH] Added support for building using CGO_ENABLED=0 with limited
codec and performance
---
.drone.yml | 6 +-
README.md | 30 ++-
audio/filter/filter.go | 85 --------
audio/filter/filter_test.go | 13 +-
audio/filter/resample_filter.go | 20 ++
audio/filter/resample_filter_cgo.go | 73 +++++++
audio/filter/resample_filter_cgo_test.go | 43 ++++
audio/filter/resample_filter_nocgo.go | 72 +++++++
audio/filter/resample_filter_nocgo_test.go | 43 ++++
audio/format/aac/libfdk-aac.go | 3 +-
audio/format/aac/libfdk-aac_test.go | 3 +-
audio/format/aac/mp4.go | 1 -
audio/format/aac/vo-aacenc.go | 3 +-
audio/format/aac/vo-aacenc_test.go | 3 +-
audio/format/alac/libalac.go | 3 +-
audio/format/alac/libalac_test.go | 3 +-
audio/format/alac/mp4.go | 1 -
audio/format/flac/goflac.go | 5 +-
audio/format/flac/libflac.go | 3 +-
audio/format/flac/libflac_test.go | 3 +-
audio/format/guess/flac_decode_test.go | 3 +-
audio/format/guess/init_aac.go | 3 +-
audio/format/guess/init_alac.go | 3 +-
audio/format/guess/init_flac.go | 1 -
audio/format/guess/init_mp3.go | 1 -
audio/format/guess/init_opus.go | 3 +-
audio/format/guess/init_tta.go | 1 -
audio/format/guess/init_vorbis.go | 1 -
audio/format/guess/mp3_decode_test.go | 1 -
audio/format/guess/opus_decode_test.go | 3 +-
audio/format/guess/tta_decode_test.go | 1 -
audio/format/guess/vorbis_decode_test.go | 1 -
audio/format/mp3/mp3.go | 3 +-
audio/format/mp3/mp3_lame.go | 3 +-
audio/format/mp3/mp3_lame_test.go | 3 +-
audio/format/mp3/mp3_nocgo.go | 65 ++++++
audio/format/mp3/mp3_nolame.go | 3 +-
audio/format/opus/opus.go | 5 +-
audio/format/opus/opus_test.go | 3 +-
audio/format/tta/tta.go | 1 -
audio/format/tta/tta_test.go | 1 -
audio/format/vorbis/vorbis.go | 1 -
audio/packetizer/adts_test.go | 3 +-
audio/packetizer/flac_test.go | 3 +-
audio/packetizer/mp3_test.go | 3 +-
audio/packetizer/ogg_test.go | 3 +-
audio/queue/queue.go | 4 +-
audio/replaygain/filter.go | 109 ----------
audio/replaygain/filter_normalization.go | 116 ++++++++++
audio/replaygain/normalization.go | 2 +
audio/replaygain/normalization_test.go | 2 +
cgo/audio.c | 11 +-
cgo/audio.go | 2 +
cgo/audio_nocgo.go | 236 +++++++++++++++++++++
go.mod | 2 +
go.sum | 10 +
56 files changed, 754 insertions(+), 278 deletions(-)
create mode 100644 audio/filter/resample_filter.go
create mode 100644 audio/filter/resample_filter_cgo.go
create mode 100644 audio/filter/resample_filter_cgo_test.go
create mode 100644 audio/filter/resample_filter_nocgo.go
create mode 100644 audio/filter/resample_filter_nocgo_test.go
create mode 100644 audio/format/mp3/mp3_nocgo.go
create mode 100644 audio/replaygain/filter_normalization.go
create mode 100644 cgo/audio_nocgo.go
diff --git a/.drone.yml b/.drone.yml
index 514a274..6823b0e 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -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 ./...
...
diff --git a/README.md b/README.md
index 2687091..cacd9a4 100644
--- a/README.md
+++ b/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.
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).
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/).
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. |
diff --git a/audio/filter/filter.go b/audio/filter/filter.go
index 21087d6..30ea64c 100644
--- a/audio/filter/filter.go
+++ b/audio/filter/filter.go
@@ -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,
- }
-}
diff --git a/audio/filter/filter_test.go b/audio/filter/filter_test.go
index 882e619..4560de6 100644
--- a/audio/filter/filter_test.go
+++ b/audio/filter/filter_test.go
@@ -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)
}
}
diff --git a/audio/filter/resample_filter.go b/audio/filter/resample_filter.go
new file mode 100644
index 0000000..e0f96ee
--- /dev/null
+++ b/audio/filter/resample_filter.go
@@ -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,
+ }
+}
diff --git a/audio/filter/resample_filter_cgo.go b/audio/filter/resample_filter_cgo.go
new file mode 100644
index 0000000..6071c12
--- /dev/null
+++ b/audio/filter/resample_filter_cgo.go
@@ -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,
+ }
+}
diff --git a/audio/filter/resample_filter_cgo_test.go b/audio/filter/resample_filter_cgo_test.go
new file mode 100644
index 0000000..6510077
--- /dev/null
+++ b/audio/filter/resample_filter_cgo_test.go
@@ -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)
+ }
+}
diff --git a/audio/filter/resample_filter_nocgo.go b/audio/filter/resample_filter_nocgo.go
new file mode 100644
index 0000000..59fe73c
--- /dev/null
+++ b/audio/filter/resample_filter_nocgo.go
@@ -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,
+ }
+}
diff --git a/audio/filter/resample_filter_nocgo_test.go b/audio/filter/resample_filter_nocgo_test.go
new file mode 100644
index 0000000..e163adb
--- /dev/null
+++ b/audio/filter/resample_filter_nocgo_test.go
@@ -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)
+ }
+}
diff --git a/audio/format/aac/libfdk-aac.go b/audio/format/aac/libfdk-aac.go
index 4130f18..df64082 100644
--- a/audio/format/aac/libfdk-aac.go
+++ b/audio/format/aac/libfdk-aac.go
@@ -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
diff --git a/audio/format/aac/libfdk-aac_test.go b/audio/format/aac/libfdk-aac_test.go
index 5517eb0..45d0bcf 100644
--- a/audio/format/aac/libfdk-aac_test.go
+++ b/audio/format/aac/libfdk-aac_test.go
@@ -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
diff --git a/audio/format/aac/mp4.go b/audio/format/aac/mp4.go
index 2f11e64..0cd8517 100644
--- a/audio/format/aac/mp4.go
+++ b/audio/format/aac/mp4.go
@@ -1,5 +1,4 @@
//go:build !disable_format_mp4
-// +build !disable_format_mp4
package aac
diff --git a/audio/format/aac/vo-aacenc.go b/audio/format/aac/vo-aacenc.go
index 713de73..8ebe92e 100644
--- a/audio/format/aac/vo-aacenc.go
+++ b/audio/format/aac/vo-aacenc.go
@@ -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
diff --git a/audio/format/aac/vo-aacenc_test.go b/audio/format/aac/vo-aacenc_test.go
index 1961844..be1bfa3 100644
--- a/audio/format/aac/vo-aacenc_test.go
+++ b/audio/format/aac/vo-aacenc_test.go
@@ -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
diff --git a/audio/format/alac/libalac.go b/audio/format/alac/libalac.go
index b18aa4c..a11dcd0 100644
--- a/audio/format/alac/libalac.go
+++ b/audio/format/alac/libalac.go
@@ -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
diff --git a/audio/format/alac/libalac_test.go b/audio/format/alac/libalac_test.go
index 2f373f8..c9dfe67 100644
--- a/audio/format/alac/libalac_test.go
+++ b/audio/format/alac/libalac_test.go
@@ -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
diff --git a/audio/format/alac/mp4.go b/audio/format/alac/mp4.go
index 71bebbf..45e5ace 100644
--- a/audio/format/alac/mp4.go
+++ b/audio/format/alac/mp4.go
@@ -1,5 +1,4 @@
//go:build !disable_format_alac
-// +build !disable_format_alac
package alac
diff --git a/audio/format/flac/goflac.go b/audio/format/flac/goflac.go
index f8dcbe5..b682b85 100644
--- a/audio/format/flac/goflac.go
+++ b/audio/format/flac/goflac.go
@@ -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) {
diff --git a/audio/format/flac/libflac.go b/audio/format/flac/libflac.go
index 50dfee7..b447c23 100644
--- a/audio/format/flac/libflac.go
+++ b/audio/format/flac/libflac.go
@@ -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
diff --git a/audio/format/flac/libflac_test.go b/audio/format/flac/libflac_test.go
index 3508e28..c3e4b36 100644
--- a/audio/format/flac/libflac_test.go
+++ b/audio/format/flac/libflac_test.go
@@ -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
diff --git a/audio/format/guess/flac_decode_test.go b/audio/format/guess/flac_decode_test.go
index 7d9d498..ee8e0b3 100644
--- a/audio/format/guess/flac_decode_test.go
+++ b/audio/format/guess/flac_decode_test.go
@@ -1,5 +1,4 @@
-//go:build !disable_format_alac
-// +build !disable_format_alac
+//go:build !disable_format_flac
package guess
diff --git a/audio/format/guess/init_aac.go b/audio/format/guess/init_aac.go
index fe5d980..978f89f 100644
--- a/audio/format/guess/init_aac.go
+++ b/audio/format/guess/init_aac.go
@@ -1,5 +1,4 @@
-//go:build !disable_format_aac
-// +build !disable_format_aac
+//go:build !disable_format_aac && cgo
package guess
diff --git a/audio/format/guess/init_alac.go b/audio/format/guess/init_alac.go
index 699fcae..858ac36 100644
--- a/audio/format/guess/init_alac.go
+++ b/audio/format/guess/init_alac.go
@@ -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
diff --git a/audio/format/guess/init_flac.go b/audio/format/guess/init_flac.go
index ae3a3d3..306c5fb 100644
--- a/audio/format/guess/init_flac.go
+++ b/audio/format/guess/init_flac.go
@@ -1,5 +1,4 @@
//go:build !disable_format_flac
-// +build !disable_format_flac
package guess
diff --git a/audio/format/guess/init_mp3.go b/audio/format/guess/init_mp3.go
index cc33755..2009df2 100644
--- a/audio/format/guess/init_mp3.go
+++ b/audio/format/guess/init_mp3.go
@@ -1,5 +1,4 @@
//go:build !disable_format_mp3
-// +build !disable_format_mp3
package guess
diff --git a/audio/format/guess/init_opus.go b/audio/format/guess/init_opus.go
index 7185df2..4e9dfc0 100644
--- a/audio/format/guess/init_opus.go
+++ b/audio/format/guess/init_opus.go
@@ -1,5 +1,4 @@
-//go:build !disable_format_opus
-// +build !disable_format_opus
+//go:build !disable_format_opus && cgo
package guess
diff --git a/audio/format/guess/init_tta.go b/audio/format/guess/init_tta.go
index 9ba446a..09c3521 100644
--- a/audio/format/guess/init_tta.go
+++ b/audio/format/guess/init_tta.go
@@ -1,5 +1,4 @@
//go:build !disable_format_tta && !disable_codec_tta
-// +build !disable_format_tta,!disable_codec_tta
package guess
diff --git a/audio/format/guess/init_vorbis.go b/audio/format/guess/init_vorbis.go
index e9f4ea8..6a9b5b0 100644
--- a/audio/format/guess/init_vorbis.go
+++ b/audio/format/guess/init_vorbis.go
@@ -1,5 +1,4 @@
//go:build !disable_format_vorbis
-// +build !disable_format_vorbis
package guess
diff --git a/audio/format/guess/mp3_decode_test.go b/audio/format/guess/mp3_decode_test.go
index 3e214f5..c4c5ced 100644
--- a/audio/format/guess/mp3_decode_test.go
+++ b/audio/format/guess/mp3_decode_test.go
@@ -1,5 +1,4 @@
//go:build !disable_format_mp3
-// +build !disable_format_mp3
package guess
diff --git a/audio/format/guess/opus_decode_test.go b/audio/format/guess/opus_decode_test.go
index 108e90b..fc2c1de 100644
--- a/audio/format/guess/opus_decode_test.go
+++ b/audio/format/guess/opus_decode_test.go
@@ -1,5 +1,4 @@
-//go:build !disable_format_opus
-// +build !disable_format_opus
+//go:build !disable_format_opus && cgo
package guess
diff --git a/audio/format/guess/tta_decode_test.go b/audio/format/guess/tta_decode_test.go
index 30309c1..3257994 100644
--- a/audio/format/guess/tta_decode_test.go
+++ b/audio/format/guess/tta_decode_test.go
@@ -1,5 +1,4 @@
//go:build !disable_format_tta && !disable_codec_tta
-// +build !disable_format_tta,!disable_codec_tta
package guess
diff --git a/audio/format/guess/vorbis_decode_test.go b/audio/format/guess/vorbis_decode_test.go
index 9206181..61246d3 100644
--- a/audio/format/guess/vorbis_decode_test.go
+++ b/audio/format/guess/vorbis_decode_test.go
@@ -1,5 +1,4 @@
//go:build !disable_format_vorbis
-// +build !disable_format_vorbis
package guess
diff --git a/audio/format/mp3/mp3.go b/audio/format/mp3/mp3.go
index 234ae93..06f69ba 100644
--- a/audio/format/mp3/mp3.go
+++ b/audio/format/mp3/mp3.go
@@ -1,5 +1,4 @@
-//go:build !disable_format_mp3
-// +build !disable_format_mp3
+//go:build !disable_format_mp3 && cgo
package mp3
diff --git a/audio/format/mp3/mp3_lame.go b/audio/format/mp3/mp3_lame.go
index 39c07ed..104c43a 100644
--- a/audio/format/mp3/mp3_lame.go
+++ b/audio/format/mp3/mp3_lame.go
@@ -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
diff --git a/audio/format/mp3/mp3_lame_test.go b/audio/format/mp3/mp3_lame_test.go
index d29108c..d8c4e2c 100644
--- a/audio/format/mp3/mp3_lame_test.go
+++ b/audio/format/mp3/mp3_lame_test.go
@@ -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
diff --git a/audio/format/mp3/mp3_nocgo.go b/audio/format/mp3/mp3_nocgo.go
new file mode 100644
index 0000000..ed7995d
--- /dev/null
+++ b/audio/format/mp3/mp3_nocgo.go
@@ -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"
+}
diff --git a/audio/format/mp3/mp3_nolame.go b/audio/format/mp3/mp3_nolame.go
index eba34b4..582e752 100644
--- a/audio/format/mp3/mp3_nolame.go
+++ b/audio/format/mp3/mp3_nolame.go
@@ -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
diff --git a/audio/format/opus/opus.go b/audio/format/opus/opus.go
index 544cc19..b9af8f2 100644
--- a/audio/format/opus/opus.go
+++ b/audio/format/opus/opus.go
@@ -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{}
diff --git a/audio/format/opus/opus_test.go b/audio/format/opus/opus_test.go
index de3c763..1f2e8a3 100644
--- a/audio/format/opus/opus_test.go
+++ b/audio/format/opus/opus_test.go
@@ -1,5 +1,4 @@
-//go:build !disable_format_opus
-// +build !disable_format_opus
+//go:build !disable_format_opus && cgo
package opus
diff --git a/audio/format/tta/tta.go b/audio/format/tta/tta.go
index 7020a99..1bbe073 100644
--- a/audio/format/tta/tta.go
+++ b/audio/format/tta/tta.go
@@ -1,5 +1,4 @@
//go:build !disable_format_tta && !disable_codec_tta
-// +build !disable_format_tta,!disable_codec_tta
package tta
diff --git a/audio/format/tta/tta_test.go b/audio/format/tta/tta_test.go
index 6188a66..b57d4c3 100644
--- a/audio/format/tta/tta_test.go
+++ b/audio/format/tta/tta_test.go
@@ -1,5 +1,4 @@
//go:build !disable_format_tta && !disable_codec_tta
-// +build !disable_format_tta,!disable_codec_tta
package tta
diff --git a/audio/format/vorbis/vorbis.go b/audio/format/vorbis/vorbis.go
index 089a4f1..f80b4a1 100644
--- a/audio/format/vorbis/vorbis.go
+++ b/audio/format/vorbis/vorbis.go
@@ -1,5 +1,4 @@
//go:build !disable_format_vorbis
-// +build !disable_format_vorbis
package vorbis
diff --git a/audio/packetizer/adts_test.go b/audio/packetizer/adts_test.go
index 7bc5e42..ce05587 100644
--- a/audio/packetizer/adts_test.go
+++ b/audio/packetizer/adts_test.go
@@ -1,5 +1,4 @@
-//go:build !disable_format_aac
-// +build !disable_format_aac
+//go:build !disable_format_aac && cgo
package packetizer
diff --git a/audio/packetizer/flac_test.go b/audio/packetizer/flac_test.go
index a93536e..7a6331d 100644
--- a/audio/packetizer/flac_test.go
+++ b/audio/packetizer/flac_test.go
@@ -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
diff --git a/audio/packetizer/mp3_test.go b/audio/packetizer/mp3_test.go
index 6add1ff..1543ee6 100644
--- a/audio/packetizer/mp3_test.go
+++ b/audio/packetizer/mp3_test.go
@@ -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
diff --git a/audio/packetizer/ogg_test.go b/audio/packetizer/ogg_test.go
index 62acb9a..9707811 100644
--- a/audio/packetizer/ogg_test.go
+++ b/audio/packetizer/ogg_test.go
@@ -1,5 +1,4 @@
-//go:build !disable_format_opus
-// +build !disable_format_opus
+//go:build !disable_format_opus && cgo
package packetizer
diff --git a/audio/queue/queue.go b/audio/queue/queue.go
index f3e31d2..3c832a0 100644
--- a/audio/queue/queue.go
+++ b/audio/queue/queue.go
@@ -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))
}
}
diff --git a/audio/replaygain/filter.go b/audio/replaygain/filter.go
index c97e132..7ceeffb 100644
--- a/audio/replaygain/filter.go
+++ b/audio/replaygain/filter.go
@@ -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,
- }
-}
diff --git a/audio/replaygain/filter_normalization.go b/audio/replaygain/filter_normalization.go
new file mode 100644
index 0000000..6cd4cd4
--- /dev/null
+++ b/audio/replaygain/filter_normalization.go
@@ -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,
+ }
+}
diff --git a/audio/replaygain/normalization.go b/audio/replaygain/normalization.go
index 6ce17bf..516027a 100644
--- a/audio/replaygain/normalization.go
+++ b/audio/replaygain/normalization.go
@@ -1,3 +1,5 @@
+//go:build cgo
+
package replaygain
import (
diff --git a/audio/replaygain/normalization_test.go b/audio/replaygain/normalization_test.go
index a79edc1..f6ef55d 100644
--- a/audio/replaygain/normalization_test.go
+++ b/audio/replaygain/normalization_test.go
@@ -1,3 +1,5 @@
+//go:build cgo
+
package replaygain
import (
diff --git a/cgo/audio.c b/cgo/audio.c
index 371248f..91eed54 100644
--- a/cgo/audio.c
+++ b/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;
diff --git a/cgo/audio.go b/cgo/audio.go
index 697549b..0395d09 100644
--- a/cgo/audio.go
+++ b/cgo/audio.go
@@ -1,3 +1,5 @@
+//go:build cgo
+
package cgo
/*
diff --git a/cgo/audio_nocgo.go b/cgo/audio_nocgo.go
new file mode 100644
index 0000000..a2b3253
--- /dev/null
+++ b/cgo/audio_nocgo.go
@@ -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
+}
diff --git a/go.mod b/go.mod
index cd5a053..28a9359 100644
--- a/go.mod
+++ b/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
)
diff --git a/go.sum b/go.sum
index 2032ac5..c35338a 100644
--- a/go.sum
+++ b/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=