Search code examples
javaandroidjava-native-interfaceandroid-ndk

How do I handle calls to AudioTrack from jni without crashing?


I was trying to write to an AudioTrack from a jni callback, and I get a signal 7 (SIGBUS), fault addr 00000000.

I have looked at the Wolf3D example for odroid and they seem to use a android.os.Handler to post a Runnable that will do an update in the correct thread context. I have also tried AttachCurrentThread, but I fail in this case also.

It works to play the sound when running from the constructor even if I wrap it in a thread and then post it to the handler. When I do the "same" via a callback from jni it fails. I assume I am braeaking some rules, but I haven't been able to figure out what they are. So far, I haven't found the answer here on SO.

So I wonder if anyone knows how this should be done.

EDIT: Answered below.

The following code is to illustrate the problem.

Java:

package com.example.jniaudiotrack;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

public class JniAudioTrackActivity extends Activity {
    AudioTrack mAudioTrack;
    byte[] mArr;
    public static final Handler mHandler = new Handler();

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mArr = new byte[2048];
        for (int i = 0; i < 2048; i++) {
            mArr[i] = (byte) (Math.sin(i) * 128);
        }

        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                11025,
                AudioFormat.CHANNEL_CONFIGURATION_MONO,
                AudioFormat.ENCODING_PCM_8BIT,
                2048,
                AudioTrack.MODE_STREAM);
        mAudioTrack.play();

        new Thread(new Runnable() {
            public void run() {
                mHandler.post(new Runnable() {
                    public void run() {
                        mAudioTrack.write(mArr, 0, 2048);
                        Log.i(TAG, "*** Handler from constructor ***");
                    }
                });
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                audioFunc();
            }
        }).start();
    }

    public native void audioFunc();

    @SuppressWarnings("unused")
    private void audioCB() {
        mHandler.post(new Runnable() {
            public void run() {
                mAudioTrack.write(mArr, 0, 2048);
                Log.i(TAG, "*** audioCB called ***");
            }
        });
    }

    private static final String TAG = "JniAudioTrackActivity";

    static {
        System.loadLibrary("jni_audiotrack");
    }
}

cpp:

#include <jni.h>

extern "C" {
    JNIEXPORT void Java_com_example_jniaudiotrack_JniAudioTrackActivity_audioFunc(JNIEnv* env, jobject obj);
}

JNIEXPORT void Java_com_example_jniaudiotrack_JniAudioTrackActivity_audioFunc(JNIEnv* env, jobject obj)
{
    JNIEnv* jniEnv;
    JavaVM* vm;
    env->GetJavaVM(&vm);
    vm->AttachCurrentThread(&jniEnv, 0);

    jclass cls = env->GetObjectClass(obj);
    jmethodID audioCBID = env->GetMethodID(cls, "audioCB", "()V");

    if (!audioCBID) {
        return;
    }

    env->CallVoidMethod(cls, audioCBID);
}

Trace snippet:

I/DEBUG   ( 1653): pid: 9811, tid: 9811  >>> com.example.jniaudiotrack <<<
I/DEBUG   ( 1653): signal 7 (SIGBUS), fault addr 00000000
I/DEBUG   ( 1653):  r0 00000800  r1 00000026  r2 00000001  r3 00000000
I/DEBUG   ( 1653):  r4 42385726  r5 41049e54  r6 bee25570  r7 ad00e540
I/DEBUG   ( 1653):  r8 000040f8  r9 41048200  10 41049e44  fp 00000000
I/DEBUG   ( 1653):  ip 000000f8  sp bee25530  lr ad02dbb5  pc ad012358  cpsr 20000010
I/DEBUG   ( 1653):          #00  pc 00012358  /system/lib/libdvm.so

Solution

  • There seems to have been a memory problem. Making mAudioTrack and mArr static solved it. I was sending the wrong object to the callback. See comment by fadden. I have removed the call to AttachCurrentThread since it did not make any difference in this case.

    Java:

    package com.example.jniaudiotrack;
    
    import android.app.Activity;
    import android.media.AudioFormat;
    import android.media.AudioManager;
    import android.media.AudioTrack;
    import android.os.Bundle;
    import android.os.Handler;
    import android.util.Log;
    
    public class JniAudioTrackActivity extends Activity {
        public AudioTrack mAudioTrack;
        public byte[] mArr;
        public static Handler mHandler = new Handler();
    
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            mArr = new byte[2048];
            for (int i = 0; i < 2048; i++) {
                mArr[i] = (byte) (Math.sin(i) * 128);
            }
    
            mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                        11025,
                        AudioFormat.CHANNEL_CONFIGURATION_MONO,
                        AudioFormat.ENCODING_PCM_8BIT,
                        2048,
                        AudioTrack.MODE_STREAM);
            mAudioTrack.play();
    
            new Thread(new Runnable() {
                public void run() {
                    audioFunc();
                }
            }).start();
        }
    
        public native void audioFunc();
    
        @SuppressWarnings("unused")
        private void audioCB() {
            mHandler.post(new Runnable() {
                public void run() {
                    mAudioTrack.write(mArr, 0, 2048);
                    Log.i(TAG, "*** audioCB called ***");
                }
            });
        }
    
        private static final String TAG = "JniAudioTrackActivity";
    
        static {
            System.loadLibrary("jni_audiotrack");
        }
    }
    

    Cpp:

    #include <jni.h>
    
    extern "C" {
        JNIEXPORT void Java_com_example_jniaudiotrack_JniAudioTrackActivity_audioFunc(JNIEnv* env, jobject obj);
    }
    
    JNIEXPORT void Java_com_example_jniaudiotrack_JniAudioTrackActivity_audioFunc(JNIEnv* env, jobject obj)
    {
        jclass cls = env->GetObjectClass(obj);
        jmethodID audioCBID = env->GetMethodID(cls, "audioCB", "()V");
    
        if (!audioCBID) {
            return;
        }
    
        env->CallVoidMethod(obj, audioCBID);
    }