3

I'm trying to decode Speex encoded audio files using Speex library but I always get ArrayIndexOutOfBoundsException

JNIEXPORT jint JNICALL Java_com_app_shared_Speex_decode
    (JNIEnv *env, jobject obj, jbyteArray encoded, jshortArray lin, jint size) {

        jbyte buffer[dec_frame_size ];
        jshort output_buffer[dec_frame_size ];
        jsize encoded_length = size;

        if (!codec_open)
            return 0;

        env->GetByteArrayRegion(encoded, rtp_header, encoded_length, buffer);

        /*  
        jboolean isCopy;
        jbyte* data = env->GetByteArrayElements(encoded, &isCopy);
        if(isCopy){
        memcpy(buffer, data, encoded_length);               
        env->ReleaseByteArrayElements(encoded, data, JNI_ABORT);
        }
        */

        speex_bits_read_from(&dbits, (char *)buffer, encoded_length);
        speex_decode_int(dec_state, &dbits, output_buffer);

        std::string bufStr;

        for(int i=0; i<20; i++){
            char buff[100];
            sprintf(buff, "Speex test %d => %d", i, output_buffer[i]);
            bufStr = buff;
            LOGI(bufStr.c_str());
        }

        bufStr = "before SetShortArrayRegion";
        LOGI(bufStr.c_str());

        (env)->SetShortArrayRegion(lin, rtp_header, dec_frame_size, output_buffer);

        bufStr = "after SetShortArrayRegion";
        LOGI(bufStr.c_str());

        return (jint)dec_frame_size;            
}

I get:

SharedSpeex before SetShortArrayRegion
dalvikvm    JNI WARNING: JNI method called with exception pending
dalvikvm              in Lcom/company/shared/Speex;.decode:([B[SI)I (SetShortArrayRegion)
dalvikvm    Pending exception is:
dalvikvm    java.lang.ArrayIndexOutOfBoundsException: [B offset=12 length=1634 src.length=1634

The interesting point is that if I fully comment the line containing SetShortArrayRegion, the exception happens upon exiting the scope:

SharedSpeex       before SetShortArrayRegion
SharedSpeex       after SetShortArrayRegion
AndroidRuntime    Shutting down VM
dalvikvm          threadid=1: thread exiting with uncaught exception (group=0xa62ca288)
AndroidRuntime    FATAL EXCEPTION: main
AndroidRuntime    java.lang.ArrayIndexOutOfBoundsException: [B offset=12 length=1634 src.length=1634

It's while in the official JNI documention it clearly prefers GetByteArrayRegion instead of GetByteArrayElements because of:

  • Reduces the risk of programmer error — no risk of forgetting to call Release after something fails.
Mohsen Afshin
  • 13,273
  • 10
  • 65
  • 90

1 Answers1

5

GetByteArrayRegion checks the bounds of the array to make sure you're not running over the end. GetByteArrayElements just gets a pointer to the start of the array, so your memcpy() is free to run past the end unnoticed.

The reason for the overrun is noted in the exception:

[B offset=12 length=1634 src.length=1634

[B means byte[]. The length of the array matches the length of the request (1634), but it's starting from an offset 12 bytes into the array. So you're trying to read 12 bytes past the end.

Exceptions thrown from Java code don't interrupt your C code; the VM just sets a flag on the current thread. You have to test for them explicitly with ExceptionCheck, and either clear them or return to Java code where the VM will continue the throw process. If you have CheckJNI enabled, and the VM sees you making any but a handful of calls with an exception raised, you will get a "JNI WARNING" and VM abort.

fadden
  • 51,356
  • 5
  • 116
  • 166