mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
audio: Bug fix: Play sound correctly on Android
This commit is contained in:
parent
a2d7b438d4
commit
12d62621e9
@ -217,6 +217,7 @@ func (c *Context) Update() error {
|
|||||||
if n != len(buf) {
|
if n != len(buf) {
|
||||||
return c.driver.Close()
|
return c.driver.Close()
|
||||||
}
|
}
|
||||||
|
// TODO: Rename this to Enqueue
|
||||||
err = c.driver.Proceed(buf)
|
err = c.driver.Proceed(buf)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return c.driver.Close()
|
return c.driver.Close()
|
||||||
|
@ -25,7 +25,8 @@ package driver
|
|||||||
// __android_log_print(ANDROID_LOG_ERROR, "NativeCode", "foo", "bar");
|
// __android_log_print(ANDROID_LOG_ERROR, "NativeCode", "foo", "bar");
|
||||||
|
|
||||||
static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject context,
|
static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject context,
|
||||||
int sampleRate, int channelNum, int bytesPerSample, jobject* audioTrack) {
|
int sampleRate, int channelNum, int bytesPerSample, jobject* audioTrack, int* bufferSize) {
|
||||||
|
*bufferSize = 0;
|
||||||
JavaVM* vm = (JavaVM*)java_vm;
|
JavaVM* vm = (JavaVM*)java_vm;
|
||||||
JNIEnv* env = (JNIEnv*)jni_env;
|
JNIEnv* env = (JNIEnv*)jni_env;
|
||||||
|
|
||||||
@ -44,6 +45,10 @@ static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject contex
|
|||||||
(*env)->GetStaticIntField(
|
(*env)->GetStaticIntField(
|
||||||
env, android_media_AudioTrack,
|
env, android_media_AudioTrack,
|
||||||
(*env)->GetStaticFieldID(env, android_media_AudioTrack, "MODE_STREAM", "I"));
|
(*env)->GetStaticFieldID(env, android_media_AudioTrack, "MODE_STREAM", "I"));
|
||||||
|
const jint android_media_AudioTrack_WRITE_BLOCKING =
|
||||||
|
(*env)->GetStaticIntField(
|
||||||
|
env, android_media_AudioTrack,
|
||||||
|
(*env)->GetStaticFieldID(env, android_media_AudioTrack, "WRITE_BLOCKING", "I"));
|
||||||
const jint android_media_AudioFormat_CHANNEL_OUT_MONO =
|
const jint android_media_AudioFormat_CHANNEL_OUT_MONO =
|
||||||
(*env)->GetStaticIntField(
|
(*env)->GetStaticIntField(
|
||||||
env, android_media_AudioFormat,
|
env, android_media_AudioFormat,
|
||||||
@ -76,16 +81,16 @@ static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject contex
|
|||||||
jint encoding = android_media_AudioFormat_ENCODING_PCM_8BIT;
|
jint encoding = android_media_AudioFormat_ENCODING_PCM_8BIT;
|
||||||
switch (bytesPerSample) {
|
switch (bytesPerSample) {
|
||||||
case 1:
|
case 1:
|
||||||
channel = android_media_AudioFormat_ENCODING_PCM_8BIT;
|
encoding = android_media_AudioFormat_ENCODING_PCM_8BIT;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
channel = android_media_AudioFormat_ENCODING_PCM_16BIT;
|
encoding = android_media_AudioFormat_ENCODING_PCM_16BIT;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return "invalid bytesPerSample";
|
return "invalid bytesPerSample";
|
||||||
}
|
}
|
||||||
|
|
||||||
const jint bufferSize =
|
*bufferSize =
|
||||||
(*env)->CallStaticIntMethod(
|
(*env)->CallStaticIntMethod(
|
||||||
env, android_media_AudioTrack,
|
env, android_media_AudioTrack,
|
||||||
(*env)->GetStaticMethodID(env, android_media_AudioTrack, "getMinBufferSize", "(III)I"),
|
(*env)->GetStaticMethodID(env, android_media_AudioTrack, "getMinBufferSize", "(III)I"),
|
||||||
@ -96,11 +101,25 @@ static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject contex
|
|||||||
env, android_media_AudioTrack,
|
env, android_media_AudioTrack,
|
||||||
(*env)->GetMethodID(env, android_media_AudioTrack, "<init>", "(IIIIII)V"),
|
(*env)->GetMethodID(env, android_media_AudioTrack, "<init>", "(IIIIII)V"),
|
||||||
android_media_AudioManager_STREAM_MUSIC,
|
android_media_AudioManager_STREAM_MUSIC,
|
||||||
sampleRate, channel, encoding, bufferSize,
|
sampleRate, channel, encoding, *bufferSize,
|
||||||
android_media_AudioTrack_MODE_STREAM);
|
android_media_AudioTrack_MODE_STREAM);
|
||||||
// Note that *audioTrack will never be released.
|
// Note that *audioTrack will never be released.
|
||||||
*audioTrack = (*env)->NewGlobalRef(env, tmpAudioTrack);
|
*audioTrack = (*env)->NewGlobalRef(env, tmpAudioTrack);
|
||||||
|
|
||||||
|
// Enqueue empty bytes before playing to avoid underrunning.
|
||||||
|
// TODO: Is this really needed? At least, SDL doesn't do the same thing.
|
||||||
|
jint writtenBytes = 0;
|
||||||
|
do {
|
||||||
|
const int length = 1024;
|
||||||
|
jbyteArray arr = (*env)->NewByteArray(env, length);
|
||||||
|
writtenBytes =
|
||||||
|
(*env)->CallIntMethod(
|
||||||
|
env, *audioTrack,
|
||||||
|
(*env)->GetMethodID(env, android_media_AudioTrack, "write", "([BIII)I"),
|
||||||
|
arr, 0, length, android_media_AudioTrack_WRITE_BLOCKING);
|
||||||
|
} while (writtenBytes != 0);
|
||||||
|
// TODO: Check if writtenBytes < 0
|
||||||
|
|
||||||
(*env)->CallVoidMethod(
|
(*env)->CallVoidMethod(
|
||||||
env, *audioTrack,
|
env, *audioTrack,
|
||||||
(*env)->GetMethodID(env, android_media_AudioTrack, "play", "()V"));
|
(*env)->GetMethodID(env, android_media_AudioTrack, "play", "()V"));
|
||||||
@ -109,8 +128,7 @@ static char* initAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
static char* writeToAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject context,
|
static char* writeToAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject context,
|
||||||
jobject audioTrack, void* data, int length) {
|
jobject audioTrack, int bytesPerSample, void* data, int length) {
|
||||||
// TODO: audioTrack object is not valid here?
|
|
||||||
JavaVM* vm = (JavaVM*)java_vm;
|
JavaVM* vm = (JavaVM*)java_vm;
|
||||||
JNIEnv* env = (JNIEnv*)jni_env;
|
JNIEnv* env = (JNIEnv*)jni_env;
|
||||||
|
|
||||||
@ -121,14 +139,40 @@ static char* writeToAudioTrack(uintptr_t java_vm, uintptr_t jni_env, jobject con
|
|||||||
env, android_media_AudioTrack,
|
env, android_media_AudioTrack,
|
||||||
(*env)->GetStaticFieldID(env, android_media_AudioTrack, "WRITE_NON_BLOCKING", "I"));
|
(*env)->GetStaticFieldID(env, android_media_AudioTrack, "WRITE_NON_BLOCKING", "I"));
|
||||||
|
|
||||||
jbyteArray arr = (*env)->NewByteArray(env, length);
|
jbyteArray arrInBytes;
|
||||||
(*env)->SetByteArrayRegion(env, arr, 0, length, data);
|
jshortArray arrInShorts;
|
||||||
|
switch (bytesPerSample) {
|
||||||
jint result =
|
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;
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
for (i = 0; i < length;) {
|
||||||
|
jint result = 0;
|
||||||
|
switch (bytesPerSample) {
|
||||||
|
case 1:
|
||||||
|
result =
|
||||||
(*env)->CallIntMethod(
|
(*env)->CallIntMethod(
|
||||||
env, audioTrack,
|
env, audioTrack,
|
||||||
(*env)->GetMethodID(env, android_media_AudioTrack, "write", "([BIII)I"),
|
(*env)->GetMethodID(env, android_media_AudioTrack, "write", "([BIII)I"),
|
||||||
arr, 0, length, android_media_AudioTrack_WRITE_NON_BLOCKING);
|
arrInBytes, i, length - i, android_media_AudioTrack_WRITE_NON_BLOCKING);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
result =
|
||||||
|
(*env)->CallIntMethod(
|
||||||
|
env, audioTrack,
|
||||||
|
(*env)->GetMethodID(env, android_media_AudioTrack, "write", "([SIII)I"),
|
||||||
|
arrInShorts, i, length - i, android_media_AudioTrack_WRITE_NON_BLOCKING);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += result;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Check the result.
|
// TODO: Check the result.
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -138,6 +182,7 @@ import "C"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -146,6 +191,10 @@ type Player struct {
|
|||||||
channelNum int
|
channelNum int
|
||||||
bytesPerSample int
|
bytesPerSample int
|
||||||
audioTrack C.jobject
|
audioTrack C.jobject
|
||||||
|
buffer []byte
|
||||||
|
bufferSize int
|
||||||
|
m sync.Mutex
|
||||||
|
chErr chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) {
|
func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) {
|
||||||
@ -153,15 +202,19 @@ func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) {
|
|||||||
sampleRate: sampleRate,
|
sampleRate: sampleRate,
|
||||||
channelNum: channelNum,
|
channelNum: channelNum,
|
||||||
bytesPerSample: bytesPerSample,
|
bytesPerSample: bytesPerSample,
|
||||||
|
buffer: []byte{},
|
||||||
|
chErr: make(chan error),
|
||||||
}
|
}
|
||||||
if err := runOnJVM(func(vm, env, ctx uintptr) error {
|
if err := runOnJVM(func(vm, env, ctx uintptr) error {
|
||||||
audioTrack := C.jobject(nil)
|
audioTrack := C.jobject(nil)
|
||||||
|
bufferSize := C.int(0)
|
||||||
if msg := C.initAudioTrack(C.uintptr_t(vm), C.uintptr_t(env), C.jobject(ctx),
|
if msg := C.initAudioTrack(C.uintptr_t(vm), C.uintptr_t(env), C.jobject(ctx),
|
||||||
C.int(sampleRate), C.int(channelNum), C.int(bytesPerSample),
|
C.int(sampleRate), C.int(channelNum), C.int(bytesPerSample),
|
||||||
&audioTrack); msg != nil {
|
&audioTrack, &bufferSize); msg != nil {
|
||||||
return errors.New(C.GoString(msg))
|
return errors.New(C.GoString(msg))
|
||||||
}
|
}
|
||||||
p.audioTrack = audioTrack
|
p.audioTrack = audioTrack
|
||||||
|
p.bufferSize = int(bufferSize)
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -170,15 +223,47 @@ func NewPlayer(sampleRate, channelNum, bytesPerSample int) (*Player, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Proceed(data []byte) error {
|
func (p *Player) Proceed(data []byte) error {
|
||||||
|
select {
|
||||||
|
case err := <-p.chErr:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
p.buffer = append(p.buffer, data...)
|
||||||
|
if len(p.buffer) < p.bufferSize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bufInBytes := p.buffer[:p.bufferSize]
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.buffer = p.buffer[p.bufferSize:]
|
||||||
|
go func() {
|
||||||
|
p.m.Lock()
|
||||||
|
defer p.m.Unlock()
|
||||||
if err := runOnJVM(func(vm, env, ctx uintptr) 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),
|
msg := (*C.char)(nil)
|
||||||
p.audioTrack, unsafe.Pointer(&data[0]), C.int(len(data))); msg != nil {
|
switch p.bytesPerSample {
|
||||||
|
case 1:
|
||||||
|
msg = C.writeToAudioTrack(C.uintptr_t(vm), C.uintptr_t(env), C.jobject(ctx),
|
||||||
|
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), C.jobject(ctx),
|
||||||
|
p.audioTrack, C.int(p.bytesPerSample),
|
||||||
|
unsafe.Pointer(&bufInShorts[0]), C.int(len(bufInShorts)))
|
||||||
|
}
|
||||||
|
if msg != nil {
|
||||||
return errors.New(C.GoString(msg))
|
return errors.New(C.GoString(msg))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
p.chErr <- err
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user