clipper/backend/media/audio_progress.go

123 lines
2.9 KiB
Go
Raw Normal View History

2021-10-27 19:34:59 +00:00
package media
import (
"bytes"
"encoding/binary"
"fmt"
"io"
2021-11-06 20:52:47 +00:00
"math"
2021-10-27 19:34:59 +00:00
)
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-11-06 20:52:47 +00:00
byteOrder binary.ByteOrder
2021-10-29 12:52:31 +00:00
framesExpected int64
channels int
framesPerBin int
samples []int16
currPeaks []int16
currCount int
framesProcessed int64
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-06 20:52:47 +00:00
func newGetAudioProgressReader(byteOrder binary.ByteOrder, framesExpected int64, channels, numBins int) *fetchAudioProgressReader {
2021-10-27 20:17:59 +00:00
return &fetchAudioProgressReader{
2021-11-06 20:52:47 +00:00
byteOrder: byteOrder,
2021-10-29 12:52:31 +00:00
channels: channels,
framesExpected: framesExpected,
2021-11-06 20:52:47 +00:00
framesPerBin: int(math.Ceil(float64(framesExpected) / float64(numBins))),
2021-10-29 12:52:31 +00:00
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-11-06 20:52:47 +00:00
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)
}
}
}
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]
2021-11-06 20:52:47 +00:00
if err := binary.Read(bytes.NewReader(p), w.byteOrder, samples); err != nil {
2021-10-27 19:34:59 +00:00
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 += int64(len(samples) / w.channels)
2021-10-29 12:52:31 +00:00
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
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
}