diff --git a/audio/audio.go b/audio/audio.go index e76c6f408..4f749588e 100644 --- a/audio/audio.go +++ b/audio/audio.go @@ -48,7 +48,6 @@ import ( const ( channelCount = 2 bitDepthInBytesInt16 = 2 - bytesPerSampleInt16 = bitDepthInBytesInt16 * channelCount ) // A Context represents a current state of audio. @@ -324,7 +323,7 @@ type Player struct { // A Player doesn't close src even if src implements io.Closer. // Closing the source is src owner's responsibility. func (c *Context) NewPlayer(src io.Reader) (*Player, error) { - pi, err := c.playerFactory.newPlayer(c, src) + pi, err := c.playerFactory.newPlayer(c, src, bitDepthInBytesInt16) if err != nil { return nil, err } diff --git a/audio/loop.go b/audio/loop.go index 6ab3b4477..5b3bd285f 100644 --- a/audio/loop.go +++ b/audio/loop.go @@ -21,12 +21,14 @@ import ( // InfiniteLoop represents a looped stream which never ends. type InfiniteLoop struct { - src io.ReadSeeker - lstart int64 - llength int64 - pos int64 + src io.ReadSeeker + lstart int64 + llength int64 + pos int64 + bitDepthInBytes int + bytesPerSample int - // extra is the remainder in the case when the read byte sizes are not multiple of bitDepthInBytesInt16. + // extra is the remainder in the case when the read byte sizes are not multiple of the bit depth. extra []byte // afterLoop is data after the loop. @@ -58,11 +60,18 @@ func NewInfiniteLoop(src io.ReadSeeker, length int64) *InfiniteLoop { // If src has data after the loop end, an InfiniteLoop uses part of the data to blend with the loop start // to make the loop joint smooth. func NewInfiniteLoopWithIntro(src io.ReadSeeker, introLength int64, loopLength int64) *InfiniteLoop { + return newInfiniteLoopWithIntro(src, introLength, loopLength, bitDepthInBytesInt16) +} + +func newInfiniteLoopWithIntro(src io.ReadSeeker, introLength int64, loopLength int64, bitDepthInBytes int) *InfiniteLoop { + bytesPerSample := bitDepthInBytes * channelCount return &InfiniteLoop{ - src: src, - lstart: introLength / bytesPerSampleInt16 * bytesPerSampleInt16, - llength: loopLength / bytesPerSampleInt16 * bytesPerSampleInt16, - pos: -1, + src: src, + lstart: introLength / int64(bytesPerSample) * int64(bytesPerSample), + llength: loopLength / int64(bytesPerSample) * int64(bytesPerSample), + pos: -1, + bitDepthInBytes: bitDepthInBytes, + bytesPerSample: bytesPerSample, } } @@ -92,8 +101,8 @@ func (i *InfiniteLoop) blendRate(pos int64) float64 { if pos >= i.lstart+int64(len(i.afterLoop)) { return 0 } - p := (pos - i.lstart) / bytesPerSampleInt16 - l := len(i.afterLoop) / bytesPerSampleInt16 + p := (pos - i.lstart) / int64(i.bytesPerSample) + l := len(i.afterLoop) / i.bytesPerSample return 1 - float64(p)/float64(l) } @@ -119,7 +128,7 @@ func (i *InfiniteLoop) Read(b []byte) (int, error) { } // Save the remainder part to extra. This will be used at the next Read. - if rem := n % bitDepthInBytesInt16; rem != 0 { + if rem := n % i.bitDepthInBytes; rem != 0 { i.extra = append(i.extra, b[n-rem:n]...) b = b[:n-rem] n = n - rem @@ -128,24 +137,27 @@ func (i *InfiniteLoop) Read(b []byte) (int, error) { // Blend afterLoop and the loop start to reduce noises (#1888). // Ideally, afterLoop and the loop start should be identical, but they can have very slight differences. if !i.noBlendForTesting && i.blending && i.pos >= i.lstart && i.pos-int64(n) < i.lstart+int64(len(i.afterLoop)) { - if n%bitDepthInBytesInt16 != 0 { - panic(fmt.Sprintf("audio: n must be a multiple of bitDepthInBytesInt16 but not: %d", n)) + if n%i.bitDepthInBytes != 0 { + panic(fmt.Sprintf("audio: n must be a multiple of bit depth %d [bytes] but not: %d", i.bitDepthInBytes, n)) } - for idx := 0; idx < n/bitDepthInBytesInt16; idx++ { - abspos := i.pos - int64(n) + int64(idx)*bitDepthInBytesInt16 + for idx := 0; idx < n/i.bitDepthInBytes; idx++ { + abspos := i.pos - int64(n) + int64(idx)*int64(i.bitDepthInBytes) rate := i.blendRate(abspos) if rate == 0 { continue } - // This assumes that bitDepthInBytesInt16 is 2. relpos := abspos - i.lstart - afterLoop := int16(i.afterLoop[relpos]) | (int16(i.afterLoop[relpos+1]) << 8) - orig := int16(b[2*idx]) | (int16(b[2*idx+1]) << 8) - - newval := int16(float64(afterLoop)*rate + float64(orig)*(1-rate)) - b[2*idx] = byte(newval) - b[2*idx+1] = byte(newval >> 8) + switch i.bitDepthInBytes { + case 2: + afterLoop := int16(i.afterLoop[relpos]) | (int16(i.afterLoop[relpos+1]) << 8) + orig := int16(b[2*idx]) | (int16(b[2*idx+1]) << 8) + newval := int16(float64(afterLoop)*rate + float64(orig)*(1-rate)) + b[2*idx] = byte(newval) + b[2*idx+1] = byte(newval >> 8) + default: + panic("not reached") + } } } @@ -156,7 +168,7 @@ func (i *InfiniteLoop) Read(b []byte) (int, error) { // Read the afterLoop part if necessary. if i.pos == i.length() && err == nil { if i.afterLoop == nil { - buflen := int64(256 * bytesPerSampleInt16) + buflen := int64(256 * i.bytesPerSample) if buflen > i.length() { buflen = i.length() } diff --git a/audio/player.go b/audio/player.go index 4fc7fcb37..1e52856f3 100644 --- a/audio/player.go +++ b/audio/player.go @@ -69,6 +69,7 @@ type playerImpl struct { stream *timeStream factory *playerFactory initBufferSize int + bytesPerSample int // adjustedPosition is the player's more accurate position. // The underlying buffer might not be changed even if the player is playing. @@ -85,15 +86,16 @@ type playerImpl struct { m sync.Mutex } -func (f *playerFactory) newPlayer(context *Context, src io.Reader) (*playerImpl, error) { +func (f *playerFactory) newPlayer(context *Context, src io.Reader, bitDepthInBytes int) (*playerImpl, error) { f.m.Lock() defer f.m.Unlock() p := &playerImpl{ - src: src, - context: context, - factory: f, - lastSamples: -1, + src: src, + context: context, + factory: f, + lastSamples: -1, + bytesPerSample: bitDepthInBytes * channelCount, } runtime.SetFinalizer(p, (*playerImpl).Close) return p, nil @@ -163,7 +165,7 @@ func (p *playerImpl) ensurePlayer() error { } if p.stream == nil { - s, err := newTimeStream(p.src, p.factory.sampleRate) + s, err := newTimeStream(p.src, p.factory.sampleRate, p.bytesPerSample/channelCount) if err != nil { return err } @@ -313,8 +315,8 @@ func (p *playerImpl) SetBufferSize(bufferSize time.Duration) { p.m.Lock() defer p.m.Unlock() - bufferSizeInBytes := int(bufferSize * bytesPerSampleInt16 * time.Duration(p.factory.sampleRate) / time.Second) - bufferSizeInBytes = bufferSizeInBytes / bytesPerSampleInt16 * bytesPerSampleInt16 + bufferSizeInBytes := int(bufferSize * time.Duration(p.bytesPerSample) * time.Duration(p.factory.sampleRate) / time.Second) + bufferSizeInBytes = bufferSizeInBytes / p.bytesPerSample * p.bytesPerSample if p.player == nil { p.initBufferSize = bufferSizeInBytes return @@ -361,7 +363,7 @@ func (p *playerImpl) updatePosition() { return } - samples := (p.stream.position() - int64(p.player.BufferedSize())) / bytesPerSampleInt16 + samples := (p.stream.position() - int64(p.player.BufferedSize())) / int64(p.bytesPerSample) var adjustingTime time.Duration if p.lastSamples >= 0 && p.lastSamples == samples { @@ -381,19 +383,21 @@ func (p *playerImpl) updatePosition() { } type timeStream struct { - r io.Reader - sampleRate int - pos int64 + r io.Reader + sampleRate int + pos int64 + bytesPerSample int // m is a mutex for this stream. // All the exported functions are protected by this mutex as Read can be read from a different goroutine than Seek. m sync.Mutex } -func newTimeStream(r io.Reader, sampleRate int) (*timeStream, error) { +func newTimeStream(r io.Reader, sampleRate int, bitDepthInBytes int) (*timeStream, error) { s := &timeStream{ - r: r, - sampleRate: sampleRate, + r: r, + sampleRate: sampleRate, + bytesPerSample: bitDepthInBytes * channelCount, } if seeker, ok := s.r.(io.Seeker); ok { // Get the current position of the source. @@ -437,11 +441,11 @@ func (s *timeStream) timeDurationToPos(offset time.Duration) int64 { s.m.Lock() defer s.m.Unlock() - o := int64(offset) * bytesPerSampleInt16 * int64(s.sampleRate) / int64(time.Second) + o := int64(offset) * int64(s.bytesPerSample) * int64(s.sampleRate) / int64(time.Second) // Align the byte position with the samples. - o -= o % bytesPerSampleInt16 - o += s.pos % bytesPerSampleInt16 + o -= o % int64(s.bytesPerSample) + o += s.pos % int64(s.bytesPerSample) return o } @@ -457,5 +461,5 @@ func (s *timeStream) positionInTimeDuration() time.Duration { s.m.Lock() defer s.m.Unlock() - return time.Duration(s.pos) * time.Second / (time.Duration(s.sampleRate) * bytesPerSampleInt16) + return time.Duration(s.pos) * time.Second / (time.Duration(s.sampleRate * s.bytesPerSample)) }