Added Hasher / Filter / Sink interfaces and implementation
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
951b3fcf54
commit
21efea8dd1
143
Kirika_test.go
143
Kirika_test.go
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
190
audio/filter.go
Normal 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
208
audio/format/analyzer.go
Normal 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
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
63
audio/sink.go
Normal 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
7
audio/source.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package audio
|
||||
|
||||
type Source struct {
|
||||
SampleRate int
|
||||
Channels int
|
||||
Blocks chan []float32
|
||||
}
|
114
audio/stream.go
114
audio/stream.go
|
@ -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
82
hasher/accuraterip.go
Normal 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
183
hasher/hasher.go
Normal 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()
|
||||
}
|
Loading…
Reference in a new issue