refactor audio.Source, it is now an interface with float32 / int16 / int32 sample formats
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Removed AnalyzerChannel in favor of raw samples
This commit is contained in:
parent
44d93971c5
commit
449b38272b
38
README.md
38
README.md
|
@ -3,7 +3,7 @@
|
|||
Collection of audio utilities for decoding/encoding files and streams.
|
||||
* Not based on ffmpeg/libav/libavcodec/libavformat and alike
|
||||
* Channel-based audio consumption/filter chain
|
||||
* Raw sample analyzer channels
|
||||
* Raw decoded samples exposed.
|
||||
* AnalyzerChannel channels / mergers / splitters / trimmers
|
||||
* Audio resampler
|
||||
* Audio downmixing to stereo/mono
|
||||
|
@ -13,15 +13,15 @@ Collection of audio utilities for decoding/encoding files and streams.
|
|||
|
||||
## Codecs supported
|
||||
|
||||
| Codec | Containers | Decoder | Analyzer | Encoder | Notes |
|
||||
|:----------:|:----------------------------------------------------------------------------------------:|:-------:|:--------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **FLAC** | [FLAC](https://xiph.org/flac/format.html), [Ogg](https://xiph.org/flac/ogg_mapping.html) | ✅ | ✅ | ✅ | Adjustable encoding compression level and block size.<br/>Decoding/encoding by [libFLAC](https://github.com/xiph/flac) via [goflac](https://git.gammaspectra.live/S.O.N.G/goflac).<br/>If [goflac](https://git.gammaspectra.live/S.O.N.G/goflac) codec is disabled, [mewkiz/flac](https://github.com/mewkiz/flac) decoder will be used. |
|
||||
| **TTA** | [TTA](https://www.tausoft.org/en/true_audio_codec_format/) | ✅ | ✅ | ✅ | Decoding/encoding via [S.O.N.G/go-tta](https://git.gammaspectra.live/S.O.N.G/go-tta). |
|
||||
| **MP3** | [MP3](http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html) | ✅ | - | ✅ | Adjustable encoding bitrate and mode.<br/>Decoding via [minimp3](https://github.com/kvark128/minimp3), encoding by [LAME](https://lame.sourceforge.io/) via [go-lame](https://github.com/viert/go-lame). |
|
||||
| **Opus** | [Ogg](https://www.xiph.org/ogg/doc/framing.html) | ✅ | - | ✅ | Adjustable encoding bitrate.<br/>Decoding/encoding by [libopus](https://github.com/xiph/opus) via [go-pus](https://git.gammaspectra.live/S.O.N.G/go-pus). |
|
||||
| **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** | [ADTS](https://wiki.multimedia.cx/index.php/ADTS), ADIF*, MP4** | ✅ | - | ✅ | Adjustable encoding bitrate and mode (LC, HEv1, HEv2).<br/>Decoding/encoding by [FDK-AAC](https://github.com/mstorsjo/fdk-aac) via [go-fdkaac](https://git.gammaspectra.live/S.O.N.G/go-fdkaac).<br/>If [go-fdkaac](https://git.gammaspectra.live/S.O.N.G/go-fdkaac) codec is disabled, [VisualOn AAC encoder](https://github.com/gen2brain/aac-go) will be used for limited encoding support.<br/>*ADIF only supported on encoding.<br/>**MP4 only supported on encoding, and fragmented MP4 currently. |
|
||||
| **ALAC** | MP4* | ✅ | ✅ | ✅ | Decoding/encoding by [libalac](https://git.gammaspectra.live/S.O.N.G/alac) via [go-alac](https://git.gammaspectra.live/S.O.N.G/go-alac).<br/>Disabled by default.<br/>*MP4 encoding only supported on fragmented MP4 currently. |
|
||||
| Codec | Containers | Decoder | Decoder Sample Format | Encoder | Notes |
|
||||
|:----------:|:----------------------------------------------------------------------------------------:|:-------:|:---------------------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **FLAC** | [FLAC](https://xiph.org/flac/format.html), [Ogg](https://xiph.org/flac/ogg_mapping.html) | ✅ | `int32` | ✅ | Adjustable encoding compression level and block size.<br/>Decoding/encoding by [libFLAC](https://github.com/xiph/flac) via [goflac](https://git.gammaspectra.live/S.O.N.G/goflac).<br/>If [goflac](https://git.gammaspectra.live/S.O.N.G/goflac) codec is disabled, [mewkiz/flac](https://github.com/mewkiz/flac) decoder will be used. |
|
||||
| **TTA** | [TTA](https://www.tausoft.org/en/true_audio_codec_format/) | ✅ | `int16, int32` | ✅ | Decoding/encoding via [S.O.N.G/go-tta](https://git.gammaspectra.live/S.O.N.G/go-tta). |
|
||||
| **MP3** | [MP3](http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html) | ✅ | `2ch. int16` | ✅ | Adjustable encoding bitrate and mode.<br/>Decoding via [minimp3](https://github.com/kvark128/minimp3), encoding by [LAME](https://lame.sourceforge.io/) via [go-lame](https://github.com/viert/go-lame). |
|
||||
| **Opus** | [Ogg](https://www.xiph.org/ogg/doc/framing.html) | ✅ | `2ch. int16` | ✅ | Adjustable encoding bitrate.<br/>Decoding/encoding by [libopus](https://github.com/xiph/opus) via [go-pus](https://git.gammaspectra.live/S.O.N.G/go-pus). |
|
||||
| **Vorbis** | [Ogg](https://www.xiph.org/ogg/doc/framing.html) | ✅ | `float32` | ❌ | Decoding by [jfreymuth/vorbis](https://github.com/jfreymuth/vorbis) via [jfreymuth/oggvorbis](https://github.com/jfreymuth/oggvorbis). |
|
||||
| **AAC** | [ADTS](https://wiki.multimedia.cx/index.php/ADTS), ADIF*, MP4** | ✅ | `int16` | ✅ | Adjustable encoding bitrate and mode (LC, HEv1, HEv2).<br/>Decoding/encoding by [FDK-AAC](https://github.com/mstorsjo/fdk-aac) via [go-fdkaac](https://git.gammaspectra.live/S.O.N.G/go-fdkaac).<br/>If [go-fdkaac](https://git.gammaspectra.live/S.O.N.G/go-fdkaac) codec is disabled, [VisualOn AAC encoder](https://github.com/gen2brain/aac-go) will be used for limited encoding support.<br/>*ADIF only supported on encoding.<br/>**MP4 only supported on encoding, and fragmented MP4 currently. |
|
||||
| **ALAC** | MP4* | ✅ | `int16, int32` | ✅ | Decoding/encoding by [libalac](https://git.gammaspectra.live/S.O.N.G/alac) via [go-alac](https://git.gammaspectra.live/S.O.N.G/go-alac).<br/>Disabled by default.<br/>*MP4 encoding only supported on fragmented MP4 currently. |
|
||||
|
||||
## Container packetizers supported
|
||||
|
||||
|
@ -113,15 +113,15 @@ 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 [S.O.N.G/go-tta](https://git.gammaspectra.live/S.O.N.G/go-tta). |
|
||||
| **MP3** | [MP3](http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html) | ✅ | - | ❌ | Decoding via [hajimehoshi/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** | - | ❌ | - | ❌ | |
|
||||
| Codec | Containers | Decoder | Decoder Sample Format | Encoder | Notes |
|
||||
|:----------:|:------------------------------------------------------------:|:-------:|:---------------------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **FLAC** | [FLAC](https://xiph.org/flac/format.html) | ✅ | `int32` | ❌ | Decoding by [mewkiz/flac](https://github.com/mewkiz/flac). |
|
||||
| **TTA** | [TTA](https://www.tausoft.org/en/true_audio_codec_format/) | ✅ | `int16, int32` | ✅ | Decoding/encoding via [S.O.N.G/go-tta](https://git.gammaspectra.live/S.O.N.G/go-tta). |
|
||||
| **MP3** | [MP3](http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html) | ✅ | `2ch. int16` | ❌ | Decoding via [hajimehoshi/go-mp3](https://github.com/hajimehoshi/go-mp3). |
|
||||
| **Opus** | - | ❌ | - | ❌ | |
|
||||
| **Vorbis** | [Ogg](https://www.xiph.org/ogg/doc/framing.html) | ✅ | `2ch. float32` | ❌ | Decoding by [jfreymuth/vorbis](https://github.com/jfreymuth/vorbis) via [jfreymuth/oggvorbis](https://github.com/jfreymuth/oggvorbis). |
|
||||
| **AAC** | - | ❌ | - | ❌ | |
|
||||
| **ALAC** | - | ❌ | - | ❌ | |
|
||||
|
||||
|
||||
### disable_format_[format]
|
||||
|
|
|
@ -18,6 +18,30 @@ func NewFilterChain(source audio.Source, filters ...Filter) audio.Source {
|
|||
return source
|
||||
}
|
||||
|
||||
type SourceFormatFilter struct {
|
||||
Format audio.SourceFormat
|
||||
}
|
||||
|
||||
func (f SourceFormatFilter) Process(source audio.Source) audio.Source {
|
||||
switch f.Format {
|
||||
case audio.SourceFloat32:
|
||||
return source.ToFloat32()
|
||||
case audio.SourceInt16:
|
||||
return source.ToInt16()
|
||||
case audio.SourceInt32:
|
||||
if int16Source, ok := source.(*audio.Int16Source); ok {
|
||||
return source.ToInt32(int16Source.GetBitDepth())
|
||||
} else if int32Source, ok := source.(*audio.Int32Source); ok {
|
||||
return int32Source
|
||||
} else {
|
||||
return source.ToInt32(32)
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: panic?
|
||||
return source
|
||||
}
|
||||
|
||||
type BufferFilter struct {
|
||||
blockBufferSize int
|
||||
}
|
||||
|
@ -29,19 +53,53 @@ func NewBufferFilter(blockBufferSize int) BufferFilter {
|
|||
}
|
||||
|
||||
func (f BufferFilter) Process(source audio.Source) audio.Source {
|
||||
outBlocks := make(chan []float32, f.blockBufferSize)
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
for block := range source.Blocks {
|
||||
outBlocks <- block
|
||||
}
|
||||
}()
|
||||
if floatSource, ok := source.(*audio.Float32Source); ok {
|
||||
outBlocks := make(chan []float32, f.blockBufferSize)
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
for block := range floatSource.GetBlocks() {
|
||||
outBlocks <- block
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: source.SampleRate,
|
||||
Blocks: outBlocks,
|
||||
return &audio.Float32Source{
|
||||
Channels: source.GetChannels(),
|
||||
SampleRate: source.GetSampleRate(),
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
} else if int16Source, ok := source.(*audio.Int16Source); ok {
|
||||
outBlocks := make(chan []int16, f.blockBufferSize)
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
for block := range int16Source.GetBlocks() {
|
||||
outBlocks <- block
|
||||
}
|
||||
}()
|
||||
|
||||
return &audio.Int16Source{
|
||||
BitDepth: source.GetBitDepth(),
|
||||
Channels: source.GetChannels(),
|
||||
SampleRate: source.GetSampleRate(),
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
} else if int32Source, ok := source.(*audio.Int32Source); ok {
|
||||
outBlocks := make(chan []int32, f.blockBufferSize)
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
for block := range int32Source.GetBlocks() {
|
||||
outBlocks <- block
|
||||
}
|
||||
}()
|
||||
|
||||
return &audio.Int32Source{
|
||||
BitDepth: source.GetBitDepth(),
|
||||
Channels: source.GetChannels(),
|
||||
SampleRate: source.GetSampleRate(),
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
}
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
type RealTimeFilter struct {
|
||||
|
@ -55,33 +113,69 @@ func NewRealTimeFilter(blocksPerSecond int) RealTimeFilter {
|
|||
}
|
||||
|
||||
func (f RealTimeFilter) Process(source audio.Source) audio.Source {
|
||||
outBlocks := make(chan []float32)
|
||||
if source.SampleRate%f.blocksPerSecond != 0 {
|
||||
log.Panicf("%d %% %d != 0", source.SampleRate, f.blocksPerSecond)
|
||||
if source.GetSampleRate()%f.blocksPerSecond != 0 {
|
||||
log.Panicf("%d %% %d != 0", source.GetSampleRate(), f.blocksPerSecond)
|
||||
}
|
||||
blockSize := (source.SampleRate / f.blocksPerSecond) * source.Channels
|
||||
blockSize := (source.GetSampleRate() / f.blocksPerSecond) * source.GetChannels()
|
||||
throttler := time.Tick(time.Second / time.Duration(f.blocksPerSecond))
|
||||
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
var buf []float32
|
||||
for block := range source.Blocks {
|
||||
buf = append(buf, block...)
|
||||
for len(buf) >= blockSize {
|
||||
outBlocks <- buf[0:blockSize]
|
||||
buf = buf[blockSize:]
|
||||
<-throttler
|
||||
if floatSource, ok := source.(*audio.Float32Source); ok {
|
||||
outSource := audio.NewFloat32Source(source.GetSampleRate(), source.GetChannels())
|
||||
go func() {
|
||||
defer outSource.Close()
|
||||
var buf []float32
|
||||
for block := range floatSource.GetBlocks() {
|
||||
buf = append(buf, block...)
|
||||
for len(buf) >= blockSize {
|
||||
outSource.IngestFloat32(buf[0:blockSize])
|
||||
buf = buf[blockSize:]
|
||||
<-throttler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outBlocks <- buf
|
||||
}()
|
||||
outSource.IngestFloat32(buf)
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: source.SampleRate,
|
||||
Blocks: outBlocks,
|
||||
return outSource
|
||||
} else if int16Source, ok := source.(*audio.Int16Source); ok {
|
||||
outSource := audio.NewInt16Source(source.GetBitDepth(), source.GetSampleRate(), source.GetChannels())
|
||||
go func() {
|
||||
defer outSource.Close()
|
||||
var buf []int16
|
||||
for block := range int16Source.GetBlocks() {
|
||||
buf = append(buf, block...)
|
||||
for len(buf) >= blockSize {
|
||||
outSource.IngestInt16(buf[0:blockSize], source.GetBitDepth())
|
||||
buf = buf[blockSize:]
|
||||
<-throttler
|
||||
}
|
||||
}
|
||||
|
||||
outSource.IngestInt16(buf, source.GetBitDepth())
|
||||
}()
|
||||
|
||||
return outSource
|
||||
} else if int32Source, ok := source.(*audio.Int32Source); ok {
|
||||
outSource := audio.NewInt32Source(source.GetBitDepth(), source.GetSampleRate(), source.GetChannels())
|
||||
go func() {
|
||||
defer outSource.Close()
|
||||
var buf []int32
|
||||
for block := range int32Source.GetBlocks() {
|
||||
buf = append(buf, block...)
|
||||
for len(buf) >= blockSize {
|
||||
outSource.IngestInt32(buf[0:blockSize], source.GetBitDepth())
|
||||
buf = buf[blockSize:]
|
||||
<-throttler
|
||||
}
|
||||
}
|
||||
|
||||
outSource.IngestInt32(buf, source.GetBitDepth())
|
||||
}()
|
||||
|
||||
return outSource
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type VolumeFilter struct {
|
||||
|
@ -95,69 +189,65 @@ func NewVolumeFilter(adjustment float32) VolumeFilter {
|
|||
}
|
||||
|
||||
func (f VolumeFilter) Process(source audio.Source) audio.Source {
|
||||
outBlocks := make(chan []float32)
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
if f.adjustment == 1. {
|
||||
return source
|
||||
}
|
||||
|
||||
for block := range source.Blocks {
|
||||
outSource := audio.NewFloat32Source(source.GetSampleRate(), source.GetChannels())
|
||||
go func() {
|
||||
defer outSource.Close()
|
||||
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
out := make([]float32, len(block))
|
||||
for i := range block {
|
||||
out[i] = block[i] * f.adjustment
|
||||
}
|
||||
outBlocks <- out
|
||||
outSource.IngestFloat32(out)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: source.SampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
return outSource
|
||||
}
|
||||
|
||||
type StereoFilter struct {
|
||||
}
|
||||
|
||||
func (f StereoFilter) Process(source audio.Source) audio.Source {
|
||||
if source.Channels == 2 { //no change
|
||||
if source.GetChannels() == 2 { //no change
|
||||
return source
|
||||
}
|
||||
outBlocks := make(chan []float32)
|
||||
|
||||
//TODO: make this for int16 and int32
|
||||
outSource := audio.NewFloat32Source(source.GetSampleRate(), 2)
|
||||
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
for block := range source.Blocks {
|
||||
outBlocks <- cgo.MultipleChannelsToStereo(block, source.Channels)
|
||||
defer outSource.Close()
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
outSource.IngestFloat32(cgo.MultipleChannelsToStereo(block, source.GetChannels()))
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: 2,
|
||||
SampleRate: source.SampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
return outSource
|
||||
}
|
||||
|
||||
type MonoFilter struct {
|
||||
}
|
||||
|
||||
func (f MonoFilter) Process(source audio.Source) audio.Source {
|
||||
if source.Channels == 1 { //no change
|
||||
if source.GetChannels() == 1 { //no change
|
||||
return source
|
||||
}
|
||||
outBlocks := make(chan []float32)
|
||||
|
||||
//TODO: make this for int16 and int32
|
||||
outSource := audio.NewFloat32Source(source.GetSampleRate(), 1)
|
||||
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
for block := range source.Blocks {
|
||||
outBlocks <- cgo.MultipleChannelsToMono(block, source.Channels)
|
||||
defer outSource.Close()
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
outSource.IngestFloat32(cgo.MultipleChannelsToMono(block, source.GetChannels()))
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: 1,
|
||||
SampleRate: source.SampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
return outSource
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ func TestFilterChainNoResample(t *testing.T) {
|
|||
sink := audio.NewForwardSink(audio.NewNullSink())
|
||||
sink.Process(result)
|
||||
|
||||
if result.Channels != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.SampleRate, 2)
|
||||
if result.GetChannels() != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.GetChannels(), 2)
|
||||
}
|
||||
if sink.SamplesRead != 17323031 {
|
||||
t.Errorf("Wrong Sample Count %d != %d", sink.SamplesRead, 17323031)
|
||||
|
|
|
@ -16,33 +16,33 @@ const (
|
|||
)
|
||||
|
||||
func (f ResampleFilter) Process(source audio.Source) audio.Source {
|
||||
if source.SampleRate == f.sampleRate { //no change
|
||||
if source.GetSampleRate() == f.sampleRate { //no change
|
||||
return source
|
||||
}
|
||||
|
||||
outBlocks := make(chan []float32)
|
||||
outSource := audio.NewFloat32Source(f.sampleRate, source.GetChannels())
|
||||
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
defer outSource.Close()
|
||||
|
||||
blockSize := f.blockSize * source.Channels
|
||||
samplerateConverter, err := gosamplerate.New(int(f.quality), source.Channels, blockSize)
|
||||
blockSize := f.blockSize * source.GetChannels()
|
||||
samplerateConverter, err := gosamplerate.New(int(f.quality), source.GetChannels(), blockSize)
|
||||
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
defer gosamplerate.Delete(samplerateConverter)
|
||||
|
||||
ratio := float64(f.sampleRate) / float64(source.SampleRate)
|
||||
ratio := float64(f.sampleRate) / float64(source.GetSampleRate())
|
||||
|
||||
for block := range source.Blocks {
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
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
|
||||
outSource.IngestFloat32(b)
|
||||
}
|
||||
block = block[blockSize:]
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func (f ResampleFilter) Process(source audio.Source) audio.Source {
|
|||
log.Panic(err)
|
||||
}
|
||||
if len(b) > 0 {
|
||||
outBlocks <- b
|
||||
outSource.IngestFloat32(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,13 +61,9 @@ func (f ResampleFilter) Process(source audio.Source) audio.Source {
|
|||
log.Panic(err)
|
||||
}
|
||||
if len(b) > 0 {
|
||||
outBlocks <- b
|
||||
outSource.IngestFloat32(b)
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: f.sampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
return outSource
|
||||
}
|
||||
|
|
|
@ -31,11 +31,11 @@ func TestFilterChainResample(t *testing.T) {
|
|||
sink := audio.NewForwardSink(audio.NewNullSink())
|
||||
sink.Process(result)
|
||||
|
||||
if result.SampleRate != sampleRate {
|
||||
t.Errorf("Wrong SampleRate %d != %d", result.SampleRate, sampleRate)
|
||||
if result.GetSampleRate() != sampleRate {
|
||||
t.Errorf("Wrong SampleRate %d != %d", result.GetSampleRate(), sampleRate)
|
||||
}
|
||||
if result.Channels != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.SampleRate, 2)
|
||||
if result.GetChannels() != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.GetChannels(), 2)
|
||||
}
|
||||
if sink.SamplesRead != 6284999 {
|
||||
t.Errorf("Wrong Sample Count %d != %d", sink.SamplesRead, 6284999)
|
||||
|
|
|
@ -16,32 +16,32 @@ const (
|
|||
)
|
||||
|
||||
func (f ResampleFilter) Process(source audio.Source) audio.Source {
|
||||
if source.SampleRate == f.sampleRate { //no change
|
||||
if source.GetSampleRate() == f.sampleRate { //no change
|
||||
return source
|
||||
}
|
||||
|
||||
outBlocks := make(chan []float32)
|
||||
outSource := audio.NewFloat32Source(f.sampleRate, source.GetChannels())
|
||||
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
defer outSource.Close()
|
||||
|
||||
samplerateConverter := resampler.New(source.Channels, source.SampleRate, f.sampleRate, int(f.quality))
|
||||
samplerateConverter := resampler.New(source.GetChannels(), source.GetSampleRate(), f.sampleRate, int(f.quality))
|
||||
|
||||
for block := range source.Blocks {
|
||||
output := make([][]float32, source.Channels)
|
||||
input := make([][]float32, source.Channels)
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
output := make([][]float32, source.GetChannels())
|
||||
input := make([][]float32, source.GetChannels())
|
||||
|
||||
outputBufferLength := 0
|
||||
for i := 0; i < source.Channels; i++ {
|
||||
input[i] = make([]float32, len(block)/source.Channels)
|
||||
for i := 0; i < source.GetChannels(); i++ {
|
||||
input[i] = make([]float32, len(block)/source.GetChannels())
|
||||
|
||||
ix := 0
|
||||
for j := i; j < len(block); j += source.Channels {
|
||||
for j := i; j < len(block); j += source.GetChannels() {
|
||||
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%
|
||||
output[i] = make([]float32, int(float64(len(block)/source.GetChannels())*(float64(f.sampleRate)/float64(source.GetSampleRate()))*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]) {
|
||||
|
@ -59,14 +59,10 @@ func (f ResampleFilter) Process(source audio.Source) audio.Source {
|
|||
}
|
||||
}
|
||||
|
||||
outBlocks <- buffer
|
||||
outSource.IngestFloat32(buffer)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: f.sampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
return outSource
|
||||
}
|
||||
|
|
|
@ -31,11 +31,11 @@ func TestFilterChainResample(t *testing.T) {
|
|||
sink := audio.NewForwardSink(audio.NewNullSink())
|
||||
sink.Process(result)
|
||||
|
||||
if result.SampleRate != sampleRate {
|
||||
t.Errorf("Wrong SampleRate %d != %d", result.SampleRate, sampleRate)
|
||||
if result.GetSampleRate() != sampleRate {
|
||||
t.Errorf("Wrong SampleRate %d != %d", result.GetSampleRate(), sampleRate)
|
||||
}
|
||||
if result.Channels != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.SampleRate, 2)
|
||||
if result.GetChannels() != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.GetChannels(), 2)
|
||||
}
|
||||
if sink.SamplesRead != 6285000 {
|
||||
t.Errorf("Wrong Sample Count %d != %d", sink.SamplesRead, 6285000)
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/cgo"
|
||||
"git.gammaspectra.live/S.O.N.G/go-fdkaac/fdkaac"
|
||||
aac_adts "github.com/edgeware/mp4ff/aac"
|
||||
"github.com/edgeware/mp4ff/mp4"
|
||||
|
@ -31,7 +30,7 @@ func (f Format) Description() string {
|
|||
return fmt.Sprintf("libfdk-aac [%s] (S.O.N.G/go-fdkaac)", fdkaac.EncoderVersion())
|
||||
}
|
||||
|
||||
func decodeFrame(decoder *fdkaac.AacDecoder, r io.Reader) ([]float32, error) {
|
||||
func decodeFrame(decoder *fdkaac.AacDecoder, r io.Reader) ([]int16, error) {
|
||||
pcm, err := tryDecodeFrame(decoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -64,7 +63,7 @@ func decodeFrame(decoder *fdkaac.AacDecoder, r io.Reader) ([]float32, error) {
|
|||
return decodeFrame(decoder, r)
|
||||
}
|
||||
|
||||
func tryDecodeFrame(decoder *fdkaac.AacDecoder) ([]float32, error) {
|
||||
func tryDecodeFrame(decoder *fdkaac.AacDecoder) ([]int16, error) {
|
||||
pcm, err := decoder.Decode()
|
||||
|
||||
if err != nil {
|
||||
|
@ -72,13 +71,15 @@ func tryDecodeFrame(decoder *fdkaac.AacDecoder) ([]float32, error) {
|
|||
}
|
||||
|
||||
if pcm != nil {
|
||||
return cgo.Int32ToFloat32(cgo.BytesToInt32(pcm, 16), 16), nil
|
||||
out := make([]int16, len(pcm)/2)
|
||||
copy(out, unsafe.Slice((*int16)(unsafe.Pointer(&pcm[0])), len(pcm)/2))
|
||||
return out, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func decodeFrameMP4(decoder *fdkaac.AacDecoder, demuxer *mp4Decoder) (result []float32, err error) {
|
||||
func decodeFrameMP4(decoder *fdkaac.AacDecoder, demuxer *mp4Decoder) (result []int16, err error) {
|
||||
pcm, err := tryDecodeFrame(decoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -116,24 +117,24 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
r.Seek(0, io.SeekStart)
|
||||
err = decoder.InitAdts()
|
||||
if err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := decodeFrame(decoder, r)
|
||||
|
||||
if err != nil {
|
||||
decoder.Close()
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
source := audio.NewFloat32Source(decoder.SampleRate(), decoder.NumChannels())
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
defer decoder.Close()
|
||||
|
||||
if len(buf) > 0 {
|
||||
newChannel <- buf
|
||||
source.IngestInt16(buf, 16)
|
||||
}
|
||||
|
||||
for {
|
||||
|
@ -144,38 +145,34 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
}
|
||||
|
||||
if len(buf) > 0 {
|
||||
newChannel <- buf
|
||||
source.IngestInt16(buf, 16)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: decoder.NumChannels(),
|
||||
SampleRate: decoder.SampleRate(),
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
} else {
|
||||
return audio.Source{}, fmt.Errorf("unsupported format mp4")
|
||||
return nil, fmt.Errorf("unsupported format mp4")
|
||||
err = decoder.InitRaw(mp4Demuxer.cookie)
|
||||
if err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err := decodeFrameMP4(decoder, mp4Demuxer)
|
||||
|
||||
if err != nil {
|
||||
decoder.Close()
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
source := audio.NewFloat32Source(decoder.SampleRate(), decoder.NumChannels())
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
defer decoder.Close()
|
||||
|
||||
if len(buf) > 0 {
|
||||
newChannel <- buf
|
||||
source.IngestInt16(buf, 16)
|
||||
}
|
||||
|
||||
for {
|
||||
|
@ -186,16 +183,12 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
}
|
||||
|
||||
if len(buf) > 0 {
|
||||
newChannel <- buf
|
||||
source.IngestInt16(buf, 16)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: decoder.NumChannels(),
|
||||
SampleRate: decoder.SampleRate(),
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -299,17 +292,17 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
|
||||
encoder := fdkaac.NewAacEncoder()
|
||||
if codecMode == 0 {
|
||||
err := encoder.InitLc(source.Channels, source.SampleRate, bitrate, muxingMode, afterburner)
|
||||
err := encoder.InitLc(source.GetChannels(), source.GetSampleRate(), bitrate, muxingMode, afterburner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if codecMode == 1 {
|
||||
err := encoder.InitHE(source.Channels, source.SampleRate, bitrate, muxingMode, afterburner)
|
||||
err := encoder.InitHE(source.GetChannels(), source.GetSampleRate(), bitrate, muxingMode, afterburner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := encoder.InitHEv2(source.Channels, source.SampleRate, bitrate, muxingMode, afterburner)
|
||||
err := encoder.InitHEv2(source.GetChannels(), source.GetSampleRate(), bitrate, muxingMode, afterburner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -320,7 +313,7 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
|
||||
if format == "mp4" {
|
||||
init := mp4.CreateEmptyInit()
|
||||
init.AddEmptyTrack(uint32(source.SampleRate), "audio", "en")
|
||||
init.AddEmptyTrack(uint32(source.GetSampleRate()), "audio", "en")
|
||||
trackId := init.Moov.Mvhd.NextTrackID - 1
|
||||
trak := init.Moov.Trak
|
||||
|
||||
|
@ -335,18 +328,18 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
stsd := trak.Mdia.Minf.Stbl.Stsd
|
||||
asc := &aac_adts.AudioSpecificConfig{
|
||||
ObjectType: byte(objType),
|
||||
ChannelConfiguration: byte(source.Channels),
|
||||
SamplingFrequency: source.SampleRate,
|
||||
ChannelConfiguration: byte(source.GetChannels()),
|
||||
SamplingFrequency: source.GetSampleRate(),
|
||||
ExtensionFrequency: 0,
|
||||
SBRPresentFlag: false,
|
||||
PSPresentFlag: false,
|
||||
}
|
||||
switch objType {
|
||||
case aac_adts.HEAACv1:
|
||||
asc.ExtensionFrequency = 2 * source.SampleRate
|
||||
asc.ExtensionFrequency = 2 * source.GetSampleRate()
|
||||
asc.SBRPresentFlag = true
|
||||
case aac_adts.HEAACv2:
|
||||
asc.ExtensionFrequency = 2 * source.SampleRate
|
||||
asc.ExtensionFrequency = 2 * source.GetSampleRate()
|
||||
asc.SBRPresentFlag = true
|
||||
asc.ChannelConfiguration = 1
|
||||
asc.PSPresentFlag = true
|
||||
|
@ -361,7 +354,7 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
esds := mp4.CreateEsdsBox(ascBytes)
|
||||
mp4a := mp4.CreateAudioSampleEntryBox("mp4a",
|
||||
uint16(asc.ChannelConfiguration),
|
||||
16, uint16(source.SampleRate), esds)
|
||||
16, uint16(source.GetSampleRate()), esds)
|
||||
stsd.AddChild(mp4a)
|
||||
}
|
||||
|
||||
|
@ -398,15 +391,15 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
outputPacket := func(packet []byte) {
|
||||
outputBuffer = append(outputBuffer, packet)
|
||||
|
||||
if time.Duration(float64(time.Second)*(float64(frameSize*len(outputBuffer))/float64(source.SampleRate))) >= segmentDuration {
|
||||
if time.Duration(float64(time.Second)*(float64(frameSize*len(outputBuffer))/float64(source.GetSampleRate()))) >= segmentDuration {
|
||||
outputSegment()
|
||||
}
|
||||
}
|
||||
|
||||
var buffer []int16
|
||||
for block := range source.Blocks {
|
||||
for block := range source.ToInt16().GetBlocks() {
|
||||
|
||||
buffer = append(buffer, cgo.Float32ToInt16(block)...)
|
||||
buffer = append(buffer, block...)
|
||||
|
||||
for len(buffer) >= frameSize {
|
||||
sl := buffer[:frameSize]
|
||||
|
@ -459,9 +452,9 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
} else {
|
||||
|
||||
var buffer []int16
|
||||
for block := range source.Blocks {
|
||||
for block := range source.ToInt16().GetBlocks() {
|
||||
|
||||
buffer = append(buffer, cgo.Float32ToInt16(block)...)
|
||||
buffer = append(buffer, block...)
|
||||
|
||||
for len(buffer) >= frameSize {
|
||||
sl := buffer[:frameSize]
|
||||
|
|
|
@ -88,7 +88,7 @@ func TestDecodeAAC(t *testing.T) {
|
|||
}
|
||||
|
||||
//Decode
|
||||
for range source.Blocks {
|
||||
for range source.ToInt16().GetBlocks() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ package aac
|
|||
import (
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/cgo"
|
||||
"github.com/gen2brain/aac-go"
|
||||
"io"
|
||||
)
|
||||
|
@ -63,8 +62,8 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
|
||||
encoder, err := aac.NewEncoder(writer, &aac.Options{
|
||||
BitRate: bitrate * 1024,
|
||||
NumChannels: source.Channels,
|
||||
SampleRate: source.SampleRate,
|
||||
NumChannels: source.GetChannels(),
|
||||
SampleRate: source.GetSampleRate(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
@ -77,8 +76,8 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
|
||||
go func() {
|
||||
defer pWriter.Close()
|
||||
for block := range source.Blocks {
|
||||
if _, decodeErr := pWriter.Write(cgo.Float32ToInt16(block)); decodeErr != nil {
|
||||
for block := range source.ToInt16().GetBlocks() {
|
||||
if _, decodeErr := pWriter.Write(block); decodeErr != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,18 +31,18 @@ func (f Format) Description() string {
|
|||
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
||||
mp4Demuxer, err := tryDecodeMP4(r)
|
||||
if err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decoder := go_alac.NewFrameDecoder(mp4Demuxer.cookie)
|
||||
if decoder == nil {
|
||||
return audio.Source{}, errors.New("could not decode")
|
||||
return nil, errors.New("could not decode")
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
source := audio.NewInt32Source(decoder.GetBitDepth(), decoder.GetSampleRate(), decoder.GetChannels())
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
|
||||
for {
|
||||
samples := mp4Demuxer.Read()
|
||||
|
@ -54,27 +54,24 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
if pcm == nil {
|
||||
return
|
||||
}
|
||||
newChannel <- cgo.Int32ToFloat32(cgo.BytesToInt32(pcm, decoder.GetBitDepth()), decoder.GetBitDepth())
|
||||
|
||||
source.IngestInt32(cgo.BytesToInt32(pcm, decoder.GetBitDepth()), decoder.GetBitDepth())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: decoder.GetChannels(),
|
||||
SampleRate: decoder.GetSampleRate(),
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) {
|
||||
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Float32Source, format.AnalyzerChannel, error) {
|
||||
mp4Demuxer, err := tryDecodeMP4(r)
|
||||
if err != nil {
|
||||
return audio.Source{}, nil, err
|
||||
return audio.Float32Source{}, nil, err
|
||||
}
|
||||
|
||||
decoder := go_alac.NewFrameDecoder(mp4Demuxer.cookie)
|
||||
if decoder == nil {
|
||||
return audio.Source{}, nil, errors.New("could not decode")
|
||||
return audio.Float32Source{}, nil, errors.New("could not decode")
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
|
@ -108,7 +105,7 @@ func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.Analyzer
|
|||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
return audio.Float32Source{
|
||||
Channels: decoder.GetChannels(),
|
||||
SampleRate: decoder.GetSampleRate(),
|
||||
Blocks: newChannel,
|
||||
|
@ -140,7 +137,7 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
}
|
||||
}
|
||||
|
||||
encoder := go_alac.NewFormatEncoder(writer, source.SampleRate, source.Channels, bitsPerSample, fastMode, segmentDuration)
|
||||
encoder := go_alac.NewFormatEncoder(writer, source.GetSampleRate(), source.GetChannels(), bitsPerSample, fastMode, segmentDuration)
|
||||
|
||||
if encoder == nil {
|
||||
return errors.New("could not create encoder")
|
||||
|
@ -150,27 +147,24 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
|
||||
switch bitsPerSample {
|
||||
case 32:
|
||||
for block := range source.Blocks {
|
||||
|
||||
samples := cgo.Float32ToInt32(block, 32)
|
||||
|
||||
encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*4))
|
||||
for block := range source.ToInt32(32).GetBlocks() {
|
||||
encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&block[0])), len(block)*4))
|
||||
}
|
||||
case 24:
|
||||
for block := range source.Blocks {
|
||||
//TODO
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
|
||||
samples := cgo.Float32ToInt24(block)
|
||||
|
||||
encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)))
|
||||
}
|
||||
case 16:
|
||||
for block := range source.Blocks {
|
||||
|
||||
samples := cgo.Float32ToInt16(block)
|
||||
encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*2))
|
||||
for block := range source.ToInt16().GetBlocks() {
|
||||
encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&block[0])), len(block)*2))
|
||||
}
|
||||
case 8:
|
||||
for block := range source.Blocks {
|
||||
//TODO
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
|
||||
samples := cgo.Float32ToInt8(block)
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ func TestDecodeALAC(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
for range source.Blocks {
|
||||
for range source.ToFloat32().GetBlocks() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ type AnalyzerChannel chan *AnalyzerPacket
|
|||
type AnalyzerDecoder interface {
|
||||
Decoder
|
||||
// OpenAnalyzer Opens a stream and decodes it into an audio.Source, and additionally copy AnalyzerPacket back
|
||||
OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, AnalyzerChannel, error)
|
||||
OpenAnalyzer(r io.ReadSeekCloser) (audio.Float32Source, AnalyzerChannel, error)
|
||||
}
|
||||
|
||||
func (c AnalyzerChannel) Split(n int) (channels []AnalyzerChannel) {
|
||||
|
|
|
@ -29,13 +29,13 @@ func (f Format) Description() string {
|
|||
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
||||
decoder, err := libflac.New(r)
|
||||
if err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
source := audio.NewInt32Source(int(decoder.Info.BitsPerSample), int(decoder.Info.SampleRate), int(decoder.Info.NChannels))
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
defer decoder.Close()
|
||||
|
||||
frameNumber := 0
|
||||
|
@ -61,24 +61,20 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
buffer[i] = currentFrame.Subframes[i%channels].Samples[i/channels]
|
||||
}
|
||||
|
||||
newChannel <- cgo.Int32ToFloat32(buffer, bitDepth)
|
||||
source.IngestInt32(buffer, bitDepth)
|
||||
|
||||
frameNumber++
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: int(decoder.Info.NChannels),
|
||||
SampleRate: int(decoder.Info.SampleRate),
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) {
|
||||
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Float32Source, format.AnalyzerChannel, error) {
|
||||
|
||||
decoder, err := libflac.New(r)
|
||||
if err != nil {
|
||||
return audio.Source{}, nil, err
|
||||
return audio.Float32Source{}, nil, err
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
|
@ -125,7 +121,7 @@ func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.Analyzer
|
|||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
return audio.Float32Source{
|
||||
Channels: int(decoder.Info.NChannels),
|
||||
SampleRate: int(decoder.Info.SampleRate),
|
||||
Blocks: newChannel,
|
||||
|
|
|
@ -32,17 +32,17 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
decoder, err := libflac.NewDecoderReader(r)
|
||||
if err != nil {
|
||||
if _, err2 := r.Seek(currentPosition, io.SeekStart); err2 != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
if decoder, err = libflac.NewDecoderReaderOgg(r); err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
source := audio.NewInt32Source(decoder.Depth, decoder.Rate, decoder.Channels)
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
defer decoder.Close()
|
||||
|
||||
frameNumber := 0
|
||||
|
@ -59,28 +59,24 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
bitDepth = currentFrame.Depth
|
||||
}
|
||||
|
||||
newChannel <- cgo.Int32ToFloat32(currentFrame.Buffer, bitDepth)
|
||||
source.IngestInt32(currentFrame.Buffer, bitDepth)
|
||||
|
||||
frameNumber++
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: decoder.Channels,
|
||||
SampleRate: decoder.Rate,
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) {
|
||||
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Float32Source, format.AnalyzerChannel, error) {
|
||||
currentPosition, _ := r.Seek(0, io.SeekCurrent)
|
||||
decoder, err := libflac.NewDecoderReader(r)
|
||||
if err != nil {
|
||||
if _, err2 := r.Seek(currentPosition, io.SeekStart); err2 != nil {
|
||||
return audio.Source{}, nil, err
|
||||
return audio.Float32Source{}, nil, err
|
||||
}
|
||||
if decoder, err = libflac.NewDecoderReaderOgg(r); err != nil {
|
||||
return audio.Source{}, nil, err
|
||||
return audio.Float32Source{}, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +119,7 @@ func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.Analyzer
|
|||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
return audio.Float32Source{
|
||||
Channels: decoder.Channels,
|
||||
SampleRate: decoder.Rate,
|
||||
Blocks: newChannel,
|
||||
|
@ -181,15 +177,15 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
|
||||
if writeSeeker, ok := writer.(libflac.FlacWriter); ok {
|
||||
if ogg {
|
||||
encoder, err = libflac.NewEncoderWriteSeekerOgg(writeSeeker, source.Channels, bitsPerSample, source.SampleRate, compressionLevel, streamable, blockSize)
|
||||
encoder, err = libflac.NewEncoderWriteSeekerOgg(writeSeeker, source.GetChannels(), bitsPerSample, source.GetSampleRate(), compressionLevel, streamable, blockSize)
|
||||
} else {
|
||||
encoder, err = libflac.NewEncoderWriteSeeker(writeSeeker, source.Channels, bitsPerSample, source.SampleRate, compressionLevel, streamable, blockSize)
|
||||
encoder, err = libflac.NewEncoderWriteSeeker(writeSeeker, source.GetChannels(), bitsPerSample, source.GetSampleRate(), compressionLevel, streamable, blockSize)
|
||||
}
|
||||
} else {
|
||||
if ogg {
|
||||
encoder, err = libflac.NewEncoderWriterOgg(writer, source.Channels, bitsPerSample, source.SampleRate, compressionLevel, streamable, blockSize)
|
||||
encoder, err = libflac.NewEncoderWriterOgg(writer, source.GetChannels(), bitsPerSample, source.GetSampleRate(), compressionLevel, streamable, blockSize)
|
||||
} else {
|
||||
encoder, err = libflac.NewEncoderWriter(writer, source.Channels, bitsPerSample, source.SampleRate, compressionLevel, streamable, blockSize)
|
||||
encoder, err = libflac.NewEncoderWriter(writer, source.GetChannels(), bitsPerSample, source.GetSampleRate(), compressionLevel, streamable, blockSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,13 +195,13 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
|
||||
defer encoder.Close()
|
||||
|
||||
for block := range source.Blocks {
|
||||
for block := range source.ToInt32(bitsPerSample).GetBlocks() {
|
||||
|
||||
err = encoder.WriteFrame(libflac.Frame{
|
||||
Rate: source.SampleRate,
|
||||
Channels: source.Channels,
|
||||
Rate: source.GetSampleRate(),
|
||||
Channels: source.GetChannels(),
|
||||
Depth: bitsPerSample,
|
||||
Buffer: cgo.Float32ToInt32(block, bitsPerSample),
|
||||
Buffer: block,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -22,11 +22,11 @@ func (f NullFormat) Description() string {
|
|||
}
|
||||
|
||||
func (f NullFormat) Open(io.ReadSeekCloser) (audio.Source, error) {
|
||||
return audio.Source{}, errors.New("null format")
|
||||
return nil, errors.New("null format")
|
||||
}
|
||||
|
||||
func (f NullFormat) OpenAnalyzer(io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) {
|
||||
return audio.Source{}, nil, errors.New("null format")
|
||||
func (f NullFormat) OpenAnalyzer(io.ReadSeekCloser) (audio.Float32Source, format.AnalyzerChannel, error) {
|
||||
return audio.Float32Source{}, nil, errors.New("null format")
|
||||
}
|
||||
|
||||
func (f NullFormat) Encode(audio.Source, io.WriteCloser, map[string]interface{}) error {
|
||||
|
@ -131,7 +131,7 @@ func Open(r io.ReadSeekCloser, decoders []format.Decoder) (source audio.Source,
|
|||
return
|
||||
}
|
||||
source, err = decoder.Open(r)
|
||||
if source.Blocks != nil && err == nil {
|
||||
if source != nil && err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -140,10 +140,10 @@ func Open(r io.ReadSeekCloser, decoders []format.Decoder) (source audio.Source,
|
|||
if _, err = r.Seek(startOffset, io.SeekStart); err != nil {
|
||||
return
|
||||
}
|
||||
return audio.Source{}, errors.New("could not open stream")
|
||||
return nil, errors.New("could not open stream")
|
||||
}
|
||||
|
||||
func OpenAnalyzer(r io.ReadSeekCloser, decoders []format.Decoder) (source audio.Source, analyzerChannel format.AnalyzerChannel, err error) {
|
||||
func OpenAnalyzer(r io.ReadSeekCloser, decoders []format.Decoder) (source audio.Float32Source, analyzerChannel format.AnalyzerChannel, err error) {
|
||||
var startOffset int64
|
||||
startOffset, err = r.Seek(0, io.SeekCurrent)
|
||||
|
||||
|
@ -163,5 +163,5 @@ func OpenAnalyzer(r io.ReadSeekCloser, decoders []format.Decoder) (source audio.
|
|||
if _, err = r.Seek(startOffset, io.SeekStart); err != nil {
|
||||
return
|
||||
}
|
||||
return audio.Source{}, nil, errors.New("could not open stream analyzer")
|
||||
return audio.Float32Source{}, nil, errors.New("could not open stream analyzer")
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ func DoTest(ext string, locations []string, t *testing.T) {
|
|||
}
|
||||
|
||||
//Decode
|
||||
for range source.Blocks {
|
||||
for range source.ToFloat32().GetBlocks() {
|
||||
|
||||
}
|
||||
})
|
||||
|
|
|
@ -5,7 +5,6 @@ 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/kvark128/minimp3"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
@ -29,13 +28,13 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
|
||||
_, err := decoder.Read([]byte{})
|
||||
if err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
source := audio.NewInt16Source(16, decoder.SampleRate(), 2)
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
samples := make([]int16, BlockSize*2)
|
||||
const SizeofInt16 = int(unsafe.Sizeof(int16(0)))
|
||||
byteSlice := unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*SizeofInt16)
|
||||
|
@ -46,16 +45,13 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
}
|
||||
n /= SizeofInt16
|
||||
|
||||
//convert 16-bit to f32 samples
|
||||
newChannel <- cgo.Int16ToFloat32(samples[:n], 16)
|
||||
buf := make([]int16, n)
|
||||
copy(buf, samples[:n])
|
||||
source.IngestInt16(buf, 16)
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: decoder.Channels(),
|
||||
SampleRate: decoder.SampleRate(),
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func (f Format) Identify(peek []byte, extension string) bool {
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/cgo"
|
||||
"github.com/viert/go-lame"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
@ -91,10 +90,10 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
return errors.New("could not create encoder")
|
||||
}
|
||||
defer encoder.Close()
|
||||
if err := encoder.SetNumChannels(source.Channels); err != nil {
|
||||
if err := encoder.SetNumChannels(source.GetChannels()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := encoder.SetInSamplerate(source.SampleRate); err != nil {
|
||||
if err := encoder.SetInSamplerate(source.GetSampleRate()); err != nil {
|
||||
return err
|
||||
}
|
||||
encoder.SetWriteID3TagAutomatic(false)
|
||||
|
@ -115,10 +114,8 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
}
|
||||
}
|
||||
|
||||
for block := range source.Blocks {
|
||||
samples := cgo.Float32ToInt16(block)
|
||||
|
||||
_, err := encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*2))
|
||||
for block := range source.ToInt16().GetBlocks() {
|
||||
_, err := encoder.Write(unsafe.Slice((*byte)(unsafe.Pointer(&block[0])), len(block)*2))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -5,7 +5,6 @@ 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"
|
||||
|
@ -30,13 +29,13 @@ func (f Format) Description() string {
|
|||
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
||||
decoder, err := mp3Lib.NewDecoder(r)
|
||||
if err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
source := audio.NewInt16Source(16, decoder.SampleRate(), 2)
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
samples := make([]int16, BlockSize*2)
|
||||
const SizeofInt16 = int(unsafe.Sizeof(int16(0)))
|
||||
byteSlice := unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*SizeofInt16)
|
||||
|
@ -47,16 +46,13 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
}
|
||||
n /= SizeofInt16
|
||||
|
||||
//convert 16-bit to f32 samples
|
||||
newChannel <- cgo.Int16ToFloat32(samples[:n], 16)
|
||||
buf := make([]int16, n)
|
||||
copy(buf, samples[:n])
|
||||
source.IngestInt16(buf, 16)
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: 2,
|
||||
SampleRate: decoder.SampleRate(),
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func (f Format) Identify(peek []byte, extension string) bool {
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/audio/filter"
|
||||
"git.gammaspectra.live/S.O.N.G/Kirika/cgo"
|
||||
libopus "git.gammaspectra.live/S.O.N.G/go-pus"
|
||||
"io"
|
||||
)
|
||||
|
@ -32,14 +31,14 @@ func (f Format) Description() string {
|
|||
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
||||
stream, err := libopus.NewStream(r)
|
||||
if err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
source := audio.NewInt16Source(16, FixedSampleRate, 2)
|
||||
|
||||
go func() {
|
||||
defer stream.Close()
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
|
||||
buf := make([]int16, 120*FixedSampleRate*2)
|
||||
|
||||
|
@ -51,17 +50,13 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
}
|
||||
|
||||
if n > 0 {
|
||||
newChannel <- cgo.Int16ToFloat32(buf[:n*2], 16)
|
||||
source.IngestInt16(buf[:n*2], 16)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
//We always get two channels via stereo api
|
||||
return audio.Source{
|
||||
Channels: 2,
|
||||
SampleRate: FixedSampleRate,
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[string]interface{}) error {
|
||||
|
@ -109,7 +104,7 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
|
||||
source = filter.NewResampleFilter(FixedSampleRate, resampleQuality, 0).Process(source)
|
||||
|
||||
encoder, err := libopus.NewEncoder(FixedSampleRate, source.Channels, libopus.AppAudio, writer)
|
||||
encoder, err := libopus.NewEncoder(FixedSampleRate, source.GetChannels(), libopus.AppAudio, writer)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -131,8 +126,8 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
}
|
||||
}
|
||||
|
||||
for block := range source.Blocks {
|
||||
err = encoder.Encode(cgo.Float32ToInt16(block))
|
||||
for block := range source.ToInt16().GetBlocks() {
|
||||
err = encoder.Encode(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -68,18 +68,31 @@ func (i fakeReadWriteSeeker2) Write(p []byte) (n int, err error) {
|
|||
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
||||
decoder := libtta.MakeDecoder(fakeReadWriteSeeker{r})
|
||||
if decoder == nil {
|
||||
return audio.Source{}, errors.New("invalid decoder")
|
||||
return nil, errors.New("invalid decoder")
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
|
||||
info := &libtta.Info{}
|
||||
if err := decoder.GetInfo(info, 0); err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var source audio.Source
|
||||
switch info.Bps {
|
||||
case 8:
|
||||
//TODO
|
||||
source = audio.NewInt16Source(8, int(info.Sps), int(info.Nch))
|
||||
case 16:
|
||||
source = audio.NewInt16Source(16, int(info.Sps), int(info.Nch))
|
||||
case 24:
|
||||
//TODO: Use Int24
|
||||
source = audio.NewInt32Source(24, int(info.Sps), int(info.Nch))
|
||||
case 32:
|
||||
source = audio.NewInt32Source(32, int(info.Sps), int(info.Nch))
|
||||
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
|
||||
smpSize := int(info.Nch) * ((int(info.Bps) + 7) / 8)
|
||||
buffer := make([]byte, BlockSize*smpSize)
|
||||
|
@ -95,33 +108,26 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
bitDepth := int(info.Bps)
|
||||
|
||||
nsamples := len(bufferSlice) / (bitDepth / 8)
|
||||
var buf []float32
|
||||
|
||||
switch bitDepth {
|
||||
case 32:
|
||||
buf = cgo.Int32ToFloat32(unsafe.Slice((*int32)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth)
|
||||
source.IngestInt32(unsafe.Slice((*int32)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth)
|
||||
case 24:
|
||||
buf = cgo.Int24ToFloat32(unsafe.Slice((*byte)(unsafe.Pointer(&bufferSlice[0])), nsamples*3), bitDepth)
|
||||
source.IngestInt24(unsafe.Slice((*byte)(unsafe.Pointer(&bufferSlice[0])), nsamples*3), bitDepth)
|
||||
case 16:
|
||||
buf = cgo.Int16ToFloat32(unsafe.Slice((*int16)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth)
|
||||
source.IngestInt16(unsafe.Slice((*int16)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth)
|
||||
case 8:
|
||||
buf = cgo.Int8ToFloat32(unsafe.Slice((*int8)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth)
|
||||
source.IngestInt8(unsafe.Slice((*int8)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth)
|
||||
}
|
||||
|
||||
newChannel <- buf
|
||||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: int(info.Nch),
|
||||
SampleRate: int(info.Sps),
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
}
|
||||
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) {
|
||||
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Float32Source, format.AnalyzerChannel, error) {
|
||||
decoder := libtta.MakeDecoder(fakeReadWriteSeeker{r})
|
||||
if decoder == nil {
|
||||
return audio.Source{}, nil, errors.New("invalid decoder")
|
||||
return audio.Float32Source{}, nil, errors.New("invalid decoder")
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
|
@ -129,7 +135,7 @@ func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.Analyzer
|
|||
|
||||
info := &libtta.Info{}
|
||||
if err := decoder.GetInfo(info, 0); err != nil {
|
||||
return audio.Source{}, nil, err
|
||||
return audio.Float32Source{}, nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
@ -159,7 +165,7 @@ func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.Analyzer
|
|||
}
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
return audio.Float32Source{
|
||||
Channels: int(info.Nch),
|
||||
SampleRate: int(info.Sps),
|
||||
Blocks: newChannel,
|
||||
|
@ -197,9 +203,9 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
|
||||
err := encoder.SetInfo(&libtta.Info{
|
||||
Format: 1,
|
||||
Nch: uint32(source.Channels),
|
||||
Nch: uint32(source.GetChannels()),
|
||||
Bps: uint32(bitsPerSample),
|
||||
Sps: uint32(source.SampleRate),
|
||||
Sps: uint32(source.GetSampleRate()),
|
||||
Samples: math.MaxUint32,
|
||||
})
|
||||
|
||||
|
@ -208,22 +214,25 @@ func (f Format) Encode(source audio.Source, writer io.WriteCloser, options map[s
|
|||
}
|
||||
|
||||
switch bitsPerSample {
|
||||
case 32:
|
||||
for block := range source.ToInt32(32).GetBlocks() {
|
||||
encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(&block[0])), len(block)*4), nil)
|
||||
}
|
||||
case 24:
|
||||
for block := range source.Blocks {
|
||||
//TODO
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
|
||||
samples := cgo.Float32ToInt24(block)
|
||||
|
||||
encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)), nil)
|
||||
}
|
||||
case 16:
|
||||
for block := range source.Blocks {
|
||||
|
||||
samples := cgo.Float32ToInt16(block)
|
||||
|
||||
encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*2), nil)
|
||||
for block := range source.ToInt16().GetBlocks() {
|
||||
encoder.ProcessStream(unsafe.Slice((*byte)(unsafe.Pointer(&block[0])), len(block)*2), nil)
|
||||
}
|
||||
case 8:
|
||||
for block := range source.Blocks {
|
||||
//TODO
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
|
||||
samples := cgo.Float32ToInt8(block)
|
||||
|
||||
|
|
|
@ -27,13 +27,13 @@ func (f Format) Description() string {
|
|||
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
||||
reader, err := libvorbis.NewReader(r)
|
||||
if err != nil {
|
||||
return audio.Source{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newChannel := make(chan []float32)
|
||||
source := audio.NewFloat32Source(reader.Channels(), reader.SampleRate())
|
||||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
defer source.Close()
|
||||
|
||||
for {
|
||||
|
||||
|
@ -45,17 +45,12 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
|
|||
}
|
||||
|
||||
if n > 0 {
|
||||
newChannel <- buffer[:n]
|
||||
source.IngestFloat32(buffer[:n])
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
//We always get two channels via stereo api
|
||||
return audio.Source{
|
||||
Channels: reader.Channels(),
|
||||
SampleRate: reader.SampleRate(),
|
||||
Blocks: newChannel,
|
||||
}, nil
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func (f Format) Identify(peek []byte, extension string) bool {
|
||||
|
|
|
@ -55,8 +55,8 @@ func TestPacketizeFLAC(t *testing.T) {
|
|||
if packetCount != 4231 {
|
||||
t.Errorf("Wrong Packet Count %d != %d", packetCount, 4231)
|
||||
}
|
||||
if packetBytes != 51513533 {
|
||||
t.Errorf("Wrong Packet Bytes %d != %d", packetBytes, 51513533)
|
||||
if packetBytes != 51512604 {
|
||||
t.Errorf("Wrong Packet Bytes %d != %d", packetBytes, 51512604)
|
||||
}
|
||||
if bytes.Compare(shaHasher.Sum([]byte{}), shaPacketHasher.Sum([]byte{})) != 0 {
|
||||
t.Errorf("Mismatch on byte output %X != %X", shaPacketHasher.Sum([]byte{}), shaHasher.Sum([]byte{}))
|
||||
|
|
|
@ -49,7 +49,7 @@ func TestPacketizeMP3(t *testing.T) {
|
|||
if packetCount != 15040 {
|
||||
t.Errorf("Wrong Packet Count %d != %d", packetCount, 15040)
|
||||
}
|
||||
if packetBytes != 13901254 {
|
||||
t.Errorf("Wrong Packet Bytes %d != %d", packetBytes, 13901254)
|
||||
if packetBytes != 13899997 {
|
||||
t.Errorf("Wrong Packet Bytes %d != %d", packetBytes, 13899997)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,9 @@ type QueueEntry struct {
|
|||
|
||||
type Queue struct {
|
||||
queue []*QueueEntry
|
||||
output audio.Source
|
||||
float32Output *audio.Float32Source
|
||||
int16Output *audio.Int16Source
|
||||
int32Output *audio.Int32Source
|
||||
interrupt chan bool
|
||||
interruptDepth int64
|
||||
closed bool
|
||||
|
@ -31,50 +33,115 @@ type Queue struct {
|
|||
identifierCounter QueueIdentifier
|
||||
}
|
||||
|
||||
func NewQueue(sampleRate, channels int) *Queue {
|
||||
func NewQueue(format audio.SourceFormat, bitDepth, sampleRate, channels int) *Queue {
|
||||
if channels != 1 && channels != 2 {
|
||||
log.Panicf("not allowed channel number %d", channels)
|
||||
}
|
||||
|
||||
q := &Queue{
|
||||
interrupt: make(chan bool, 1),
|
||||
output: audio.Source{
|
||||
SampleRate: sampleRate,
|
||||
Channels: channels,
|
||||
Blocks: make(chan []float32),
|
||||
},
|
||||
}
|
||||
|
||||
switch format {
|
||||
case audio.SourceFloat32:
|
||||
q.float32Output = audio.NewFloat32Source(sampleRate, channels)
|
||||
case audio.SourceInt16:
|
||||
q.int16Output = audio.NewInt16Source(bitDepth, sampleRate, channels)
|
||||
case audio.SourceInt32:
|
||||
q.int32Output = audio.NewInt32Source(bitDepth, sampleRate, channels)
|
||||
default:
|
||||
log.Panicf("not found source format %d", int(format))
|
||||
}
|
||||
|
||||
q.start()
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Queue) spliceSources(input audio.Source) (output audio.Source, cancel chan bool) {
|
||||
cancel = make(chan bool, 1)
|
||||
output = audio.Source{
|
||||
Channels: input.Channels,
|
||||
SampleRate: input.SampleRate,
|
||||
Blocks: make(chan []float32),
|
||||
}
|
||||
go func() {
|
||||
defer close(output.Blocks)
|
||||
L:
|
||||
for {
|
||||
select {
|
||||
case <-cancel:
|
||||
break L
|
||||
case block, more := <-input.Blocks:
|
||||
if !more {
|
||||
//no more blocks!
|
||||
break L
|
||||
} else {
|
||||
output.Blocks <- block
|
||||
|
||||
switch q.GetSource().GetFormat() {
|
||||
case audio.SourceFloat32:
|
||||
output = audio.NewFloat32Source(input.GetSampleRate(), input.GetChannels())
|
||||
|
||||
sourceChannel := input.ToFloat32().GetBlocks()
|
||||
go func() {
|
||||
defer output.Close()
|
||||
L1:
|
||||
for {
|
||||
select {
|
||||
case <-cancel:
|
||||
break L1
|
||||
case block, more := <-sourceChannel:
|
||||
if !more {
|
||||
//no more blocks!
|
||||
break L1
|
||||
} else {
|
||||
output.IngestFloat32(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//sink remaining
|
||||
go audio.NewNullSink().Process(input)
|
||||
}()
|
||||
input.Unlock()
|
||||
|
||||
//sink remaining
|
||||
go audio.NewNullSink().Process(input)
|
||||
}()
|
||||
case audio.SourceInt16:
|
||||
output = audio.NewInt16Source(input.GetBitDepth(), input.GetSampleRate(), input.GetChannels())
|
||||
|
||||
sourceChannel := input.ToInt16().GetBlocks()
|
||||
go func() {
|
||||
defer output.Close()
|
||||
L2:
|
||||
for {
|
||||
select {
|
||||
case <-cancel:
|
||||
break L2
|
||||
case block, more := <-sourceChannel:
|
||||
if !more {
|
||||
//no more blocks!
|
||||
break L2
|
||||
} else {
|
||||
output.IngestInt16(block, input.GetBitDepth())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input.Unlock()
|
||||
|
||||
//sink remaining
|
||||
go audio.NewNullSink().Process(input)
|
||||
}()
|
||||
case audio.SourceInt32:
|
||||
output = audio.NewInt32Source(input.GetBitDepth(), input.GetSampleRate(), input.GetChannels())
|
||||
|
||||
sourceChannel := input.ToInt32(input.GetBitDepth()).GetBlocks()
|
||||
go func() {
|
||||
defer output.Close()
|
||||
L3:
|
||||
for {
|
||||
select {
|
||||
case <-cancel:
|
||||
break L3
|
||||
case block, more := <-sourceChannel:
|
||||
if !more {
|
||||
//no more blocks!
|
||||
break L3
|
||||
} else {
|
||||
output.IngestInt32(block, input.GetBitDepth())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input.Unlock()
|
||||
|
||||
//sink remaining
|
||||
go audio.NewNullSink().Process(input)
|
||||
}()
|
||||
default:
|
||||
log.Panicf("not found source format %d", int(input.GetFormat()))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -85,47 +152,135 @@ func (q *Queue) start() {
|
|||
defer q.wg.Done()
|
||||
var current *QueueEntry
|
||||
|
||||
L:
|
||||
for {
|
||||
q.lock.RLock()
|
||||
if q.closed {
|
||||
close(q.output.Blocks)
|
||||
break L
|
||||
}
|
||||
if len(q.queue) == 0 { //no more entries, wait for interrupt
|
||||
q.lock.RUnlock()
|
||||
<-q.interrupt
|
||||
atomic.AddInt64(&q.interruptDepth, -1)
|
||||
continue
|
||||
}
|
||||
current = q.queue[0]
|
||||
q.lock.RUnlock()
|
||||
|
||||
F:
|
||||
if q.float32Output != nil {
|
||||
L1:
|
||||
for {
|
||||
select {
|
||||
case <-q.interrupt:
|
||||
q.lock.RLock()
|
||||
if q.closed {
|
||||
q.float32Output.Close()
|
||||
break L1
|
||||
}
|
||||
if len(q.queue) == 0 { //no more entries, wait for interrupt
|
||||
q.lock.RUnlock()
|
||||
<-q.interrupt
|
||||
atomic.AddInt64(&q.interruptDepth, -1)
|
||||
//force recheck
|
||||
break F
|
||||
case block, more := <-current.Source.Blocks:
|
||||
if !more {
|
||||
//no more blocks! skip
|
||||
if current.EndCallback != nil {
|
||||
current.EndCallback(q, current)
|
||||
continue
|
||||
}
|
||||
current = q.queue[0]
|
||||
q.lock.RUnlock()
|
||||
|
||||
F1:
|
||||
for {
|
||||
select {
|
||||
case <-q.interrupt:
|
||||
atomic.AddInt64(&q.interruptDepth, -1)
|
||||
//force recheck
|
||||
break F1
|
||||
case block, more := <-current.Source.(*audio.Float32Source).Blocks:
|
||||
if !more {
|
||||
//no more blocks! skip
|
||||
if current.EndCallback != nil {
|
||||
current.EndCallback(q, current)
|
||||
}
|
||||
q.Remove(current.Identifier)
|
||||
break F1
|
||||
} else {
|
||||
if current.StartCallback != nil && current.ReadSamples == 0 && len(block) > 0 {
|
||||
current.StartCallback(q, current)
|
||||
}
|
||||
current.ReadSamples += len(block) / current.Source.GetChannels()
|
||||
q.float32Output.Blocks <- block
|
||||
}
|
||||
q.Remove(current.Identifier)
|
||||
break F
|
||||
} else {
|
||||
if current.StartCallback != nil && current.ReadSamples == 0 && len(block) > 0 {
|
||||
current.StartCallback(q, current)
|
||||
}
|
||||
current.ReadSamples += len(block) / current.Source.Channels
|
||||
q.output.Blocks <- block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else if q.int16Output != nil {
|
||||
L2:
|
||||
for {
|
||||
q.lock.RLock()
|
||||
if q.closed {
|
||||
q.int16Output.Close()
|
||||
break L2
|
||||
}
|
||||
if len(q.queue) == 0 { //no more entries, wait for interrupt
|
||||
q.lock.RUnlock()
|
||||
<-q.interrupt
|
||||
atomic.AddInt64(&q.interruptDepth, -1)
|
||||
continue
|
||||
}
|
||||
current = q.queue[0]
|
||||
q.lock.RUnlock()
|
||||
|
||||
F2:
|
||||
for {
|
||||
select {
|
||||
case <-q.interrupt:
|
||||
atomic.AddInt64(&q.interruptDepth, -1)
|
||||
//force recheck
|
||||
break F2
|
||||
case block, more := <-current.Source.(*audio.Int16Source).Blocks:
|
||||
if !more {
|
||||
//no more blocks! skip
|
||||
if current.EndCallback != nil {
|
||||
current.EndCallback(q, current)
|
||||
}
|
||||
q.Remove(current.Identifier)
|
||||
break F2
|
||||
} else {
|
||||
if current.StartCallback != nil && current.ReadSamples == 0 && len(block) > 0 {
|
||||
current.StartCallback(q, current)
|
||||
}
|
||||
current.ReadSamples += len(block) / current.Source.GetChannels()
|
||||
q.int16Output.Blocks <- block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else if q.int32Output != nil {
|
||||
L3:
|
||||
for {
|
||||
q.lock.RLock()
|
||||
if q.closed {
|
||||
q.int32Output.Close()
|
||||
break L3
|
||||
}
|
||||
if len(q.queue) == 0 { //no more entries, wait for interrupt
|
||||
q.lock.RUnlock()
|
||||
<-q.interrupt
|
||||
atomic.AddInt64(&q.interruptDepth, -1)
|
||||
continue
|
||||
}
|
||||
current = q.queue[0]
|
||||
q.lock.RUnlock()
|
||||
|
||||
F3:
|
||||
for {
|
||||
select {
|
||||
case <-q.interrupt:
|
||||
atomic.AddInt64(&q.interruptDepth, -1)
|
||||
//force recheck
|
||||
break F3
|
||||
case block, more := <-current.Source.(*audio.Int32Source).Blocks:
|
||||
if !more {
|
||||
//no more blocks! skip
|
||||
if current.EndCallback != nil {
|
||||
current.EndCallback(q, current)
|
||||
}
|
||||
q.Remove(current.Identifier)
|
||||
break F3
|
||||
} else {
|
||||
if current.StartCallback != nil && current.ReadSamples == 0 && len(block) > 0 {
|
||||
current.StartCallback(q, current)
|
||||
}
|
||||
current.ReadSamples += len(block) / current.Source.GetChannels()
|
||||
q.int32Output.Blocks <- block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
@ -133,9 +288,13 @@ 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.QualityFastest, 0))
|
||||
return filter.NewFilterChain(source, filter.MonoFilter{}, filter.NewResampleFilter(q.GetSampleRate(), filter.QualityFastest, 0), filter.SourceFormatFilter{
|
||||
Format: q.GetSource().GetFormat(),
|
||||
})
|
||||
} else {
|
||||
return filter.NewFilterChain(source, filter.StereoFilter{}, filter.NewResampleFilter(q.GetSampleRate(), filter.QualityFastest, 0))
|
||||
return filter.NewFilterChain(source, filter.StereoFilter{}, filter.NewResampleFilter(q.GetSampleRate(), filter.QualityFastest, 0), filter.SourceFormatFilter{
|
||||
Format: q.GetSource().GetFormat(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +304,7 @@ func (q *Queue) AddHead(source audio.Source, startCallback func(q *Queue, entry
|
|||
splicedOutput, cancel := q.spliceSources(source)
|
||||
identifier = q.identifierCounter
|
||||
if len(q.queue) > 0 {
|
||||
q.queue = append(q.queue[:1], append([]*QueueEntry{&QueueEntry{
|
||||
q.queue = append(q.queue[:1], append([]*QueueEntry{{
|
||||
Identifier: identifier,
|
||||
Source: q.getFilterChain(splicedOutput),
|
||||
cancel: cancel,
|
||||
|
@ -203,6 +362,9 @@ func (q *Queue) Remove(identifier QueueIdentifier) bool {
|
|||
if e.Identifier == identifier {
|
||||
q.sendInterrupt()
|
||||
e.cancel <- true
|
||||
|
||||
e.Source.Unlock()
|
||||
|
||||
go audio.NewNullSink().Process(e.Source)
|
||||
//delete entry
|
||||
q.queue = append(q.queue[:i], q.queue[i+1:]...)
|
||||
|
@ -275,15 +437,22 @@ func (q *Queue) GetQueue() (entries []*QueueEntry) {
|
|||
}
|
||||
|
||||
func (q *Queue) GetSource() audio.Source {
|
||||
return q.output
|
||||
if q.float32Output != nil {
|
||||
return q.float32Output
|
||||
} else if q.int16Output != nil {
|
||||
return q.int16Output
|
||||
} else if q.int32Output != nil {
|
||||
return q.int32Output
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queue) GetSampleRate() int {
|
||||
return q.output.SampleRate
|
||||
return q.GetSource().GetSampleRate()
|
||||
}
|
||||
|
||||
func (q *Queue) GetChannels() int {
|
||||
return q.output.Channels
|
||||
return q.GetSource().GetChannels()
|
||||
}
|
||||
|
||||
func (q *Queue) sendInterrupt() {
|
||||
|
|
|
@ -14,7 +14,7 @@ func TestQueue(t *testing.T) {
|
|||
|
||||
const sampleRate = 44100
|
||||
|
||||
q := NewQueue(sampleRate, 2)
|
||||
q := NewQueue(audio.SourceInt16, 16, sampleRate, 2)
|
||||
flacFormat := guess.GetDecoder("flac")
|
||||
|
||||
for _, location := range test.TestSampleLocations {
|
||||
|
@ -62,11 +62,11 @@ func TestQueue(t *testing.T) {
|
|||
|
||||
q.Wait()
|
||||
|
||||
if result.SampleRate != sampleRate {
|
||||
t.Errorf("Wrong SampleRate %d != %d", result.SampleRate, sampleRate)
|
||||
if result.GetSampleRate() != sampleRate {
|
||||
t.Errorf("Wrong SampleRate %d != %d", result.GetSampleRate(), sampleRate)
|
||||
}
|
||||
if result.Channels != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.SampleRate, 2)
|
||||
if result.GetChannels() != 2 {
|
||||
t.Errorf("Wrong Channel Count %d != %d", result.GetChannels(), 2)
|
||||
}
|
||||
if sink.SamplesRead != 470828559 {
|
||||
t.Errorf("Wrong Sample Count %d != %d", sink.SamplesRead, 470828559)
|
||||
|
|
|
@ -21,11 +21,11 @@ func NewNormalizationFilter(delayInSeconds int) NormalizationFilter {
|
|||
}
|
||||
|
||||
func (f NormalizationFilter) Process(source audio.Source) audio.Source {
|
||||
outBlocks := make(chan []float32)
|
||||
outSource := audio.NewFloat32Source(source.GetSampleRate(), source.GetChannels())
|
||||
go func() {
|
||||
defer close(outBlocks)
|
||||
defer outSource.Close()
|
||||
|
||||
state := libebur128.NewState(source.Channels, source.SampleRate, libebur128.LoudnessShortTerm|libebur128.SamplePeak)
|
||||
state := libebur128.NewState(source.GetChannels(), source.GetSampleRate(), libebur128.LoudnessShortTerm|libebur128.SamplePeak)
|
||||
if state == nil {
|
||||
return
|
||||
}
|
||||
|
@ -38,7 +38,8 @@ func (f NormalizationFilter) Process(source audio.Source) audio.Source {
|
|||
var sampleBuffer []float32
|
||||
var adjustment float32 = 1.0
|
||||
|
||||
for block := range source.Blocks {
|
||||
//TODO: do this for int16 and int32
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
if state.AddFloat(block) != nil {
|
||||
return
|
||||
}
|
||||
|
@ -64,7 +65,7 @@ func (f NormalizationFilter) Process(source audio.Source) audio.Source {
|
|||
|
||||
volume := math.Pow(10, (gain)/20)
|
||||
|
||||
nsamples := source.SampleRate * source.Channels * f.delay
|
||||
nsamples := source.GetSampleRate() * source.GetChannels() * f.delay
|
||||
|
||||
ratio := float32(math.Min(1, float64(len(block))/float64(nsamples)))
|
||||
|
||||
|
@ -86,7 +87,7 @@ func (f NormalizationFilter) Process(source audio.Source) audio.Source {
|
|||
for i, e := range sampleBuffer[:size] {
|
||||
out[i] = e * adjustment
|
||||
}
|
||||
outBlocks <- out
|
||||
outSource.IngestFloat32(out)
|
||||
sampleBuffer = sampleBuffer[size:]
|
||||
} else {
|
||||
adjustment = float32(volume)
|
||||
|
@ -103,14 +104,10 @@ func (f NormalizationFilter) Process(source audio.Source) audio.Source {
|
|||
for i := range sampleBuffer {
|
||||
out[i] = sampleBuffer[i] * adjustment
|
||||
}
|
||||
outBlocks <- out
|
||||
outSource.IngestFloat32(out)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return audio.Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: source.SampleRate,
|
||||
Blocks: outBlocks,
|
||||
}
|
||||
return outSource
|
||||
}
|
||||
|
|
|
@ -11,16 +11,17 @@ import (
|
|||
|
||||
const referenceLevel = -18.0
|
||||
|
||||
//GetTrackReplayGain calculates track ReplayGain 2.0
|
||||
// GetTrackReplayGain calculates track ReplayGain 2.0
|
||||
func GetTrackReplayGain(source audio.Source) (gain, peak float64, err error) {
|
||||
state := libebur128.NewState(source.Channels, source.SampleRate, libebur128.LoudnessGlobalMomentary|libebur128.SamplePeak)
|
||||
state := libebur128.NewState(source.GetChannels(), source.GetSampleRate(), libebur128.LoudnessGlobalMomentary|libebur128.SamplePeak)
|
||||
if state == nil {
|
||||
err = errors.New("could not initialize state")
|
||||
return
|
||||
}
|
||||
defer state.Close()
|
||||
|
||||
for block := range source.Blocks {
|
||||
//TODO: do this for int32 and int16
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
if err = state.AddFloat(block); err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -47,7 +48,7 @@ func GetTrackReplayGain(source audio.Source) (gain, peak float64, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
//GetAlbumReplayGain calculates album and tracks ReplayGain 2.0
|
||||
// GetAlbumReplayGain calculates album and tracks ReplayGain 2.0
|
||||
func GetAlbumReplayGain(sources []audio.Source) (albumGain, albumPeak float64, trackGains []float64, trackPeaks []float64, err error) {
|
||||
var states []*libebur128.State
|
||||
var wg sync.WaitGroup
|
||||
|
@ -59,7 +60,7 @@ func GetAlbumReplayGain(sources []audio.Source) (albumGain, albumPeak float64, t
|
|||
}()
|
||||
|
||||
for _, source := range sources {
|
||||
state := libebur128.NewState(source.Channels, source.SampleRate, libebur128.LoudnessGlobalMomentary|libebur128.SamplePeak)
|
||||
state := libebur128.NewState(source.GetChannels(), source.GetSampleRate(), libebur128.LoudnessGlobalMomentary|libebur128.SamplePeak)
|
||||
if state == nil {
|
||||
err = errors.New("could not initialize state")
|
||||
return
|
||||
|
@ -67,9 +68,10 @@ func GetAlbumReplayGain(sources []audio.Source) (albumGain, albumPeak float64, t
|
|||
states = append(states, state)
|
||||
|
||||
wg.Add(1)
|
||||
//TODO: do this for int32 and int16
|
||||
go func(source audio.Source) {
|
||||
defer wg.Done()
|
||||
for block := range source.Blocks {
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
if err = state.AddFloat(block); err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func NewNullSink() *NullSink {
|
|||
}
|
||||
|
||||
func (n *NullSink) Process(source Source) {
|
||||
for range source.Blocks {
|
||||
for range source.ToFloat32().GetBlocks() {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -45,17 +45,13 @@ func (n *ForwardSink) GetSamplesRead() int64 {
|
|||
}
|
||||
|
||||
func (n *ForwardSink) Process(source Source) {
|
||||
processor := Source{
|
||||
Channels: source.Channels,
|
||||
SampleRate: source.SampleRate,
|
||||
Blocks: make(chan []float32),
|
||||
}
|
||||
processor := NewFloat32Source(source.GetSampleRate(), source.GetChannels())
|
||||
go func() {
|
||||
defer close(processor.Blocks)
|
||||
for block := range source.Blocks {
|
||||
atomic.AddInt64((*int64)(&n.Duration), int64((time.Second*time.Duration(len(block)/source.Channels))/time.Duration(source.SampleRate)))
|
||||
atomic.AddInt64(&n.SamplesRead, int64(len(block)/source.Channels))
|
||||
processor.Blocks <- block
|
||||
defer processor.Close()
|
||||
for block := range source.ToFloat32().GetBlocks() {
|
||||
atomic.AddInt64((*int64)(&n.Duration), int64((time.Second*time.Duration(len(block)/source.GetChannels()))/time.Duration(source.GetSampleRate())))
|
||||
atomic.AddInt64(&n.SamplesRead, int64(len(block)/source.GetChannels()))
|
||||
processor.IngestFloat32(block)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
572
audio/source.go
572
audio/source.go
|
@ -1,31 +1,585 @@
|
|||
package audio
|
||||
|
||||
type Source struct {
|
||||
import "git.gammaspectra.live/S.O.N.G/Kirika/cgo"
|
||||
|
||||
type Float32Source struct {
|
||||
SampleRate int
|
||||
Channels int
|
||||
Blocks chan []float32
|
||||
locked bool
|
||||
}
|
||||
|
||||
func (s Source) Split(n int) (sources []Source) {
|
||||
func NewFloat32Source(sampleRate, channels int) *Float32Source {
|
||||
return &Float32Source{
|
||||
SampleRate: sampleRate,
|
||||
Channels: channels,
|
||||
Blocks: make(chan []float32),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Float32Source) Split(n int) (sources []Source) {
|
||||
if s.Locked() {
|
||||
return
|
||||
}
|
||||
sources = make([]Source, n)
|
||||
for i := range sources {
|
||||
sources[i] = s
|
||||
sources[i].Blocks = make(chan []float32, cap(s.Blocks))
|
||||
copiedSource := *s
|
||||
copiedSource.Blocks = make(chan []float32, cap(s.Blocks))
|
||||
sources[i] = &copiedSource
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
for i := range sources {
|
||||
close(sources[i].Blocks)
|
||||
for _, source := range sources {
|
||||
source.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for block := range s.Blocks {
|
||||
for i := range sources {
|
||||
sources[i].Blocks <- block
|
||||
for block := range s.GetBlocks() {
|
||||
for _, source := range sources {
|
||||
source.IngestFloat32(block)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Float32Source) GetBlocks() chan []float32 {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
s.locked = true
|
||||
return s.Blocks
|
||||
}
|
||||
|
||||
func (s *Float32Source) Close() {
|
||||
close(s.Blocks)
|
||||
}
|
||||
|
||||
func (s *Float32Source) GetSampleRate() int {
|
||||
return s.SampleRate
|
||||
}
|
||||
|
||||
func (s *Float32Source) GetChannels() int {
|
||||
return s.Channels
|
||||
}
|
||||
|
||||
func (s *Float32Source) GetBitDepth() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *Float32Source) GetFormat() SourceFormat {
|
||||
return SourceFloat32
|
||||
}
|
||||
|
||||
func (s *Float32Source) Locked() bool {
|
||||
return s.locked
|
||||
}
|
||||
|
||||
func (s *Float32Source) Unlock() {
|
||||
s.locked = false
|
||||
}
|
||||
|
||||
func (s *Float32Source) ToFloat32() *Float32Source {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Float32Source) ToInt16() *Int16Source {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
source := &Int16Source{
|
||||
SampleRate: s.SampleRate,
|
||||
Channels: s.Channels,
|
||||
BitDepth: 16,
|
||||
Blocks: make(chan []int16),
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer source.Close()
|
||||
for block := range s.GetBlocks() {
|
||||
source.Blocks <- cgo.Float32ToInt16(block)
|
||||
}
|
||||
}()
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
func (s *Float32Source) ToInt32(bitDepth int) *Int32Source {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
source := &Int32Source{
|
||||
SampleRate: s.SampleRate,
|
||||
Channels: s.Channels,
|
||||
BitDepth: bitDepth,
|
||||
Blocks: make(chan []int32),
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer source.Close()
|
||||
for block := range s.GetBlocks() {
|
||||
source.Blocks <- cgo.Float32ToInt32(block, bitDepth)
|
||||
}
|
||||
}()
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
func (s *Float32Source) IngestFloat32(buf []float32) {
|
||||
s.Blocks <- buf
|
||||
}
|
||||
|
||||
func (s *Float32Source) IngestInt8(buf []int8, bitDepth int) {
|
||||
s.Blocks <- cgo.Int8ToFloat32(buf, bitDepth)
|
||||
}
|
||||
|
||||
func (s *Float32Source) IngestInt16(buf []int16, bitDepth int) {
|
||||
s.Blocks <- cgo.Int16ToFloat32(buf, bitDepth)
|
||||
}
|
||||
|
||||
func (s *Float32Source) IngestInt24(buf []byte, bitDepth int) {
|
||||
s.Blocks <- cgo.Int24ToFloat32(buf, bitDepth)
|
||||
}
|
||||
|
||||
func (s *Float32Source) IngestInt32(buf []int32, bitDepth int) {
|
||||
s.Blocks <- cgo.Int32ToFloat32(buf, bitDepth)
|
||||
}
|
||||
|
||||
type Int16Source struct {
|
||||
BitDepth int
|
||||
SampleRate int
|
||||
Channels int
|
||||
Blocks chan []int16
|
||||
locked bool
|
||||
}
|
||||
|
||||
func NewInt16Source(bitDepth, sampleRate, channels int) *Int16Source {
|
||||
return &Int16Source{
|
||||
BitDepth: bitDepth,
|
||||
SampleRate: sampleRate,
|
||||
Channels: channels,
|
||||
Blocks: make(chan []int16),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Int16Source) Split(n int) (sources []Source) {
|
||||
if s.Locked() {
|
||||
return
|
||||
}
|
||||
sources = make([]Source, n)
|
||||
for i := range sources {
|
||||
copiedSource := *s
|
||||
copiedSource.Blocks = make(chan []int16, cap(s.Blocks))
|
||||
sources[i] = &copiedSource
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
for _, source := range sources {
|
||||
source.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for block := range s.GetBlocks() {
|
||||
for _, source := range sources {
|
||||
source.IngestInt16(block, s.BitDepth)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Int16Source) GetBlocks() chan []int16 {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
s.locked = true
|
||||
return s.Blocks
|
||||
}
|
||||
|
||||
func (s *Int16Source) Close() {
|
||||
close(s.Blocks)
|
||||
}
|
||||
|
||||
func (s *Int16Source) GetSampleRate() int {
|
||||
return s.SampleRate
|
||||
}
|
||||
|
||||
func (s *Int16Source) GetChannels() int {
|
||||
return s.Channels
|
||||
}
|
||||
|
||||
func (s *Int16Source) GetBitDepth() int {
|
||||
return s.BitDepth
|
||||
}
|
||||
|
||||
func (s *Int16Source) GetFormat() SourceFormat {
|
||||
return SourceInt16
|
||||
}
|
||||
|
||||
func (s *Int16Source) Locked() bool {
|
||||
return s.locked
|
||||
}
|
||||
|
||||
func (s *Int16Source) Unlock() {
|
||||
s.locked = false
|
||||
}
|
||||
|
||||
func (s *Int16Source) ToFloat32() *Float32Source {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
|
||||
source := &Float32Source{
|
||||
SampleRate: s.SampleRate,
|
||||
Channels: s.Channels,
|
||||
Blocks: make(chan []float32),
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer source.Close()
|
||||
for block := range s.GetBlocks() {
|
||||
source.Blocks <- cgo.Int16ToFloat32(block, s.BitDepth)
|
||||
}
|
||||
}()
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
func (s *Int16Source) ToInt16() *Int16Source {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Int16Source) ToInt32(bitDepth int) *Int32Source {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
|
||||
source := &Int32Source{
|
||||
SampleRate: s.SampleRate,
|
||||
Channels: s.Channels,
|
||||
BitDepth: bitDepth,
|
||||
Blocks: make(chan []int32),
|
||||
}
|
||||
|
||||
go func() {
|
||||
diff := s.BitDepth - bitDepth
|
||||
|
||||
if diff >= 0 {
|
||||
defer source.Close()
|
||||
for block := range s.GetBlocks() {
|
||||
buf := make([]int32, len(block))
|
||||
for i := range block {
|
||||
buf[i] = int32(block[i]) >> diff
|
||||
}
|
||||
source.Blocks <- buf
|
||||
}
|
||||
} else {
|
||||
diff = -diff
|
||||
defer source.Close()
|
||||
for block := range s.GetBlocks() {
|
||||
buf := make([]int32, len(block))
|
||||
for i := range block {
|
||||
buf[i] = int32(block[i]) << diff
|
||||
}
|
||||
source.Blocks <- buf
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
func (s *Int16Source) IngestFloat32(buf []float32) {
|
||||
s.Blocks <- cgo.Float32ToInt16(buf)
|
||||
}
|
||||
|
||||
func (s *Int16Source) IngestInt8(buf []int8, bitDepth int) {
|
||||
block := make([]int16, len(buf))
|
||||
for i := range buf {
|
||||
block[i] = int16(buf[i]) << (s.BitDepth - bitDepth)
|
||||
}
|
||||
s.Blocks <- block
|
||||
}
|
||||
|
||||
func (s *Int16Source) IngestInt16(buf []int16, bitDepth int) {
|
||||
if bitDepth == s.BitDepth {
|
||||
s.Blocks <- buf
|
||||
} else {
|
||||
block := make([]int16, len(buf))
|
||||
for i := range buf {
|
||||
block[i] = buf[i] << (s.BitDepth - bitDepth)
|
||||
}
|
||||
s.Blocks <- block
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Int16Source) IngestInt24(buf []byte, bitDepth int) {
|
||||
block := make([]int16, len(buf)/3)
|
||||
|
||||
for i := 0; i < len(buf); i += 3 {
|
||||
sample := uint32(buf[i])
|
||||
sample += uint32(buf[i+1]) << 8
|
||||
sample += uint32(buf[i+2]) << 16
|
||||
|
||||
//sign extend
|
||||
block[i/3] = int16(int32(sample<<8) >> (bitDepth - s.BitDepth + 8))
|
||||
}
|
||||
s.Blocks <- block
|
||||
}
|
||||
|
||||
func (s *Int16Source) IngestInt32(buf []int32, bitDepth int) {
|
||||
block := make([]int16, len(buf))
|
||||
for i := range buf {
|
||||
block[i] = int16(buf[i] >> (bitDepth - s.BitDepth))
|
||||
}
|
||||
s.Blocks <- block
|
||||
}
|
||||
|
||||
type Int32Source struct {
|
||||
BitDepth int
|
||||
SampleRate int
|
||||
Channels int
|
||||
Blocks chan []int32
|
||||
locked bool
|
||||
}
|
||||
|
||||
func NewInt32Source(bitDepth, sampleRate, channels int) *Int32Source {
|
||||
return &Int32Source{
|
||||
BitDepth: bitDepth,
|
||||
SampleRate: sampleRate,
|
||||
Channels: channels,
|
||||
Blocks: make(chan []int32),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Int32Source) Split(n int) (sources []Source) {
|
||||
if s.Locked() {
|
||||
return
|
||||
}
|
||||
sources = make([]Source, n)
|
||||
for i := range sources {
|
||||
copiedSource := *s
|
||||
copiedSource.Blocks = make(chan []int32, cap(s.Blocks))
|
||||
sources[i] = &copiedSource
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
for _, source := range sources {
|
||||
source.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for block := range s.GetBlocks() {
|
||||
for _, source := range sources {
|
||||
source.IngestInt32(block, s.BitDepth)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Int32Source) GetBlocks() chan []int32 {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
s.locked = true
|
||||
return s.Blocks
|
||||
}
|
||||
|
||||
func (s *Int32Source) Unlock() {
|
||||
s.locked = false
|
||||
}
|
||||
|
||||
func (s *Int32Source) Close() {
|
||||
close(s.Blocks)
|
||||
}
|
||||
|
||||
func (s *Int32Source) GetSampleRate() int {
|
||||
return s.SampleRate
|
||||
}
|
||||
|
||||
func (s *Int32Source) GetChannels() int {
|
||||
return s.Channels
|
||||
}
|
||||
|
||||
func (s *Int32Source) GetBitDepth() int {
|
||||
return s.BitDepth
|
||||
}
|
||||
|
||||
func (s *Int32Source) GetFormat() SourceFormat {
|
||||
return SourceInt32
|
||||
}
|
||||
|
||||
func (s *Int32Source) Locked() bool {
|
||||
return s.locked
|
||||
}
|
||||
|
||||
func (s *Int32Source) ToInt16() *Int16Source {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
|
||||
source := &Int16Source{
|
||||
SampleRate: s.SampleRate,
|
||||
Channels: s.Channels,
|
||||
BitDepth: 16,
|
||||
Blocks: make(chan []int16),
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer source.Close()
|
||||
for block := range s.GetBlocks() {
|
||||
buf := make([]int16, len(block))
|
||||
for i := range block {
|
||||
buf[i] = int16(block[i] >> (s.BitDepth - 16))
|
||||
}
|
||||
source.Blocks <- buf
|
||||
}
|
||||
}()
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
func (s *Int32Source) ToInt32(bitDepth int) *Int32Source {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
|
||||
source := &Int32Source{
|
||||
SampleRate: s.SampleRate,
|
||||
Channels: s.Channels,
|
||||
BitDepth: bitDepth,
|
||||
Blocks: make(chan []int32),
|
||||
}
|
||||
|
||||
go func() {
|
||||
diff := s.BitDepth - bitDepth
|
||||
|
||||
if diff >= 0 {
|
||||
defer source.Close()
|
||||
for block := range s.GetBlocks() {
|
||||
buf := make([]int32, len(block))
|
||||
for i := range block {
|
||||
buf[i] = block[i] >> diff
|
||||
}
|
||||
source.IngestInt32(buf, bitDepth)
|
||||
}
|
||||
} else {
|
||||
diff = -diff
|
||||
defer source.Close()
|
||||
for block := range s.GetBlocks() {
|
||||
buf := make([]int32, len(block))
|
||||
for i := range block {
|
||||
buf[i] = block[i] << diff
|
||||
}
|
||||
source.IngestInt32(buf, bitDepth)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
func (s *Int32Source) ToFloat32() *Float32Source {
|
||||
if s.Locked() {
|
||||
return nil
|
||||
}
|
||||
|
||||
source := &Float32Source{
|
||||
SampleRate: s.SampleRate,
|
||||
Channels: s.Channels,
|
||||
Blocks: make(chan []float32),
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer source.Close()
|
||||
for block := range s.GetBlocks() {
|
||||
source.Blocks <- cgo.Int32ToFloat32(block, s.BitDepth)
|
||||
}
|
||||
}()
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
func (s *Int32Source) IngestFloat32(buf []float32) {
|
||||
s.Blocks <- cgo.Float32ToInt32(buf, s.BitDepth)
|
||||
}
|
||||
|
||||
func (s *Int32Source) IngestInt8(buf []int8, bitDepth int) {
|
||||
block := make([]int32, len(buf))
|
||||
for i := range buf {
|
||||
block[i] = int32(buf[i]) << (s.BitDepth - bitDepth)
|
||||
}
|
||||
s.Blocks <- block
|
||||
}
|
||||
|
||||
func (s *Int32Source) IngestInt16(buf []int16, bitDepth int) {
|
||||
block := make([]int32, len(buf))
|
||||
for i := range buf {
|
||||
block[i] = int32(buf[i]) << (s.BitDepth - bitDepth)
|
||||
}
|
||||
s.Blocks <- block
|
||||
}
|
||||
|
||||
func (s *Int32Source) IngestInt24(buf []byte, bitDepth int) {
|
||||
block := make([]int32, len(buf)/3)
|
||||
|
||||
for i := 0; i < len(buf); i += 3 {
|
||||
sample := uint32(buf[i])
|
||||
sample += uint32(buf[i+1]) << 8
|
||||
sample += uint32(buf[i+2]) << 16
|
||||
|
||||
//sign extend
|
||||
block[i/3] = int32(sample << (s.BitDepth - bitDepth))
|
||||
}
|
||||
s.Blocks <- block
|
||||
}
|
||||
|
||||
func (s *Int32Source) IngestInt32(buf []int32, bitDepth int) {
|
||||
if bitDepth == s.BitDepth {
|
||||
s.Blocks <- buf
|
||||
} else {
|
||||
block := make([]int32, len(buf))
|
||||
for i := range buf {
|
||||
block[i] = buf[i] << (s.BitDepth - bitDepth)
|
||||
}
|
||||
s.Blocks <- block
|
||||
}
|
||||
}
|
||||
|
||||
type SourceFormat int
|
||||
|
||||
const (
|
||||
SourceFloat32 = SourceFormat(iota)
|
||||
SourceInt16
|
||||
SourceInt24
|
||||
SourceInt32
|
||||
)
|
||||
|
||||
type Source interface {
|
||||
GetBitDepth() int
|
||||
GetSampleRate() int
|
||||
GetChannels() int
|
||||
GetFormat() SourceFormat
|
||||
ToFloat32() *Float32Source
|
||||
ToInt16() *Int16Source
|
||||
ToInt32(bitDepth int) *Int32Source
|
||||
|
||||
IngestFloat32(buf []float32)
|
||||
IngestInt8(buf []int8, bitDepth int)
|
||||
IngestInt16(buf []int16, bitDepth int)
|
||||
IngestInt24(buf []byte, bitDepth int)
|
||||
IngestInt32(buf []int32, bitDepth int)
|
||||
|
||||
//Split a Source into multiple ones. After calling this function, the source is locked.
|
||||
Split(n int) []Source
|
||||
Close()
|
||||
Unlock()
|
||||
Locked() bool
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package audio
|
|||
|
||||
type Stream struct {
|
||||
source Source
|
||||
channel chan []float32
|
||||
samplesProcessed int
|
||||
buffer []float32
|
||||
blockSize int
|
||||
|
@ -15,11 +16,11 @@ func NewStream(source Source, blockSize int) *Stream {
|
|||
}
|
||||
|
||||
func (s *Stream) GetChannels() int {
|
||||
return s.source.Channels
|
||||
return s.source.GetChannels()
|
||||
}
|
||||
|
||||
func (s *Stream) GetSampleRate() int {
|
||||
return s.source.SampleRate
|
||||
return s.source.GetSampleRate()
|
||||
}
|
||||
|
||||
func (s *Stream) GetSamplesProcessed() int {
|
||||
|
@ -47,12 +48,12 @@ func (s *Stream) GetAsBlockChannel() chan []float32 {
|
|||
|
||||
go func() {
|
||||
defer close(newChannel)
|
||||
for buf := range s.source.Blocks {
|
||||
for buf := range s.source.ToFloat32().GetBlocks() {
|
||||
s.buffer = append(s.buffer, buf...)
|
||||
for len(s.buffer) >= s.blockSize*s.source.Channels {
|
||||
newChannel <- s.buffer[0 : s.blockSize*s.source.Channels]
|
||||
s.samplesProcessed += s.blockSize * s.source.Channels
|
||||
s.buffer = s.buffer[s.blockSize*s.source.Channels:]
|
||||
for len(s.buffer) >= s.blockSize*s.source.GetChannels() {
|
||||
newChannel <- s.buffer[0 : s.blockSize*s.source.GetChannels()]
|
||||
s.samplesProcessed += s.blockSize * s.source.GetChannels()
|
||||
s.buffer = s.buffer[s.blockSize*s.source.GetChannels():]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,9 +67,12 @@ func (s *Stream) GetAsBlockChannel() chan []float32 {
|
|||
}
|
||||
|
||||
func (s *Stream) Get() (float32, bool) {
|
||||
if s.channel == nil {
|
||||
s.channel = s.source.ToFloat32().GetBlocks()
|
||||
}
|
||||
var more bool
|
||||
if len(s.buffer) == 0 {
|
||||
s.buffer, more = <-s.source.Blocks
|
||||
s.buffer, more = <-s.channel
|
||||
if !more {
|
||||
return 0, false
|
||||
}
|
||||
|
@ -91,5 +95,5 @@ func (s *Stream) AdvanceSeconds(seconds float64) bool {
|
|||
}
|
||||
|
||||
func (s *Stream) secondsIndex(seconds float64) int {
|
||||
return int(seconds * float64(s.source.SampleRate))
|
||||
return int(seconds * float64(s.source.GetSampleRate()))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue