clipper/backend/media/audio_progress.go

116 lines
2.8 KiB
Go
Raw Normal View History

2021-10-27 19:34:59 +00:00
package media
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
2021-10-27 20:17:59 +00:00
type FetchAudioProgress struct {
2021-10-27 19:34:59 +00:00
percentComplete float32
Peaks []int16
}
2021-10-27 20:17:59 +00:00
type FetchAudioProgressReader interface {
Read() (FetchAudioProgress, error)
Close() error
}
// fetchAudioProgressReader accepts a byte stream containing little endian
2021-10-27 19:34:59 +00:00
// signed int16s and, given a target number of bins, emits a stream of peaks
// corresponding to each channel of the audio data.
2021-10-27 20:17:59 +00:00
type fetchAudioProgressReader struct {
2021-10-27 19:34:59 +00:00
channels int
framesPerBin int
samples []int16
currPeaks []int16
currCount int
total int
2021-10-27 20:17:59 +00:00
progress chan FetchAudioProgress
2021-10-27 19:34:59 +00:00
errorChan chan error
}
// TODO: validate inputs, debugging is confusing otherwise
2021-10-27 20:17:59 +00:00
func newFetchAudioProgressReader(expFrames int64, channels, numBins int) *fetchAudioProgressReader {
return &fetchAudioProgressReader{
2021-10-27 19:34:59 +00:00
channels: channels,
framesPerBin: int(expFrames / int64(numBins)),
samples: make([]int16, 8_192),
currPeaks: make([]int16, channels),
2021-10-27 20:17:59 +00:00
progress: make(chan FetchAudioProgress),
2021-10-27 19:34:59 +00:00
errorChan: make(chan error, 1),
}
}
2021-10-27 20:17:59 +00:00
func (w *fetchAudioProgressReader) Abort(err error) {
2021-10-27 19:34:59 +00:00
w.errorChan <- err
}
2021-10-27 20:17:59 +00:00
func (w *fetchAudioProgressReader) Close() error {
2021-10-27 19:34:59 +00:00
close(w.progress)
return nil
}
2021-10-27 20:17:59 +00:00
func (w *fetchAudioProgressReader) Write(p []byte) (int, error) {
2021-10-27 19:34:59 +00:00
// 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()
}
}
return len(p), nil
}
2021-10-27 20:17:59 +00:00
func (w *fetchAudioProgressReader) nextBin() {
var progress FetchAudioProgress
2021-10-27 19:34:59 +00:00
// TODO: avoid an allocation?
progress.Peaks = append(progress.Peaks, w.currPeaks...)
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.total++
}
2021-10-27 20:17:59 +00:00
func (w *fetchAudioProgressReader) Read() (FetchAudioProgress, error) {
2021-10-27 19:34:59 +00:00
for {
select {
case progress, ok := <-w.progress:
if !ok {
2021-10-27 20:17:59 +00:00
return FetchAudioProgress{}, io.EOF
2021-10-27 19:34:59 +00:00
}
2021-10-27 20:17:59 +00:00
return FetchAudioProgress{Peaks: progress.Peaks}, nil
2021-10-27 19:34:59 +00:00
case err := <-w.errorChan:
2021-10-27 20:17:59 +00:00
return FetchAudioProgress{}, fmt.Errorf("error waiting for progress: %v", err)
2021-10-27 19:34:59 +00:00
}
}
}