ijkplayer 单视频流直播延迟问题解决过程

一开始我尝试是通过设置ijkplayer的参数去修改延迟,参数的修改能把ijkplayer的开播延迟拉到200ms左右,但是随着播放时间增加延迟也在增加,然后带着问题去网上寻找答案,找到暴走大牙Gongjia两位大神的解决方案,但是这种方案仅适用于带有音频流的,现在适配的流仅有视频流,用了这两位的方案后,丢帧是可以,但是延迟的问题并没有解决,因为没有音频流的视频的时间基准是用视频流的时间,丢完帧后视频会卡顿等待视频的时间基准。后来我在ijkplayer的issue上找了很多关于卡顿的问题,都没有找到解决方案,后面尝试通过变速的方式去解决这个问题,找到了Mr_xkHuang的一篇ijkplayer-音视频变速播放实现,才了解到了单视频流的视频和有音视频流的视频关于时间基准的差别。修改了时间基准之后,长时直播的延迟问题解决了。
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;
    }
.....

}


经过上面的修改,长时放置直播软解码延迟稳定在400ms左右硬解码延迟稳定在200ms左右,但是出现了一个更加严重的问题,那就是闪退

代码会在renderer_yuv420sp_vtb.m这个类


static GLboolean yuv420sp_vtb_uploadTexture(IJK_GLES2_Renderer *renderer, SDL_VoutOverlay *overlay)
{
.....
}

这个方法内的随机几行代码奔溃,报的错误是EXC_BAD_ACCESS,通过打开僵尸对象检测,发现是

CVPixelBufferRef pixel_buffer

这个对象的原因,然后通过和一个做c语言的大神沟通后他告诉我要从对象初始化和查对象被释放这两个地方入手。
经过一番查找,找到pixel_buffer是通过SDL_VoutOverlay *overlay这个类拿到的,然后我就在ijksdl_vout_overlay_videotoolbox.m这个类的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;
}

当时我是用的模拟器测试的,结果很美好,没有闪退了,年轻的我以为bug已经解决了,但是我想用真机测试下延迟问题时,突然发现闪退依然存在。然后我就继续去找还有没有哪里有使用pixel_buffer这个结构体的。但是仅有创建pixel_buffer的ijksdl_vout_overlay_videotoolbox.m和使用pixel_buffer的renderer_yuv420sp_vtb.m这两个类使用了pixel_buffer,一时间大神给的方向找不到路了。
后面我考虑到既然是EXC_BAD_ACCESS,那么久应该是野指针的问题,我就冲这个方向入手,在使用pixel_buffer的地方对这个结构体进行retain操作,增加它的引用计数使它不被释放,结果是有一点用处,奔溃的概率降低了,以前1-5分钟就会奔溃的代码现在可以到20-30分钟奔溃,这让我以为找到了一条正确的路,结果确实我把引用计数增加做到了极致,仍然会有闪退。
这时候我知道我是找错了方向,之后想到pixel_buffer是通过解码后的frame数据转换而来的,有没有可能是frame被释放了导致pixel_buffer这个结构体内部的指针形成野指针。带着这样的疑问,我开始打印所有释放frame的地方,结果真的找到了,每次奔溃的时候都连着调用了两次释放frame。后面我顺着这个释放的流程,将渲染与释放的各个地方按上打印,终于理清楚了ijkplayer渲染的流程。

通过对这里流程的理清楚,我发现终于找到了为什么会发生这样的问题,既然知道了是由于在渲染之前frame就被释放了,这样就好解决问题了。
整体是frame在还么有进行渲染就进行了引用释放,释放后frame在进行渲染时就会导致上面的情况

我的最终如下:
修改对ijksdl_vout.h 文件overlay的定义位置添加了一个属性:

struct SDL_VoutOverlay {

    //add
    bool nowUseing;//当前是否还在使用中
}

然后在ijksdl_vout_overlay_videotoolbox.m这个类中

//这是overlay的frame数据重载方法
static int func_fill_frame(SDL_VoutOverlay *overlay, const AVFrame *frame)
{
//add
//    printf("new func_fill_frame :%p\n",overlay);
if (overlay->NowUseing) {//如果当前frame正在使用中,就不进行数据加载
//         printf("nowUsing want  new func_fill_frame:%p\n",overlay);
        return 1;
    }
    overlay->nowUseing = true;//数据重载后将overlay的使用状态置为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这个类中

//渲染的方法
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这个类中

//这是item的引用释放方法,修改为如果该frame还没有渲染,这不释放引用。
static void frame_queue_unref_item(Frame *vp)
{
//添加这行,防止刚启动APP或者切换前后台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);
    }
}

这个解决办法并不完美,只是粗暴的处理不让程序崩溃,我觉得最好的解决办法应该是找到为什么overlay会在使用前释放。然后让这种情况不再出现。最近没有时间去处理这个问题,后面有时间了再来解决。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353