From b65aeb7ac313a045c3dc79c15ffba1a4477f7d33 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Wed, 25 May 2016 02:02:58 +0900 Subject: [PATCH] audio/internal/driver: Add driver_android.go (WIP) --- audio/internal/driver/driver_android.go | 187 ++++++++++++++++++++++++ audio/internal/driver/driver_mobile.go | 3 +- audio/internal/driver/jni_android.go | 102 +++++++++++++ 3 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 audio/internal/driver/driver_android.go create mode 100644 audio/internal/driver/jni_android.go diff --git a/audio/internal/driver/driver_android.go b/audio/internal/driver/driver_android.go new file mode 100644 index 000000000..0dbe2eae7 --- /dev/null +++ b/audio/internal/driver/driver_android.go @@ -0,0 +1,187 @@ +// 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 + +/* + +#cgo LDFLAGS: -llog + +#include +#include +#include + +// __android_log_print(ANDROID_LOG_ERROR, "NativeCode", "foo", "bar"); + +static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject context, + int sampleRate, int channelNum, int bytesPerSample, jobject* audioTrack) { + JavaVM* vm = (JavaVM*)java_vm; + JNIEnv* env = (JNIEnv*)jni_env; + + const jclass android_media_AudioFormat = + (*env)->FindClass(env, "android/media/AudioFormat"); + const jclass android_media_AudioManager = + (*env)->FindClass(env, "android/media/AudioManager"); + const jclass android_media_AudioTrack = + (*env)->FindClass(env, "android/media/AudioTrack"); + + 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: + channel = android_media_AudioFormat_ENCODING_PCM_8BIT; + break; + case 2: + channel = android_media_AudioFormat_ENCODING_PCM_16BIT; + break; + default: + return "invalid bytesPerSample"; + } + + const jint 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)->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 context, + jobject audioTrack, void* data, int length) { + // TODO: audioTrack object is not valid here? + JavaVM* vm = (JavaVM*)java_vm; + JNIEnv* env = (JNIEnv*)jni_env; + + const jclass android_media_AudioTrack = + (*env)->FindClass(env, "android/media/AudioTrack"); + const jint android_media_AudioTrack_WRITE_NON_BLOCKING = + (*env)->GetStaticIntField( + env, android_media_AudioTrack, + (*env)->GetStaticFieldID(env, android_media_AudioTrack, "WRITE_NON_BLOCKING", "I")); + + jbyteArray arr = (*env)->NewByteArray(env, length); + (*env)->SetByteArrayRegion(env, arr, 0, length, data); + + jint result = + (*env)->CallIntMethod( + env, audioTrack, + (*env)->GetMethodID(env, android_media_AudioTrack, "write", "([BIII)I"), + arr, 0, length, android_media_AudioTrack_WRITE_NON_BLOCKING); + // TODO: Check the result. + return NULL; +} + +*/ +import "C" + +import ( + "errors" + "unsafe" +) + +type Player struct { + sampleRate int + channelNum int + bytesPerSample int + audioTrack C.jobject +} + +func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) { + p := &Player{ + sampleRate: sampleRate, + channelNum: channelNum, + bytesPerSample: bytesPerSample, + } + if err := runOnJVM(func(vm, env, ctx uintptr) error { + audioTrack := C.jobject(nil) + if msg := C.initAudioTrack(C.uintptr_t(vm), C.uintptr_t(env), C.jobject(ctx), + C.int(sampleRate), C.int(channelNum), C.int(bytesPerSample), + &audioTrack); msg != nil { + return errors.New(C.GoString(msg)) + } + p.audioTrack = audioTrack + return nil + }); err != nil { + return nil, err + } + return p, nil +} + +func (p *Player) Proceed(data []byte) error { + if err := runOnJVM(func(vm, env, ctx uintptr) error { + if msg := C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env), C.jobject(ctx), + p.audioTrack, unsafe.Pointer(&data[0]), C.int(len(data))); msg != nil { + return errors.New(C.GoString(msg)) + } + return nil + }); err != nil { + return err + } + return nil +} + +func (p *Player) Close() error { + return nil +} diff --git a/audio/internal/driver/driver_mobile.go b/audio/internal/driver/driver_mobile.go index f8d6ad0dc..f46c9b6f1 100644 --- a/audio/internal/driver/driver_mobile.go +++ b/audio/internal/driver/driver_mobile.go @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build android +// This is not used anywhere so far. I plan to use this for iOS. +// +build ignore package driver diff --git a/audio/internal/driver/jni_android.go b/audio/internal/driver/jni_android.go new file mode 100644 index 000000000..5f28113c1 --- /dev/null +++ b/audio/internal/driver/jni_android.go @@ -0,0 +1,102 @@ +// Copyright 2015 The Go Authors. All rights reserved. + +// This file is copied from golang.org/x/mobile/internal/mobileinit/ctx_android.go +// and editted. +// This file is licensed under the 3-clause BSD license. + +package driver + +/* +#include +#include + +JavaVM* current_vm; +jobject current_ctx; + +static char* lockJNI(uintptr_t* envp, int* attachedp) { + JNIEnv* env; + + if (current_vm == NULL) { + return "no current JVM"; + } + + *attachedp = 0; + switch ((*current_vm)->GetEnv(current_vm, (void**)&env, JNI_VERSION_1_6)) { + case JNI_OK: + break; + case JNI_EDETACHED: + if ((*current_vm)->AttachCurrentThread(current_vm, &env, 0) != 0) { + return "cannot attach to JVM"; + } + *attachedp = 1; + break; + case JNI_EVERSION: + return "bad JNI version"; + default: + return "unknown JNI error from GetEnv"; + } + + *envp = (uintptr_t)env; + return NULL; +} + +static char* checkException(uintptr_t jnienv) { + jthrowable exc; + JNIEnv* env = (JNIEnv*)jnienv; + + if (!(*env)->ExceptionCheck(env)) { + return NULL; + } + + exc = (*env)->ExceptionOccurred(env); + (*env)->ExceptionClear(env); + + jclass clazz = (*env)->FindClass(env, "java/lang/Throwable"); + jmethodID toString = (*env)->GetMethodID(env, clazz, "toString", "()Ljava/lang/String;"); + jobject msgStr = (*env)->CallObjectMethod(env, exc, toString); + return (char*)(*env)->GetStringUTFChars(env, msgStr, 0); +} + +static void unlockJNI() { + (*current_vm)->DetachCurrentThread(current_vm); +} +*/ +import "C" + +import ( + "errors" + "runtime" + "unsafe" +) + +func runOnJVM(fn func(vm, env, ctx uintptr) error) error { + errch := make(chan error) + go func() { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + env := C.uintptr_t(0) + attached := C.int(0) + if errStr := C.lockJNI(&env, &attached); errStr != nil { + errch <- errors.New(C.GoString(errStr)) + return + } + if attached != 0 { + defer C.unlockJNI() + } + + vm := uintptr(unsafe.Pointer(C.current_vm)) + if err := fn(vm, uintptr(env), uintptr(C.current_ctx)); err != nil { + errch <- err + return + } + + if exc := C.checkException(env); exc != nil { + errch <- errors.New(C.GoString(exc)) + C.free(unsafe.Pointer(exc)) + return + } + errch <- nil + }() + return <-errch +}