JNI调用Libspeexjni编码函数出现段错误的问题排查与咨询
问题描述
涉及的Java本地方法
// targetFunctionPtr1 对应的方法 public static native long nInitSpxEncoder(); // targetFunctionPtr 对应的方法 public static native long nEncodeBuffer(long encoderHandle, short[] pcmInput, byte[] encodedOutput, long outputBufSize);
Java示例伪代码
long encoderHandle = LibSpeex.nInitSpxEncoder(); long encodedLen = LibSpeex.nEncodeBuffer(encoderHandle, pcmInputArr, outputByteArr, 65536);
本人的JNI直接调用代码
char b[16]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x00}; jstring jinput = env->NewStringUTF(b); jclass stringClass = env->FindClass("java/lang/String"); jmethodID getBytesMId = env->GetMethodID(stringClass, "getBytes", "()[B"); jbyteArray in1 = (jbyteArray)env->CallObjectMethod(jinput, getBytesMId); jlong init; init = targetFunctionPtr1(env, CallerObj1); jshort outCArray[] = {100}; jshortArray retval = env->NewShortArray(10000); env->SetShortArrayRegion(retval, 0 , 1, outCArray); jlong nenc = targetFunctionPtr(env, CallerObj, init, retval, in1, 10);
Java侧JNI实现示例(对比参考)
long encoderHandle = LibSpeex.nInitSpxEncoder(); if (encoderHandle != 0) { int frameSize = (int) LibSpeex.nGetEncoderFrameSize(encoderHandle); System.err.println(String.format("Frame size = %d", frameSize)); short[] pcmInputArr = new short[frameSize]; AudioRecord audioRecord2 = null; byte[] outputByteArr = bArr2; long j12 = 0; while (j12 < this.f13733d) { try { synchronized (this) { if (audioRecord2 != this.f13731b) { Log.i("AudioPttRecorder", "source changed"); } audioRecord = this.f13731b; } if (audioRecord == null || audioRecord.read(pcmInputArr, 0, frameSize) != frameSize) { break; } if (this.f13730a != null) { this.f13730a.a(j12, a(frameSize, pcmInputArr)); } long encodedLen = LibSpeex.nEncodeBuffer(encoderHandle, pcmInputArr, outputByteArr, 65536); if (encodedLen >= 0) { Log.d("AudioPttRecorder", String.format("Write packet len=%d", encodedLen)); outputByteArr[0] = (byte) (255 & encodedLen); outputByteArr[1] = (byte) (encodedLen >> 8); bufferedOutputStream.write(outputByteArr, 0, 2); bufferedOutputStream.write(outputByteArr, 0, (int) encodedLen); j12 += 1; audioRecord2 = audioRecord; } else { throw new RuntimeException("Something wrong with encoding Speex"); } } finally { LibSpeex.nDestroySpxEncoder(encoderHandle); Log.i("AudioPttRecorder", "exited"); } } bufferedOutputStream.flush(); Log.d("AudioPttRecorder", "finished"); return; }
核心问题
初始化调用正常,但调用nEncodeBuffer时出现段错误,调试可见字节数组和短数组已加载,但函数不返回直至触发崩溃。JNI调用逻辑与Java侧示例类似,请问Native代码存在什么问题?如何正确调用Libspeex编码字节数组?
问题分析与解决方法
1. 参数匹配错误
问题点
- 静态Native方法的JNI调用签名:第一个参数是
JNIEnv*,第二个是jclass,之后才是方法定义的业务参数。你的调用中参数顺序虽未完全错,但输入输出缓冲区的逻辑完全颠倒:
Java方法中nEncodeBuffer的参数是「编码器句柄 → 输入PCM短数组 → 输出编码字节数组 → 输出缓冲区大小」,但你用retval(短数组)作为输入、in1(字节数组)作为输出,同时给的输出缓冲区大小10远小于in1的实际长度16,直接导致缓冲区溢出。 - 输入短数组仅初始化了第一个元素,其余都是未初始化的垃圾值,Speex编码器需要固定帧大小的有效PCM数据,垃圾数据会引发内部逻辑异常。
修正
严格按照Java方法的参数顺序和用途传参:
// 正确参数顺序:env → jclass → 编码器句柄 → 输入短数组 → 输出字节数组 → 输出缓冲区大小 jlong encodedLen = targetFunctionPtr(env, CallerObj, init, inputSArr, outputBArr, outputBufSize);
2. 缓冲区尺寸与数据有效性问题
问题点
- 输入短数组:Speex编码器要求输入长度与编码器帧大小完全匹配,你创建的10000长度数组远大于实际需要,且仅填充一个元素,其余为垃圾数据。
- 输出字节数组:通过String转成的字节数组长度仅16,且传入的缓冲区大小为10,远小于Speex编码所需的最小缓冲区,直接触发内存越界。
修正
- 获取正确的编码器帧大小:
先调用nGetEncoderFrameSize获取输入PCM的固定帧大小:// 假设targetGetFrameSizeFunc是nGetEncoderFrameSize的函数指针 jint frameSize = (jint)targetGetFrameSizeFunc(env, CallerObj, init); - 创建并填充有效输入短数组:
jshortArray inputSArr = env->NewShortArray(frameSize); jshort* inputBuf = env->GetShortArrayElements(inputSArr, NULL); // 填充有效PCM数据,示例为静音数据,实际需替换为真实音频输入 memset(inputBuf, 0, frameSize * sizeof(jshort)); env->ReleaseShortArrayElements(inputSArr, inputBuf, 0); - 创建足够大的输出字节数组:
参考Java侧使用65536作为输出缓冲区大小,避免溢出:jint outputBufSize = 65536; jbyteArray outputBArr = env->NewByteArray(outputBufSize);
3. 资源管理缺失
问题点
Java侧示例在finally块中调用nDestroySpxEncoder释放编码器资源,你的代码未做资源释放,会导致内存泄漏,长期运行引发异常。
修正
编码完成后释放所有资源:
env->DeleteLocalRef(inputSArr); env->DeleteLocalRef(outputBArr); // 假设targetDestroyFunc是nDestroySpxEncoder的函数指针 targetDestroyFunc(env, CallerObj, init);
内容的提问来源于stack exchange,提问作者dev
相关产品推荐
相关产品推荐

