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
}
}
}