vitakot
vitakot

Reputation: 3844

NullPointerException when writing to AudioTrack instance in a callback from native code

I perform some decoding process in the native code and call a Java method from it to write samples into the AudioTrack instance. Decoding process runs fine, the callback from native code to Java:

private void writeToAudioTrack(final byte[] buffer)

is called , but once the Runnable object starts to write samples into the AudioTrack I get a NullPointerException immediately. I am quite sure it is caused by wrong threading, but cannot figure out what is wrong there. I am attaching the complete Java code:

public class Player
{
    private AudioTrack track;
    private boolean isInitialized = false;
    private static Handler handler = new Handler();

    public void init(String mediaSource)
    {
        // Call native function initEngine
        isInitialized = initEngine(mediaSource);

        if (isInitialized)
        {
            int bufSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
            track = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufSize, AudioTrack.MODE_STREAM);
            track.play();
        }
    }

    public void play()
    {
        // Call native rendering function in a separate thread
        if (isInitialized)
        {
            new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    renderAudio();
                }
            }).start();
        }
    }

    public void release()
    {
        isInitialized = false;
        releaseEngine();
    }

    // This is callback from native code
    private void writeToAudioTrack(final byte[] buffer)
    {
        handler.post(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    track.write(buffer, 0, buffer.length);
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        });
    }

    // 
    static
    {
        // Load native library
        System.loadLibrary("decoder");
    }

    // Private native methods
    private static native boolean initEngine(String mediaSource);
    private static native void releaseEngine();
    private static native void renderAudio();

Even if I create a buffer in place of try-catch block:

byte [] buffer = new byte[256];
track.write(buffer, 0, buffer.length);

I get the same result - NullPointerException.

Stack Trace:

java.lang.NullPointerException at com.mautilus.audioplayer.Player$2.run(Player.java:99) at android.os.Handler.handleCallback(Handler.java:587) at android.os.Handler.dispatchMessage(Handler.java:92) at android.os.Looper.loop(Looper.java:123) at android.app.ActivityThread.main(ActivityThread.java:4627) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:521) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:876) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:634) at dalvik.system.NativeStart.main(Native Method)

Upvotes: 0

Views: 525

Answers (2)

vitakot
vitakot

Reputation: 3844

The error was in the type of one of the native methods - initEngine, it must not be declared as static - in this case.

According to the documentation:

http://docs.oracle.com/javase/1.4.2/docs/guide/jni/spec/functions.html#wp16660

The GetMethodID() causes an uninitialized class to be initialized. So if the library loading is not tied with any class instance (that's normal):

static
{
    // Load native library
    System.loadLibrary("decoder");
}

then the native code:

jmethodID writeToAudioTrackMethodID = NULL;
jclass cls = (*env)->FindClass(env, "com.mautilus.audioplayer.Player");

if (!cls)
writeToAudioTrackMethodID = (*env)->GetMethodID(env, cls, "writeToAudioTrack", "([B)V");

called from a static method - initEngine in my case, can't obtain any existing instance of the class (even if such instance exists) and creates new one (with fields initialized to default values) - that's it...

Upvotes: 0

Gray
Gray

Reputation: 116908

Edit:

To add some more protection, I'd add a:

if (!isInitialized) {
   return;
}

to the front of the writeToAudioTrack method to make sure that the callback is not being called when isInitialized is false. How is the callback registered? Can you show that code?


Maybe a stupid answer but in your constructor, are you sure it should not be:

if (!isInitialized) {
    ...
}

If not then are you sure that the run() method is not being called if isInitialized was returned as false from the call to initEngine? If this is the case then track would be null which would explain the NPE in the run() method.

An easy way to figure this out would be to put an assert in the run():

assertNotNull(trace);

or do a proper test and throw an IllegalStateException if trace is null.

The only other thing that could cause the NPE is if a null value for buffer was passed into the writeToAudioTrack method. Testing for null there and throwing an IllegalArgumentException might be a good idea.

Hope this helps.

Upvotes: 1

Related Questions