简介:其实ijkplayer主要控制器就是ff_ffplay.c,基本上沿用了官方ffplay.c
ijkplayer的ff_ffplay.c主要有三大类线程。
- 读数据read_thread。
- 解码线程 和 渲染线程。
(包含在audio_thread和video_thread,既做解码又做渲染。 比如audio_thread:一个线程做解码,重采样,通过回调数据的方式将音频用sdl渲染输出)。
所有的回调消息,都是在读数据线程:read_thread抛出来的。
回调消息主要有两类:ERROR 和 COMPLETED。
对应的代码定义分别是:FFP_MSG_ERROR 和 FFP_MSG_COMPLETED。
ERROR消息总共有6处, COMPLETED只有1处。
先说ERROR,前面几处都是收流(while 循环)前的准备。
第一处:这个是创建对象互斥锁。
一般都不会失败,忽略。
SDL_mutex *wait_mutex = SDL_CreateMutex();
if (!wait_mutex) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
ret = AVERROR(ENOMEM);
goto fail;
}
第二处:avformat_alloc_context, 这个是播放器的全局准备(调用av_malloc()为AVFormatContext结构体分配了内存,而且同时也给AVFormatContext中的internal字段分配内存)。
一般都不会失败,忽略。
ic = avformat_alloc_context();
if (!ic) {
av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
ret = AVERROR(ENOMEM);
goto fail;
}
第三处:打开url失败:一般是url已经超时失效了,或者是有个错误的url。
err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
if (err < 0) {
print_error(is->filename, err);
ret = -1;
goto fail;
}
第四处:探测数据失败,拿不到视频解码信息和宽高之类的。
一般都不会失败,忽略。
err = avformat_find_stream_info(ic, opts);
for (i = 0; i < orig_nb_streams; i++)
av_dict_free(&opts[i]);
av_freep(&opts);
if (err < 0) {
av_log(NULL, AV_LOG_WARNING,
"%s: could not find codec parameters\n", is->filename);
ret = -1;
//ffp->last_error = last_error;
goto fail;
}
第五处: 没有音视频流。这种情况是链接有效,但是不是可播放链接。
一般都不会失败,忽略。
if (is->video_stream < 0 && is->audio_stream < 0) {
av_log(NULL, AV_LOG_FATAL, "Failed to open file '%s' or configure filtergraph\n",
is->filename);
ret = -1;
goto fail;
}
准备工作完成,如果前面都没error异常,那恭喜你,接下来是进入收数据包了。
第六处:
循环收数据流的消息其实很简单,只有1处:
当没有数据了,流断开了(具体场景可能是你的wifi断了,主播断了等等等)
//http network cut
if (ffp->error) {
ffp_notify_msg1(ffp, FFP_MSG_ERROR);
}
// 1. http or rtmp zhubo active close
// 2. rtmp network cut(eg. close wifi)
else {
ffp_notify_msg1(ffp, FFP_MSG_COMPLETED);
}
注释:当没流数据的时候,
如果数据异常ffp->error,那么就是ERROR,
否则就是COMPLETED。
不同的协议这里的提示不一样的,这里我做过验证.
断开wifi的时候:
a. 如果是http+flv的直播,会提示ERROR. (因为走http.c http_read读取失败,返回-110的ERROR)
b. 如果是rtmp的流,没有ERROR提示(ffp->error=0), 会提示COMPLETED。如果主播主动关闭
都是提示COMPLETED
** 代码分析完毕,再说说方案(参考我最近几天的思考,之前项目的做法,可能也有考虑不到的地方) :**
流程和逻辑要尽量简洁,清晰,因为越简洁的逻辑,越正确,bug越少。
总共有三层逻辑关系:
A: 对ijkplayer底层C代码而言,ff_ffplay.c唯一能做的就是根据各种异常,抛出对应的错误提示。
B: 中间层API把底下的接口重新封装,并把回调消息透传到上面
这是之前和产品讨论的一直方案:
参考方案:(以前公司做过的线上直播项目:中国移动物联网监控平台)
底层抛出的ERROR和COMPLETED, 应用层不管拿到哪种消息,都是重连两次,每次间隔3秒。如果还失败了就报直播结束(无需判断网络情况或者其它,这种方案简洁实用,应用层只有调一下重连接口即可,因为用户也不会过度关注提示)。
我的想法基本类似:这也是FY目前iOS项目的做法,就是当应用C层收到不管是ERROR和COMPLETED, 都重连N次,每次间隔N秒。如果还失败了就报直播结束。
如果产品一定要参考处理流程图.png的做法,那最好是简化一点,因为这个做法判断有的多,建议简化应用层的处理逻辑,我觉得应用层的代码不要超过10行。
应用层可以自己用中间层api的接口做重连;
如果需要,中间层也可以提供重连接口,iOS就是调用的底下封装的重连接口replay,逻辑很简单,Android的我也已经做好:
public void replay {
setDisplay(sh); // 显示的Surface,备份之前显示的
reset(); //重置
if(player != null)
{
player.setDataSource(url); //设置url
}
prepareAsync(); // 准备播放
}
回到我们的产品:因为服务器也会发主播主动停止推流的消息,而且一旦收到这个消息,那100%就是准的,所以提示语的话,我个人建议是:
收到服务器主播关闭直播的消息:主播关闭了直播。
其它底下抛出的消息,重连后失败一概是:直播结束。
一语:就是应用层C收到中间层B的消息,重连一次(调replay),不成功的话,N秒后,再重连一次,失败了就弹出错误提示。