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

View File

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