121 lines
3.0 KiB
Go
121 lines
3.0 KiB
Go
package media
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
type GetAudioProgress struct {
|
|
PercentComplete float32
|
|
Peaks []int16
|
|
}
|
|
|
|
type GetAudioProgressReader interface {
|
|
Read() (GetAudioProgress, error)
|
|
Close() error
|
|
}
|
|
|
|
// fetchAudioProgressReader accepts a byte stream containing little endian
|
|
// signed int16s and, given a target number of bins, emits a stream of peaks
|
|
// corresponding to each channel of the audio data.
|
|
type fetchAudioProgressReader struct {
|
|
framesExpected int64
|
|
channels int
|
|
framesPerBin int
|
|
|
|
samples []int16
|
|
currPeaks []int16
|
|
currCount int
|
|
framesProcessed int
|
|
progress chan GetAudioProgress
|
|
errorChan chan error
|
|
}
|
|
|
|
// TODO: validate inputs, debugging is confusing otherwise
|
|
func newGetAudioProgressReader(framesExpected int64, channels, numBins int) *fetchAudioProgressReader {
|
|
return &fetchAudioProgressReader{
|
|
channels: channels,
|
|
framesExpected: framesExpected,
|
|
framesPerBin: int(framesExpected / int64(numBins)),
|
|
samples: make([]int16, 8_192),
|
|
currPeaks: make([]int16, channels),
|
|
progress: make(chan GetAudioProgress),
|
|
errorChan: make(chan error, 1),
|
|
}
|
|
}
|
|
|
|
func (w *fetchAudioProgressReader) Abort(err error) {
|
|
w.errorChan <- err
|
|
}
|
|
|
|
func (w *fetchAudioProgressReader) Close() error {
|
|
close(w.progress)
|
|
return nil
|
|
}
|
|
|
|
func (w *fetchAudioProgressReader) Write(p []byte) (int, error) {
|
|
// expand our target slice if it is of insufficient size:
|
|
numSamples := len(p) / SizeOfInt16
|
|
if len(w.samples) < numSamples {
|
|
w.samples = append(w.samples, make([]int16, numSamples-len(w.samples))...)
|
|
}
|
|
|
|
samples := w.samples[:numSamples]
|
|
|
|
if err := binary.Read(bytes.NewReader(p), binary.LittleEndian, samples); err != nil {
|
|
return 0, fmt.Errorf("error parsing samples: %v", err)
|
|
}
|
|
|
|
for i := 0; i < len(samples); i += w.channels {
|
|
for j := 0; j < w.channels; j++ {
|
|
samp := samples[i+j]
|
|
if samp < 0 {
|
|
samp = -samp
|
|
}
|
|
if samp > w.currPeaks[j] {
|
|
w.currPeaks[j] = samp
|
|
}
|
|
}
|
|
w.currCount++
|
|
if w.currCount == w.framesPerBin {
|
|
w.nextBin()
|
|
}
|
|
}
|
|
|
|
w.framesProcessed += len(samples) / w.channels
|
|
|
|
return len(p), nil
|
|
}
|
|
|
|
func (w *fetchAudioProgressReader) nextBin() {
|
|
var progress GetAudioProgress
|
|
// TODO: avoid an allocation?
|
|
progress.Peaks = append(progress.Peaks, w.currPeaks...)
|
|
progress.PercentComplete = (float32(w.framesProcessed) / float32(w.framesExpected)) * 100.0
|
|
|
|
w.progress <- progress
|
|
|
|
w.currCount = 0
|
|
// log.Printf("got peak for %d frames, which is equal to target of %d frames per bin, %d total bins processed, peaks: %+v", w.currCount, w.framesPerBin, w.total+1, w.currPeaks)
|
|
for i := 0; i < len(w.currPeaks); i++ {
|
|
w.currPeaks[i] = 0
|
|
}
|
|
w.framesProcessed++
|
|
}
|
|
|
|
func (w *fetchAudioProgressReader) Read() (GetAudioProgress, error) {
|
|
for {
|
|
select {
|
|
case progress, ok := <-w.progress:
|
|
if !ok {
|
|
return GetAudioProgress{}, io.EOF
|
|
}
|
|
return progress, nil
|
|
case err := <-w.errorChan:
|
|
return GetAudioProgress{}, fmt.Errorf("error waiting for progress: %v", err)
|
|
}
|
|
}
|
|
}
|