mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-11 19:48:54 +01:00
audio/internal/driver: Add driver_android.go (WIP)
This commit is contained in:
parent
64afa87def
commit
b65aeb7ac3
187
audio/internal/driver/driver_android.go
Normal file
187
audio/internal/driver/driver_android.go
Normal file
@ -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 <android/log.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// __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, "<init>", "(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
|
||||||
|
}
|
@ -12,7 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// +build android
|
// This is not used anywhere so far. I plan to use this for iOS.
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
package driver
|
package driver
|
||||||
|
|
||||||
|
102
audio/internal/driver/jni_android.go
Normal file
102
audio/internal/driver/jni_android.go
Normal file
@ -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 <jni.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user