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:
parent
5cddc1460e
commit
a8aa7bbd72
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue