一个h264视频的渲染问题的解决过程

前段时间在学音视频的过程中想用ffmpeg解码h264,然后通过opengles 来渲染,于是找了些网上的资料参考,实现了视频的解码和渲染。
解码部分:
参考ffplay ,通过demuxer和decoder 两个对象实现视频的解封装和解码,两个对象有各自的线程,确保性能不受影响

音视频同步:
视频向音频靠齐,通过对比 pts ,实现方法为

  • (BOOL)scheduleVideoFrame:(AVFrame*)avFrame fps:(double)fps frame_delay:(double)frame_delay

渲染部分:
由于测试的视频都是使用yuv420编码,因此使用了Y和UV双平面编码的方式,将解码后的数据打包到CVPixelBufferRef对象中,传递给openglES进行渲染。也可以直接传递数据给openglES,但不能使用CVOpenGLESTextureCacheCreateTextureFromImage这个方法,可以参考注释掉的代码使用glTexImage2D来载入纹理数据。
代码已经上传到github : https://github.com/fingerplay/FFMpegDecodeDemo,有需要的可以下载下来看看

问题

在测试过程中发现有个别视频显示的不对,例如resource.bundle里面的sintel.mov,显示如下:

IMG_0444.PNG

视频只有上半部分是正常的,下半部分全是绿色,但是用ffplay却可以正常显示。如果如果按下面的代码把pixelBuffer的高度改为原来的一半,倒是显示正常了, 这是为什么呢?

[attributes setObject:[NSNumber numberWithInt:frame->height/2] forKey: (NSString*)kCVPixelBufferHeightKey];

我对比了我自己的代码和 ffplay的源码,发现有两个不同之处:

  1. 我的代码直接使用了 openGL ES进行渲染,而ffplay里面使用了SDL作为渲染引擎,会不会SDL里面做了什么特殊的处理呢?
  2. 我的代码用了CVPixelBufferRef对解码后的数据进行包装,而ffplay 是直接传递数据给 SDL,会不会是从AVFrame到CVPixelBufferRef的过程中出现了数据丢失呢?
    于是我便尝试接入SDL来渲染,发现确实能正常显示。
    接入的代码见https://github.com/fingerplay/FFMpegDecodeDemo/commit/d709e14599acd35143649daed2458952c4731325

这就证明了SDL里面确实是有一些特殊处理 ,于是我又仔细研究SDL的源码, 发现它里面也是使用的openGLES,只不过对YUV的渲染不是使用双平面而是三平面,也就是Y、U、V各自绘制一次,具体代码可以看GLES2_CreateTexture和GLES2_UpdateTextureYUV这个方法

GLES2_CreateTexture(SDL_Renderer *renderer, SDL_Texture *texture)

 static int
GLES2_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
                    const SDL_Rect * rect,
                    const Uint8 *Yplane, int Ypitch,
                    const Uint8 *Uplane, int Upitch,
                    const Uint8 *Vplane, int Vpitch)

然后我改成了三平面渲染(https://github.com/fingerplay/FFMpegDecodeDemo/commit/efb90950508cfdd3b4cef44ed2a4d4ace74fe039), 得到了下面的图像

IMG_0447.PNG

这效果看起来还不如之前的,我不禁再次思考是不是这个视频的数据本身就有问题,只是SDL对其进行了纠错。
我仔细对比了我的代码和SDL的代码,发现SDL的方法会多传YUV三个通道的数据长度这些参数,而我的代码并没有用到这些,会不会是数据长度的问题呢?我又对比了一下AVFrame的数据,发现正常的图像解码出来 Y通道的lineSize和 图像的宽度是一样的,而有问题的图像Y通道的lineSize比图像的宽度大了几个字节,会不会正好就是多处的这几个字节导致了openGL渲染时下一行的数据错位呢? 带着疑问我又查看了SDL跟渲染相关的另外几个方法,发现在GLES2_TexSubImage2D这个方法里,对比了lineSize和图像宽度,如果两者相等就直接使用传进来的数据,也就是frame->data,如果不想等,则截取data前面width个字节

static int
GLES2_TexSubImage2D(GLES2_DriverContext *data, GLenum target, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels, GLint pitch, GLint bpp)
{
    Uint8 *blob = NULL;
    Uint8 *src;
    int src_pitch;
    int y;

    /* Reformat the texture data into a tightly packed array */
    src_pitch = width * bpp;
    src = (Uint8 *)pixels;
    if (pitch != src_pitch) {
        blob = (Uint8 *)SDL_malloc(src_pitch * height);
        if (!blob) {
            return SDL_OutOfMemory();
        }
        src = blob;
        for (y = 0; y < height; ++y)
        {
            SDL_memcpy(src, pixels, src_pitch);
            src += src_pitch;
            pixels = (Uint8 *)pixels + pitch;
        }
        src = blob;
    }

    data->glTexSubImage2D(target, 0, xoffset, yoffset, width, height, format, type, src);
    if (blob) {
        SDL_free(blob);
    }
    return 0;
}

而通过对源码的断点调试,也确实证明了我的猜想,SDL对 sintel.mov这个视频的每一帧图像的每一行数据都进行了截断,并把多余的字节移到了下一行。于是我仿照SDL的代码,写了一个类似的方法对AVFrame的数据进行修复再传给openGLES(https://github.com/fingerplay/FFMpegDecodeDemo/commit/c826398c0ed2784aec5fe70ccef0ff83ca9072a4),果然图像正常显示了

IMG_0448.PNG

结论:

  • 由于linesize>width,导致图像数据错位,而无法显示,可以对实际显示的大小对数据进行调整。
  • 而使用CVPixelBufferRef 进行包装的数据,猜测是因为行对齐的原因,多余的数据被忽略,从而导致视频高度只有原来的一半。

知识扩展

关于linesize 为什么会大于width, 可以看这篇文章,linesize是如何计算的
https://www.jianshu.com/p/aaef3631a802

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

推荐阅读更多精彩内容