我有一个使用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();
}
}
}
}
}
}
请注意,即使我根本不发送音频,也会发生崩溃。
答案 0 :(得分:5)
&#34;您可以将数据存储在
byte[]
中。这允许非常快速的访问 托管代码。然而,在本土方面,你并不能保证 无需复制即可访问数据。&#34;
请参阅https://developer.android.com/training/articles/perf-jni.html
有些想法和事情要尝试:
frame
数据已被删除/损坏/锁定/移动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 Catching exceptions thrown from native code的一些代码:
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,
答:所以我们不想把我们有限的资源用于 支持它。
see: 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
答案 1 :(得分:2)
根据问题的症状/描述,您的程序很可能会遇到某种无效的内存访问/损坏 这与多线程竞争条件场景有某种关系。从我过去的经验来看,调试内存损坏本身 非常困难,如果它与多线程环境相关联,则变得非常困难。我以前的一些帖子可能会 提供帮助并提供有关这些主题的一般指导原则。请注意,这些帖子与Windows / Linux和 不适用于Android平台。
cpp - valgrind - Invalid read of size 8
在进一步阅读类似问题和你的代码时,我遇到了一个帖子,如下所述:
您应用的客户端代码段
synchronized (mWriteFence) {
if (!mConnected) {
continue;
}
if (frame.getFrameType() == Frame.VIDEO_FRAME) {
writeResult = mRTMPMuxer.writeVideo(frame.getData(), frame.getOffset(), frame.getSize(), frame.getTime());
calcVideoFpsAndBitrate(frame.getSize());
} else if (frame.getFrameType() == Frame.AUDIO_FRAME) {
writeResult = mRTMPMuxer.writeAudio(frame.getData(), frame.getOffset(), frame.getSize(), frame.getTime());
calcAudioBitrate(frame.getSize());
}
}
从上面的代码中,在我看来,如果您的应用程序按特定顺序收到Frame.VIDEO_FRAME & Frame.AUDIO_FRAME
,则在使用frame
变量时可能会导致某种竞争条件(可能是异步模型实现)在RtmpMuxerMix.writeThread
模块中。
结束这些问题:
。