簡(jiǎn)介:其實(shí)ijkplayer主要控制器就是ff_ffplay.c,基本上沿用了官方ffplay.c
ijkplayer的ff_ffplay.c主要有三大類線程谓娃。
- 讀數(shù)據(jù)read_thread蜂莉。
- 解碼線程 和 渲染線程。
(包含在audio_thread和video_thread,既做解碼又做渲染蜈抓。 比如audio_thread:一個(gè)線程做解碼鸯绿,重采樣芹血,通過(guò)回調(diào)數(shù)據(jù)的方式將音頻用sdl渲染輸出)贮泞。
所有的回調(diào)消息,都是在讀數(shù)據(jù)線程:read_thread拋出來(lái)的幔烛。
回調(diào)消息主要有兩類:ERROR 和 COMPLETED啃擦。
對(duì)應(yīng)的代碼定義分別是:FFP_MSG_ERROR 和 FFP_MSG_COMPLETED。
ERROR消息總共有6處饿悬, COMPLETED只有1處令蛉。
先說(shuō)ERROR,前面幾處都是收流(while 循環(huán))前的準(zhǔn)備狡恬。
第一處:這個(gè)是創(chuàng)建對(duì)象互斥鎖珠叔。
一般都不會(huì)失敗,忽略弟劲。
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祷安, 這個(gè)是播放器的全局準(zhǔn)備(調(diào)用av_malloc()為AVFormatContext結(jié)構(gòu)體分配了內(nèi)存,而且同時(shí)也給AVFormatContext中的internal字段分配內(nèi)存)。
一般都不會(huì)失敗兔乞,忽略汇鞭。
ic = avformat_alloc_context();
if (!ic) {
av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
ret = AVERROR(ENOMEM);
goto fail;
}
第三處:打開url失敗:一般是url已經(jīng)超時(shí)失效了庸追,或者是有個(gè)錯(cuò)誤的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;
}
第四處:探測(cè)數(shù)據(jù)失敗,拿不到視頻解碼信息和寬高之類的淡溯。
一般都不會(huì)失敗读整,忽略。
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;
}
第五處: 沒(méi)有音視頻流咱娶。這種情況是鏈接有效米间,但是不是可播放鏈接。
一般都不會(huì)失敗膘侮,忽略车伞。
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;
}
準(zhǔn)備工作完成,如果前面都沒(méi)error異常喻喳,那恭喜你另玖,接下來(lái)是進(jìn)入收數(shù)據(jù)包了。
第六處:
循環(huán)收數(shù)據(jù)流的消息其實(shí)很簡(jiǎn)單表伦,只有1處:
當(dāng)沒(méi)有數(shù)據(jù)了谦去,流斷開了(具體場(chǎng)景可能是你的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);
}
注釋:當(dāng)沒(méi)流數(shù)據(jù)的時(shí)候蹦哼,
如果數(shù)據(jù)異常ffp->error鳄哭,那么就是ERROR,
否則就是COMPLETED纲熏。
不同的協(xié)議這里的提示不一樣的妆丘,這里我做過(guò)驗(yàn)證.
斷開wifi的時(shí)候:
a. 如果是http+flv的直播锄俄,會(huì)提示ERROR. (因?yàn)樽遠(yuǎn)ttp.c http_read讀取失敗,返回-110的ERROR)
b. 如果是rtmp的流勺拣,沒(méi)有ERROR提示(ffp->error=0)奶赠, 會(huì)提示COMPLETED。如果主播主動(dòng)關(guān)閉
都是提示COMPLETED
** 代碼分析完畢药有,再說(shuō)說(shuō)方案(參考我最近幾天的思考毅戈,之前項(xiàng)目的做法,可能也有考慮不到的地方) :**
流程和邏輯要盡量簡(jiǎn)潔愤惰,清晰苇经,因?yàn)樵胶?jiǎn)潔的邏輯,越正確宦言,bug越少扇单。
總共有三層邏輯關(guān)系:
A: 對(duì)ijkplayer底層C代碼而言,ff_ffplay.c唯一能做的就是根據(jù)各種異常奠旺,拋出對(duì)應(yīng)的錯(cuò)誤提示蜘澜。
B: 中間層API把底下的接口重新封裝,并把回調(diào)消息透?jìng)鞯缴厦?/p>
這是之前和產(chǎn)品討論的一直方案:
參考方案:(以前公司做過(guò)的線上直播項(xiàng)目:中國(guó)移動(dòng)物聯(lián)網(wǎng)監(jiān)控平臺(tái))
底層拋出的ERROR和COMPLETED凉倚, 應(yīng)用層不管拿到哪種消息,都是重連兩次嫂沉,每次間隔3秒稽寒。如果還失敗了就報(bào)直播結(jié)束(無(wú)需判斷網(wǎng)絡(luò)情況或者其它,這種方案簡(jiǎn)潔實(shí)用趟章,應(yīng)用層只有調(diào)一下重連接口即可杏糙,因?yàn)橛脩粢膊粫?huì)過(guò)度關(guān)注提示)。
我的想法基本類似:這也是FY目前iOS項(xiàng)目的做法蚓土,就是當(dāng)應(yīng)用C層收到不管是ERROR和COMPLETED宏侍, 都重連N次,每次間隔N秒蜀漆。如果還失敗了就報(bào)直播結(jié)束谅河。
如果產(chǎn)品一定要參考處理流程圖.png的做法,那最好是簡(jiǎn)化一點(diǎn)确丢,因?yàn)檫@個(gè)做法判斷有的多绷耍,建議簡(jiǎn)化應(yīng)用層的處理邏輯,我覺(jué)得應(yīng)用層的代碼不要超過(guò)10行鲜侥。
應(yīng)用層可以自己用中間層api的接口做重連褂始;
如果需要,中間層也可以提供重連接口描函,iOS就是調(diào)用的底下封裝的重連接口replay崎苗,邏輯很簡(jiǎn)單狐粱,Android的我也已經(jīng)做好:
public void replay {
setDisplay(sh); // 顯示的Surface,備份之前顯示的
reset(); //重置
if(player != null)
{
player.setDataSource(url); //設(shè)置url
}
prepareAsync(); // 準(zhǔn)備播放
}
回到我們的產(chǎn)品:因?yàn)榉?wù)器也會(huì)發(fā)主播主動(dòng)停止推流的消息胆数,而且一旦收到這個(gè)消息肌蜻,那100%就是準(zhǔn)的,所以提示語(yǔ)的話幅慌,我個(gè)人建議是:
收到服務(wù)器主播關(guān)閉直播的消息:主播關(guān)閉了直播宋欺。
其它底下拋出的消息,重連后失敗一概是:直播結(jié)束胰伍。
一語(yǔ):就是應(yīng)用層C收到中間層B的消息齿诞,重連一次(調(diào)replay),不成功的話骂租,N秒后祷杈,再重連一次,失敗了就彈出錯(cuò)誤提示渗饮。