From 766072cdbb44409f81d854db340d12c29af6238e Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Thu, 4 May 2017 21:09:02 +0900 Subject: [PATCH] audio: Make 'oto' package and use that (#351) --- audio/audio.go | 6 +- audio/internal/driver/driver_android.go | 269 ------------------------ audio/internal/driver/driver_ios.go | 141 ------------- audio/internal/driver/driver_js.go | 131 ------------ audio/internal/driver/driver_openal.go | 158 -------------- audio/internal/driver/driver_windows.go | 135 ------------ 6 files changed, 3 insertions(+), 837 deletions(-) delete mode 100644 audio/internal/driver/driver_android.go delete mode 100644 audio/internal/driver/driver_ios.go delete mode 100644 audio/internal/driver/driver_js.go delete mode 100644 audio/internal/driver/driver_openal.go delete mode 100644 audio/internal/driver/driver_windows.go diff --git a/audio/audio.go b/audio/audio.go index 57fea1538..0daa22b0c 100644 --- a/audio/audio.go +++ b/audio/audio.go @@ -35,7 +35,7 @@ import ( "time" "github.com/hajimehoshi/ebiten" - "github.com/hajimehoshi/ebiten/audio/internal/driver" + "github.com/hajimehoshi/oto" ) type players struct { @@ -188,7 +188,7 @@ func (p *players) hasSource(src ReadSeekCloser) bool { // In this case, audio goes on even when the game stops e.g. by diactivating the screen. type Context struct { players *players - driver *driver.Player + driver *oto.Player sampleRate int frames int64 writtenBytes int64 @@ -239,7 +239,7 @@ func (c *Context) Update() error { // e.g. a variable for JVM on Android might not be set. if c.driver == nil { // TODO: Rename this other than player - p, err := driver.NewPlayer(c.sampleRate, channelNum, bytesPerSample) + p, err := oto.NewPlayer(c.sampleRate, channelNum, bytesPerSample) c.driver = p if err != nil { return err diff --git a/audio/internal/driver/driver_android.go b/audio/internal/driver/driver_android.go deleted file mode 100644 index d6a567e94..000000000 --- a/audio/internal/driver/driver_android.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2016 Hajime Hoshi -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package driver - -/* - -#include -#include - -static jclass android_media_AudioFormat; -static jclass android_media_AudioManager; -static jclass android_media_AudioTrack; - -static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env, - int sampleRate, int channelNum, int bytesPerSample, jobject* audioTrack, int* bufferSize) { - *bufferSize = 0; - JavaVM* vm = (JavaVM*)java_vm; - JNIEnv* env = (JNIEnv*)jni_env; - - jclass local = (*env)->FindClass(env, "android/media/AudioFormat"); - android_media_AudioFormat = (*env)->NewGlobalRef(env, local); - (*env)->DeleteLocalRef(env, local); - - local = (*env)->FindClass(env, "android/media/AudioManager"); - android_media_AudioManager = (*env)->NewGlobalRef(env, local); - (*env)->DeleteLocalRef(env, local); - - local = (*env)->FindClass(env, "android/media/AudioTrack"); - android_media_AudioTrack = (*env)->NewGlobalRef(env, local); - (*env)->DeleteLocalRef(env, local); - - const jint android_media_AudioManager_STREAM_MUSIC = - (*env)->GetStaticIntField( - env, android_media_AudioManager, - (*env)->GetStaticFieldID(env, android_media_AudioManager, "STREAM_MUSIC", "I")); - const jint android_media_AudioTrack_MODE_STREAM = - (*env)->GetStaticIntField( - env, android_media_AudioTrack, - (*env)->GetStaticFieldID(env, android_media_AudioTrack, "MODE_STREAM", "I")); - const jint android_media_AudioFormat_CHANNEL_OUT_MONO = - (*env)->GetStaticIntField( - env, android_media_AudioFormat, - (*env)->GetStaticFieldID(env, android_media_AudioFormat, "CHANNEL_OUT_MONO", "I")); - const jint android_media_AudioFormat_CHANNEL_OUT_STEREO = - (*env)->GetStaticIntField( - env, android_media_AudioFormat, - (*env)->GetStaticFieldID(env, android_media_AudioFormat, "CHANNEL_OUT_STEREO", "I")); - const jint android_media_AudioFormat_ENCODING_PCM_8BIT = - (*env)->GetStaticIntField( - env, android_media_AudioFormat, - (*env)->GetStaticFieldID(env, android_media_AudioFormat, "ENCODING_PCM_8BIT", "I")); - const jint android_media_AudioFormat_ENCODING_PCM_16BIT = - (*env)->GetStaticIntField( - env, android_media_AudioFormat, - (*env)->GetStaticFieldID(env, android_media_AudioFormat, "ENCODING_PCM_16BIT", "I")); - - jint channel = android_media_AudioFormat_CHANNEL_OUT_MONO; - switch (channelNum) { - case 1: - channel = android_media_AudioFormat_CHANNEL_OUT_MONO; - break; - case 2: - channel = android_media_AudioFormat_CHANNEL_OUT_STEREO; - break; - default: - return "invalid channel"; - } - - jint encoding = android_media_AudioFormat_ENCODING_PCM_8BIT; - switch (bytesPerSample) { - case 1: - encoding = android_media_AudioFormat_ENCODING_PCM_8BIT; - break; - case 2: - encoding = android_media_AudioFormat_ENCODING_PCM_16BIT; - break; - default: - return "invalid bytesPerSample"; - } - - *bufferSize = - (*env)->CallStaticIntMethod( - env, android_media_AudioTrack, - (*env)->GetStaticMethodID(env, android_media_AudioTrack, "getMinBufferSize", "(III)I"), - sampleRate, channel, encoding); - - const jobject tmpAudioTrack = - (*env)->NewObject( - env, android_media_AudioTrack, - (*env)->GetMethodID(env, android_media_AudioTrack, "", "(IIIIII)V"), - android_media_AudioManager_STREAM_MUSIC, - sampleRate, channel, encoding, *bufferSize, - android_media_AudioTrack_MODE_STREAM); - // Note that *audioTrack will never be released. - *audioTrack = (*env)->NewGlobalRef(env, tmpAudioTrack); - (*env)->DeleteLocalRef(env, tmpAudioTrack); - - (*env)->CallVoidMethod( - env, *audioTrack, - (*env)->GetMethodID(env, android_media_AudioTrack, "play", "()V")); - - return NULL; -} - -static char* writeToAudioTrack(uintptr_t java_vm, uintptr_t jni_env, - jobject audioTrack, int bytesPerSample, void* data, int length) { - JavaVM* vm = (JavaVM*)java_vm; - JNIEnv* env = (JNIEnv*)jni_env; - - jbyteArray arrInBytes; - jshortArray arrInShorts; - switch (bytesPerSample) { - case 1: - arrInBytes = (*env)->NewByteArray(env, length); - (*env)->SetByteArrayRegion(env, arrInBytes, 0, length, data); - break; - case 2: - arrInShorts = (*env)->NewShortArray(env, length); - (*env)->SetShortArrayRegion(env, arrInShorts, 0, length, data); - break; - } - - jint result; - static jmethodID write1 = NULL; - static jmethodID write2 = NULL; - if (!write1) { - write1 = (*env)->GetMethodID(env, android_media_AudioTrack, "write", "([BII)I"); - } - if (!write2) { - write2 = (*env)->GetMethodID(env, android_media_AudioTrack, "write", "([SII)I"); - } - switch (bytesPerSample) { - case 1: - result = (*env)->CallIntMethod(env, audioTrack, write1, arrInBytes, 0, length); - (*env)->DeleteLocalRef(env, arrInBytes); - break; - case 2: - result = (*env)->CallIntMethod(env, audioTrack, write2, arrInShorts, 0, length); - (*env)->DeleteLocalRef(env, arrInShorts); - break; - } - - switch (result) { - case -3: // ERROR_INVALID_OPERATION - return "invalid operation"; - case -2: // ERROR_BAD_VALUE - return "bad value"; - case -1: // ERROR - return "error"; - } - if (result < 0) { - return "unknown error"; - } - return NULL; -} - -*/ -import "C" - -import ( - "errors" - "unsafe" - - "github.com/hajimehoshi/ebiten/internal/jni" -) - -type Player struct { - sampleRate int - channelNum int - bytesPerSample int - audioTrack C.jobject - buffer []byte - bufferSize int - chErr chan error - chBuffer chan []byte -} - -func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) { - p := &Player{ - sampleRate: sampleRate, - channelNum: channelNum, - bytesPerSample: bytesPerSample, - buffer: []byte{}, - chErr: make(chan error), - chBuffer: make(chan []byte, 8), - } - if err := jni.RunOnJVM(func(vm, env, ctx uintptr) error { - audioTrack := C.jobject(nil) - bufferSize := C.int(0) - if msg := C.initAudioTrack(C.uintptr_t(vm), C.uintptr_t(env), - C.int(sampleRate), C.int(channelNum), C.int(bytesPerSample), - &audioTrack, &bufferSize); msg != nil { - return errors.New("driver: " + C.GoString(msg)) - } - p.audioTrack = audioTrack - p.bufferSize = int(bufferSize) - return nil - }); err != nil { - return nil, err - } - go p.loop() - return p, nil -} - -func (p *Player) loop() { - for bufInBytes := range p.chBuffer { - var bufInShorts []int16 - if p.bytesPerSample == 2 { - bufInShorts = make([]int16, len(bufInBytes)/2) - for i := 0; i < len(bufInShorts); i++ { - bufInShorts[i] = int16(bufInBytes[2*i]) | (int16(bufInBytes[2*i+1]) << 8) - } - } - - if err := jni.RunOnJVM(func(vm, env, ctx uintptr) error { - msg := (*C.char)(nil) - switch p.bytesPerSample { - case 1: - msg = C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env), - p.audioTrack, C.int(p.bytesPerSample), - unsafe.Pointer(&bufInBytes[0]), C.int(len(bufInBytes))) - case 2: - msg = C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env), - p.audioTrack, C.int(p.bytesPerSample), - unsafe.Pointer(&bufInShorts[0]), C.int(len(bufInShorts))) - default: - panic("not reach") - } - if msg != nil { - return errors.New(C.GoString(msg)) - } - return nil - }); err != nil { - p.chErr <- err - return - } - } -} - -func (p *Player) Proceed(data []byte) error { - p.buffer = append(p.buffer, data...) - if len(p.buffer) < p.bufferSize { - return nil - } - buf := p.buffer[:p.bufferSize] - select { - case p.chBuffer <- buf: - case err := <-p.chErr: - return err - } - p.buffer = p.buffer[p.bufferSize:] - return nil -} - -func (p *Player) Close() error { - return nil -} diff --git a/audio/internal/driver/driver_ios.go b/audio/internal/driver/driver_ios.go deleted file mode 100644 index 9c665a6b7..000000000 --- a/audio/internal/driver/driver_ios.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2016 Hajime Hoshi -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// TODO: Can we unify this into driver_openal.go? - -// +build ios - -package driver - -import ( - "fmt" - "runtime" - - "golang.org/x/mobile/exp/audio/al" -) - -const ( - maxBufferNum = 8 -) - -type Player struct { - alSource al.Source - alBuffers []al.Buffer - sampleRate int - isClosed bool - alFormat uint32 -} - -func alFormat(channelNum, bytesPerSample int) uint32 { - switch { - case channelNum == 1 && bytesPerSample == 1: - return al.FormatMono8 - case channelNum == 1 && bytesPerSample == 2: - return al.FormatMono16 - case channelNum == 2 && bytesPerSample == 1: - return al.FormatStereo8 - case channelNum == 2 && bytesPerSample == 2: - return al.FormatStereo16 - } - panic(fmt.Sprintf("driver: invalid channel num (%d) or bytes per sample (%d)", channelNum, bytesPerSample)) -} - -func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) { - var p *Player - if err := al.OpenDevice(); err != nil { - return nil, fmt.Errorf("driver: OpenAL initialization failed: %v", err) - } - s := al.GenSources(1) - if e := al.Error(); e != 0 { - return nil, fmt.Errorf("driver: al.GenSources error: %d", e) - } - p = &Player{ - alSource: s[0], - alBuffers: []al.Buffer{}, - sampleRate: sampleRate, - alFormat: alFormat(channelNum, bytesPerSample), - } - runtime.SetFinalizer(p, (*Player).Close) - - bs := al.GenBuffers(maxBufferNum) - const bufferSize = 1024 - emptyBytes := make([]byte, bufferSize) - for _, b := range bs { - // Note that the third argument of only the first buffer is used. - b.BufferData(p.alFormat, emptyBytes, int32(p.sampleRate)) - p.alSource.QueueBuffers(b) - } - al.PlaySources(p.alSource) - return p, nil -} - -func (p *Player) Proceed(data []byte) error { - if err := al.Error(); err != 0 { - return fmt.Errorf("driver: before proceed: %d", err) - } - processedNum := p.alSource.BuffersProcessed() - if 0 < processedNum { - bufs := make([]al.Buffer, processedNum) - p.alSource.UnqueueBuffers(bufs...) - if err := al.Error(); err != 0 { - return fmt.Errorf("driver: Unqueue in process: %d", err) - } - p.alBuffers = append(p.alBuffers, bufs...) - } - - if len(p.alBuffers) == 0 { - // This can happen (#207) - return nil - } - buf := p.alBuffers[0] - p.alBuffers = p.alBuffers[1:] - buf.BufferData(p.alFormat, data, int32(p.sampleRate)) - p.alSource.QueueBuffers(buf) - if err := al.Error(); err != 0 { - return fmt.Errorf("driver: Queue in process: %d", err) - } - - if p.alSource.State() == al.Stopped || p.alSource.State() == al.Initial { - al.RewindSources(p.alSource) - al.PlaySources(p.alSource) - if err := al.Error(); err != 0 { - return fmt.Errorf("driver: PlaySource in process: %d", err) - } - } - - return nil -} - -func (p *Player) Close() error { - if err := al.Error(); err != 0 { - return fmt.Errorf("driver: error before closing: %d", err) - } - if p.isClosed { - return nil - } - var bs []al.Buffer - al.RewindSources(p.alSource) - al.StopSources(p.alSource) - if n := p.alSource.BuffersQueued(); 0 < n { - bs = make([]al.Buffer, n) - p.alSource.UnqueueBuffers(bs...) - p.alBuffers = append(p.alBuffers, bs...) - } - p.isClosed = true - if err := al.Error(); err != 0 { - return fmt.Errorf("driver: error after closing: %d", err) - } - runtime.SetFinalizer(p, nil) - return nil -} diff --git a/audio/internal/driver/driver_js.go b/audio/internal/driver/driver_js.go deleted file mode 100644 index 5333c8894..000000000 --- a/audio/internal/driver/driver_js.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2015 Hajime Hoshi -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build js - -package driver - -import ( - "errors" - "strings" - - "github.com/gopherjs/gopherjs/js" -) - -// positionDelay is buffer in sample numbers for p.positionInSamples. -// Without this, adjusting p.positionInSamples with the context's currenTime -// much more often especially on Safari, which is the cause of noise (#307). -const positionDelay = 256 - -type Player struct { - sampleRate int - channelNum int - bytesPerSample int - positionInSamples int64 - bufferedData []byte - context *js.Object -} - -func isIOS() bool { - ua := js.Global.Get("navigator").Get("userAgent").String() - if !strings.Contains(ua, "iPhone") { - return false - } - return true -} - -func isAndroidChrome() bool { - ua := js.Global.Get("navigator").Get("userAgent").String() - if !strings.Contains(ua, "Android") { - return false - } - if !strings.Contains(ua, "Chrome") { - return false - } - return true -} - -func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) { - class := js.Global.Get("AudioContext") - if class == js.Undefined { - class = js.Global.Get("webkitAudioContext") - } - if class == js.Undefined { - return nil, errors.New("driver: audio couldn't be initialized") - } - p := &Player{ - sampleRate: sampleRate, - channelNum: channelNum, - bytesPerSample: bytesPerSample, - bufferedData: []byte{}, - context: class.New(), - } - // iOS and Android Chrome requires touch event to use AudioContext. - if isIOS() || isAndroidChrome() { - js.Global.Get("document").Call("addEventListener", "touchend", func() { - // Resuming is necessary as of Chrome 55+ in some cases like different - // domain page in an iframe. - p.context.Call("resume") - p.context.Call("createBufferSource").Call("start", 0) - p.positionInSamples = int64(p.context.Get("currentTime").Float()*float64(p.sampleRate)) + positionDelay - }) - } - p.positionInSamples = int64(p.context.Get("currentTime").Float()*float64(p.sampleRate)) + positionDelay - return p, nil -} - -func toLR(data []byte) ([]int16, []int16) { - l := make([]int16, len(data)/4) - r := make([]int16, len(data)/4) - for i := 0; i < len(data)/4; i++ { - l[i] = int16(data[4*i]) | int16(data[4*i+1])<<8 - r[i] = int16(data[4*i+2]) | int16(data[4*i+3])<<8 - } - return l, r -} - -func (p *Player) Proceed(data []byte) error { - p.bufferedData = append(p.bufferedData, data...) - c := int64(p.context.Get("currentTime").Float() * float64(p.sampleRate)) - if p.positionInSamples+positionDelay < c { - p.positionInSamples = c - } - // Heuristic data size which doesn't cause too much noise and too much delay (#299) - dataSize := int(float64(p.sampleRate)/13.5) / 4 * 4 - for dataSize <= len(p.bufferedData) { - data := p.bufferedData[:dataSize] - size := len(data) / p.bytesPerSample / p.channelNum - // TODO: size must be const or you'll get noise (e.g. sample rate is 22050) - buf := p.context.Call("createBuffer", p.channelNum, size, p.sampleRate) - l := buf.Call("getChannelData", 0) - r := buf.Call("getChannelData", 1) - il, ir := toLR(data) - const max = 1 << 15 - for i := 0; i < len(il); i++ { - l.SetIndex(i, float64(il[i])/max) - r.SetIndex(i, float64(ir[i])/max) - } - s := p.context.Call("createBufferSource") - s.Set("buffer", buf) - s.Call("connect", p.context.Get("destination")) - s.Call("start", float64(p.positionInSamples+positionDelay)/float64(p.sampleRate)) - p.positionInSamples += int64(len(il)) - p.bufferedData = p.bufferedData[dataSize:] - } - return nil -} - -func (p *Player) Close() error { - return nil -} diff --git a/audio/internal/driver/driver_openal.go b/audio/internal/driver/driver_openal.go deleted file mode 100644 index 48b419c37..000000000 --- a/audio/internal/driver/driver_openal.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2015 Hajime Hoshi -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build darwin linux -// +build !js -// +build !android -// +build !ios - -package driver - -import ( - "fmt" - "runtime" - - "github.com/hajimehoshi/go-openal/openal" -) - -// As x/mobile/exp/audio/al is broken on Mac OS X (https://github.com/golang/go/issues/15075), -// let's use github.com/hajimehoshi/go-openal instead. - -const ( - maxBufferNum = 8 -) - -type Player struct { - alDevice *openal.Device - alSource openal.Source - alBuffers []openal.Buffer - sampleRate int - isClosed bool - alFormat openal.Format -} - -func alFormat(channelNum, bytesPerSample int) openal.Format { - switch { - case channelNum == 1 && bytesPerSample == 1: - return openal.FormatMono8 - case channelNum == 1 && bytesPerSample == 2: - return openal.FormatMono16 - case channelNum == 2 && bytesPerSample == 1: - return openal.FormatStereo8 - case channelNum == 2 && bytesPerSample == 2: - return openal.FormatStereo16 - } - panic(fmt.Sprintf("driver: invalid channel num (%d) or bytes per sample (%d)", channelNum, bytesPerSample)) -} - -func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) { - d := openal.OpenDevice("") - if d == nil { - return nil, fmt.Errorf("driver: OpenDevice must not return nil") - } - c := d.CreateContext() - if c == nil { - return nil, fmt.Errorf("driver: CreateContext must not return nil") - } - // Don't check openal.Err until making the current context is done. - // Linux might fail this check even though it succeeds (#204). - c.Activate() - if err := openal.Err(); err != nil { - return nil, fmt.Errorf("driver: Activate: %v", err) - } - s := openal.NewSource() - if err := openal.Err(); err != nil { - return nil, fmt.Errorf("driver: NewSource: %v", err) - } - p := &Player{ - alDevice: d, - alSource: s, - alBuffers: []openal.Buffer{}, - sampleRate: sampleRate, - alFormat: alFormat(channelNum, bytesPerSample), - } - runtime.SetFinalizer(p, (*Player).Close) - - bs := openal.NewBuffers(maxBufferNum) - const bufferSize = 1024 - emptyBytes := make([]byte, bufferSize) - for _, b := range bs { - // Note that the third argument of only the first buffer is used. - b.SetData(p.alFormat, emptyBytes, int32(p.sampleRate)) - p.alBuffers = append(p.alBuffers, b) - } - p.alSource.Play() - return p, nil -} - -func (p *Player) Proceed(data []byte) error { - if err := openal.Err(); err != nil { - return fmt.Errorf("driver: starting Proceed: %v", err) - } - processedNum := p.alSource.BuffersProcessed() - if 0 < processedNum { - bufs := make([]openal.Buffer, processedNum) - p.alSource.UnqueueBuffers(bufs) - if err := openal.Err(); err != nil { - return fmt.Errorf("driver: UnqueueBuffers: %v", err) - } - p.alBuffers = append(p.alBuffers, bufs...) - } - - if len(p.alBuffers) == 0 { - // This can happen (#207) - return nil - } - buf := p.alBuffers[0] - p.alBuffers = p.alBuffers[1:] - buf.SetData(p.alFormat, data, int32(p.sampleRate)) - p.alSource.QueueBuffer(buf) - if err := openal.Err(); err != nil { - return fmt.Errorf("driver: QueueBuffer: %v", err) - } - - if p.alSource.State() == openal.Stopped || p.alSource.State() == openal.Initial { - p.alSource.Rewind() - p.alSource.Play() - if err := openal.Err(); err != nil { - return fmt.Errorf("driver: Rewind or Play: %v", err) - } - } - - return nil -} - -func (p *Player) Close() error { - if err := openal.Err(); err != nil { - return fmt.Errorf("driver: starting Close: %v", err) - } - if p.isClosed { - return nil - } - var bs []openal.Buffer - p.alSource.Rewind() - p.alSource.Play() - if n := p.alSource.BuffersQueued(); 0 < n { - bs = make([]openal.Buffer, n) - p.alSource.UnqueueBuffers(bs) - p.alBuffers = append(p.alBuffers, bs...) - } - p.alDevice.CloseDevice() - p.isClosed = true - if err := openal.Err(); err != nil { - return fmt.Errorf("driver: CloseDevice: %v", err) - } - runtime.SetFinalizer(p, nil) - return nil -} diff --git a/audio/internal/driver/driver_windows.go b/audio/internal/driver/driver_windows.go deleted file mode 100644 index 1532f1314..000000000 --- a/audio/internal/driver/driver_windows.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2015 Hajime Hoshi -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build !js - -package driver - -// TODO: Use golang.org/x/sys/windows (NewLazyDLL) instead of cgo. - -// #cgo LDFLAGS: -lwinmm -// -// #include -// #include -// -// #define sizeOfWavehdr (sizeof(WAVEHDR)) -import "C" - -import ( - "errors" - "fmt" - "unsafe" -) - -type header struct { - buffer unsafe.Pointer - bufferSize int - waveHdr C.WAVEHDR -} - -func newHeader(waveOut C.HWAVEOUT, bufferSize int) (*header, error) { - // NOTE: This is never freed so far, and we don't have to because newHeader is called a certain number of times. - buf := C.malloc(C.size_t(bufferSize)) - h := &header{ - buffer: buf, - bufferSize: bufferSize, - waveHdr: C.WAVEHDR{ - lpData: C.LPSTR(buf), - dwBufferLength: C.DWORD(bufferSize), - }, - } - if err := C.waveOutPrepareHeader(waveOut, &h.waveHdr, C.sizeOfWavehdr); err != C.MMSYSERR_NOERROR { - return nil, fmt.Errorf("driver: waveOutPrepareHeader error: %d", err) - } - return h, nil -} - -func (h *header) Write(waveOut C.HWAVEOUT, data []byte) error { - if len(data) != h.bufferSize { - return errors.New("driver: len(data) must equal to h.bufferSize") - } - C.memcpy(h.buffer, unsafe.Pointer(&data[0]), C.size_t(h.bufferSize)) - if err := C.waveOutWrite(waveOut, &h.waveHdr, C.sizeOfWavehdr); err != C.MMSYSERR_NOERROR { - return fmt.Errorf("driver: waveOutWriter error: %d", err) - } - return nil -} - -const numHeader = 8 - -type Player struct { - out C.HWAVEOUT - buffer []byte - headers []*header -} - -const bufferSize = 4096 - -func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) { - numBlockAlign := channelNum * bytesPerSample - f := C.WAVEFORMATEX{ - wFormatTag: C.WAVE_FORMAT_PCM, - nChannels: C.WORD(channelNum), - nSamplesPerSec: C.DWORD(sampleRate), - nAvgBytesPerSec: C.DWORD(sampleRate * numBlockAlign), - wBitsPerSample: C.WORD(bytesPerSample * 8), - nBlockAlign: C.WORD(numBlockAlign), - } - var w C.HWAVEOUT - if err := C.waveOutOpen(&w, C.WAVE_MAPPER, &f, 0, 0, C.CALLBACK_NULL); err != C.MMSYSERR_NOERROR { - return nil, fmt.Errorf("driver: waveOutOpen error: %d", err) - } - p := &Player{ - out: w, - buffer: []byte{}, - headers: make([]*header, numHeader), - } - for i := 0; i < numHeader; i++ { - var err error - p.headers[i], err = newHeader(w, bufferSize) - if err != nil { - return nil, err - } - } - return p, nil -} - -func (p *Player) Proceed(data []byte) error { - p.buffer = append(p.buffer, data...) - if bufferSize > len(p.buffer) { - return nil - } - headerToWrite := (*header)(nil) - for _, h := range p.headers { - // TODO: Need to check WHDR_DONE? - if h.waveHdr.dwFlags&C.WHDR_INQUEUE == 0 { - headerToWrite = h - break - } - } - if headerToWrite == nil { - // This can happen (#207) - return nil - } - if err := headerToWrite.Write(p.out, p.buffer[:bufferSize]); err != nil { - return err - } - p.buffer = p.buffer[bufferSize:] - return nil -} - -func (p *Player) Close() error { - // TODO: Implement this - return nil -}