ijkplayer回调消息处理方案

简介:其实ijkplayer主要控制器就是ff_ffplay.c,基本上沿用了官方ffplay.c

ijkplayer的ff_ffplay.c主要有三大类线程。

  1. 读数据read_thread。
  2. 解码线程 和 渲染线程。
    (包含在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越少。
总共有三层逻辑关系:

三层关系.png

A: 对ijkplayer底层C代码而言,ff_ffplay.c唯一能做的就是根据各种异常,抛出对应的错误提示。

B: 中间层API把底下的接口重新封装,并把回调消息透传到上面

这是之前和产品讨论的一直方案:


处理流程图.png

参考方案:(以前公司做过的线上直播项目:中国移动物联网监控平台)
底层抛出的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秒后,再重连一次,失败了就弹出错误提示。

你可能感兴趣的:(ijkplayer回调消息处理方案)