mobile/audio: avoid locks during IO and fix seek precision

Change-Id: Ida8d07f87fe118094bc36c6be5c576fd0999abdf
Reviewed-on: https://go-review.googlesource.com/6080
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/audio/al/const.go b/audio/al/const.go
index 6de4691..db4450d 100644
--- a/audio/al/const.go
+++ b/audio/al/const.go
@@ -4,6 +4,8 @@
 
 package al
 
+// +build darwin
+
 // Error returns one of these error codes.
 const (
 	InvalidName      = 0xA001
diff --git a/audio/audio.go b/audio/audio.go
index 904968c..1a58044 100644
--- a/audio/audio.go
+++ b/audio/audio.go
@@ -17,7 +17,7 @@
 	"golang.org/x/mobile/audio/alc"
 )
 
-// Format represents an audio file format.
+// Format represents an PCM data format.
 type Format string
 
 const (
@@ -28,10 +28,10 @@
 )
 
 var formatToCode = map[Format]int32{
-	Mono8:    0x1100,
-	Mono16:   0x1101,
-	Stereo8:  0x1102,
-	Stereo16: 0x1103,
+	Mono8:    al.FormatMono8,
+	Mono16:   al.FormatMono16,
+	Stereo8:  al.FormatStereo8,
+	Stereo16: al.FormatStereo16,
 }
 
 // State indicates the current playing state of the player.
@@ -46,11 +46,11 @@
 )
 
 var codeToState = map[int32]State{
-	0:      Unknown,
-	0x1011: Initial,
-	0x1012: Playing,
-	0x1013: Paused,
-	0x1014: Stopped,
+	0:          Unknown,
+	al.Initial: Initial,
+	al.Playing: Playing,
+	al.Paused:  Paused,
+	al.Stopped: Stopped,
 }
 
 var device struct {
@@ -59,9 +59,9 @@
 }
 
 type track struct {
-	format Format
-	rate   int64 // sample rate
-	src    io.ReadSeeker
+	format           Format
+	samplesPerSecond int64
+	src              io.ReadSeeker
 }
 
 // Player is a basic audio player that plays PCM data.
@@ -71,15 +71,15 @@
 	t      *track
 	source al.Source
 
-	muPrep sync.Mutex // guards prep and bufs
-	prep   bool
-	bufs   []al.Buffer
-	size   int64 // size of the audio source
+	mu        sync.Mutex
+	prep      bool
+	bufs      []al.Buffer // buffers are created and queued to source during prepare.
+	sizeBytes int64       // size of the audio source
 }
 
 // NewPlayer returns a new Player.
 // It initializes the underlying audio devices and the related resources.
-func NewPlayer(src io.ReadSeeker, format Format, sampleRate int64) (*Player, error) {
+func NewPlayer(src io.ReadSeeker, format Format, samplesPerSecond int64) (*Player, error) {
 	device.Lock()
 	defer device.Unlock()
 
@@ -87,38 +87,31 @@
 		device.d = alc.Open("")
 		c := device.d.CreateContext(nil)
 		if !alc.MakeContextCurrent(c) {
-			return nil, fmt.Errorf("player: cannot initiate a new player")
+			return nil, fmt.Errorf("audio: cannot initiate a new player")
 		}
 	}
 	s := al.GenSources(1)
 	if code := al.Error(); code != 0 {
-		return nil, fmt.Errorf("player: cannot generate an audio source [err=%x]", code)
-	}
-	bufs := al.GenBuffers(2)
-	if err := lastErr(); err != nil {
-		return nil, err
+		return nil, fmt.Errorf("audio: cannot generate an audio source [err=%x]", code)
 	}
 	return &Player{
-		t:      &track{format: format, src: src, rate: sampleRate},
+		t:      &track{format: format, src: src, samplesPerSecond: samplesPerSecond},
 		source: s[0],
-		bufs:   bufs,
 	}, nil
 }
 
 func (p *Player) prepare(offset int64, force bool) error {
-	p.muPrep.Lock()
-	defer p.muPrep.Unlock()
+	p.mu.Lock()
 	if !force && p.prep {
+		p.mu.Unlock()
 		return nil
 	}
-	if len(p.bufs) > 0 {
-		p.source.UnqueueBuffers(p.bufs)
-		al.DeleteBuffers(p.bufs)
-	}
+	p.mu.Unlock()
+
 	if _, err := p.t.src.Seek(offset, 0); err != nil {
 		return err
 	}
-	p.bufs = []al.Buffer{}
+	var bufs []al.Buffer
 	// TODO(jbd): Limit the number of buffers in use, unqueue and reuse
 	// the existing buffers as buffers are processed.
 	buf := make([]byte, 128*1024)
@@ -128,8 +121,8 @@
 		if n > 0 {
 			size += int64(n)
 			b := al.GenBuffers(1)
-			b[0].BufferData(uint32(formatToCode[p.t.format]), buf[:n], int32(p.t.rate))
-			p.bufs = append(p.bufs, b[0])
+			b[0].BufferData(uint32(formatToCode[p.t.format]), buf[:n], int32(p.t.samplesPerSecond))
+			bufs = append(bufs, b[0])
 		}
 		if err == io.EOF {
 			break
@@ -138,11 +131,19 @@
 			return err
 		}
 	}
-	p.size = size
+
+	p.mu.Lock()
 	if len(p.bufs) > 0 {
-		p.source.QueueBuffers(p.bufs)
+		p.source.UnqueueBuffers(p.bufs)
+		al.DeleteBuffers(p.bufs)
 	}
+	p.sizeBytes = size
+	p.bufs = bufs
 	p.prep = true
+	if len(bufs) > 0 {
+		p.source.QueueBuffers(bufs)
+	}
+	p.mu.Unlock()
 	return nil
 }
 
@@ -199,7 +200,7 @@
 // Current returns the current playback position of the audio that is being played.
 func (p *Player) Current() time.Duration {
 	if p == nil {
-		return time.Duration(0)
+		return 0
 	}
 	// TODO(jbd): Current never returns the Total when the playing is finished.
 	// OpenAL may be returning the last buffer's start point as an OffsetByte.
@@ -214,7 +215,7 @@
 	// Prepare is required to determine the length of the source.
 	// We need to read the entire source to calculate the length.
 	p.prepare(0, false)
-	return byteOffsetToDur(p.t, p.size)
+	return byteOffsetToDur(p.t, p.sizeBytes)
 }
 
 // Volume returns the current player volume. The range of the volume is [0, 1].
@@ -250,31 +251,28 @@
 	if p.source != 0 {
 		al.DeleteSources(p.source)
 	}
-	p.muPrep.Lock()
+	p.mu.Lock()
 	if len(p.bufs) > 0 {
 		al.DeleteBuffers(p.bufs)
 	}
-	p.muPrep.Unlock()
+	p.mu.Unlock()
 }
 
 func byteOffsetToDur(t *track, offset int64) time.Duration {
-	size := float64(offset)
+	samples := offset
 	if t.format == Mono16 || t.format == Stereo16 {
-		size /= 2
+		samples /= 2
 	}
 	if t.format == Stereo8 || t.format == Stereo16 {
-		size /= 2
+		samples /= 2
 	}
-	size /= float64(t.rate)
-	// Casting size back to int64. Work in milliseconds,
-	// so that size doesn't get rounded excessively.
-	return time.Duration(size*1000) * time.Duration(time.Millisecond)
+	return time.Duration(samples * int64(time.Second) / t.samplesPerSecond)
 }
 
 func durToByteOffset(t *track, dur time.Duration) int64 {
-	size := int64(dur/time.Second) * t.rate
-	// Each sample is represented by 16-bits. Move twice further.
+	size := t.samplesPerSecond * int64(dur) / int64(time.Second)
 	if t.format == Mono16 || t.format == Stereo16 {
+		// Each sample is represented by 16-bits. Move twice further.
 		size *= 2
 	}
 	if t.format == Stereo8 || t.format == Stereo16 {
diff --git a/audio/audio_test.go b/audio/audio_test.go
index a652b11..fd04217 100644
--- a/audio/audio_test.go
+++ b/audio/audio_test.go
@@ -22,19 +22,19 @@
 	if err := p.Stop(); err != nil {
 		t.Errorf("no-op player failed to stop: %v", err)
 	}
-	if c := p.Current(); c != time.Duration(0) {
+	if c := p.Current(); c != 0 {
 		t.Errorf("no-op player returns a non-zero playback position: %v", c)
 	}
-	if tot := p.Total(); tot != time.Duration(0) {
+	if tot := p.Total(); tot != 0 {
 		t.Errorf("no-op player returns a non-zero total: %v", tot)
 	}
 	if vol := p.Volume(); vol != 0 {
-		t.Errorf("no-op player returns a non-zero total: %v", vol)
+		t.Errorf("no-op player returns a non-zero volume: %v", vol)
 	}
 	if s := p.State(); s != Unknown {
 		t.Errorf("playing state: %v", s)
 	}
 	p.SetVolume(0.1)
-	p.Seek(time.Duration(100))
+	p.Seek(1 * time.Second)
 	p.Destroy()
 }