Added Hasher / Filter / Sink interfaces and implementation
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
DataHoarder 2022-02-22 19:30:48 +01:00
parent 951b3fcf54
commit 21efea8dd1
14 changed files with 954 additions and 149 deletions

View file

@ -1,11 +1,12 @@
package Kirika
import (
"bytes"
"encoding/hex"
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/mp3"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/opus"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/tta"
"git.gammaspectra.live/S.O.N.G/Kirika/hasher"
"os"
"path"
"testing"
@ -16,7 +17,9 @@ var TestSampleLocations = []string{
"resources/samples/Babbe Music - RADIANT DANCEFLOOR/",
}
const BlockSize = 1024 * 64
const TestSingleSample24 = "resources/samples/cYsmix - Haunted House/11. The Great Rigid Desert.flac"
const TestSingleSample16 = "resources/samples/Babbe Music - RADIANT DANCEFLOOR/01. ENTER.flac"
func doTest(format format.Format, ext string, t *testing.T) {
for _, location := range TestSampleLocations {
@ -36,14 +39,14 @@ func doTest(format format.Format, ext string, t *testing.T) {
return
}
defer fp.Close()
stream, err := format.Open(fp, BlockSize)
source, err := format.Open(fp)
if err != nil {
t.Error(err)
return
}
//Decode
for range stream.GetAsBlockChannel() {
for range source.Blocks {
}
})
@ -52,6 +55,7 @@ func doTest(format format.Format, ext string, t *testing.T) {
}
}
/*
func TestFLACDecode(t *testing.T) {
doTest(flac.NewFormat(), ".flac", t)
}
@ -64,3 +68,130 @@ func TestOpusDecode(t *testing.T) {
func TestMP3Decode(t *testing.T) {
doTest(mp3.NewFormat(), ".mp3", t)
}
*/
func TestHasher24(t *testing.T) {
fp, err := os.Open(TestSingleSample24)
if err != nil {
t.Error(err)
return
}
defer fp.Close()
source, analyzerChannel, err := flac.NewFormat().OpenAnalyzer(fp)
if err != nil {
t.Error(err)
return
}
chans := analyzerChannel.Split(2)
crc32 := hasher.NewHasher(chans[0], hasher.HashtypeCrc32)
sha256 := hasher.NewHasher(chans[1], hasher.HashtypeSha256)
//Decode
for range source.Blocks {
}
crc32.Wait()
sha256.Wait()
expectCrc32, _ := hex.DecodeString("A54636CD")
if bytes.Compare(crc32.GetResult(), expectCrc32) != 0 {
t.Errorf("Wrong CRC32 %08X != %08X", crc32.GetResult(), expectCrc32)
}
expectSha256, _ := hex.DecodeString("9B6715ED75B6C8074B749C630AC9C626994080DEACBEA363976391366DA4E4FA")
if bytes.Compare(sha256.GetResult(), expectSha256) != 0 {
t.Errorf("Wrong SHA256 %08X != %08X", sha256.GetResult(), expectSha256)
}
}
func TestHasher16(t *testing.T) {
fp, err := os.Open(TestSingleSample16)
if err != nil {
t.Error(err)
return
}
defer fp.Close()
source, analyzerChannel, err := flac.NewFormat().OpenAnalyzer(fp)
if err != nil {
t.Error(err)
return
}
channels := analyzerChannel.Split(4)
cueToolsCrc32 := hasher.NewHasher(channels[0].SkipStartSamples(hasher.Int16SamplesPerSector*10), hasher.HashtypeCrc32)
arChannels := channels[1].SkipStartSamples(hasher.Int16SamplesPerSector*5 - 1).Split(2)
accurateRipV1 := hasher.NewHasher(arChannels[0], hasher.HashtypeAccurateRipV1Start)
accurateRipV2 := hasher.NewHasher(arChannels[1], hasher.HashtypeAccurateRipV2Start)
crc32 := hasher.NewHasher(channels[2], hasher.HashtypeCrc32)
sha256 := hasher.NewHasher(channels[3], hasher.HashtypeSha256)
//Decode
for range source.Blocks {
}
cueToolsCrc32.Wait()
accurateRipV1.Wait()
accurateRipV2.Wait()
crc32.Wait()
sha256.Wait()
expectCueToolsCrc32, _ := hex.DecodeString("18701E02")
if bytes.Compare(cueToolsCrc32.GetResult(), expectCueToolsCrc32) != 0 {
t.Errorf("Wrong CTDB CRC32 %08X != %08X", cueToolsCrc32.GetResult(), expectCueToolsCrc32)
}
expectAccurateRipV1, _ := hex.DecodeString("5593DA89")
if bytes.Compare(accurateRipV1.GetResult(), expectAccurateRipV1) != 0 {
t.Errorf("Wrong AccurateRip V1 %08X != %08X", accurateRipV1.GetResult(), expectAccurateRipV1)
}
expectAccurateRipV2, _ := hex.DecodeString("DAA40E75")
if bytes.Compare(accurateRipV2.GetResult(), expectAccurateRipV2) != 0 {
t.Errorf("Wrong AccurateRip V2 %08X != %08X", accurateRipV2.GetResult(), expectAccurateRipV2)
}
expectCrc32, _ := hex.DecodeString("50CE5057")
if bytes.Compare(crc32.GetResult(), expectCrc32) != 0 {
t.Errorf("Wrong CRC32 %08X != %08X", crc32.GetResult(), expectCrc32)
}
expectSha256, _ := hex.DecodeString("FEDF080D500D1A49DF8366BE619918D2A5D00413B7C7613A39DC00659FA25AC6")
if bytes.Compare(sha256.GetResult(), expectSha256) != 0 {
t.Errorf("Wrong SHA256 %X != %X", sha256.GetResult(), expectSha256)
}
}
func TestFilterChain(t *testing.T) {
fp, err := os.Open(TestSingleSample24)
if err != nil {
t.Error(err)
return
}
defer fp.Close()
source, err := flac.NewFormat().Open(fp)
if err != nil {
t.Error(err)
return
}
const sampleRate = 16000
result := audio.NewFilterChain(source, audio.MonoFilter{}, audio.NewResampleFilter(sampleRate, audio.BandlimitedFastest, 0), audio.StereoFilter{})
sink := audio.NewForwardSink(audio.NewNullSink())
sink.Process(result)
if result.SampleRate != sampleRate {
t.Errorf("Wrong SampleRate %d != %d", result.SampleRate, sampleRate)
}
if result.Channels != 2 {
t.Errorf("Wrong Channel Count %d != %d", result.SampleRate, 2)
}
if sink.SamplesRead != 6284999 {
t.Errorf("Wrong Sample Count %d != %d", sink.SamplesRead, 6284999)
}
}

View file

@ -1,8 +1,11 @@
# [![](resources/kirikas.png)](resources/kirika.png) Kirika
Collection of audio utilities for decoding/encoding files and streams.
* channel-based audio consumption chain
* Channel-based audio consumption/filter chain
* Raw sample analyzer channels
* AnalyzerChannel channels / mergers / splitters / trimmers
* Audio resampler
* Audio downmixing to stereo/mono
* FLAC stream decoder and encoder
* TTA stream decoder
* MP3 stream decoder

190
audio/filter.go Normal file
View file

@ -0,0 +1,190 @@
package audio
/*
#cgo CFLAGS: -I"${SRCDIR}/../cgo" -march=native -Ofast -std=c99
#include "audio.h"
*/
import "C"
import (
"github.com/dh1tw/gosamplerate"
"log"
"time"
)
type Filter interface {
Process(source Source) Source
}
func NewFilterChain(source Source, filters ...Filter) Source {
for _, filter := range filters {
source = filter.Process(source)
}
return source
}
type RealTimeFilter struct {
}
func (f RealTimeFilter) Process(source Source) Source {
outBlocks := make(chan []float32)
const blocksPerSecond = 10
if source.SampleRate%blocksPerSecond != 0 {
log.Panicf("%d %% %d != 0", source.SampleRate, blocksPerSecond)
}
blockSize := (source.SampleRate / blocksPerSecond) * source.Channels
throttler := time.Tick(time.Second / 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
}
}
outBlocks <- buf
}()
return Source{
Channels: source.Channels,
SampleRate: source.SampleRate,
Blocks: outBlocks,
}
}
type StereoFilter struct {
}
func (f StereoFilter) Process(source Source) Source {
if source.Channels == 2 { //no change
return source
}
outBlocks := make(chan []float32)
go func() {
defer close(outBlocks)
for block := range source.Blocks {
//bring any number of channels to stereo, using downmix formulas when necessary
buf := make([]float32, (len(block)/source.Channels)*2)
C.audio_multiple_channels_to_stereo((*C.float)(&block[0]), C.size_t(len(block)), (*C.float)(&buf[0]), C.int(source.Channels))
outBlocks <- buf
}
}()
return Source{
Channels: 2,
SampleRate: source.SampleRate,
Blocks: outBlocks,
}
}
type MonoFilter struct {
}
func (f MonoFilter) Process(source Source) Source {
if source.Channels == 1 { //no change
return source
}
outBlocks := make(chan []float32)
go func() {
defer close(outBlocks)
for block := range source.Blocks {
//bring any number of channels to mono, equally weighted, reusing buffer backwards
C.audio_multiple_channels_to_mono((*C.float)(&block[0]), C.size_t(len(block)), C.int(source.Channels))
outBlocks <- block[0:(len(block) / source.Channels)]
}
}()
return Source{
Channels: 1,
SampleRate: source.SampleRate,
Blocks: outBlocks,
}
}
type ResampleFilter struct {
sampleRate int
quality ResampleQuality
blockSize int
}
type ResampleQuality int
const (
BandlimitedBest ResampleQuality = gosamplerate.SRC_SINC_BEST_QUALITY
BandlimitedMedium ResampleQuality = gosamplerate.SRC_SINC_MEDIUM_QUALITY
BandlimitedFastest ResampleQuality = gosamplerate.SRC_SINC_FASTEST
ZeroOrderHold ResampleQuality = gosamplerate.SRC_ZERO_ORDER_HOLD
Linear ResampleQuality = gosamplerate.SRC_LINEAR
)
func NewResampleFilter(sampleRate int, quality ResampleQuality, blockSize int) ResampleFilter {
if blockSize == 0 {
blockSize = 1024 * 64
}
return ResampleFilter{
sampleRate: sampleRate,
quality: quality,
blockSize: blockSize,
}
}
func (f ResampleFilter) Process(source Source) Source {
if source.SampleRate == f.sampleRate { //no change
return source
}
outBlocks := make(chan []float32)
go func() {
defer close(outBlocks)
blockSize := f.blockSize * source.Channels
samplerateConverter, err := gosamplerate.New(int(f.quality), source.Channels, blockSize)
if err != nil {
log.Panic(err)
}
defer gosamplerate.Delete(samplerateConverter)
ratio := float64(f.sampleRate) / float64(source.SampleRate)
for block := range source.Blocks {
for len(block) >= blockSize {
b, err := samplerateConverter.Process(block[0:blockSize], ratio, false)
if err != nil {
log.Panic(err)
}
if len(b) > 0 {
outBlocks <- b
}
block = block[0:blockSize]
}
b, err := samplerateConverter.Process(block, ratio, false)
if err != nil {
log.Panic(err)
}
if len(b) > 0 {
outBlocks <- b
}
}
b, err := samplerateConverter.Process([]float32{}, ratio, true)
if err != nil {
log.Panic(err)
}
if len(b) > 0 {
outBlocks <- b
}
}()
return Source{
Channels: source.Channels,
SampleRate: f.sampleRate,
Blocks: outBlocks,
}
}

208
audio/format/analyzer.go Normal file
View file

@ -0,0 +1,208 @@
package format
import (
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
"io"
"sync"
"sync/atomic"
)
const chanBuf = 16
type AnalyzerChannel chan *AnalyzerPacket
type AnalyzerFormat interface {
Format
// OpenAnalyzer Opens a stream and decodes it into an audio.Source, and additionally copy AnalyzerPacket back
OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, AnalyzerChannel, error)
}
func (c AnalyzerChannel) Split(n int) (channels []AnalyzerChannel) {
channels = make([]AnalyzerChannel, n)
for i := range channels {
channels[i] = make(AnalyzerChannel, chanBuf)
}
go func() {
defer func() {
for _, channel := range channels {
close(channel)
}
}()
for packet := range c {
for _, channel := range channels {
channel <- packet
}
}
}()
return
}
func (c AnalyzerChannel) PrependGap(samples, sampleRate, channels, bitDepth int) (channel AnalyzerChannel) {
return MergeHasherChannels(NewHasherAudioGap(samples, sampleRate, channels, bitDepth), c)
}
func (c AnalyzerChannel) AppendGap(samples, sampleRate, channels, bitDepth int) (channel AnalyzerChannel) {
return MergeHasherChannels(c, NewHasherAudioGap(samples, sampleRate, channels, bitDepth))
}
func (c AnalyzerChannel) SkipStartSamples(samples int) (channel AnalyzerChannel) {
channel = make(AnalyzerChannel, chanBuf)
go func() {
defer close(channel)
for samples > 0 {
packet, ok := <-c
if !ok {
return
}
if len(packet.Samples)/packet.Channels > samples {
startIndex := samples * packet.Channels
channel <- &AnalyzerPacket{
Samples: packet.Samples[startIndex:],
Channels: packet.Channels,
SampleRate: packet.SampleRate,
BitDepth: packet.BitDepth,
}
samples = 0
break
} else {
samples -= len(packet.Samples) / packet.Channels
continue
}
}
for packet := range c {
channel <- packet
}
}()
return
}
func (c AnalyzerChannel) SkipEndSamples(samples int) (channel AnalyzerChannel) {
channel = make(AnalyzerChannel, chanBuf)
go func() {
defer close(channel)
var buffer []*AnalyzerPacket
bufferSamples := 0
for packet := range c {
for len(buffer) > 0 && (bufferSamples-len(buffer[0].Samples)/buffer[0].Channels) > samples {
channel <- buffer[0]
bufferSamples -= len(buffer[0].Samples) / buffer[0].Channels
buffer = buffer[1:]
}
bufferSamples += len(packet.Samples) / packet.Channels
buffer = append(buffer, packet)
}
for _, packet := range buffer {
//TODO: check this
leftSamples := bufferSamples - len(packet.Samples)/packet.Channels
if leftSamples <= samples {
endIndex := len(packet.Samples) - (samples-leftSamples)*packet.Channels
channel <- &AnalyzerPacket{
Samples: packet.Samples[:endIndex],
Channels: packet.Channels,
SampleRate: packet.SampleRate,
BitDepth: packet.BitDepth,
}
samples = 0
break
} else {
channel <- packet
bufferSamples -= len(packet.Samples) / packet.Channels
}
}
}()
return
}
func (c AnalyzerChannel) SkipEndSamplesMultiple(wg *sync.WaitGroup, offset *uint32, samples int) (channel AnalyzerChannel) {
channel = make(AnalyzerChannel, chanBuf)
go func() {
defer close(channel)
var buffer []*AnalyzerPacket
bufferSamples := 0
maxSamples := samples * 2
samplesRead := 0
for packet := range c {
for len(buffer) > 0 && (bufferSamples-len(buffer[0].Samples)/buffer[0].Channels) > maxSamples {
channel <- buffer[0]
samplesRead += len(buffer[0].Samples) / buffer[0].Channels
bufferSamples -= len(buffer[0].Samples) / buffer[0].Channels
buffer = buffer[1:]
}
bufferSamples += len(packet.Samples) / packet.Channels
buffer = append(buffer, packet)
}
wg.Wait()
totalSampleOffset := samplesRead + int(atomic.LoadUint32(offset))
if len(buffer) > 0 {
p := &AnalyzerPacket{
Channels: buffer[0].Channels,
SampleRate: buffer[0].SampleRate,
BitDepth: buffer[0].BitDepth,
}
for _, packet := range buffer {
p.Samples = append(p.Samples, packet.Samples...)
}
nsamples := samples + (((len(p.Samples) / p.Channels) + totalSampleOffset) % samples)
if len(p.Samples)/p.Channels > nsamples {
endIndex := len(p.Samples) - nsamples*p.Channels
channel <- &AnalyzerPacket{
Samples: p.Samples[:endIndex],
Channels: p.Channels,
SampleRate: p.SampleRate,
BitDepth: p.BitDepth,
}
}
}
}()
return
}
func NewHasherAudioGap(samples, sampleRate, channels, bitDepth int) (channel AnalyzerChannel) {
channel = make(AnalyzerChannel, 1)
channel <- &AnalyzerPacket{
Samples: make([]int32, samples*channels),
Channels: channels,
SampleRate: sampleRate,
BitDepth: bitDepth,
}
close(channel)
return
}
func MergeHasherChannels(channels ...AnalyzerChannel) (channel AnalyzerChannel) {
channel = make(AnalyzerChannel, chanBuf)
go func() {
defer close(channel)
for _, c := range channels {
for packet := range c {
channel <- packet
}
}
}()
return
}

View file

@ -22,10 +22,10 @@ func NewFormat() Format {
return Format{}
}
func (f Format) Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error) {
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
decoder, err := libflac.NewDecoderReader(r)
if err != nil {
return nil, err
return audio.Source{}, err
}
newChannel := make(chan []float32)
@ -58,16 +58,21 @@ func (f Format) Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error)
}
}()
return audio.NewStream(newChannel, decoder.Channels, float64(decoder.Rate), blockSize), nil
return audio.Source{
Channels: decoder.Channels,
SampleRate: decoder.Rate,
Blocks: newChannel,
}, nil
}
func (f Format) OpenAnalyzer(r io.ReadSeekCloser, blockSize int) (*audio.Stream, chan *format.AnalyzerPacket, error) {
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) {
decoder, err := libflac.NewDecoderReader(r)
if err != nil {
return nil, nil, err
return audio.Source{}, nil, err
}
newChannel := make(chan []float32)
analyzerChannel := make(chan *format.AnalyzerPacket)
analyzerChannel := make(format.AnalyzerChannel)
go func() {
defer close(newChannel)
@ -108,10 +113,14 @@ func (f Format) OpenAnalyzer(r io.ReadSeekCloser, blockSize int) (*audio.Stream,
}
}()
return audio.NewStream(newChannel, decoder.Channels, float64(decoder.Rate), blockSize), analyzerChannel, nil
return audio.Source{
Channels: decoder.Channels,
SampleRate: decoder.Rate,
Blocks: newChannel,
}, analyzerChannel, nil
}
func (f Format) Encode(stream *audio.Stream, writer format.WriteSeekCloser, options map[string]interface{}) error {
func (f Format) Encode(source audio.Source, writer format.WriteSeekCloser, options map[string]interface{}) error {
var bitsPerSample = 16
if options != nil {
@ -125,21 +134,21 @@ func (f Format) Encode(stream *audio.Stream, writer format.WriteSeekCloser, opti
}
}
encoder, err := libflac.NewEncoderWriter(writer, stream.GetChannels(), bitsPerSample, int(stream.GetSampleRate()))
encoder, err := libflac.NewEncoderWriter(writer, source.Channels, bitsPerSample, source.SampleRate)
if err != nil {
return err
}
defer encoder.Close()
for block := range stream.GetAsBlockChannel() {
for block := range source.Blocks {
samples := make([]int32, len(block))
C.audio_float32_to_int32((*C.float)(&block[0]), C.size_t(len(block)), (*C.int32_t)(&samples[0]), C.int(bitsPerSample))
err = encoder.WriteFrame(libflac.Frame{
Rate: int(stream.GetSampleRate()),
Channels: stream.GetChannels(),
Rate: source.SampleRate,
Channels: source.Channels,
Depth: bitsPerSample,
Buffer: samples,
})

View file

@ -9,14 +9,8 @@ type Format interface {
// Identify checks whether a format is of a type. peek includes a few first bytes, extension is the lowercase file extension without a dot.
Identify(peek []byte, extension string) bool
// Open Opens a stream and decodes it into an audio.Stream
Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error)
}
type AnalyzerFormat interface {
Format
// OpenAnalyzer Opens a stream and decodes it into an audio.Stream, and additionally copy AnalyzerPacket back
OpenAnalyzer(r io.ReadSeekCloser, blockSize int) (*audio.Stream, chan *AnalyzerPacket, error)
// Open Opens a stream and decodes it into an audio.Source
Open(r io.ReadSeekCloser) (audio.Source, error)
}
type WriteSeekCloser interface {
@ -27,8 +21,8 @@ type WriteSeekCloser interface {
type Encoder interface {
Format
// Encode Receives a stream and encodes it into a writer
Encode(stream *audio.Stream, writer WriteSeekCloser, options map[string]interface{}) error
// Encode Receives an audio.Source and encodes it into a writer
Encode(source audio.Source, writer WriteSeekCloser, options map[string]interface{}) error
}
type AnalyzerPacket struct {

View file

@ -13,6 +13,8 @@ import (
"unsafe"
)
const BlockSize = 1024 * 128
type Format struct {
format.Format
}
@ -21,19 +23,19 @@ func NewFormat() Format {
return Format{}
}
func (f Format) Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error) {
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
decoder := mp3Lib.NewDecoder(r)
_, err := decoder.Read([]byte{})
if err != nil {
return nil, err
return audio.Source{}, err
}
newChannel := make(chan []float32)
go func() {
defer close(newChannel)
samples := make([]int16, blockSize*2)
samples := make([]int16, BlockSize*2)
const SizeofInt16 = int(unsafe.Sizeof(int16(0)))
byteSlice := unsafe.Slice((*byte)(unsafe.Pointer(&samples[0])), len(samples)*SizeofInt16)
for {
@ -52,7 +54,11 @@ func (f Format) Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error)
}
}()
return audio.NewStream(newChannel, decoder.Channels(), float64(decoder.SampleRate()), blockSize), nil
return audio.Source{
Channels: decoder.Channels(),
SampleRate: decoder.SampleRate(),
Blocks: newChannel,
}, nil
}
func (f Format) Identify(peek []byte, extension string) bool {

View file

@ -8,7 +8,8 @@ import (
"io"
)
const OPUS_SAMPLE_RATE int = 48000
const FixedSampleRate int = 48000
const BlockSize = 1024 * 128
type Format struct {
format.Format
@ -18,10 +19,10 @@ func NewFormat() Format {
return Format{}
}
func (f Format) Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error) {
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
stream, err := libopus.NewStream(r)
if err != nil {
return nil, err
return audio.Source{}, err
}
newChannel := make(chan []float32)
@ -32,7 +33,7 @@ func (f Format) Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error)
for {
buf := make([]float32, blockSize*2)
buf := make([]float32, BlockSize*2)
n, err := stream.ReadStereoFloat32(buf)
if err != nil {
@ -46,7 +47,11 @@ func (f Format) Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error)
}()
//We always get two channels via stereo api
return audio.NewStream(newChannel, 2, float64(OPUS_SAMPLE_RATE), blockSize), nil
return audio.Source{
Channels: 2,
SampleRate: FixedSampleRate,
Blocks: newChannel,
}, nil
}
func (f Format) Identify(peek []byte, extension string) bool {

View file

@ -15,6 +15,8 @@ import (
"unsafe"
)
const BlockSize = 1024 * 128
type Format struct {
format.Format
format.Encoder
@ -58,24 +60,24 @@ func (i fakeReadWriteSeeker2) Write(p []byte) (n int, err error) {
return i.w.Write(p)
}
func (f Format) Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error) {
func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) {
decoder := libtta.MakeDecoder(fakeReadWriteSeeker{r})
if decoder == nil {
return nil, errors.New("invalid decoder")
return audio.Source{}, errors.New("invalid decoder")
}
newChannel := make(chan []float32)
info := &libtta.Info{}
if err := decoder.GetInfo(info, 0); err != nil {
return nil, err
return audio.Source{}, err
}
go func() {
defer close(newChannel)
smpSize := int(info.Nch) * ((int(info.Bps) + 7) / 8)
buffer := make([]byte, blockSize*smpSize)
buffer := make([]byte, BlockSize*smpSize)
var writeLen int
for {
@ -105,27 +107,31 @@ func (f Format) Open(r io.ReadSeekCloser, blockSize int) (*audio.Stream, error)
}
}()
return audio.NewStream(newChannel, int(info.Nch), float64(info.Sps), blockSize), nil
return audio.Source{
Channels: int(info.Nch),
SampleRate: int(info.Sps),
Blocks: newChannel,
}, nil
}
func (f Format) OpenAnalyzer(r io.ReadSeekCloser, blockSize int) (*audio.Stream, chan *format.AnalyzerPacket, error) {
func (f Format) OpenAnalyzer(r io.ReadSeekCloser) (audio.Source, format.AnalyzerChannel, error) {
decoder := libtta.MakeDecoder(fakeReadWriteSeeker{r})
if decoder == nil {
return nil, nil, errors.New("invalid decoder")
return audio.Source{}, nil, errors.New("invalid decoder")
}
newChannel := make(chan []float32)
analyzerChannel := make(chan *format.AnalyzerPacket)
analyzerChannel := make(format.AnalyzerChannel)
info := &libtta.Info{}
if err := decoder.GetInfo(info, 0); err != nil {
return nil, nil, err
return audio.Source{}, nil, err
}
go func() {
defer close(newChannel)
smpSize := int(info.Nch) * ((int(info.Bps) + 7) / 8)
buffer := make([]byte, blockSize*smpSize)
buffer := make([]byte, BlockSize*smpSize)
var writeLen int
for {
@ -154,7 +160,11 @@ func (f Format) OpenAnalyzer(r io.ReadSeekCloser, blockSize int) (*audio.Stream,
}
}()
return audio.NewStream(newChannel, int(info.Nch), float64(info.Sps), blockSize), analyzerChannel, nil
return audio.Source{
Channels: int(info.Nch),
SampleRate: int(info.Sps),
Blocks: newChannel,
}, analyzerChannel, nil
}
func (f Format) Identify(peek []byte, extension string) bool {

63
audio/sink.go Normal file
View file

@ -0,0 +1,63 @@
package audio
import (
"sync/atomic"
"time"
)
type Sink interface {
Process(source Source)
}
type NullSink struct {
Sink
}
func NewNullSink() *NullSink {
return &NullSink{}
}
func (n *NullSink) Process(source Source) {
for range source.Blocks {
}
}
type ForwardSink struct {
Sink
Target Sink
SamplesRead int64
Duration time.Duration
}
func NewForwardSink(target Sink) *ForwardSink {
return &ForwardSink{
Target: target,
}
}
func (n *ForwardSink) GetDuration() time.Duration {
return time.Duration(atomic.LoadInt64((*int64)(&n.Duration)))
}
func (n *ForwardSink) GetSamplesRead() int64 {
return atomic.LoadInt64(&n.SamplesRead)
}
func (n *ForwardSink) Process(source Source) {
processor := Source{
Channels: source.Channels,
SampleRate: source.SampleRate,
Blocks: make(chan []float32),
}
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
}
}()
n.Target.Process(processor)
}

7
audio/source.go Normal file
View file

@ -0,0 +1,7 @@
package audio
type Source struct {
SampleRate int
Channels int
Blocks chan []float32
}

View file

@ -1,40 +1,25 @@
package audio
/*
#cgo CFLAGS: -I"${SRCDIR}/../cgo" -march=native -Ofast -std=c99
#include "audio.h"
*/
import "C"
import (
"fmt"
"github.com/dh1tw/gosamplerate"
"log"
)
type Stream struct {
source chan []float32
channels int
sampleRate float64
source Source
samplesProcessed int
buffer []float32
blockSize int
}
func NewStream(source chan []float32, channels int, sampleRate float64, blockSize int) *Stream {
func NewStream(source Source, blockSize int) *Stream {
return &Stream{
source: source,
channels: channels,
sampleRate: sampleRate,
blockSize: blockSize,
source: source,
blockSize: blockSize,
}
}
func (s *Stream) GetChannels() int {
return s.channels
return s.source.Channels
}
func (s *Stream) GetSampleRate() float64 {
return s.sampleRate
func (s *Stream) GetSampleRate() int {
return s.source.SampleRate
}
func (s *Stream) GetSamplesProcessed() int {
@ -62,12 +47,12 @@ func (s *Stream) GetAsBlockChannel() chan []float32 {
go func() {
defer close(newChannel)
for buf := range s.source {
for buf := range s.source.Blocks {
s.buffer = append(s.buffer, buf...)
for len(s.buffer) >= s.blockSize*s.channels {
newChannel <- s.buffer[0 : s.blockSize*s.channels]
s.samplesProcessed += s.blockSize * s.channels
s.buffer = s.buffer[s.blockSize*s.channels:]
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:]
}
}
@ -83,7 +68,7 @@ func (s *Stream) GetAsBlockChannel() chan []float32 {
func (s *Stream) Get() (float32, bool) {
var more bool
if len(s.buffer) == 0 {
s.buffer, more = <-s.source
s.buffer, more = <-s.source.Blocks
if !more {
return 0, false
}
@ -106,76 +91,5 @@ func (s *Stream) AdvanceSeconds(seconds float64) bool {
}
func (s *Stream) secondsIndex(seconds float64) int {
return int(seconds * s.sampleRate)
}
const (
RESAMPLER_QUALITY_BANDLIMITED_BEST = gosamplerate.SRC_SINC_BEST_QUALITY
RESAMPLER_QUALITY_BANDLIMITED_MEDIUM = gosamplerate.SRC_SINC_MEDIUM_QUALITY
RESAMPLER_QUALITY_BANDLIMITED_FASTEST = gosamplerate.SRC_SINC_FASTEST
RESAMPLER_QUALITY_HOLD = gosamplerate.SRC_ZERO_ORDER_HOLD
RESAMPLER_QUALITY_LINEAR = gosamplerate.SRC_LINEAR
)
func (s *Stream) DoResample(channels int, sampleRate float64, quality int) (*Stream, error) {
if channels != 1 && channels != 2 && s.channels != channels {
return nil, fmt.Errorf("cannot convert from %d channels to %d", s.channels, channels)
}
if channels == s.channels && sampleRate == s.sampleRate {
return s, nil
}
samplerateConverter, err := gosamplerate.New(quality, channels, s.blockSize*s.channels)
if err != nil {
return nil, err
}
newChannel := make(chan []float32)
go func() {
defer gosamplerate.Delete(samplerateConverter)
defer close(newChannel)
ratio := sampleRate / s.sampleRate
for buf := range s.GetAsBlockChannel() {
if channels != s.channels && channels == 1 {
if channels == 1 {
//bring any number of channels to mono, equally weighted, reusing buffer backwards
C.audio_multiple_channels_to_mono((*C.float)(&buf[0]), C.size_t(len(buf)), C.int(s.channels))
buf = buf[0:(len(buf) / s.channels)]
} else if channels == 2 {
//bring any number of channels to stereo, using downmix formulas when necessary
out := make([]float32, (len(buf)/s.channels)*2)
C.audio_multiple_channels_to_stereo((*C.float)(&buf[0]), C.size_t(len(buf)), (*C.float)(&out[0]), C.int(s.channels))
buf = out
}
}
for len(buf) > 0 {
n := len(buf)
if n > s.blockSize*channels {
n = s.blockSize * channels
}
b, err := samplerateConverter.Process(buf[0:n], ratio, false)
if err != nil {
log.Panic(err)
}
if len(b) > 0 {
newChannel <- b
}
buf = buf[n:]
}
}
b, err := samplerateConverter.Process([]float32{}, ratio, true)
if err != nil {
log.Panic(err)
}
if len(b) > 0 {
newChannel <- b
}
}()
return NewStream(newChannel, channels, sampleRate, s.blockSize), nil
return int(seconds * float64(s.source.SampleRate))
}

82
hasher/accuraterip.go Normal file
View file

@ -0,0 +1,82 @@
package hasher
import (
"hash"
"unsafe"
)
type accurateRipDigestV1 struct {
crc uint32
pos uint32
offset uint32
}
func NewAccurateRipV1(offset uint32) hash.Hash32 {
return &accurateRipDigestV1{0, offset + 1, offset}
}
func (d *accurateRipDigestV1) Size() int { return 4 }
func (d *accurateRipDigestV1) BlockSize() int { return 1 }
func (d *accurateRipDigestV1) Reset() { d.crc = 0; d.pos = d.offset + 1 }
func (d *accurateRipDigestV1) Sum32() uint32 { return d.crc }
func (d *accurateRipDigestV1) Sum(in []byte) []byte {
s := d.Sum32()
return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
}
func (d *accurateRipDigestV1) Write(p []byte) (n int, err error) {
numWords := uintptr(len(p)) * unsafe.Sizeof(p[0]) / unsafe.Sizeof(uint32(0))
words := unsafe.Slice((*uint32)(unsafe.Pointer(&p[0])), numWords)
for _, w := range words {
//this can wrap
d.crc += d.pos * w
d.pos++
}
return len(p), nil
}
type accurateRipDigestV2 struct {
crc uint32
multiplier uint32
offset uint32
}
func NewAccurateRipV2(offset uint32) hash.Hash32 {
return &accurateRipDigestV2{0, offset + 1, offset}
}
func (d *accurateRipDigestV2) Size() int { return 4 }
func (d *accurateRipDigestV2) BlockSize() int { return 1 }
func (d *accurateRipDigestV2) Reset() { d.crc = 0; d.multiplier = d.offset + 1 }
func (d *accurateRipDigestV2) Sum32() uint32 { return d.crc }
func (d *accurateRipDigestV2) Sum(in []byte) []byte {
s := d.Sum32()
return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
}
func (d *accurateRipDigestV2) Write(p []byte) (n int, err error) {
numWords := uintptr(len(p)) * unsafe.Sizeof(p[0]) / unsafe.Sizeof(uint32(0))
words := unsafe.Slice((*uint32)(unsafe.Pointer(&p[0])), numWords)
for _, w := range words {
crcNew := uint64(w) * uint64(d.multiplier)
LO := crcNew & 0xFFFFFFFF
HI := crcNew / 0x100000000
//this can wrap
d.crc += uint32(HI)
d.crc += uint32(LO)
d.multiplier++
}
return len(p), nil
}

183
hasher/hasher.go Normal file
View file

@ -0,0 +1,183 @@
package hasher
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/binary"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format"
"hash"
"hash/crc32"
"sync"
"time"
)
const TocPregap = 150
const SectorsPerSecond = 75
const DataTrackGap = 11400
const BytesPerSector = 2352
const CDChannels = 2
const Int16SamplesPerSector = BytesPerSector / (2 * CDChannels)
const CDSampleRate = Int16SamplesPerSector * SectorsPerSecond
type HashType int
const (
HashtypeCrc32 = HashType(iota)
HashtypeSha256
HashtypeSha1
HashtypeMd5
HashtypeAccurateRipV1
HashtypeAccurateRipV1Start
HashtypeAccurateRipV2
HashtypeAccurateRipV2Start
)
type Hasher struct {
hash HashType
hasher hash.Hash
result []byte
channel format.AnalyzerChannel
wg sync.WaitGroup
samples int
duration float64
sampleRate int
bitDepth int
channels int
buffer [][]int32
}
func NewHasher(channel format.AnalyzerChannel, hashType HashType) (h *Hasher) {
h = &Hasher{
hash: hashType,
channel: channel,
}
switch hashType {
case HashtypeCrc32:
h.hasher = crc32.NewIEEE()
case HashtypeSha256:
h.hasher = sha256.New()
case HashtypeSha1:
h.hasher = sha1.New()
case HashtypeMd5:
h.hasher = md5.New()
case HashtypeAccurateRipV1:
h.hasher = NewAccurateRipV1(0)
case HashtypeAccurateRipV1Start:
h.hasher = NewAccurateRipV1(Int16SamplesPerSector*5 - 1)
case HashtypeAccurateRipV2:
h.hasher = NewAccurateRipV2(0)
case HashtypeAccurateRipV2Start:
h.hasher = NewAccurateRipV2(Int16SamplesPerSector*5 - 1)
}
h.startRoutine()
return
}
func (h *Hasher) startRoutine() {
h.wg.Add(1)
go func() {
defer h.wg.Done()
for packet := range h.channel {
h.handlePacket(packet)
}
h.result = h.hasher.Sum([]byte{})
}()
}
func (h *Hasher) handlePacket(packet *format.AnalyzerPacket) {
samples := len(packet.Samples) / packet.Channels
h.samples += samples
if h.sampleRate == 0 {
h.sampleRate = packet.SampleRate
} else if h.sampleRate != packet.SampleRate {
h.sampleRate = -1
}
if h.bitDepth == 0 {
h.bitDepth = packet.BitDepth
} else if h.bitDepth != packet.BitDepth {
h.bitDepth = -1
}
if h.channels == 0 {
h.channels = packet.Channels
} else if h.channels != packet.Channels {
h.channels = -1
}
h.duration += float64(samples) / float64(packet.SampleRate)
var buf []byte
switch packet.BitDepth {
case 8:
buf = make([]byte, len(packet.Samples))
for i := range packet.Samples {
buf[i] = byte(packet.Samples[i])
}
case 16:
buf = make([]byte, len(packet.Samples)*2)
for i := range packet.Samples {
binary.LittleEndian.PutUint16(buf[i*2:], uint16(int16(packet.Samples[i])))
}
case 24:
buf = make([]byte, len(packet.Samples)*3)
for i := range packet.Samples {
buf[i*3] = byte((packet.Samples[i]) & 0xFF)
buf[i*3+1] = byte((packet.Samples[i] >> 8) & 0xFF)
buf[i*3+2] = byte((packet.Samples[i] >> 16) & 0xFF)
}
default:
buf = make([]byte, len(packet.Samples)*4)
for i := range packet.Samples {
binary.LittleEndian.PutUint32(buf[i*4:], uint32(packet.Samples[i]))
}
}
h.hasher.Write(buf)
}
func (h *Hasher) GetSampleCount() int {
return h.samples
}
func (h *Hasher) GetChannels() int {
return h.channels
}
func (h *Hasher) GetSampleRate() int {
return h.sampleRate
}
func (h *Hasher) GetHashType() HashType {
return h.hash
}
func (h *Hasher) GetResult() []byte {
return h.result
}
func (h *Hasher) GetDuration() time.Duration {
if h.sampleRate > 0 {
return time.Duration(float64(time.Second) * (float64(h.samples) / float64(h.sampleRate)))
}
//Fallback calculated duration
return time.Duration(float64(time.Second) * h.duration)
}
func (h *Hasher) GetWaitGroup() *sync.WaitGroup {
return &h.wg
}
func (h *Hasher) Wait() {
h.wg.Wait()
}