audio: have a global audio device and context

On mobile devices, there is typically only a single audio device. We may
open it once and reuse it to stream audio buffers from multiple sources.
This assumption also applies to the context since OpenAL contextes are
process-wide. A shared global pointer is reusable across multiple
OpenAL sources.

This CL also removes the Device and Context types and the unexpected
garbage collection issue introduced by Context's finalizer.

Fixes golang/go#10636

Change-Id: I82e6e6e6a1500ba91d66a7848cf37d1a5e01af9b
Reviewed-on: https://go-review.googlesource.com/9782
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
Burcu Dogan 2015-05-05 21:57:21 -04:00
parent 5cddc1460e
commit a8aa7bbd72
2 changed files with 49 additions and 57 deletions

View File

@ -7,62 +7,66 @@
package al package al
import ( import (
"runtime" "errors"
"sync"
"unsafe" "unsafe"
) )
// Device represents an audio device. var (
type Device struct { mu sync.Mutex // mu protects Device and context
ptr unsafe.Pointer
// device is the currently open audio device or nil.
device unsafe.Pointer
context unsafe.Pointer
)
// DeviceError returns the last known error from the current device.
func DeviceError() int32 {
return alcGetError(device)
} }
// Error returns the last known error from the current device. // TODO(jbd): Investigate the cases where multiple audio output
func (d *Device) Error() int32 { // devices might be needed.
return alcGetError(d.ptr)
}
// Context represents a context created in the OpenAL layer. A valid current // OpenDevice opens the default audio device.
// context is required to run OpenAL functions. func OpenDevice() error {
// The returned context will be available process-wide if it's made the mu.Lock()
// current by calling MakeContextCurrent. defer mu.Unlock()
type Context struct {
ptr unsafe.Pointer
}
// Open opens a new device in the OpenAL layer. // already opened
func Open(name string) *Device { if device != nil {
ptr := alcOpenDevice(name)
if ptr == nil {
return nil return nil
} }
return &Device{ptr: ptr}
}
// Close closes the device. ptr := alcOpenDevice("")
func (d *Device) Close() bool {
return alcCloseDevice(d.ptr)
}
// CreateContext creates a new context.
func (d *Device) CreateContext(attrs []int32) *Context {
ptr := alcCreateContext(d.ptr, attrs)
if ptr == nil { if ptr == nil {
return errors.New("al: cannot open the default audio device")
}
ctx := alcCreateContext(ptr, nil)
if ctx == nil {
alcCloseDevice(ptr)
return errors.New("al: cannot create a new context")
}
alcMakeContextCurrent(ctx)
device = ptr
context = ctx
return nil return nil
} }
c := &Context{ptr: ptr}
runtime.SetFinalizer(c, (*Context).Destroy) // CloseDevice closes the device and frees related resources.
return c func CloseDevice() {
mu.Lock()
defer mu.Unlock()
alcMakeContextCurrent(nil)
if context != nil {
alcDestroyContext(context)
context = nil
} }
// MakeContextCurrent makes a context current. The context available if device != nil {
// process-wide, you don't need to lock the current OS thread to alcCloseDevice(device)
// access the current context. device = nil
func MakeContextCurrent(c *Context) bool {
return alcMakeContextCurrent(c.ptr)
} }
// Destroy destroys the current context and frees the related resources.
func (c *Context) Destroy() {
alcDestroyContext(c.ptr)
runtime.SetFinalizer(c, nil)
} }

View File

@ -92,11 +92,6 @@ var codeToState = map[int32]State{
al.Stopped: Stopped, al.Stopped: Stopped,
} }
var device struct {
sync.Mutex
d *al.Device
}
type track struct { type track struct {
format Format format Format
samplesPerSecond int64 samplesPerSecond int64
@ -119,15 +114,8 @@ type Player struct {
// NewPlayer returns a new Player. // NewPlayer returns a new Player.
// It initializes the underlying audio devices and the related resources. // It initializes the underlying audio devices and the related resources.
func NewPlayer(src io.ReadSeeker, format Format, samplesPerSecond int64) (*Player, error) { func NewPlayer(src io.ReadSeeker, format Format, samplesPerSecond int64) (*Player, error) {
device.Lock() if err := al.OpenDevice(); err != nil {
defer device.Unlock() return nil, err
if device.d == nil {
device.d = al.Open("")
c := device.d.CreateContext(nil)
if !al.MakeContextCurrent(c) {
return nil, fmt.Errorf("audio: cannot initiate a new player")
}
} }
s := al.GenSources(1) s := al.GenSources(1)
if code := al.Error(); code != 0 { if code := al.Error(); code != 0 {