Added support for building using CGO_ENABLED=0 with limited codec and performance
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
DataHoarder 2022-05-20 17:23:50 +02:00
parent aa67533e41
commit a6134dfd3e
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
56 changed files with 754 additions and 278 deletions

View file

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

View file

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

View file

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

View file

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

View 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,
}
}

View 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,
}
}

View 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)
}
}

View 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,
}
}

View 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)
}
}

View file

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

View file

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

View file

@ -1,5 +1,4 @@
//go:build !disable_format_mp4
// +build !disable_format_mp4
package aac

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
//go:build !disable_format_alac
// +build !disable_format_alac
package alac

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
//go:build !disable_format_alac
// +build !disable_format_alac
//go:build !disable_format_flac
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_aac
// +build !disable_format_aac
//go:build !disable_format_aac && cgo
package guess

View file

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

View file

@ -1,5 +1,4 @@
//go:build !disable_format_flac
// +build !disable_format_flac
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_mp3
// +build !disable_format_mp3
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_opus
// +build !disable_format_opus
//go:build !disable_format_opus && cgo
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_tta && !disable_codec_tta
// +build !disable_format_tta,!disable_codec_tta
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_vorbis
// +build !disable_format_vorbis
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_mp3
// +build !disable_format_mp3
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_opus
// +build !disable_format_opus
//go:build !disable_format_opus && cgo
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_tta && !disable_codec_tta
// +build !disable_format_tta,!disable_codec_tta
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_vorbis
// +build !disable_format_vorbis
package guess

View file

@ -1,5 +1,4 @@
//go:build !disable_format_mp3
// +build !disable_format_mp3
//go:build !disable_format_mp3 && cgo
package mp3

View file

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

View file

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

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
//go:build !disable_format_opus
// +build !disable_format_opus
//go:build !disable_format_opus && cgo
package opus

View file

@ -1,5 +1,4 @@
//go:build !disable_format_tta && !disable_codec_tta
// +build !disable_format_tta,!disable_codec_tta
package tta

View file

@ -1,5 +1,4 @@
//go:build !disable_format_tta && !disable_codec_tta
// +build !disable_format_tta,!disable_codec_tta
package tta

View file

@ -1,5 +1,4 @@
//go:build !disable_format_vorbis
// +build !disable_format_vorbis
package vorbis

View file

@ -1,5 +1,4 @@
//go:build !disable_format_aac
// +build !disable_format_aac
//go:build !disable_format_aac && cgo
package packetizer

View file

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

View file

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

View file

@ -1,5 +1,4 @@
//go:build !disable_format_opus
// +build !disable_format_opus
//go:build !disable_format_opus && cgo
package packetizer

View file

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

View file

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

View 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,
}
}

View file

@ -1,3 +1,5 @@
//go:build cgo
package replaygain
import (

View file

@ -1,3 +1,5 @@
//go:build cgo
package replaygain
import (

View file

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

View file

@ -1,3 +1,5 @@
//go:build cgo
package cgo
/*

236
cgo/audio_nocgo.go Normal file
View 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
View file

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

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