aboutsummaryrefslogtreecommitdiff
path: root/backends
diff options
context:
space:
mode:
Diffstat (limited to 'backends')
-rw-r--r--backends/platform/android/android.cpp87
-rw-r--r--backends/platform/android/android.h10
-rw-r--r--backends/platform/android/jni.cpp111
-rw-r--r--backends/platform/android/jni.h23
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/ScummVM.java168
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java12
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() {