clipper/backend/media/audio_progress.go

121 lines
3.0 KiB
Go
Raw Normal View History

2021-10-27 19:34:59 +00:00
package media
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
2021-11-01 05:28:40 +00:00
type GetAudioProgress struct {
2021-10-29 12:52:31 +00:00
PercentComplete float32
2021-10-27 19:34:59 +00:00
Peaks []int16
}
2021-11-01 05:28:40 +00:00
type GetAudioProgressReader interface {
Read() (GetAudioProgress, error)
2021-10-27 20:17:59 +00:00
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-29 12:52:31 +00:00
framesExpected int64
channels int
framesPerBin int
samples []int16
currPeaks []int16
currCount int
framesProcessed int
2021-11-01 05:28:40 +00:00
progress chan GetAudioProgress
2021-10-29 12:52:31 +00:00
errorChan chan error
2021-10-27 19:34:59 +00:00
}
// TODO: validate inputs, debugging is confusing otherwise
2021-11-01 05:28:40 +00:00
func newGetAudioProgressReader(framesExpected int64, channels, numBins int) *fetchAudioProgressReader {
2021-10-27 20:17:59 +00:00
return &fetchAudioProgressReader{
2021-10-29 12:52:31 +00:00
channels: channels,
framesExpected: framesExpected,
framesPerBin: int(framesExpected / int64(numBins)),
samples: make([]int16, 8_192),
currPeaks: make([]int16, channels),
2021-11-01 05:28:40 +00:00
progress: make(chan GetAudioProgress),
2021-10-29 12:52:31 +00:00
errorChan: make(chan error, 1),
2021-10-27 19:34:59 +00:00
}
}
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()
}
}
2021-10-29 12:52:31 +00:00
w.framesProcessed += len(samples) / w.channels
2021-10-27 19:34:59 +00:00
return len(p), nil
}
2021-10-27 20:17:59 +00:00
func (w *fetchAudioProgressReader) nextBin() {
2021-11-01 05:28:40 +00:00
var progress GetAudioProgress
2021-10-27 19:34:59 +00:00
// TODO: avoid an allocation?
progress.Peaks = append(progress.Peaks, w.currPeaks...)
2021-10-29 12:52:31 +00:00
progress.PercentComplete = (float32(w.framesProcessed) / float32(w.framesExpected)) * 100.0
2021-10-27 19:34:59 +00:00
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
}
2021-10-29 12:52:31 +00:00
w.framesProcessed++
2021-10-27 19:34:59 +00:00
}
2021-11-01 05:28:40 +00:00
func (w *fetchAudioProgressReader) Read() (GetAudioProgress, error) {
2021-10-27 19:34:59 +00:00
for {
select {
case progress, ok := <-w.progress:
if !ok {
2021-11-01 05:28:40 +00:00
return GetAudioProgress{}, io.EOF
2021-10-27 19:34:59 +00:00
}
2021-10-29 12:52:31 +00:00
return progress, nil
2021-10-27 19:34:59 +00:00
case err := <-w.errorChan:
2021-11-01 05:28:40 +00:00
return GetAudioProgress{}, fmt.Errorf("error waiting for progress: %v", err)
2021-10-27 19:34:59 +00:00
}
}
}