soundfiles on android

hello,
I’d be interested to know whether there is any support for soundfile playback in of-android yet? i’m in the process of installing the tool-chain and am looking at the libs to see if they have android builds. It looks like ofSoundPLayer doesn’t have one. Since FMOD seems to be on the way out, what will be the replacement?
I’m trying to find out whether to go the libpd-route from the start or try to implement soundfile-playback in “pure” oF.

as a side note: i see the GPS addon is here: great! what about accessing the digital compass in some of the devices? do i need to hack together some java-code to access the sensors? any leads on that?

thanks a lot

/*j

no there’s no ofSoundPlayer yet, i think the easiest would be to use android’s SoundPools but haven’t looked into it yet.

And yes to interface with sensors you need to go through java and make a jni interface so you can use it from c++

ok, thanks for the heads up: i’ll have to go look at soundPool. that’s in the java domain, right?

the sensors seem to be also available through the NDK, at least there’s a header called sensor.h in the android headers. together with a looper this should work directly from C++.

gracias arturo

saludos

/*j

i think you can access them directly from native only since 2.3 so that won’t be compatible with anything older.

And yes SoundPool is in java

ok,
i’m finally getting around to implementing the Compass and GPS on android.
I managed to get the compass (magnotometer) to run by copying the accelerometer stuff.

But i’m stumped on how to go about the GPS.
Is there an example project that i could have a look at to see how it’s done?

I’m playing around with events etc. but all get is this telling crash-log:

  
 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()  
     at android.os.Handler.<init>(Handler.java:121)  
     at android.location.LocationManager$ListenerTransport$1.<init>(LocationManager.java:173)  
     at android.location.LocationManager$ListenerTransport.<init>(LocationManager.java:173)  
     at android.location.LocationManager._requestLocationUpdates(LocationManager.java:583)  
     at android.location.LocationManager.requestLocationUpdates(LocationManager.java:450)  
     at cc.openframeworks.OFAndroidGPS.startGPS(OFAndroidGPS.java:23)  
     at cc.openframeworks.OFAndroid.setupGPS(OFAndroid.java:286)  
     at cc.openframeworks.OFAndroid.setup(Native Method)  
     at cc.openframeworks.OFAndroidWindow.onSurfaceCreated(OFAndroid.java:473)  
     at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1351)  
     at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1121)  
  

I feel very much out of my depth with these android specific problems…

I’d like to share the compass code i’ve generated, what would be the easiest way?

thanks

/*j

weird, i’ve been working with it without problem. Anyway the error you are getting is because the gps is not running in the UI thread. i’ve just uploaded a fix that should solve it, but post if you continue having the problem.

About the compass code, the ideal is github but if you are not very used to it, just post the code here in the forum

ok,

I need to play several streams of mp3’s within my app.
I figured that i’d use the MediaPlayer class for this.

So i’m in the process of writing the JNI wrapper and have a few questions:

This is the function in my .cpp class-file to call the .java play() function i have defined:
[font=Courier]

  
void ofxAndroidMediaPlayer::play(string inFileName)  
{	  
	JNIEnv *env = ofGetJNIEnv();  
	if (!env) {  
		ofLog(OF_LOG_ERROR,"Failed to get the environment using GetEnv()");  
		return false;  
	}		  
	jclass javaClass = env->FindClass("cc.openframeworks.OFAndroidMediaPlayer");  
	if(javaClass == 0){  
		ofLog(OF_LOG_ERROR,"cannot find OFAndroidMediaPlayer java class");  
		return false;  
	}  
	  
	jmethodID play = env->GetStaticMethodID(javaClass,"mediaPlayerPlay","()L");  
	if(!play){  
		ofLog(OF_LOG_ERROR,"cannot find mediaPlayerPlay method");  
		return false;  
	}  
	jstring str = env->NewStringUTF(inFileName.c_str());  
	env->CallStaticIntMethod(javaClass, play, str);  
	ofLogError("ofxAndroidMediaPlayer: play called with string " << str);  
	return;	  
}  

So far so good, i learned that for the [font=Courier]jmethodID GetStaticMethodID()[/font] it needs a signature and for a function passing a string this seems to be L (as in object?) Then I need to convert my C+±string to a jstring with NewStringUTF call. This is where i’m lost.

And one more question: for the moment i’m calling [font=Courier]JNIEnv *env = ofGetJNIEnv();[/font] for every function call in the .cpp class-file. wouldn’t it be more efficient to just call this in the constructor and cache the env-pointer in the as an instance or even class variable? Or is there a risk of the JVM / JNIEnv disappearing under our noses during runtime? :wink:

  
  
[code=cpp]  
hey, this is great!  
  
first of all, if you are implementing a c++ class for this, it should inherit from ofSoundPlayer, so it can be used as a replacement for the FMOD or openAL ones in android. If it's too complicated, no worries i can adapt the code later.  
  
then this:  
  
  
[code=cpp]  
env->FindClass("cc.openframeworks.OFAndroidMediaPlayer");  

should be:

  
env->FindClass("cc/openframeworks/OFAndroidMediaPlayer");  

android JNI supports both syntaxes but the second is more correct and some checking tools fail with the first.

also

  
env->GetStaticMethodID(javaClass,"mediaPlayerPlay","()L");  

should be

  
env->GetStaticMethodID(javaClass,"mediaPlayerPlay","(Ljava/lang/String)V");  

if the method is

  
void mediaPlayerPlay(String file)  

btw, better if the methods in the java class are named the same as in the c++ one, so that one should be play instead of medaiPlayerPlay

also to call that method once you get it, instead of:

  
env->CallStaticIntMethod(javaClass, play, str);  

if should be:

  
env->CallStaticVoidMethod(javaClass, play, str);  

since the method returns void

to convert the string to a java one you need to use:

  
jstring jStr = ofGetJNIEnv()->NewStringUTF(str.c_str());  
env->CallStaticVoidMethod(javaClass, play, jStr);  

about the environment, the garbage collector can actually move things around to optimize the memory so you cannot keep memory addresses for too long or you can get crashes
[/code]

[/code]

thanks alot arturo,

yes I’m implementing a C++ class for this. but I’m neither fit enough in C++ AND java nor do i have the time apart from my project deadline to make a proper addon. but I’ll post the code as soon as it runs properly.


thanks for all the detailed corrections, here’s the function with cleanups:

  
void ofxAndroidMediaPlayer::play(string inFileName)  
{	  
	JNIEnv *env = ofGetJNIEnv();  
	if (!env) {  
		ofLog(OF_LOG_ERROR,"Failed to get the environment using GetEnv()");  
		return;  
	}		  
	jclass javaClass = env->FindClass("cc/openframeworks/OFAndroidMediaPlayer");  
	if(javaClass == 0){  
		ofLog(OF_LOG_ERROR,"cannot find OFAndroidMediaPlayer java class");  
		return;  
	}  
	  
	jmethodID play = env->GetStaticMethodID(javaClass,"mediaPlayerPlay","(Ljava/lang/String)V");  
	if(!play){  
		ofLog(OF_LOG_ERROR,"cannot find mediaPlayerPlay method");  
		return;  
	}  
	jstring jStr = ofGetJNIEnv()->NewStringUTF(inFileName.c_str());  
	env->CallStaticVoidMethod(javaClass, play, jStr);  
	ofLogError("ofxAndroidMediaPlayer: play called with string %s", inFileName.c_str());  
	return;	  
}  

about the repeated calls to ofGetJNIEnv(): is it possible that the GC would cleanup the env-pointer _during_ my function call ???


concerning the naming of the C++ amd the Java functions:
I tried to avoid having the same name in the wrapper than in the actual java-class, in this case MediaPlayer implements start(), stop(), pause(), resume() etc. I wanted to differentiate the names. But of course it would be cleaner to have everything called the same across all layers.

I have a name-conflict when compiling, though: it seems that your OFAndroidObject has defined an a function called stop() and this prevents the MediaPlayer.stop() function from being valid, here’s the error thrown by GCC:

  
    [javac] /Users/jasch/Documents/_code-base/android-sdk-mac_x86/tools/ant/main_rules.xml:384: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds  
    [javac] Compiling 3 source files to /Users/jasch/Documents/_code-base/oF/openframeworks-openFrameworks-988f970/apps/androidExamples/androidTouchExample/bin/classes  
    [javac] /Users/jasch/Documents/_code-base/oF/openframeworks-openFrameworks-988f970/addons/ofxAndroid/ofAndroidLib/src/cc/openframeworks/OFAndroidMediaPlayer.java:11: cc.openframeworks.OFAndroidMediaPlayer is not abstract and does not override abstract method stop() in cc.openframeworks.OFAndroidObject  
    [javac] public class OFAndroidMediaPlayer extends OFAndroidObject implements Runnable  
    [javac]        ^  
    [javac] 1 error  

any ideas, tricks how to fix this?


one more question: the audio-files will be in res/raw, what will the absolute filepaths have to look like for a direct call to [font=Courier]mMediaPlayer.setDataSource(inFileName);[/font]?
is it /res/raw/myFabulousFile.mp3 or just the filename, which i know works for images/fonts located in res/raw.

here’s the play function in the .java file:

  
	public void mediaPlayerPlay(String inFileName)  
	{  
		if(mMediaPlayer == null) {  
			mMediaPlayer = new MediaPlayer();  
		} else {  
			mMediaPlayer.reset();  
		}  
		mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);  
		mMediaPlayer.setDataSource(inFileName); // <- aboslute or relative filePath?  
		mMediaPlayer.prepare();  
		mMediaPlayer.seekTo(0); // start in ms  
		mMediaPlayer.start();  
	}  

thanks a million for your assistance

/*j

about the problem with stop, you can probably solve it temporarily by adding @override before the method. but yes, those methods should have a different, no so common name

don’t know exactly how the mediaPlayer works but everything in res/raw or bin/data gets copied to /sdcard/cc.openframeworks.packagename

if you use ofToDataPath(“file.mp3”) in c++ it will return the complete path

thanks for the @override annotation, this seems to work.

I continue to bang my head on the JNI interface for the MediaPlayer class.

ATM it compiles, the app starts but as soon as there is a call to an instance of MediaPlayer the app crashes.

Logcat says: (excerpt):

  
08-15 16:05:17.741: INFO/OF(17640): initializing app  
<snip>  
08-15 16:05:21.555: ERROR/OF(17640): env->GetStaticMethodID(javaClass,"play","(Ljava/lang/String;)V" failed  
08-15 16:05:21.605: INFO/OF(17640): onPause  

(this is my own debug post - see below)

somehow the call in the C++ class to get the Method ID from the java class fails.

here are the beginnings of the 3 files:

  
////////   ofxAndroidMediaPlayer.h  
  
class ofxAndroidMediaPlayer {  
	public:  
		ofxAndroidMediaPlayer();  
		~ofxAndroidMediaPlayer();  
  
		void play(string inFileName);  
<snip>  

  
////////  ofxAndroidMediaPlayer.cpp  
  
#include <jni.h>  
#include "ofBaseApp.h"  
#include "ofxAndroidMediaPlayer.h"  
#include "ofUtils.h"  
#include "ofxAndroidUtils.h"  
#include "ofAppRunner.h"  
  
ofxAndroidMediaPlayer::ofxAndroidMediaPlayer()  
{  
	Log.v("OF", "C++: MediaPlayer: making Instance"); // never gets called  
}  
  
ofxAndroidMediaPlayer::~ofxAndroidMediaPlayer()  
{  
}  
  
void ofxAndroidMediaPlayer::play(string inFileName)  
{	  
	JNIEnv *env = ofGetJNIEnv();  
	if (!env) {  
		ofLog(OF_LOG_ERROR,"Failed to get the environment using GetEnv()");  
		return;  
	}		  
	jclass javaClass = env->FindClass("cc/openframeworks/OFAndroidMediaPlayer");  
	if(javaClass == 0) {  
		ofLog(OF_LOG_ERROR,"cannot find OFAndroidMediaPlayer java class");  
		return;  
	}  
	  
	jmethodID jplay = env->GetMethodID(javaClass,"play","(Ljava/lang/String;)V");  
	if(!jplay){  
		ofLog(OF_LOG_ERROR,"GetMethodID(javaClass,\"play\",\"(Ljava/lang/String;)V\" failed");  
		return; //<---- this is where the call fails  
	}  
	jstring jStr = env->NewStringUTF(inFileName.c_str());  
	env->CallObjectMethod(javaClass, jplay, jStr);  
	return;	  
}  
<snip>  

  
  
////////   OFAndroidMediaPlayer.java  
  
package cc.openframeworks;  
  
import java.lang.String;  
import java.io.IOException;  
  
import android.media.MediaPlayer;  
import android.media.AudioManager;  
import android.util.Log;  
  
public class OFAndroidMediaPlayer extends OFAndroidObject //implements Runnable  
{  
    private MediaPlayer mMediaPlayer; // instanciate the MediaPlayer class  
      
	public OFAndroidMediaPlayer()  
	{  
		Log.v("OF", "java: MediaPlayer: making Instance"); // never called  
	}  
	  
	public void play(String inFileName)  
	{  
		if(mMediaPlayer == null) {  
			mMediaPlayer = new MediaPlayer();  
		} else {  
			mMediaPlayer.reset();  
		}  
		try {  
			mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);  
			mMediaPlayer.setDataSource(inFileName);  
			mMediaPlayer.prepare();  
			mMediaPlayer.seekTo(0); // start in ms  
			mMediaPlayer.start();  
		} catch(IOException e) {  
		    System.out.println("Error preparing audio stream\n" + e);  
		}  
	}  
<snip>  

I think that all the JNI bindings etc. are correct. right now i have the suspicion that somehow the correct javaclass is never found when building and linking, thus my earlier question about forcing a rebuild of classes.

is there someplace else where the C++ and the java files have to linked/connected/made aware of eachother?
I already added the ofxMediaPlayer to the addons.make makefile.

**EDIT: **i’ve now found out that my methods are not static, hence the GetMethodID call. Furthermore the Method call with a string argument should be env->CallObjectMethod(javaClass, jplay, jStr); since a String is a fullblown Object in Java.
Now my app doesn’t crash anymore, but the call never goes through to the Java side.
Mmmmmh, more digging to do…

cheers

/*j