audio, audio/mp3, audio/vorbis, audio/wav: Remove Close functions

Fixes #859
This commit is contained in:
Hajime Hoshi 2020-10-07 23:09:02 +09:00
parent 946cf1d250
commit f1f7b350de
15 changed files with 58 additions and 139 deletions

View File

@ -162,7 +162,7 @@ func (c *Context) addPlayer(p *playerImpl) {
c.players[p] = struct{}{}
// Check the source duplication
srcs := map[io.ReadCloser]struct{}{}
srcs := map[io.Reader]struct{}{}
for p := range c.players {
if _, ok := srcs[p.src]; ok {
c.err = errors.New("audio: a same source is used by multiple Player")
@ -211,34 +211,6 @@ func (c *Context) SampleRate() int {
return c.sampleRate
}
// ReadSeekCloser is an io.ReadSeeker and io.Closer.
type ReadSeekCloser interface {
io.ReadSeeker
io.Closer
}
type bytesReadSeekCloser struct {
reader *bytes.Reader
}
func (b *bytesReadSeekCloser) Read(buf []byte) (int, error) {
return b.reader.Read(buf)
}
func (b *bytesReadSeekCloser) Seek(offset int64, whence int) (int64, error) {
return b.reader.Seek(offset, whence)
}
func (b *bytesReadSeekCloser) Close() error {
b.reader = nil
return nil
}
// BytesReadSeekCloser creates ReadSeekCloser from bytes.
func BytesReadSeekCloser(b []byte) ReadSeekCloser {
return &bytesReadSeekCloser{reader: bytes.NewReader(b)}
}
// Player is an audio player which has one stream.
//
// Even when all references to a Player object is gone,
@ -251,7 +223,7 @@ type Player struct {
type playerImpl struct {
context *Context
src io.ReadCloser
src io.Reader
sampleRate int
playing bool
closedExplicitly bool
@ -278,8 +250,9 @@ type playerImpl struct {
// NewPlayer tries to call Seek of src to get the current position.
// NewPlayer returns error when the Seek returns error.
//
// NewPlayer takes the ownership of src. Player's Close calls src's Close.
func NewPlayer(context *Context, src io.ReadCloser) (*Player, error) {
// A Player doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility.
func NewPlayer(context *Context, src io.Reader) (*Player, error) {
p := &Player{
&playerImpl{
context: context,
@ -308,7 +281,7 @@ func NewPlayer(context *Context, src io.ReadCloser) (*Player, error) {
//
// The format of src should be same as noted at NewPlayer.
func NewPlayerFromBytes(context *Context, src []byte) *Player {
b := BytesReadSeekCloser(src)
b := bytes.NewReader(src)
p, err := NewPlayer(context, b)
if err != nil {
// Errors should never happen.
@ -329,7 +302,7 @@ func (p *Player) finalize() {
// When closing, the stream owned by the player will also be closed by calling its Close.
// This means that the source stream passed via NewPlayer will also be closed.
//
// Close returns error when closing the source returns error.
// Close returns error when the player is already closed.
func (p *Player) Close() error {
runtime.SetFinalizer(p, nil)
return p.p.Close()
@ -344,11 +317,6 @@ func (p *playerImpl) Close() error {
return fmt.Errorf("audio: the player is already closed")
}
p.closedExplicitly = true
// src.Close is called only when Player's Close is called.
// TODO: Is it ok not to call src.Close when GCed?
if err := p.src.Close(); err != nil {
return err
}
return nil
}

View File

@ -15,6 +15,7 @@
package audio_test
import (
"bytes"
"runtime"
"testing"
"time"
@ -38,7 +39,7 @@ func TestGC(t *testing.T) {
setup()
defer teardown()
p, _ := NewPlayer(context, BytesReadSeekCloser(make([]byte, 4)))
p, _ := NewPlayer(context, bytes.NewReader(make([]byte, 4)))
got := PlayersNumForTesting()
if want := 0; got != want {
t.Errorf("PlayersNum(): got: %d, want: %d", got, want)
@ -74,7 +75,7 @@ func TestSameSourcePlayers(t *testing.T) {
setup()
defer teardown()
src := BytesReadSeekCloser(make([]byte, 4))
src := bytes.NewReader(make([]byte, 4))
p0, err := NewPlayer(context, src)
if err != nil {
t.Fatal(err)
@ -101,7 +102,7 @@ func TestPauseBeforeInit(t *testing.T) {
setup()
defer teardown()
src := BytesReadSeekCloser(make([]byte, 4))
src := bytes.NewReader(make([]byte, 4))
p, err := NewPlayer(context, src)
if err != nil {
t.Fatal(err)

View File

@ -15,11 +15,11 @@
package convert_test
import (
"bytes"
"io/ioutil"
"math"
"testing"
"github.com/hajimehoshi/ebiten/v2/audio"
. "github.com/hajimehoshi/ebiten/v2/audio/internal/convert"
)
@ -69,7 +69,7 @@ func TestResampling(t *testing.T) {
}
for _, c := range cases {
inB := newSoundBytes(c.In)
outS := NewResampling(audio.BytesReadSeekCloser(inB), int64(len(inB)), c.In, c.Out)
outS := NewResampling(bytes.NewReader(inB), int64(len(inB)), c.In, c.Out)
gotB, err := ioutil.ReadAll(outS)
if err != nil {
t.Fatal(err)

View File

@ -16,17 +16,15 @@ package convert
import (
"io"
"github.com/hajimehoshi/ebiten/v2/audio"
)
type Stereo16 struct {
source audio.ReadSeekCloser
source io.ReadSeeker
mono bool
eight bool
}
func NewStereo16(source audio.ReadSeekCloser, mono, eight bool) *Stereo16 {
func NewStereo16(source io.ReadSeeker, mono, eight bool) *Stereo16 {
return &Stereo16{
source: source,
mono: mono,
@ -91,7 +89,3 @@ func (s *Stereo16) Seek(offset int64, whence int) (int64, error) {
}
return s.source.Seek(offset, whence)
}
func (s *Stereo16) Close() error {
return s.source.Close()
}

View File

@ -21,20 +21,20 @@ import (
// InfiniteLoop represents a looped stream which never ends.
type InfiniteLoop struct {
src ReadSeekCloser
src io.ReadSeeker
lstart int64
llength int64
pos int64
}
// NewInfiniteLoop creates a new infinite loop stream with a source stream and length in bytes.
func NewInfiniteLoop(src ReadSeekCloser, length int64) *InfiniteLoop {
func NewInfiniteLoop(src io.ReadSeeker, length int64) *InfiniteLoop {
return NewInfiniteLoopWithIntro(src, 0, length)
}
// NewInfiniteLoopWithIntro creates a new infinite loop stream with an intro part.
// NewInfiniteLoopWithIntro accepts a source stream src, introLength in bytes and loopLength in bytes.
func NewInfiniteLoopWithIntro(src ReadSeekCloser, introLength int64, loopLength int64) *InfiniteLoop {
func NewInfiniteLoopWithIntro(src io.ReadSeeker, introLength int64, loopLength int64) *InfiniteLoop {
return &InfiniteLoop{
src: src,
lstart: introLength,
@ -121,8 +121,3 @@ func (i *InfiniteLoop) Seek(offset int64, whence int) (int64, error) {
i.pos = next
return i.pos, nil
}
// Close is implementation of ReadSeekCloser's Close.
func (l *InfiniteLoop) Close() error {
return l.src.Close()
}

View File

@ -15,6 +15,7 @@
package audio_test
import (
"bytes"
"io"
"math"
"testing"
@ -31,7 +32,7 @@ func TestInfiniteLoop(t *testing.T) {
for i := range src {
src[i] = indexToByte(i)
}
l := NewInfiniteLoop(BytesReadSeekCloser(src), int64(len(src)))
l := NewInfiniteLoop(bytes.NewReader(src), int64(len(src)))
buf := make([]byte, len(src)*4)
if _, err := io.ReadFull(l, buf); err != nil {
@ -93,7 +94,7 @@ func TestInfiniteLoopWithIntro(t *testing.T) {
for i := range src {
src[i] = indexToByte(i)
}
srcInf := NewInfiniteLoop(BytesReadSeekCloser(src), srcLength)
srcInf := NewInfiniteLoop(bytes.NewReader(src), srcLength)
l := NewInfiniteLoopWithIntro(srcInf, introLength, loopLength)
buf := make([]byte, srcLength*4)

View File

@ -20,7 +20,6 @@ package mp3
import (
"io"
"runtime"
"github.com/hajimehoshi/go-mp3"
@ -32,7 +31,6 @@ import (
type Stream struct {
orig *mp3.Decoder
resampling *convert.Resampling
toClose io.Closer
}
// Read is implementation of io.Reader's Read.
@ -51,12 +49,6 @@ func (s *Stream) Seek(offset int64, whence int) (int64, error) {
return s.orig.Seek(offset, whence)
}
// Close is implementation of io.Closer's Close.
func (s *Stream) Close() error {
runtime.SetFinalizer(s, nil)
return s.toClose.Close()
}
// Length returns the size of decoded stream in bytes.
func (s *Stream) Length() int64 {
if s.resampling != nil {
@ -71,8 +63,9 @@ func (s *Stream) Length() int64 {
//
// Decode automatically resamples the stream to fit with the audio context if necessary.
//
// Decode takes the ownership of src, and Stream's Close function closes src.
func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) {
// A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility.
func Decode(context *audio.Context, src io.ReadSeeker) (*Stream, error) {
d, err := mp3.NewDecoder(src)
if err != nil {
return nil, err
@ -85,8 +78,6 @@ func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) {
s := &Stream{
orig: d,
resampling: r,
toClose: src,
}
runtime.SetFinalizer(s, (*Stream).Close)
return s, nil
}

View File

@ -18,7 +18,6 @@ package vorbis
import (
"fmt"
"io"
"runtime"
"github.com/jfreymuth/oggvorbis"
@ -28,7 +27,7 @@ import (
// Stream is a decoded audio stream.
type Stream struct {
decoded audio.ReadSeekCloser
decoded io.ReadSeeker
size int64
}
@ -44,15 +43,6 @@ func (s *Stream) Seek(offset int64, whence int) (int64, error) {
return s.decoded.Seek(offset, whence)
}
// Close is implementation of io.Closer's Close.
func (s *Stream) Close() error {
runtime.SetFinalizer(s, nil)
if err := s.decoded.Close(); err != nil {
return err
}
return nil
}
// Length returns the size of decoded stream in bytes.
func (s *Stream) Length() int64 {
return s.size
@ -69,7 +59,6 @@ type decoder interface {
type decoded struct {
totalBytes int
posInBytes int
source io.Closer
decoder decoder
decoderr io.Reader
}
@ -122,22 +111,12 @@ func (d *decoded) Seek(offset int64, whence int) (int64, error) {
return next, nil
}
func (d *decoded) Close() error {
runtime.SetFinalizer(d, nil)
if err := d.source.Close(); err != nil {
return err
}
d.decoder = nil
d.decoderr = nil
return nil
}
func (d *decoded) Length() int64 {
return int64(d.totalBytes)
}
// decode accepts an ogg stream and returns a decorded stream.
func decode(in audio.ReadSeekCloser) (*decoded, int, int, error) {
func decode(in io.ReadSeeker) (*decoded, int, int, error) {
r, err := oggvorbis.NewReader(in)
if err != nil {
return nil, 0, 0, err
@ -147,10 +126,8 @@ func decode(in audio.ReadSeekCloser) (*decoded, int, int, error) {
// Should we check that?
totalBytes: int(r.Length()) * r.Channels() * 2, // 2 means 16bit per sample.
posInBytes: 0,
source: in,
decoder: r,
}
runtime.SetFinalizer(d, (*decoded).Close)
if _, err := d.Read(make([]byte, 65536)); err != nil && err != io.EOF {
return nil, 0, 0, err
}
@ -166,8 +143,9 @@ func decode(in audio.ReadSeekCloser) (*decoded, int, int, error) {
//
// Decode automatically resamples the stream to fit with the audio context if necessary.
//
// Decode takes the ownership of src, and Stream's Close function closes src.
func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) {
// A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility.
func Decode(context *audio.Context, src io.ReadSeeker) (*Stream, error) {
decoded, channelNum, sampleRate, err := decode(src)
if err != nil {
return nil, err
@ -175,7 +153,7 @@ func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) {
if channelNum != 1 && channelNum != 2 {
return nil, fmt.Errorf("vorbis: number of channels must be 1 or 2 but was %d", channelNum)
}
var s audio.ReadSeekCloser = decoded
var s io.ReadSeeker = decoded
size := decoded.Length()
if channelNum == 1 {
s = convert.NewStereo16(s, true, false)
@ -187,6 +165,5 @@ func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) {
size = r.Length()
}
stream := &Stream{decoded: s, size: size}
runtime.SetFinalizer(stream, (*Stream).Close)
return stream, nil
}

View File

@ -15,6 +15,7 @@
package vorbis_test
import (
"bytes"
"testing"
"github.com/jfreymuth/oggvorbis"
@ -28,12 +29,12 @@ var audioContext = audio.NewContext(44100)
func TestMono(t *testing.T) {
bs := test_mono_ogg
s, err := Decode(audioContext, audio.BytesReadSeekCloser(bs))
s, err := Decode(audioContext, bytes.NewReader(bs))
if err != nil {
t.Fatal(err)
}
r, err := oggvorbis.NewReader(audio.BytesReadSeekCloser(bs))
r, err := oggvorbis.NewReader(bytes.NewReader(bs))
if err != nil {
t.Fatal(err)
}
@ -54,7 +55,7 @@ func TestMono(t *testing.T) {
func TestTooShort(t *testing.T) {
bs := test_tooshort_ogg
s, err := Decode(audioContext, audio.BytesReadSeekCloser(bs))
s, err := Decode(audioContext, bytes.NewReader(bs))
if err != nil {
t.Fatal(err)
}

View File

@ -19,7 +19,6 @@ import (
"bytes"
"fmt"
"io"
"runtime"
"github.com/hajimehoshi/ebiten/v2/audio"
"github.com/hajimehoshi/ebiten/v2/audio/internal/convert"
@ -27,7 +26,7 @@ import (
// Stream is a decoded audio stream.
type Stream struct {
inner audio.ReadSeekCloser
inner io.ReadSeeker
size int64
}
@ -43,19 +42,13 @@ func (s *Stream) Seek(offset int64, whence int) (int64, error) {
return s.inner.Seek(offset, whence)
}
// Read is implementation of io.Closer's Close.
func (s *Stream) Close() error {
runtime.SetFinalizer(s, nil)
return s.inner.Close()
}
// Length returns the size of decoded stream in bytes.
func (s *Stream) Length() int64 {
return s.size
}
type stream struct {
src audio.ReadSeekCloser
src io.ReadSeeker
headerSize int64
dataSize int64
remaining int64
@ -100,12 +93,6 @@ func (s *stream) Seek(offset int64, whence int) (int64, error) {
return n - s.headerSize, nil
}
// Close is implementation of io.Closer's Close.
func (s *stream) Close() error {
runtime.SetFinalizer(s, nil)
return s.src.Close()
}
// Decode decodes WAV (RIFF) data to playable stream.
//
// The format must be 1 or 2 channels, 8bit or 16bit little endian PCM.
@ -115,8 +102,9 @@ func (s *stream) Close() error {
//
// Decode automatically resamples the stream to fit with the audio context if necessary.
//
// Decode takes the ownership of src, and Stream's Close function closes src.
func Decode(context *audio.Context, src audio.ReadSeekCloser) (*Stream, error) {
// A Stream doesn't close src even if src implements io.Closer.
// Closing the source is src owner's responsibility.
func Decode(context *audio.Context, src io.ReadSeeker) (*Stream, error) {
buf := make([]byte, 12)
n, err := io.ReadFull(src, buf)
if n != len(buf) {
@ -203,13 +191,12 @@ chunks:
headerSize += size
}
}
var s audio.ReadSeekCloser = &stream{
var s io.ReadSeeker = &stream{
src: src,
headerSize: headerSize,
dataSize: dataSize,
remaining: dataSize,
}
runtime.SetFinalizer(s, (*stream).Close)
if mono || bitsPerSample != 16 {
s = convert.NewStereo16(s, mono, bitsPerSample != 16)
@ -226,6 +213,5 @@ chunks:
dataSize = r.Length()
}
ss := &Stream{inner: s, size: dataSize}
runtime.SetFinalizer(ss, (*Stream).Close)
return ss, nil
}

View File

@ -20,8 +20,10 @@
package main
import (
"bytes"
"fmt"
"image/color"
"io"
"io/ioutil"
"log"
"time"
@ -87,7 +89,7 @@ func playerBarRect() (x, y, w, h int) {
func NewPlayer(audioContext *audio.Context, musicType musicType) (*Player, error) {
type audioStream interface {
audio.ReadSeekCloser
io.ReadSeeker
Length() int64
}
@ -98,13 +100,13 @@ func NewPlayer(audioContext *audio.Context, musicType musicType) (*Player, error
switch musicType {
case typeOgg:
var err error
s, err = vorbis.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Ragtime_ogg))
s, err = vorbis.Decode(audioContext, bytes.NewReader(raudio.Ragtime_ogg))
if err != nil {
return nil, err
}
case typeMP3:
var err error
s, err = mp3.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Classic_mp3))
s, err = mp3.Decode(audioContext, bytes.NewReader(raudio.Classic_mp3))
if err != nil {
return nil, err
}
@ -128,7 +130,7 @@ func NewPlayer(audioContext *audio.Context, musicType musicType) (*Player, error
}
player.audioPlayer.Play()
go func() {
s, err := wav.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Jab_wav))
s, err := wav.Decode(audioContext, bytes.NewReader(raudio.Jab_wav))
if err != nil {
log.Fatal(err)
return

View File

@ -17,6 +17,7 @@
package main
import (
"bytes"
"fmt"
"log"
"time"
@ -50,7 +51,7 @@ func (g *Game) Update() error {
// Decode an Ogg file.
// oggS is a decoded io.ReadCloser and io.Seeker.
oggS, err := vorbis.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Ragtime_ogg))
oggS, err := vorbis.Decode(audioContext, bytes.NewReader(raudio.Ragtime_ogg))
if err != nil {
return err
}

View File

@ -21,6 +21,7 @@ import (
"fmt"
"image"
_ "image/png"
"io"
"log"
"math"
"time"
@ -64,7 +65,7 @@ func (g *Game) initAudio() {
// Decode an Ogg file.
// oggS is a decoded io.ReadCloser and io.Seeker.
oggS, err := vorbis.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Ragtime_ogg))
oggS, err := vorbis.Decode(audioContext, bytes.NewReader(raudio.Ragtime_ogg))
if err != nil {
log.Fatal(err)
}
@ -145,12 +146,12 @@ func main() {
// StereoPanStream is an audio buffer that changes the stereo channel's signal
// based on the Panning.
type StereoPanStream struct {
audio.ReadSeekCloser
io.ReadSeeker
pan float64 // -1: left; 0: center; 1: right
}
func (s *StereoPanStream) Read(p []byte) (n int, err error) {
n, err = s.ReadSeekCloser.Read(p)
n, err = s.ReadSeeker.Read(p)
if err != nil {
return
}
@ -187,8 +188,8 @@ func (s *StereoPanStream) Pan() float64 {
// The src's format must be linear PCM (16bits little endian, 2 channel stereo)
// without a header (e.g. RIFF header). The sample rate must be same as that
// of the audio context.
func NewStereoPanStreamFromReader(src audio.ReadSeekCloser) *StereoPanStream {
func NewStereoPanStreamFromReader(src io.ReadSeeker) *StereoPanStream {
return &StereoPanStream{
ReadSeekCloser: src,
ReadSeeker: src,
}
}

View File

@ -122,7 +122,7 @@ var (
)
func init() {
jumpD, err := vorbis.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Jump_ogg))
jumpD, err := vorbis.Decode(audioContext, bytes.NewReader(raudio.Jump_ogg))
if err != nil {
log.Fatal(err)
}
@ -131,7 +131,7 @@ func init() {
log.Fatal(err)
}
jabD, err := wav.Decode(audioContext, audio.BytesReadSeekCloser(raudio.Jab_wav))
jabD, err := wav.Decode(audioContext, bytes.NewReader(raudio.Jab_wav))
if err != nil {
log.Fatal(err)
}

View File

@ -17,6 +17,7 @@
package main
import (
"bytes"
"log"
"github.com/hajimehoshi/ebiten/v2"
@ -59,7 +60,7 @@ func init() {
// ...
// Decode wav-formatted data and retrieve decoded PCM stream.
d, err := wav.Decode(g.audioContext, audio.BytesReadSeekCloser(raudio.Jab_wav))
d, err := wav.Decode(g.audioContext, bytes.NewReader(raudio.Jab_wav))
if err != nil {
log.Fatal(err)
}