0


iOS:OpenGLES 实验室之2D篇 第一弹 の 智能弹幕

3c06ca5da17d4d0ace3b902bc799a827.jpeg

9d7490cfb701ec1231b1a81cc3459952.gif

本文字数:3046

预计阅读时间:**15 **分钟

iOS:OpenGLES 实验室之2D篇 第一弹 の 智能弹幕

笔者之前发表的音视频文章,有图像的处理,音频的重采样等等,都属于入门级别。通过阅读它们,读者能对音视频有了了解。可在 Gitee 上面回顾。

2023年,笔者将整理下关于 OpenGLES 的实验室系列并进行发表。首先为读者带来 2D 篇的系列,它大多是 x y 坐标,不涉及 z 坐标,所以用 2D篇。内容上,它不对 OpenGLES 的基础知识进行细说与讨论。但如果对 OpenGLES 不了解或者了解一点,仍可通过本实验室系列了解 OpenGLES。它旨在激起读者的兴趣,扩展到实际的应用上。总的来说,这些实验 & Demo 将是额外的,即对基础学习的补充,通过这些它们的实践和运用,能让读者进一步了解 OpenGLES 。

前言

本次实验室带来的是《OpenGLES 实验室之2D篇 第一弹の智能弹幕》。其实这里取这个名字有点牵强,是弹幕,却不智能哈,因为它不包含人脸识别功能,使用固定的矩形区域。

首先简单介绍下智能弹幕,它是弹幕在视频播放的时候不遮挡人物。它在很多点播视频上运用,直播也有平台支持,它让弹幕更友好,提升观看视频的体验,可以说让弹幕和视频达成一个平衡。那它的实现,其实之前刘壮童靴这篇《带你实现完整的视频弹幕系统》 最后有提到弹幕防挡探索。而笔者也写过用 mask layer 蒙层的实现《iOS 弹幕系统之智能弹幕学习篇》,分别使用 CAShapeLayer & UIBezierPath ,CGGradientRef 的圆半径方向渐变 UIImage,带 Alpha 的 UIImage 等实现不遮挡的效果,都是 native 的实现,但性能和效果都比较难满足不了直播,就不再介绍了。

进入主题,使用 OpenGLES 实现智能弹幕,核心就是人景分离,简单说就是绘制两次,一次原来的视频,一次只有人物,然后叠在一起播放,所谓叠在一起,本 Demo 是基于 IJKPlayer 分两个 opengles layer 绘制。

Demo

Demo 包含 IJKPlayer 实现本次实验的改动 Git Patch(IJKPlayer 仓库比较大就没上传到 Demo)和基于CoreImage 实现的图片人脸识别弹幕的项目QHVisionDemo。

Git 地址:QHAIDanmuMan : iOS :OpenGLES 实验室之2D篇第一弹の智能弹幕

实验

效果

这是用千帆直播的直播流在 IJKPlayer 播放的效果:

45612913c3f0c469c96c49084789209c.gif

结构图

在播放器原先单层画面显示的基础上添加多一个图层画面,并对此图层开启 alpha 混合模式,再将弹幕图层放置于这两个图层之间,从而实现本次实验。

e75834e571e48fab9b55106559e5351e.png

IJKSDLGLView

1、 IJKPlayer 的显示是 IJKSDLGLView ,由于要绘制两个,所以需要将它原本实现的 render 抽离到子 view 。

因此新增 QHIJKSDLGLShowView ,主要就是调用 OpenGLES 绘制,然后 IJKSDLGLView持有两个该view,一个作为前景view,一个作为后景view。

@interface QHIJKSDLGLShowView : UIView

@property(nonatomic, readonly)        CGFloat  fps;
@property(nonatomic)        CGFloat  scaleFactor;

@property (nonatomic, weak) id<IJKSDLGLViewProtocol> protocol;

@end

@interface IJKSDLGLView : UIView <IJKSDLGLViewProtocol>

@property (nonatomic, strong) QHIJKSDLGLShowView *showFV;// 前景 view
@property (nonatomic, strong) QHIJKSDLGLShowView *showBV;// 后景 view

@end

2、绘制图像数据的控制,将原本的 display 的操作,分发到前后景 View 去分别处理。这部分逻辑其实跟原来是一模一样,只是简单抽出,由一变二。

- (void)display: (SDL_VoutOverlay *) overlay
{
    if (![self setupGLOnce])
        return;

    if (![self tryLockGLActive]) {
        if (0 == (_tryLockErrorCount % 100)) {
            NSLog(@"IJKSDLGLView:display: unable to tryLock GL active: %d\n", _tryLockErrorCount);
        }
        _tryLockErrorCount++;
        return;
    }

    _tryLockErrorCount = 0;
    // 分发到 前后景
    [self.showBV display:overlay];
    [self.showFV display:overlay];
    [self displayInternal:overlay];

    [self unlockGLActive];
}

3、分发后就会有两个 view 同时显示视频画面。由于是叠在一起,所以看不出来,但实际已经是两个了。

bd03dee5eea98b8029530a16898ea81e.png

Render

接下来就是对前景的处理,后景保持原来的绘制

原本的 IJKSDLGLView 有下面这句代码,用于创建渲染对象,它里面就是 OpenGLES 的 Program,Shader 等的执行(这里是 OpenGLES 的基础知识,可以理解创建如何绘制图像的程序)。

_renderer = IJK_GLES2_Renderer_create(overlay);

那么这里需要修改,增加前景的入参,用于区分前后景。对应的路径修改如下:

// 前景 self.bFront = YES
_renderer = QH_IJK_GLES2_Renderer_create_for(overlay, self.bFront);
 ->
 // bFront = YES
 renderer = QH_IJK_GLES2_Renderer_create_yuv420p(bFront); break;
  ->
   // 创建对应的 render
   IJK_GLES2_Renderer *renderer = IJK_GLES2_Renderer_create_base(bFront ? IJK_GLES2_getFragmentShader_yuv420p_4Front() : IJK_GLES2_getFragmentShader_yuv420p());
   ->
    // 创建片段着色器
    const char *IJK_GLES2_getFragmentShader_yuv420p_4Front()
     ->
     // 片段着色器的GLSL
        g_shader_front;

Shader

这里主要是修改 Shader 了,也是真正实现 OpenGLES 智能弹幕的关键。fsh 通过纹理坐标,输出要绘制的图像对应的像素值,也就是图片上的一个点。

那么怎么处理呢?主要逻辑是在指定区域内,如果该纹理的坐标在区域内则原样像素输出,不在区域内则将其 Alpha 值则 0 ,即透明。

代码如下:

static const char g_shader_front[] = IJK_GLES_STRING(
    precision highp float;
    varying   highp vec2 vv2_Texcoord;
    uniform         mat3 um3_ColorConversion;
    uniform   lowp  sampler2D us2_SamplerX;
    uniform   lowp  sampler2D us2_SamplerY;
    uniform   lowp  sampler2D us2_SamplerZ;
                                                     
//    uniform mediump float v_mesh[8];
                                                     
    void main()
    {
        mediump float fx = vv2_Texcoord.x;
        mediump float fy = vv2_Texcoord.y;
        
        mediump float x[4];
        mediump float y[4];
        
        x[0] = 0.3;
        x[1] = 0.6;
        x[3] = 0.3;
        x[2] = 0.6;
        
        y[0] = 0.2;
        y[1] = 0.2;
        y[3] = 0.6;
        y[2] = 0.6;
        
        mediump float a;
        mediump float b;
        mediump float c;
        mediump float d;//分别存四个向量的计算结果;
        a = (x[1] - x[0])*(fy - y[0]) - (y[1] - y[0])*(fx - x[0]);
        b = (x[2] - x[1])*(fy - y[1]) - (y[2] - y[1])*(fx - x[1]);
        c = (x[3] - x[2])*(fy - y[2]) - (y[3] - y[2])*(fx - x[2]);
        d = (x[0] - x[3])*(fy - y[3]) - (y[0] - y[3])*(fx - x[3]);
        if ((a >= 0.0 && b >= 0.0 && c >= 0.0 && d >= 0.0) || (a <= 0.0 && b <= 0.0 && c <= 0.0 && d <= 0.0)) {
            mediump vec3 yuv;
            lowp    vec3 rgb;

            yuv.x = (texture2D(us2_SamplerX, vv2_Texcoord).r - (16.0 / 255.0));
            yuv.y = (texture2D(us2_SamplerY, vv2_Texcoord).r - 0.5);
            yuv.z = (texture2D(us2_SamplerZ, vv2_Texcoord).r - 0.5);
            
            rgb = um3_ColorConversion * yuv;
            gl_FragColor = vec4(rgb, 1);
        }
        else {
            gl_FragColor = vec4(1, 1, 1, 0);
        }
    }
);

由于这里是正矩形,判断区域其实可以简单点写在 **x[0] < fx < x[2] && y[0] < fy < y[2]**。

但这里用的方式为了通用性,它兼容其他形状和多边形,它也是在下一个实验被应用到,读者可以稍微记住下该算法。它是计算点与边(两点),即三点构成的平面,类似“右手螺旋法则”判断该点是方向。然后,该点与四边的分别计算的结果,如果是同正或者同负,即为矩阵内;如果都是 0 则表示在矩阵边上;其余情况则为矩阵外。

混合

如果修改完上面的操作后再加入后面的弹幕,发现没有效果。这是虽然已经将不在区域内的 Alpha 设置 0 实现智能弹幕,但还需设置开启混合模式才能有效让 Alpha 值生效。

glEnable(GL_BLEND) // 开启混合
glBlendFunc(sourceFactor, destinationFactor) // 设置混合函数
// GL_SRC_ALPHA / GL_ONE_MINUS_SRC_ALPHA

弹幕

最后,加入弹幕 view ,记得加在前后景 view 之间喔。Demo 的弹幕是笔者开发的一个弹幕组件,读者也可以更换自己或者其他第三方的弹幕库。

- (void)addDanmu:(UIView *)view {
    [_glView insertSubview:view belowSubview:_glView.showFV];
}

CoreImage 的识别

QHVisionDemo 实现了基于 CoreImage 实现对静止图片中人脸的识别 & 智能弹幕的结合来实现

效果如下:

59049141708793963f1eead82a012b39.gif

里面使用了 CIDetector 来识别人脸区域并将数据加载到缓存里面,再由 OpenGLES 进行渲染,实现跟上述是一样的哈。

// 将图像转换为CIImage
CIImage *faceImage = [CIImage imageWithCGImage:image.CGImage];
CIDetector *faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context:nil options:opts];
// 识别出人脸数组
NSArray *features = [faceDetector featuresInImage:faceImage];

// 传入片段着色器
if (_bFront) {
    GLfloat va[_v_mesh_a.count];
    for (int i = 0; i < _v_mesh_a.count; i++) {
        va[i] = [_v_mesh_a[i] floatValue];
    }
    glUniform1fv(_v_mesh, (GLsizei)_v_mesh_a.count, va);
}

最后

本实验目的旨在实现了弹幕防挡的原理。这里仍然缺少 1、人脸识别;2、人像抠图。

在实现 IJKPlayer 智能弹幕,固定了前景区域,没有加入人脸识别。如果要实现可借助第三方的 sdk ,不过这样 Demo 修改就比较大,还有一种就是提前识别做好蒙层(点播比较多选择这种方案),再下发识别后的该蒙层数据。所以完整的智能弹幕还需要 人像识别+人像抠图。当然笔者还没实现,如果读者有实现了,欢迎分享给笔者来进一步学习。

感谢各位读者,那就下个实验,再见啦👋!

链接

标签: ios ffmpeg git

本文转载自: https://blog.csdn.net/SOHU_TECH/article/details/128739274
版权归原作者 Mo_mo??? 所有, 如有侵权,请联系我们删除。

“iOS:OpenGLES 实验室之2D篇 第一弹 の 智能弹幕”的评论:

还没有评论