ijkplayer在播放rtsp流结束后,和后台的心跳连接一直在,没有被断开。通过抓包分析发现是因为没有发送teardown结束标志。
问题分析
我们先研究一下ffmpeg关闭rtsp流的过程。我们可以大致了解方法avformat_close_input(),可参考下面文章:
FFmpeg源代码分析:avformat_close_input()
通过跟踪代码我们发现具体过程如下图所示:
按照正常的流程,ffmpeg关闭rtsp流的时候,走了ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL)生成rtsp的完整TEARDOWN命令,然后通过tcp的send发送,通过抓包验证确实是发送了的。
抓到的TEARDOWN信息:
TEARDOWN rtsp://192.168.2.82:8554/SubStream1 RTSP/1.0
CSeq: 7
User-Agent: Lavf57.56.100
Session: 162572178
Reply: RTSP/1.0 200 OK
那么为什么ijkplayer在播放rtsp流结束后没有发送呢?我们再具体分析一下ijkplayer调用ffmpeg关闭rtsp流的过程:
通过跟踪代码流程,我们发现,在走到retry_transfer_wrapper的时候调用ff_check_interrupt对URLContext的interrupt_callback进行了检测,结果不为NULL,就调用interrupt_callback后返回了,没有执行到tcp_write。而interrupt_callback是在前面的ijkmp_stop中设置的。
解决方案
ffmpeg官方已经对这个问题给出了patch:
然而并没有效果。
方案是增加了预关闭操作,为AVInputFormat添加了read_preclose成员方法,并在ff_rtsp_demuxer中定义了它的实现,就是rtsp_read_preclose,也就是调用ff_rtsp_send_cmd_async(s, "TEARDOWN", rt->control_uri, NULL),方法调用的位置是在stream_close,而ijkplayer在播放rtsp流结束的时候,这个方法是在调用ijkmp_stop之后,所以仍然会有这个问题。
解决方法就是在调用ijkmp_stop之前通过avformat_preclose_input
调用这个方法:
添加方法:
int ffp_prestop_l(FFPlayer *ffp)
{
assert(ffp);
VideoState *is = ffp->is;
if (is){
avformat_preclose_input(&is->ic);
}
return 0;
}
在关闭player时,调用ijkmp_stop(_mediaPlayer)前增加此方法,也就是在android\ijkplayer_jni.c的IjkMediaPlayer_stop中增加ffp_prestop_l。
static void
IjkMediaPlayer_stop(JNIEnv *env, jobject thiz)
{
IjkMediaPlayer *mp = jni_get_media_player(env, thiz);
JNI_CHECK_GOTO(mp, env, "java/lang/IllegalStateException", "mpjni: stop: null mp", LABEL_RETURN);
ffp_prestop_l(mp->ffplayer);
ijkmp_stop(mp);
LABEL_RETURN:
ijkmp_dec_ref_p(&mp);
}