diff options
Diffstat (limited to 'backends')
-rw-r--r-- | backends/platform/android/android.cpp | 87 | ||||
-rw-r--r-- | backends/platform/android/android.h | 10 | ||||
-rw-r--r-- | backends/platform/android/jni.cpp | 111 | ||||
-rw-r--r-- | backends/platform/android/jni.h | 23 | ||||
-rw-r--r-- | backends/platform/android/org/inodes/gus/scummvm/ScummVM.java | 168 | ||||
-rw-r--r-- | backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java | 12 |
6 files changed, 231 insertions, 180 deletions
diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index 4968e4bba1..08b957999e 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -26,6 +26,7 @@ #if defined(__ANDROID__) #include <sys/time.h> +#include <sys/resource.h> #include <time.h> #include <unistd.h> @@ -98,7 +99,9 @@ static inline T scalef(T in, float numerator, float denominator) { return static_cast<float>(in) * numerator / denominator; } -OSystem_Android::OSystem_Android() : +OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) : + _audio_sample_rate(audio_sample_rate), + _audio_buffer_size(audio_buffer_size), _screen_changeid(0), _force_redraw(false), _game_texture(0), @@ -137,6 +140,10 @@ void *OSystem_Android::timerThreadFunc(void *arg) { OSystem_Android *system = (OSystem_Android *)arg; DefaultTimerManager *timer = (DefaultTimerManager *)(system->_timer); + // renice this thread to boost the audio thread + if (setpriority(PRIO_PROCESS, 0, 19) < 0) + warning("couldn't renice the timer thread"); + JNI::attachThread(); struct timespec tv; @@ -153,6 +160,72 @@ void *OSystem_Android::timerThreadFunc(void *arg) { return 0; } +void *OSystem_Android::audioThreadFunc(void *arg) { + JNI::attachThread(); + + JNI::setAudioPlay(); + + OSystem_Android *system = (OSystem_Android *)arg; + Audio::MixerImpl *mixer = system->_mixer; + + uint buf_size = system->_audio_buffer_size; + + JNIEnv *env = JNI::getEnv(); + + jbyteArray bufa = env->NewByteArray(buf_size); + + byte *buf; + int offset, left, written; + + struct timespec tv; + tv.tv_sec = 0; + tv.tv_nsec = 20 * 1000 * 1000; + + while (!system->_audio_thread_exit) { + buf = (byte *)env->GetPrimitiveArrayCritical(bufa, 0); + assert(buf); + + mixer->mixCallback(buf, buf_size); + + env->ReleasePrimitiveArrayCritical(bufa, buf, 0); + + offset = 0; + left = buf_size; + written = 0; + + while (left > 0) { + written = JNI::writeAudio(env, bufa, offset, left); + + if (written < 0) { + error("AudioTrack error: %d", written); + break; + } + + // buffer full + if (written < left) + nanosleep(&tv, 0); + + offset += written; + left -= written; + } + + if (written < 0) + break; + + // sleep a little, prepare the next buffer, and run into the + // blocking AudioTrack.write + nanosleep(&tv, 0); + } + + JNI::setAudioStop(); + + env->DeleteLocalRef(bufa); + + JNI::detachThread(); + + return 0; +} + void OSystem_Android::initBackend() { ENTER(); @@ -173,7 +246,7 @@ void OSystem_Android::initBackend() { gettimeofday(&_startTime, 0); - _mixer = new Audio::MixerImpl(this, JNI::getAudioSampleRate()); + _mixer = new Audio::MixerImpl(this, _audio_sample_rate); _mixer->setReady(true); JNI::initBackend(); @@ -181,9 +254,16 @@ void OSystem_Android::initBackend() { _timer_thread_exit = false; pthread_create(&_timer_thread, 0, timerThreadFunc, this); + _audio_thread_exit = false; + pthread_create(&_audio_thread, 0, audioThreadFunc, this); + OSystem::initBackend(); setupSurface(); + + // renice this thread to boost the audio thread + if (setpriority(PRIO_PROCESS, 0, 19) < 0) + warning("couldn't renice the main thread"); } void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const { @@ -391,6 +471,9 @@ void OSystem_Android::deleteMutex(MutexRef mutex) { void OSystem_Android::quit() { ENTER(); + _audio_thread_exit = true; + pthread_join(_audio_thread, 0); + _timer_thread_exit = true; pthread_join(_timer_thread, 0); } diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h index 23c1e85a43..f4e9d611d4 100644 --- a/backends/platform/android/android.h +++ b/backends/platform/android/android.h @@ -92,6 +92,10 @@ protected: class OSystem_Android : public BaseBackend, public PaletteManager { private: + // passed from the dark side + int _audio_sample_rate; + int _audio_buffer_size; + int _screen_changeid; int _egl_surface_width; int _egl_surface_height; @@ -123,6 +127,10 @@ private: pthread_t _timer_thread; static void *timerThreadFunc(void *arg); + bool _audio_thread_exit; + pthread_t _audio_thread; + static void *audioThreadFunc(void *arg); + bool _enable_zoning; bool _virtkeybd_on; @@ -137,7 +145,7 @@ private: void _setCursorPalette(const byte *colors, uint start, uint num); public: - OSystem_Android(); + OSystem_Android(int audio_sample_rate, int audio_buffer_size); virtual ~OSystem_Android(); virtual void initBackend(); diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp index f24a60d48d..5e11203db8 100644 --- a/backends/platform/android/jni.cpp +++ b/backends/platform/android/jni.cpp @@ -39,6 +39,7 @@ jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { JavaVM *JNI::_vm = 0; jobject JNI::_jobj = 0; +jobject JNI::_jobj_audio_track = 0; Common::Archive *JNI::_asset_archive = 0; OSystem_Android *JNI::_system = 0; @@ -56,7 +57,6 @@ jfieldID JNI::_FID_ScummVM_nativeScummVM = 0; jmethodID JNI::_MID_displayMessageOnOSD = 0; jmethodID JNI::_MID_setWindowCaption = 0; jmethodID JNI::_MID_initBackend = 0; -jmethodID JNI::_MID_audioSampleRate = 0; jmethodID JNI::_MID_showVirtualKeyboard = 0; jmethodID JNI::_MID_getSysArchives = 0; jmethodID JNI::_MID_getPluginDirectories = 0; @@ -64,8 +64,14 @@ jmethodID JNI::_MID_setupScummVMSurface = 0; jmethodID JNI::_MID_destroyScummVMSurface = 0; jmethodID JNI::_MID_swapBuffers = 0; +jmethodID JNI::_MID_AudioTrack_pause = 0; +jmethodID JNI::_MID_AudioTrack_play = 0; +jmethodID JNI::_MID_AudioTrack_stop = 0; +jmethodID JNI::_MID_AudioTrack_write = 0; + const JNINativeMethod JNI::_natives[] = { - { "create", "(Landroid/content/res/AssetManager;)V", + { "create", "(Landroid/content/res/AssetManager;" + "Landroid/media/AudioTrack;II)V", (void *)JNI::create }, { "nativeDestroy", "()V", (void *)JNI::destroy }, @@ -73,8 +79,6 @@ const JNINativeMethod JNI::_natives[] = { (void *)JNI::main }, { "pushEvent", "(Lorg/inodes/gus/scummvm/Event;)V", (void *)JNI::pushEvent }, - { "audioMixCallback", "([B)V", - (void *)JNI::audioMixCallback }, { "setConfMan", "(Ljava/lang/String;I)V", (void *)JNI::setConfManInt }, { "setConfMan", "(Ljava/lang/String;Ljava/lang/String;)V", @@ -194,23 +198,6 @@ void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) { // calls to the dark side -int JNI::getAudioSampleRate() { - JNIEnv *env = JNI::getEnv(); - - jint sample_rate = env->CallIntMethod(_jobj, _MID_audioSampleRate); - - if (env->ExceptionCheck()) { - warning("Error finding audio sample rate - assuming 11025HZ"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - return 11025; - } - - return sample_rate; -} - void JNI::initBackend() { JNIEnv *env = JNI::getEnv(); @@ -342,15 +329,55 @@ void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { } } +void JNI::setAudioPause() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_pause); + + if (env->ExceptionCheck()) { + warning("Error setting AudioTrack: pause"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::setAudioPlay() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_play); + + if (env->ExceptionCheck()) { + warning("Error setting AudioTrack: play"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::setAudioStop() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj_audio_track, _MID_AudioTrack_stop); + + if (env->ExceptionCheck()) { + warning("Error setting AudioTrack: stop"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + // natives for the dark side -void JNI::create(JNIEnv *env, jobject self, jobject am) { +void JNI::create(JNIEnv *env, jobject self, jobject am, jobject at, + jint audio_sample_rate, jint audio_buffer_size) { assert(!_system); _asset_archive = new AndroidAssetArchive(am); assert(_asset_archive); - _system = new OSystem_Android(); + _system = new OSystem_Android(audio_sample_rate, audio_buffer_size); assert(_system); // weak global ref to allow class to be unloaded @@ -369,7 +396,6 @@ void JNI::create(JNIEnv *env, jobject self, jobject am) { FIND_METHOD(setWindowCaption, "(Ljava/lang/String;)V"); FIND_METHOD(displayMessageOnOSD, "(Ljava/lang/String;)V"); FIND_METHOD(initBackend, "()V"); - FIND_METHOD(audioSampleRate, "()I"); FIND_METHOD(showVirtualKeyboard, "(Z)V"); FIND_METHOD(getSysArchives, "()[Ljava/lang/String;"); FIND_METHOD(getPluginDirectories, "()[Ljava/lang/String;"); @@ -381,6 +407,23 @@ void JNI::create(JNIEnv *env, jobject self, jobject am) { env->SetLongField(self, _FID_ScummVM_nativeScummVM, (jlong)_system); + _jobj_audio_track = env->NewGlobalRef(at); + + cls = env->GetObjectClass(_jobj_audio_track); + +#define FIND_METHOD(name, signature) do { \ + _MID_AudioTrack_ ## name = env->GetMethodID(cls, #name, signature); \ + if (_MID_AudioTrack_ ## name == 0) \ + return; \ + } while (0) + + FIND_METHOD(pause, "()V"); + FIND_METHOD(play, "()V"); + FIND_METHOD(stop, "()V"); + FIND_METHOD(write, "([BII)I"); + +#undef FIND_METHOD + g_system = _system; } @@ -397,6 +440,7 @@ void JNI::destroy(JNIEnv *env, jobject self) { // see above //JNI::getEnv()->DeleteWeakGlobalRef(_jobj); + JNI::getEnv()->DeleteGlobalRef(_jobj_audio_track); JNI::getEnv()->DeleteGlobalRef(_jobj); } @@ -514,25 +558,6 @@ void JNI::pushEvent(JNIEnv *env, jobject self, jobject java_event) { _system->pushEvent(event); } -void JNI::audioMixCallback(JNIEnv *env, jobject self, jbyteArray jbuf) { - assert(_system); - - jsize len = env->GetArrayLength(jbuf); - jbyte *buf = env->GetByteArrayElements(jbuf, 0); - - if (buf == 0) { - warning("Unable to get Java audio byte array. Skipping"); - return; - } - - Audio::MixerImpl *mixer = - static_cast<Audio::MixerImpl *>(_system->getMixer()); - assert(mixer); - mixer->mixCallback(reinterpret_cast<byte *>(buf), len); - - env->ReleaseByteArrayElements(jbuf, buf, 0); -} - void JNI::setConfManInt(JNIEnv *env, jclass cls, jstring key_obj, jint value) { ENTER("%p, %d", key_obj, (int)value); diff --git a/backends/platform/android/jni.h b/backends/platform/android/jni.h index 02eabba569..a1fc3df67e 100644 --- a/backends/platform/android/jni.h +++ b/backends/platform/android/jni.h @@ -48,7 +48,6 @@ public: static void attachThread(); static void detachThread(); - static int getAudioSampleRate(); static void initBackend(); static void getPluginDirectories(Common::FSList &dirs); static void setWindowCaption(const char *caption); @@ -60,10 +59,18 @@ public: static inline void destroySurface(); static inline bool swapBuffers(); + static void setAudioPause(); + static void setAudioPlay(); + static void setAudioStop(); + + static inline int writeAudio(JNIEnv *env, jbyteArray &data, int offset, + int size); + private: static JavaVM *_vm; // back pointer to (java) peer instance static jobject _jobj; + static jobject _jobj_audio_track; static Common::Archive *_asset_archive; static OSystem_Android *_system; @@ -81,7 +88,6 @@ private: static jmethodID _MID_displayMessageOnOSD; static jmethodID _MID_setWindowCaption; static jmethodID _MID_initBackend; - static jmethodID _MID_audioSampleRate; static jmethodID _MID_showVirtualKeyboard; static jmethodID _MID_getSysArchives; static jmethodID _MID_getPluginDirectories; @@ -89,16 +95,21 @@ private: static jmethodID _MID_destroyScummVMSurface; static jmethodID _MID_swapBuffers; + static jmethodID _MID_AudioTrack_pause; + static jmethodID _MID_AudioTrack_play; + static jmethodID _MID_AudioTrack_stop; + static jmethodID _MID_AudioTrack_write; + static const JNINativeMethod _natives[]; static void throwByName(JNIEnv *env, const char *name, const char *msg); // natives for the dark side - static void create(JNIEnv *env, jobject self, jobject am); + static void create(JNIEnv *env, jobject self, jobject am, jobject at, + jint sample_rate, jint buffer_size); static void destroy(JNIEnv *env, jobject self); static jint main(JNIEnv *env, jobject self, jobjectArray args); static void pushEvent(JNIEnv *env, jobject self, jobject java_event); - static void audioMixCallback(JNIEnv *env, jobject self, jbyteArray jbuf); static void setConfManInt(JNIEnv *env, jclass cls, jstring key_obj, jint value); static void setConfManString(JNIEnv *env, jclass cls, jstring key_obj, @@ -128,6 +139,10 @@ inline bool JNI::swapBuffers() { return env->CallBooleanMethod(_jobj, _MID_swapBuffers); } +inline int JNI::writeAudio(JNIEnv *env, jbyteArray &data, int offset, int size) { + return env->CallIntMethod(_jobj_audio_track, _MID_AudioTrack_write, data, offset, size); +} + #endif #endif diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java index 698e508605..7a326454d9 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java @@ -35,19 +35,48 @@ import java.util.LinkedHashMap; public class ScummVM implements SurfaceHolder.Callback { protected final static String LOG_TAG = "ScummVM"; - // bytes. 16bit audio * stereo - private final int AUDIO_FRAME_SIZE = 2 * 2; - public static class AudioSetupException extends Exception {} - // native code hangs itself here private long nativeScummVM; boolean scummVMRunning = false; - private native void create(AssetManager am); + private native void create(AssetManager am, AudioTrack audio_track, + int sample_rate, int buffer_size); + + public ScummVM(Context context) throws Exception { + int sample_rate = AudioTrack.getNativeOutputSampleRate( + AudioManager.STREAM_MUSIC); + int buffer_size = AudioTrack.getMinBufferSize(sample_rate, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT); + + // ~100ms + int buffer_size_want = (sample_rate * 2 * 2 / 10) & ~1023; + + if (buffer_size < buffer_size_want) { + Log.w(LOG_TAG, String.format( + "adjusting audio buffer size (was: %d)", buffer_size)); + + buffer_size = buffer_size_want; + } + + Log.i(LOG_TAG, String.format("Using %d bytes buffer for %dHz audio", + buffer_size, sample_rate)); + + AudioTrack audio_track = + new AudioTrack(AudioManager.STREAM_MUSIC, + sample_rate, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT, + buffer_size, + AudioTrack.MODE_STREAM); + + if (audio_track.getState() != AudioTrack.STATE_INITIALIZED) + throw new Exception( + String.format("Error initialising AudioTrack: %d", + audio_track.getState())); - public ScummVM(Context context) { // Init C++ code, set nativeScummVM - create(context.getAssets()); + create(context.getAssets(), audio_track, sample_rate, buffer_size); } private native void nativeDestroy(); @@ -345,8 +374,6 @@ public class ScummVM implements SurfaceHolder.Callback { // Feed an event to ScummVM. Safe to call from other threads. final public native void pushEvent(Event e); - final private native void audioMixCallback(byte[] buf); - // Runs the actual ScummVM program and returns when it does. // This should not be called from multiple threads simultaneously... final public native int scummVMMain(String[] argv); @@ -359,131 +386,18 @@ public class ScummVM implements SurfaceHolder.Callback { protected String[] getSysArchives() { return new String[0]; } protected String[] getPluginDirectories() { return new String[0]; } - protected void initBackend() throws AudioSetupException { + protected void initBackend() { createScummVMGLContext(); - initAudio(); - } - - private static class AudioThread extends Thread { - final private int buf_size; - private boolean is_paused = false; - final private ScummVM scummvm; - final private AudioTrack audio_track; - - AudioThread(ScummVM scummvm, AudioTrack audio_track, int buf_size) { - super("AudioThread"); - this.scummvm = scummvm; - this.audio_track = audio_track; - this.buf_size = buf_size; - setPriority(Thread.MAX_PRIORITY); - setDaemon(true); - } - - public void pauseAudio() { - synchronized (this) { - is_paused = true; - } - audio_track.pause(); - } - - public void resumeAudio() { - synchronized (this) { - is_paused = false; - notifyAll(); - } - audio_track.play(); - } - - public void run() { - byte[] buf = new byte[buf_size]; - audio_track.play(); - int offset = 0; - try { - while (true) { - synchronized (this) { - while (is_paused) - wait(); - } - - if (offset == buf.length) { - // Grab new audio data - scummvm.audioMixCallback(buf); - offset = 0; - } - - int len = buf.length - offset; - int ret = audio_track.write(buf, offset, len); - if (ret < 0) { - Log.w(LOG_TAG, String.format( - "AudioTrack.write(%dB) returned error %d", - buf.length, ret)); - break; - } else if (ret != len) { - Log.w(LOG_TAG, String.format( - "Short audio write. Wrote %dB, not %dB", - ret, buf.length)); - - // Buffer is full, so yield cpu for a while - Thread.sleep(100); - } - offset += ret; - } - } catch (InterruptedException e) { - Log.e(LOG_TAG, "Audio thread interrupted", e); - } - } - } - private AudioThread audio_thread; - - final public int audioSampleRate() { - return AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); - } - - private void initAudio() throws AudioSetupException { - int sample_rate = audioSampleRate(); - int buf_size = - AudioTrack.getMinBufferSize(sample_rate, - AudioFormat.CHANNEL_CONFIGURATION_STEREO, - AudioFormat.ENCODING_PCM_16BIT); - if (buf_size < 0) { - // 10ms of audio - int guess = AUDIO_FRAME_SIZE * sample_rate / 100; - - Log.w(LOG_TAG, String.format( - "Unable to get min audio buffer size (error %d). Guessing %dB.", - buf_size, guess)); - - buf_size = guess; - } - - Log.d(LOG_TAG, String.format("Using %dB buffer for %dHZ audio", - buf_size, sample_rate)); - - AudioTrack audio_track = - new AudioTrack(AudioManager.STREAM_MUSIC, - sample_rate, - AudioFormat.CHANNEL_CONFIGURATION_STEREO, - AudioFormat.ENCODING_PCM_16BIT, - buf_size, - AudioTrack.MODE_STREAM); - - if (audio_track.getState() != AudioTrack.STATE_INITIALIZED) { - Log.e(LOG_TAG, "Error initialising Android audio system."); - throw new AudioSetupException(); - } - - audio_thread = new AudioThread(this, audio_track, buf_size); - audio_thread.start(); } public void pause() { - audio_thread.pauseAudio(); - // TODO: need to pause engine too + // TODO: need to pause audio + // TODO: need to pause engine } public void resume() { - // TODO: need to resume engine too - audio_thread.resumeAudio(); + // TODO: need to resume audio + // TODO: need to resume engine } static { diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java index 0dd110942b..fb6020cf1c 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java @@ -49,7 +49,7 @@ public class ScummVMActivity extends Activity { } } - public MyScummVM() { + public MyScummVM() throws Exception { super(ScummVMActivity.this); // Enable ScummVM zoning on 'small' screens. @@ -59,7 +59,7 @@ public class ScummVMActivity extends Activity { } @Override - protected void initBackend() throws ScummVM.AudioSetupException { + protected void initBackend() { synchronized (this) { scummvmRunning = true; notifyAll(); @@ -153,7 +153,13 @@ public class ScummVMActivity extends Activity { main_surface.requestFocus(); // Start ScummVM - scummvm = new MyScummVM(); + try { + scummvm = new MyScummVM(); + } catch (Exception e) { + Log.e(ScummVM.LOG_TAG, "Fatal error", e); + finish(); + return; + } scummvm_thread = new Thread(new Runnable() { public void run() { |