exp/audio: remove the high-level player

Since nothing about this high-level player will stay the same after
the audio work core types are finalized, there is no good point in
keeping the naive implementation around.

Removing also the audio example.

Updates golang/go#9551.

Change-Id: I5a7666c77e043aeacf44356e20e8d90822fd78e7
Reviewed-on: https://go-review.googlesource.com/27671
Reviewed-by: David Crawshaw <crawshaw@golang.org>
Run-TryBot: Jaana Burcu Dogan <jbd@google.com>
This commit is contained in:
Jaana Burcu Dogan 2016-08-24 12:48:30 -07:00
parent ed036a869f
commit 7573efae75
6 changed files with 0 additions and 644 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

@ -1,206 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin linux
// An app that makes a sound as the gopher hits the walls of the screen.
//
// Note: This demo is an early preview of Go 1.5. In order to build this
// program as an Android APK using the gomobile tool.
//
// See http://godoc.org/golang.org/x/mobile/cmd/gomobile to install gomobile.
//
// Get the audio example and use gomobile to build or install it on your device.
//
// $ go get -d golang.org/x/mobile/example/audio
// $ gomobile build golang.org/x/mobile/example/audio # will build an APK
//
// # plug your Android device to your computer or start an Android emulator.
// # if you have adb installed on your machine, use gomobile install to
// # build and deploy the APK to an Android target.
// $ gomobile install golang.org/x/mobile/example/audio
//
// Additionally, you can run the sample on your desktop environment
// by using the go tool.
//
// $ go install golang.org/x/mobile/example/audio && audio
//
// On Linux, you need to install OpenAL developer library by
// running the command below.
//
// $ apt-get install libopenal-dev
package main
import (
"image"
"log"
"time"
_ "image/jpeg"
"golang.org/x/mobile/app"
"golang.org/x/mobile/asset"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/audio"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/exp/gl/glutil"
"golang.org/x/mobile/exp/sprite"
"golang.org/x/mobile/exp/sprite/clock"
"golang.org/x/mobile/exp/sprite/glsprite"
"golang.org/x/mobile/gl"
)
const (
width = 72
height = 60
)
var (
startTime = time.Now()
images *glutil.Images
eng sprite.Engine
scene *sprite.Node
player *audio.Player
sz size.Event
)
func main() {
app.Main(func(a app.App) {
var glctx gl.Context
for e := range a.Events() {
switch e := a.Filter(e).(type) {
case lifecycle.Event:
switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn:
glctx, _ = e.DrawContext.(gl.Context)
onStart(glctx)
a.Send(paint.Event{})
case lifecycle.CrossOff:
onStop()
glctx = nil
}
case size.Event:
sz = e
case paint.Event:
if glctx == nil || e.External {
continue
}
onPaint(glctx)
a.Publish()
a.Send(paint.Event{}) // keep animating
}
}
})
}
func onStart(glctx gl.Context) {
images = glutil.NewImages(glctx)
eng = glsprite.Engine(images)
loadScene()
rc, err := asset.Open("boing.wav")
if err != nil {
log.Fatal(err)
}
player, err = audio.NewPlayer(rc, 0, 0)
if err != nil {
log.Fatal(err)
}
}
func onStop() {
eng.Release()
images.Release()
player.Close()
}
func onPaint(glctx gl.Context) {
glctx.ClearColor(1, 1, 1, 1)
glctx.Clear(gl.COLOR_BUFFER_BIT)
now := clock.Time(time.Since(startTime) * 60 / time.Second)
eng.Render(scene, now, sz)
}
func newNode() *sprite.Node {
n := &sprite.Node{}
eng.Register(n)
scene.AppendChild(n)
return n
}
func loadScene() {
gopher := loadGopher()
scene = &sprite.Node{}
eng.Register(scene)
eng.SetTransform(scene, f32.Affine{
{1, 0, 0},
{0, 1, 0},
})
var x, y float32
dx, dy := float32(1), float32(1)
n := newNode()
// TODO: Shouldn't arranger pass the size.Event?
n.Arranger = arrangerFunc(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
eng.SetSubTex(n, gopher)
if x < 0 {
dx = 1
boing()
}
if y < 0 {
dy = 1
boing()
}
if x+width > float32(sz.WidthPt) {
dx = -1
boing()
}
if y+height > float32(sz.HeightPt) {
dy = -1
boing()
}
x += dx
y += dy
eng.SetTransform(n, f32.Affine{
{width, 0, x},
{0, height, y},
})
})
}
func boing() {
player.Seek(0)
player.Play()
}
func loadGopher() sprite.SubTex {
a, err := asset.Open("gopher.jpeg")
if err != nil {
log.Fatal(err)
}
defer a.Close()
img, _, err := image.Decode(a)
if err != nil {
log.Fatal(err)
}
t, err := eng.LoadTexture(img)
if err != nil {
log.Fatal(err)
}
return sprite.SubTex{t, image.Rect(0, 0, 360, 300)}
}
type arrangerFunc func(e sprite.Engine, n *sprite.Node, t clock.Time)
func (a arrangerFunc) Arrange(e sprite.Engine, n *sprite.Node, t clock.Time) { a(e, n, t) }

View File

@ -1,10 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !darwin,!linux
package main
func main() {
}

View File

@ -1,388 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin linux
// Package audio provides a basic audio player.
//
// In order to use this package on Linux desktop distros,
// you will need OpenAL library as an external dependency.
// On Ubuntu 14.04 'Trusty', you may have to install this library
// by running the command below.
//
// sudo apt-get install libopenal-dev
//
// When compiled for Android, this package uses OpenAL Soft as a backend.
// Please add its license file to the open source notices of your
// application.
// OpenAL Soft's license file could be found at
// http://repo.or.cz/w/openal-soft.git/blob/HEAD:/COPYING.
package audio // import "golang.org/x/mobile/exp/audio"
import (
"bytes"
"errors"
"fmt"
"io"
"sync"
"time"
"golang.org/x/mobile/exp/audio/al"
)
// ReadSeekCloser is an io.ReadSeeker and io.Closer.
type ReadSeekCloser interface {
io.ReadSeeker
io.Closer
}
// Format represents a PCM data format.
type Format int
const (
Mono8 Format = iota + 1
Mono16
Stereo8
Stereo16
)
func (f Format) String() string { return formatStrings[f] }
// formatBytes is the product of bytes per sample and number of channels.
var formatBytes = [...]int64{
Mono8: 1,
Mono16: 2,
Stereo8: 2,
Stereo16: 4,
}
var formatCodes = [...]uint32{
Mono8: al.FormatMono8,
Mono16: al.FormatMono16,
Stereo8: al.FormatStereo8,
Stereo16: al.FormatStereo16,
}
var formatStrings = [...]string{
0: "unknown",
Mono8: "mono8",
Mono16: "mono16",
Stereo8: "stereo8",
Stereo16: "stereo16",
}
// State indicates the current playing state of the player.
type State int
const (
Unknown State = iota
Initial
Playing
Paused
Stopped
)
func (s State) String() string { return stateStrings[s] }
var stateStrings = [...]string{
Unknown: "unknown",
Initial: "initial",
Playing: "playing",
Paused: "paused",
Stopped: "stopped",
}
var codeToState = map[int32]State{
0: Unknown,
al.Initial: Initial,
al.Playing: Playing,
al.Paused: Paused,
al.Stopped: Stopped,
}
type track struct {
format Format
samplesPerSecond int64
src ReadSeekCloser
// hasHeader represents whether the audio source contains
// a PCM header. If true, the audio data starts 44 bytes
// later in the source.
hasHeader bool
}
// Player is a basic audio player that plays PCM data.
// Operations on a nil *Player are no-op, a nil *Player can
// be used for testing purposes.
type Player struct {
t *track
source al.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.
// If zero values are provided for format and sample rate values, the player
// determines them from the source's WAV header.
// An error is returned if the format and sample rate can't be determined.
//
// The audio package is only designed for small audio sources.
func NewPlayer(src ReadSeekCloser, format Format, samplesPerSecond int64) (*Player, error) {
if err := al.OpenDevice(); err != nil {
return nil, err
}
s := al.GenSources(1)
if code := al.Error(); code != 0 {
return nil, fmt.Errorf("audio: cannot generate an audio source [err=%x]", code)
}
p := &Player{
t: &track{format: format, src: src, samplesPerSecond: samplesPerSecond},
source: s[0],
}
if err := p.discoverHeader(); err != nil {
return nil, err
}
if p.t.format == 0 {
return nil, errors.New("audio: cannot determine the format")
}
if p.t.samplesPerSecond == 0 {
return nil, errors.New("audio: cannot determine the sample rate")
}
return p, nil
}
// headerSize is the size of WAV headers.
// See http://www.topherlee.com/software/pcm-tut-wavformat.html.
const headerSize = 44
var (
riffHeader = []byte("RIFF")
waveHeader = []byte("WAVE")
)
func (p *Player) discoverHeader() error {
buf := make([]byte, headerSize)
if n, _ := io.ReadFull(p.t.src, buf); n != headerSize {
// No header present or read error.
return nil
}
if !(bytes.Equal(buf[0:4], riffHeader) && bytes.Equal(buf[8:12], waveHeader)) {
return nil
}
p.t.hasHeader = true
var format Format
switch channels, depth := buf[22], buf[34]; {
case channels == 1 && depth == 8:
format = Mono8
case channels == 1 && depth == 16:
format = Mono16
case channels == 2 && depth == 8:
format = Stereo8
case channels == 2 && depth == 16:
format = Stereo16
default:
return fmt.Errorf("audio: unsupported format; num of channels=%d, bit rate=%d", channels, depth)
}
if p.t.format == 0 {
p.t.format = format
}
if p.t.format != format {
return fmt.Errorf("audio: given format %v does not match header %v", p.t.format, format)
}
sampleRate := int64(buf[24]) | int64(buf[25])<<8 | int64(buf[26])<<16 | int64(buf[27]<<24)
if p.t.samplesPerSecond == 0 {
p.t.samplesPerSecond = sampleRate
}
if p.t.samplesPerSecond != sampleRate {
return fmt.Errorf("audio: given sample rate %v does not match header", p.t.samplesPerSecond, sampleRate)
}
return nil
}
func (p *Player) prepare(offset int64, force bool) error {
p.mu.Lock()
if !force && p.prep {
p.mu.Unlock()
return nil
}
p.mu.Unlock()
if p.t.hasHeader {
offset += headerSize
}
if _, err := p.t.src.Seek(offset, 0); err != nil {
return err
}
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)
size := offset
for {
n, err := p.t.src.Read(buf)
if n > 0 {
size += int64(n)
b := al.GenBuffers(1)
b[0].BufferData(formatCodes[p.t.format], buf[:n], int32(p.t.samplesPerSecond))
bufs = append(bufs, b[0])
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}
p.mu.Lock()
if len(p.bufs) > 0 {
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
}
// Play buffers the source audio to the audio device and starts
// to play the source.
// If the player paused or stopped, it reuses the previously buffered
// resources to keep playing from the time it has paused or stopped.
func (p *Player) Play() error {
if p == nil {
return nil
}
// Prepares if the track hasn't been buffered before.
if err := p.prepare(0, false); err != nil {
return err
}
al.PlaySources(p.source)
return lastErr()
}
// Pause pauses the player.
func (p *Player) Pause() error {
if p == nil {
return nil
}
al.PauseSources(p.source)
return lastErr()
}
// Stop stops the player.
func (p *Player) Stop() error {
if p == nil {
return nil
}
al.StopSources(p.source)
return lastErr()
}
// Seek moves the play head to the given offset relative to the start of the source.
func (p *Player) Seek(offset time.Duration) error {
if p == nil {
return nil
}
if err := p.Stop(); err != nil {
return err
}
size := durToByteOffset(p.t, offset)
if err := p.prepare(size, true); err != nil {
return err
}
al.PlaySources(p.source)
return lastErr()
}
// Current returns the current playback position of the audio that is being played.
func (p *Player) Current() time.Duration {
if p == nil {
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.
return byteOffsetToDur(p.t, int64(p.source.OffsetByte()))
}
// Total returns the total duration of the audio source.
func (p *Player) Total() time.Duration {
if p == nil {
return 0
}
// 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.sizeBytes)
}
// Volume returns the current player volume. The range of the volume is [0, 1].
func (p *Player) Volume() float64 {
if p == nil {
return 0
}
return float64(p.source.Gain())
}
// SetVolume sets the volume of the player. The range of the volume is [0, 1].
func (p *Player) SetVolume(vol float64) {
if p == nil {
return
}
p.source.SetGain(float32(vol))
}
// State returns the player's current state.
func (p *Player) State() State {
if p == nil {
return Unknown
}
return codeToState[p.source.State()]
}
// Close closes the device and frees the underlying resources
// used by the player.
// It should be called as soon as the player is not in-use anymore.
func (p *Player) Close() error {
if p == nil {
return nil
}
if p.source != 0 {
al.DeleteSources(p.source)
}
p.mu.Lock()
if len(p.bufs) > 0 {
al.DeleteBuffers(p.bufs...)
}
p.mu.Unlock()
p.t.src.Close()
return nil
}
func byteOffsetToDur(t *track, offset int64) time.Duration {
return time.Duration(offset * formatBytes[t.format] * int64(time.Second) / t.samplesPerSecond)
}
func durToByteOffset(t *track, dur time.Duration) int64 {
return int64(dur) * t.samplesPerSecond / (formatBytes[t.format] * int64(time.Second))
}
// lastErr returns the last error or nil if the last operation
// has been succesful.
func lastErr() error {
if code := al.Error(); code != 0 {
return fmt.Errorf("audio: openal failed with %x", code)
}
return nil
}
// TODO(jbd): Close the device.

View File

@ -1,40 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin linux,!android
package audio
import (
"testing"
"time"
)
func TestNoOp(t *testing.T) {
var p *Player
if err := p.Play(); err != nil {
t.Errorf("no-op player failed to play: %v", err)
}
if err := p.Pause(); err != nil {
t.Errorf("no-op player failed to pause: %v", err)
}
if err := p.Stop(); err != nil {
t.Errorf("no-op player failed to stop: %v", err)
}
if c := p.Current(); c != 0 {
t.Errorf("no-op player returns a non-zero playback position: %v", c)
}
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 volume: %v", vol)
}
if s := p.State(); s != Unknown {
t.Errorf("playing state: %v", s)
}
p.SetVolume(0.1)
p.Seek(1 * time.Second)
p.Close()
}