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

Removed AnalyzerChannel in favor of raw samples
This commit is contained in:
DataHoarder 2022-07-19 10:36:22 +02:00
parent 44d93971c5
commit 449b38272b
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
32 changed files with 1218 additions and 448 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -88,7 +88,7 @@ func TestDecodeAAC(t *testing.T) {
}
//Decode
for range source.Blocks {
for range source.ToInt16().GetBlocks() {
}
}

View file

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

View file

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

View file

@ -68,7 +68,7 @@ func TestDecodeALAC(t *testing.T) {
return
}
for range source.Blocks {
for range source.ToFloat32().GetBlocks() {
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -36,7 +36,7 @@ func DoTest(ext string, locations []string, t *testing.T) {
}
//Decode
for range source.Blocks {
for range source.ToFloat32().GetBlocks() {
}
})

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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