我有一个使用Kickflip和ButterflyTV libRTMP传输视频的应用程序
现在有99%的时间应用程序正常工作,但我不时会遇到一个我无法调试的本机分段错误,因为消息太含糊了:
01-24 10:52:25.576 199-199/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 01-24 10:52:25.576 199-199/? A/DEBUG: Build fingerprint: 'google/hammerhead/hammerhead:6.0.1/M4B30Z/3437181:user/release-keys' 01-24 10:52:25.576 199-199/? A/DEBUG: Revision: '11' 01-24 10:52:25.576 199-199/? A/DEBUG: ABI: 'arm' 01-24 10:52:25.576 199-199/? A/DEBUG: pid: 14302, tid: 14382, name: MuxerThread >>> tv.myapp.broadcast.dev <<< 01-24 10:52:25.576 199-199/? A/DEBUG: signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x9fef1000 01-24 10:52:25.636 199-199/? A/DEBUG: Abort message: 'Setting to ready!' 01-24 10:52:25.636 199-199/? A/DEBUG: r0 9c6f9500 r1 9c6f94fc r2 9fee900c r3 00007ff4 01-24 10:52:25.636 199-199/? A/DEBUG: r4 9fee9010 r5 9fef0ffd r6 00007ff1 r7 9fef0d88 01-24 10:52:25.636 199-199/? A/DEBUG: r8 cfe40980 r9 9e0a6900 sl 00007ff4 fp 9c6f94fc 01-24 10:52:25.636 199-199/? A/DEBUG: ip 9c6f9058 sp 9c6f94dc lr 000000e9 pc b3a33cb6 cpsr 800f0030 01-24 10:52:25.650 199-199/? A/DEBUG: backtrace: 01-24 10:52:25.651 199-199/? A/DEBUG: #00 pc 00004cb6 /data/app/tv.myapp.broadcast.dev-2/lib/arm/librtmp-jni.so 01-24 10:52:25.651 199-199/? A/DEBUG: #01 pc 00005189 /data/app/tv.myapp.broadcast.dev-2/lib/arm/librtmp-jni.so (rtmp_sender_write_video_frame+28) 01-24 10:52:25.651 199-199/? A/DEBUG: #02 pc 00005599 /data/app/tv.myapp.broadcast.dev-2/lib/arm/librtmp-jni.so (Java_net_butterflytv_rtmp_1client_RTMPMuxer_writeVideo+60) 01-24 10:52:25.651 199-199/? A/DEBUG: #03 pc 014e84e7 /data/app/tv.myapp.broadcast.dev-2/oat/arm/base.odex (offset 0xa66000) (int net.butterflytv.rtmp_client.RTMPMuxer.writeVideo(byte[], int, int, int)+122) 01-24 10:52:25.651 199-199/? A/DEBUG: #04 pc 014dbd55 /data/app/tv.myapp.broadcast.dev-2/oat/arm/base.odex (offset 0xa66000) (void io.kickflip.sdk.av.muxer.RtmpMuxerMix.writeThread()+2240) 01-24 10:52:25.651 199-199/? A/DEBUG: #05 pc 014d8c41 /data/app/tv.myapp.broadcast.dev-2/oat/arm/base.odex (offset 0xa66000) (void io.kickflip.sdk.av.muxer.RtmpMuxerMix.access$000(io.kickflip.sdk.av.muxer.RtmpMuxerMix)+60) 01-24 10:52:25.651 199-199/? A/DEBUG: #06 pc 014d819f /data/app/tv.myapp.broadcast.dev-2/oat/arm/base.odex (offset 0xa66000) (void io.kickflip.sdk.av.muxer.RtmpMuxerMix$1.run()+98) 01-24 10:52:25.651 199-199/? A/DEBUG: #07 pc 721e78d1 /data/dalvik-cache/arm/system@framework@boot.oat (offset 0x1ed6000)
同样,在2小时的流中,这可能不会发生,或者可能发生在流中10分钟.调试非常困难,因为我无法强制发生错误.
有没有办法改善我得到的调试信息?SEGV_ACCER究竟是什么意思?我已经读过这个"意味着你试图访问一个你无权访问的地址." 但我不确定这是什么意思,因为我可以在没有发生错误的情况下流几个小时.
有没有办法捕获信号并继续?
编辑:要添加更多信息,这是应用程序崩溃的本机库的一部分(使用ndk-stack找到):
JNIEXPORT jint JNICALL Java_net_butterflytv_rtmp_1client_RTMPMuxer_writeVideo(JNIEnv *env, jobject instance, jbyteArray data_, jint offset, jint length, jint timestamp) { jbyte *data = (*env)->GetByteArrayElements(env, data_, NULL); jint result = rtmp_sender_write_video_frame(data, length, timestamp, 0, 0); (*env)->ReleaseByteArrayElements(env, data_, data, 0); return result; } int rtmp_sender_write_video_frame(uint8_t *data, int size, uint64_t dts_us, int key, uint32_t abs_ts) { uint8_t * buf; uint8_t * buf_offset; int val = 0; int total; uint32_t ts; uint32_t nal_len; uint32_t nal_len_n; uint8_t *nal; uint8_t *nal_n; char *output ; uint32_t offset = 0; uint32_t body_len; uint32_t output_len; buf = data; buf_offset = data; total = size; ts = (uint32_t)dts_us; //ts = RTMP_GetTime() - start_time; offset = 0; nal = get_nal(&nal_len, &buf_offset, buf, total); (...) } static uint8_t * get_nal(uint32_t *len, uint8_t **offset, uint8_t *start, uint32_t total) { uint32_t info; uint8_t *q ; uint8_t *p = *offset; *len = 0; if ((p - start) >= total) return NULL; while(1) { info = find_start_code(p, 3); if (info == 1) break; p++; if ((p - start) >= total) return NULL; } q = p + 4; p = q; while(1) { info = find_start_code(p, 3); if (info == 1) break; p++; if ((p - start) >= total) //return NULL; break; } *len = (p - q); *offset = p; return q; } static uint32_t find_start_code(uint8_t *buf, uint32_t zeros_in_startcode) { uint32_t info; uint32_t i; info = 1; if ((info = (buf[zeros_in_startcode] != 1)? 0: 1) == 0) return 0; for (i = 0; i < zeros_in_startcode; i++) if (buf[i] != 0) { info = 0; break; }; return info; }
崩溃发生buf[zeros_in_startcode]
在find_start_code
.我也删除了一些android_log行(不要认为这很重要?).
根据我的理解,这个缓冲区应该是可访问的,没有意义它只是"有时"崩溃.
PS.这是我从Java调用本机代码的地方:
private void writeThread() { while (true) { Frame frame = null; synchronized (mBufferLock) { if (!mConfigBuffer.isEmpty()) { frame = mConfigBuffer.peek(); } else if (!mBuffer.isEmpty()) { frame = mBuffer.remove(); } if (frame == null) { try { mBufferLock.wait(); } catch (InterruptedException e) { } } } if (frame == null) { continue; } else if (frame instanceof Sentinel) { break; } int writeResult = 0; synchronized (mWriteFence) { if (!mConnected) { debug(WARN, "Skipping frame due to disconnection"); continue; } if (frame.getFrameType() == Frame.VIDEO_FRAME) { writeResult = mRTMPMuxer.writeVideo(frame.getData(), frame.getOffset(), frame.getSize(), frame.getTime()); } else if (frame.getFrameType() == Frame.AUDIO_FRAME) { writeResult = mRTMPMuxer.writeAudio(frame.getData(), frame.getOffset(), frame.getSize(), frame.getTime()); } if (writeResult < 0) { mRtmpListener.onDisconnected(); mConnected = false; } else { //Now we remove the config frame, only if sending was successful! if (frame.isConfig()) { synchronized (mBufferLock) { mConfigBuffer.remove(); } } } } } }
请注意,即使我根本不发送音频,也会发生崩溃.
"您可以将数据存储在a中
byte[]
.这样可以非常快速地从托管代码访问.但是,在本机方面,您无法保证能够访问数据而无需复制它."
请参阅https://developer.android.com/training/articles/perf-jni.html
一些想法和事情要尝试:
它倒下的代码是非常通用的,所以可能没有错误
必须是frame
数据已被删除/损坏/锁定/移动
Java垃圾收集器是否已删除或重新定位数据?
您可以将详细的调试写入文件,在每个帧上覆盖它,因此您只有一个包含最后调试信息的小日志.
发送frame
变量信息(使用ByteBuffer
)的本地副本mRTMPMuxer.writeVideo
与常规byte
缓冲区不同,ByteBuffer
存储未在托管上分配heap
,并且始终可以直接从本机代码访问.
履行
//allocates memory from the native heap ByteBuffer data = ByteBuffer.allocateDirect(frame.getData().length); data.clear(); //System.gc(); //copy data data.get(frame.getData(), 0, frame.getData().length); //data = (frame.getData() == null) ? null : frame.getData().clone(); int offset = frame.getOffset(); int size = frame.getSize(); int time = frame.getTime(); writeResult = mRTMPMuxer.writeVideo(data , offset, size, time); JNIEXPORT jint JNICALL Java_net_butterflytv_rtmp_1client_RTMPMuxer_writeVideo( JNIEnv *env, jobject instance, jobject data_, //NOT jbyteArray data_, jint offset, jint length, jint timestamp) { jbyte *data = env->GetDirectBufferAddress(env, data);//GetDirectBufferAddress NOT GetByteArrayElements jint result = rtmp_sender_write_video_frame(data, length, timestamp, 0, 0); //(*env)->ReleaseByteArrayElements(env, data_, data, 0);//???? return result; }调试
来自本机代码的SO捕获异常的一些代码:
static uint32_t find_start_code(uint8_t *buf, uint32_t zeros_in_startcode){ //... try { if ((info = (buf[zeros_in_startcode] != 1)? 0: 1) == 0) return 0;//your code } // You can catch std::exception for more generic error handling catch (std::exception e){ throwJavaException (env, e.what());//see method below } //...
然后一个新的方法:
void throwJavaException(JNIEnv *env, const char *msg) { // You can put your own exception here jclass c = env->FindClass("java/lang/RuntimeException"); if (NULL == c) { //B plan: null pointer ... c = env->FindClass("java/lang/NullPointerException"); } env->ThrowNew(c, msg); } }
不要太挂SEGV_ACCERR
,你有一个分段错误,SIGSEGV
(由程序试图读取或写入非法的内存位置,在你的情况下读取).
来自siginfo.h:
SEGV_MAPERR表示您尝试访问未映射到任何内容的地址. SEGV_ACCERR表示您尝试访问您无权访问的地址.
其他这可能是有趣的:
问:我注意到有RTMP支持.但是已经合并了一个删除RTMP的补丁.
问:你能告诉我为什么吗?
答:我们不认为RTMP服务于移动广播用例以及HLS,
A:所以我们不希望将有限的资源用于支持它.
请参阅:https://github.com/Kickflip/kickflip-android-sdk/issues/33
我建议你注册一个问题:
https://github.com/Kickflip/kickflip-android-sdk/issues
https://github.com/ButterflyTV/LibRtmp-Client-for-Android/issues