一開始我嘗試是通過設(shè)置ijkplayer的參數(shù)去修改延遲锻拘,參數(shù)的修改能把ijkplayer的開播延遲拉到200ms左右绪杏,但是隨著播放時(shí)間增加延遲也在增加下愈,然后帶著問題去網(wǎng)上尋找答案,找到暴走大牙和Gongjia兩位大神的解決方案蕾久,但是這種方案僅適用于帶有音頻流的势似,現(xiàn)在適配的流僅有視頻流,用了這兩位的方案后僧著,丟幀是可以履因,但是延遲的問題并沒有解決,因?yàn)闆]有音頻流的視頻的時(shí)間基準(zhǔn)是用視頻流的時(shí)間盹愚,丟完幀后視頻會(huì)卡頓等待視頻的時(shí)間基準(zhǔn)栅迄。后來我在ijkplayer的issue上找了很多關(guān)于卡頓的問題,都沒有找到解決方案皆怕,后面嘗試通過變速的方式去解決這個(gè)問題毅舆,找到了Mr_xkHuang的一篇ijkplayer-音視頻變速播放實(shí)現(xiàn),才了解到了單視頻流的視頻和有音視頻流的視頻關(guān)于時(shí)間基準(zhǔn)的差別愈腾。修改了時(shí)間基準(zhǔn)之后憋活,長(zhǎng)時(shí)直播的延遲問題解決了。
ff_ffplay.c代碼修改如下:
static int read_thread(void *arg)
{
.....
if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
stream_component_open(ffp,st_index[AVMEDIA_TYPE_AUDIO]);
} else {
//添加新代碼
ffp->av_sync_type = AV_SYNC_EXTERNAL_CLOCK;
//注釋掉原有代碼
// ffp->av_sync_type = AV_SYNC_VIDEO_MASTER;
is->av_sync_type = ffp->av_sync_type;
}
.....
}
經(jīng)過上面的修改虱黄,長(zhǎng)時(shí)放置直播軟解碼延遲穩(wěn)定在400ms左右硬解碼延遲穩(wěn)定在200ms左右悦即,但是出現(xiàn)了一個(gè)更加嚴(yán)重的問題,那就是閃退
代碼會(huì)在renderer_yuv420sp_vtb.m這個(gè)類
static GLboolean yuv420sp_vtb_uploadTexture(IJK_GLES2_Renderer *renderer, SDL_VoutOverlay *overlay)
{
.....
}
這個(gè)方法內(nèi)的隨機(jī)幾行代碼奔潰橱乱,報(bào)的錯(cuò)誤是EXC_BAD_ACCESS辜梳,通過打開僵尸對(duì)象檢測(cè),發(fā)現(xiàn)是
CVPixelBufferRef pixel_buffer
這個(gè)對(duì)象的原因泳叠,然后通過和一個(gè)做c語(yǔ)言的大神溝通后他告訴我要從對(duì)象初始化和查對(duì)象被釋放這兩個(gè)地方入手作瞄。
經(jīng)過一番查找,找到pixel_buffer是通過SDL_VoutOverlay *overlay這個(gè)類拿到的析二,然后我就在ijksdl_vout_overlay_videotoolbox.m這個(gè)類的overlay的初始化方法里面加了
SDL_VoutOverlay *SDL_VoutVideoToolBox_CreateOverlay(int width, int height, SDL_Vout *display)
{
...
//add
if (opaque->pixel_buffer != NULL) {
CVBufferRelease(opaque->pixel_buffer);
}
opaque->pixel_buffer = NULL;
return overlay;
}
當(dāng)時(shí)我是用的模擬器測(cè)試的粉洼,結(jié)果很美好,沒有閃退了叶摄,年輕的我以為bug已經(jīng)解決了,但是我想用真機(jī)測(cè)試下延遲問題時(shí)安拟,突然發(fā)現(xiàn)閃退依然存在蛤吓。然后我就繼續(xù)去找還有沒有哪里有使用pixel_buffer這個(gè)結(jié)構(gòu)體的。但是僅有創(chuàng)建pixel_buffer的ijksdl_vout_overlay_videotoolbox.m和使用pixel_buffer的renderer_yuv420sp_vtb.m這兩個(gè)類使用了pixel_buffer糠赦,一時(shí)間大神給的方向找不到路了会傲。
后面我考慮到既然是EXC_BAD_ACCESS锅棕,那么久應(yīng)該是野指針的問題,我就沖這個(gè)方向入手淌山,在使用pixel_buffer的地方對(duì)這個(gè)結(jié)構(gòu)體進(jìn)行retain操作裸燎,增加它的引用計(jì)數(shù)使它不被釋放,結(jié)果是有一點(diǎn)用處泼疑,奔潰的概率降低了德绿,以前1-5分鐘就會(huì)奔潰的代碼現(xiàn)在可以到20-30分鐘奔潰,這讓我以為找到了一條正確的路退渗,結(jié)果確實(shí)我把引用計(jì)數(shù)增加做到了極致移稳,仍然會(huì)有閃退。
這時(shí)候我知道我是找錯(cuò)了方向会油,之后想到pixel_buffer是通過解碼后的frame數(shù)據(jù)轉(zhuǎn)換而來的个粱,有沒有可能是frame被釋放了導(dǎo)致pixel_buffer這個(gè)結(jié)構(gòu)體內(nèi)部的指針形成野指針。帶著這樣的疑問翻翩,我開始打印所有釋放frame的地方都许,結(jié)果真的找到了,每次奔潰的時(shí)候都連著調(diào)用了兩次釋放frame嫂冻。后面我順著這個(gè)釋放的流程胶征,將渲染與釋放的各個(gè)地方按上打印,終于理清楚了ijkplayer渲染的流程絮吵。
通過對(duì)這里流程的理清楚弧烤,我發(fā)現(xiàn)終于找到了為什么會(huì)發(fā)生這樣的問題,既然知道了是由于在渲染之前frame就被釋放了,這樣就好解決問題了蹬敲。
整體是frame在還么有進(jìn)行渲染就進(jìn)行了引用釋放暇昂,釋放后frame在進(jìn)行渲染時(shí)就會(huì)導(dǎo)致上面的情況
我的最終如下:
修改對(duì)ijksdl_vout.h 文件overlay的定義位置添加了一個(gè)屬性:
struct SDL_VoutOverlay {
//add
bool nowUseing;//當(dāng)前是否還在使用中
}
然后在ijksdl_vout_overlay_videotoolbox.m這個(gè)類中
//這是overlay的frame數(shù)據(jù)重載方法
static int func_fill_frame(SDL_VoutOverlay *overlay, const AVFrame *frame)
{
//add
// printf("new func_fill_frame :%p\n",overlay);
if (overlay->NowUseing) {//如果當(dāng)前frame正在使用中,就不進(jìn)行數(shù)據(jù)加載
// printf("nowUsing want new func_fill_frame:%p\n",overlay);
return 1;
}
overlay->nowUseing = true;//數(shù)據(jù)重載后將overlay的使用狀態(tài)置為true
....
}
//overlay的初始化方法
SDL_VoutOverlay *SDL_VoutVideoToolBox_CreateOverlay(int width, int height, SDL_Vout *display)
{
.....
//add
overlay->nowUseing = false;//初始化nowUsing
//初始化pixel_buffer
if (opaque->pixel_buffer != NULL) {
CVBufferRelease(opaque->pixel_buffer);
}
opaque->pixel_buffer = NULL;
return overlay;
}
在renderer_yuv420sp_vtb.m這個(gè)類中
//渲染的方法
static GLboolean yuv420sp_vtb_uploadTexture(IJK_GLES2_Renderer *renderer, SDL_VoutOverlay *overlay)
{
//add
if (!overlay->nowUseing) {
return GL_FALSE;
}
.....
//add
// printf("display over:%p\n",overlay);
overlay->nowUseing = false;
return GL_TRUE;
}
在ff_ffplay.c這個(gè)類中
//這是item的引用釋放方法伴嗡,修改為如果該frame還沒有渲染急波,這不釋放引用。
static void frame_queue_unref_item(Frame *vp)
{
//添加這行瘪校,防止剛啟動(dòng)APP或者切換前后臺(tái)bmp為null澄暮,bmp->nowUseing閃退
if (!vp->bmp) {
av_frame_unref(vp->frame);
vp->frame = NULL;
SDL_VoutUnrefYUVOverlay(vp->bmp);
avsubtitle_free(&vp->sub);
return;
}
if (vp->bmp->nowUseing) {
printf("nowUsing wait av_frame_unref:%p\n",vp->bmp);
}else {
// printf("SDL_VoutUnrefYUVOverlay(vp->bmp):%p\n",vp->bmp);
av_frame_unref(vp->frame);
vp->frame = NULL;
SDL_VoutUnrefYUVOverlay(vp->bmp);
avsubtitle_free(&vp->sub);
}
}
這個(gè)解決辦法并不完美,只是粗暴的處理不讓程序崩潰阱扬,我覺得最好的解決辦法應(yīng)該是找到為什么overlay會(huì)在使用前釋放泣懊。然后讓這種情況不再出現(xiàn)。最近沒有時(shí)間去處理這個(gè)問題麻惶,后面有時(shí)間了再來解決馍刮。