IJKPLAYER源码分析-iOS端显示

1 简介

1.1 EAGL(Embedded Apple Graphics Library)

    与Android系统使用EGL连接OpenGL ES与原生窗口进行surface输出类似,iOS则用EAGLCAEAGLLayer作为OpenGL ES输出目标。

    与 Android EGL 不同的是,iOS EAGL 不会让应用直接向 BackendFrameBufferFrontFrameBuffer 进行绘制,也不会让app直接控制双缓冲区的交换,系统自己保留了操作权,以便可以随时使用 Core Animation 合成器来控制显示的最终外观。

    OpenGL ES 通过 CAEAGLLayer 与 Core Animation 连接,CAEAGLLayer 是一种特殊类型的 Core Animation 图层,它的内容来自 OpenGL ES 的 RenderBuffer,Core Animation 将 RenderBuffer 的内容与其他图层合成,并在屏幕上显示生成的图像。所以同一时刻可以有任意数量的层。Core Animation 合成器会联合这些层并在后帧缓存中产生最终的像素颜色,然后切换缓存。

    一句话就是,OpenGL ES可与CAEAGLLayer共享RenderBuffer,用EAGLContextCAEGLLayerRenderBuffer进行绑定,可实现OpenGL ES最终输出到CAEGLLayer上并显示出

来。

 

2 显示

2.1 软解

  • FFmpeg软解后的输出是AVFrame,其像素数据在uint8_t* data[AV_NUM_DATA_POINTERS]所指向的内存中;
  • 在软解码线程里解码后可以播放时,调用SDL_VoutSDL_VoutOverlayfunc_fill_frame方法,对AVFrame增加引用计数,将overlay的pixels[]也指向这块儿内存;
  • 然后将AVFrame放到FrameQueue队列尾部,待video_refresh_thread线程消费render;
  • 此后在video_refresh_thread线程里把AVFrame取出来,将pixels[]所指像素数据喂给OpenGL ES进行render;
  • 循环以上步骤;

2.1.1 像素源

    因此,FFmpeg软解后的像素数据来源了然了,来自AVFrame的uint8_t* data[AV_NUM_DATA_POINTERS]。

    此处举例说明,不需要转换像素格式的填充:

static void overlay_fill(SDL_VoutOverlay *overlay, AVFrame *frame, int planes)
{
    overlay->planes = planes;

    for (int i = 0; i < AV_NUM_DATA_POINTERS; ++i) {
        overlay->pixels[i] = frame->data[i];
        overlay->pitches[i] = frame->linesize[i];
    }
}

    需要转换像素格式的,调用libyuv或sws_scale处理,再入FrameQueue队列。 

2.2 硬解

  • videotoolbox解码后输出是CVPixelBuffer,为了与FFmpeg统一接口,硬解后将CVPixelBuffer由AVFrame的opaque指向;
  • 然后,在硬解码线程入队FrameQueue尾部,待video_refresh_thread线程消费render;
  • 随后的逻辑基本与FFmpeg软解一致,在video_refresh_thread线程里把AVFrame取出来,将pixels[]所指向的像素喂给OpenGL ES进行render;
  • 具体差异在yuv420sp_vtb_uploadTexture方法里;
  • 循环以上步骤;

2.2.1 像素源

    所以,videotoolbox硬解后的像素数据来源也清楚了,来自AVFrame的opaque,实际是CVPixelBuffer

static int func_fill_frame(SDL_VoutOverlay *overlay, const AVFrame *frame)
{
    assert(frame->format == IJK_AV_PIX_FMT__VIDEO_TOOLBOX);

    CVBufferRef pixel_buffer = CVBufferRetain(frame->opaque);
    SDL_VoutOverlay_Opaque *opaque = overlay->opaque;
    if (opaque->pixel_buffer != NULL) {
        CVBufferRelease(opaque->pixel_buffer);
    }
    opaque->pixel_buffer = pixel_buffer;
    overlay->format = SDL_FCC__VTB;
    overlay->planes = 2;

    if (CVPixelBufferLockBaseAddress(pixel_buffer, 0) != kCVReturnSuccess) {
        overlay->pixels[0]  = NULL;
        overlay->pixels[1]  = NULL;
        overlay->pitches[0] = 0;
        overlay->pitches[1] = 0;
        overlay->w = 0;
        overlay->h = 0;
        CVBufferRelease(pixel_buffer);
        opaque->pixel_buffer = NULL;
        return -1;
    }
    overlay->pixels[0]  = NULL;
    overlay->pixels[1]  = NULL;
    overlay->pitches[0] = CVPixelBufferGetWidthOfPlane(pixel_buffer, 0);
    overlay->pitches[1] = CVPixelBufferGetWidthOfPlane(pixel_buffer, 1);
    CVPixelBufferUnlockBaseAddress(pixel_buffer, 0);

    overlay->is_private = 1;

    overlay->w = (int)frame->width;
    overlay->h = (int)frame->height;
    return 0;
}

2.3 render

  • 首先让UIView的子类视频窗口的layerClass返回CAEAGLLayer;
  • 设置CAEAGLLayer的属性,创建EAGLContext,并设置render的上下文;
  • 创建FBORBO并绑定,再用所创建的EAGLContext调用renderbufferStorage,将GL_RENDERBUFFERCAEAGLLayer进行绑定;
  • RBO作为GL_COLOR_ATTACHMENT0附加到FBO上;
  • 将EAGLContext上下文切为当前所创建的Context,并用OpenGL ES开始1帧的render工作;
  • 然后,调用EAGLContext的presentRenderbuffer方法将RenderBuffer输出到CAEAGLLayer再显示;
  • 最后,将把Context切为render之前的Context;
  • 重复4~6步骤,继续render下一帧;

     让UIView子类返回CAEAGLLayer class:

+ (Class) layerClass
{
	return [CAEAGLLayer class];
}

    设置CAEAGLLayer属性为不透明,并创建EAGLContext,设置render的上下文:

- (BOOL)setupGL
{
    if (_didSetupGL)
        return YES;

    CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer;
    // 设置为不透明
    eaglLayer.opaque = YES;
    eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                    [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
                                    kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
                                    nil];

    _scaleFactor = [[UIScreen mainScreen] scale];
    if (_scaleFactor < 0.1f)
        _scaleFactor = 1.0f;

    [eaglLayer setContentsScale:_scaleFactor];

    _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (_context == nil) {
        NSLog(@"failed to setup EAGLContext\n");
        return NO;
    }

    EAGLContext *prevContext = [EAGLContext currentContext];
    [EAGLContext setCurrentContext:_context];

    _didSetupGL = NO;
    if ([self setupEAGLContext:_context]) {
        NSLog(@"OK setup GL\n");
        _didSetupGL = YES;
    }

    [EAGLContext setCurrentContext:prevContext];
    return _didSetupGL;
}

     创建FBORBO并绑定,并将RBOGL_COLOR_ATTACHMENT0附加到FBO上:

- (BOOL)setupEAGLContext:(EAGLContext *)context
{
    glGenFramebuffers(1, &_framebuffer);
    glGenRenderbuffers(1, &_renderbuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer);

    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"failed to make complete framebuffer object %x\n", status);
        return NO;
    }

    GLenum glError = glGetError();
    if (GL_NO_ERROR != glError) {
        NSLog(@"failed to setup GL %x\n", glError);
        return NO;
    }

    return YES;
}

    iOS端显示的主函数,是在iOS端SDL_Vout接口display_overlay中回调的: 

- (void)display: (SDL_VoutOverlay *) overlay
{
    if (_didSetupGL == NO)
        return;

    if ([self isApplicationActive] == NO)
        return;

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

    _tryLockErrorCount = 0;
    if (_context && !_didStopGL) {
        EAGLContext *prevContext = [EAGLContext currentContext];
        [EAGLContext setCurrentContext:_context];
        [self displayInternal:overlay];
        [EAGLContext setCurrentContext:prevContext];
    }

    [self unlockGLActive];
}

     具体执行render显示工作:

// NOTE: overlay could be NULl
- (void)displayInternal: (SDL_VoutOverlay *) overlay
{
    if (![self setupRenderer:overlay]) {
        if (!overlay && !_renderer) {
            NSLog(@"IJKSDLGLView: setupDisplay not ready\n");
        } else {
            NSLog(@"IJKSDLGLView: setupDisplay failed\n");
        }
        return;
    }

    [[self eaglLayer] setContentsScale:_scaleFactor];

    if (_isRenderBufferInvalidated) {
        NSLog(@"IJKSDLGLView: renderbufferStorage fromDrawable\n");
        _isRenderBufferInvalidated = NO;

        glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
        [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
        glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
        IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, _backingWidth, _backingHeight);
    }

    glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
    glViewport(0, 0, _backingWidth, _backingHeight);

    if (!IJK_GLES2_Renderer_renderOverlay(_renderer, overlay))
        ALOGE("[EGL] IJK_GLES2_render failed\n");

    glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
    [_context presentRenderbuffer:GL_RENDERBUFFER];

    int64_t current = (int64_t)SDL_GetTickHR();
    int64_t delta   = (current > _lastFrameTime) ? current - _lastFrameTime : 0;
    if (delta <= 0) {
        _lastFrameTime = current;
    } else if (delta >= 1000) {
        _fps = ((CGFloat)_frameCount) * 1000 / delta;
        _frameCount = 0;
        _lastFrameTime = current;
    } else {
        _frameCount++;
    }
}

    首次或overlay的format发生变更,则创建 IJK_GLES2_Renderer实例:

- (BOOL)setupRenderer: (SDL_VoutOverlay *) overlay
{
    if (overlay == nil)
        return _renderer != nil;

    if (!IJK_GLES2_Renderer_isValid(_renderer) ||
        !IJK_GLES2_Renderer_isFormat(_renderer, overlay->format)) {

        IJK_GLES2_Renderer_reset(_renderer);
        IJK_GLES2_Renderer_freeP(&_renderer);

        _renderer = IJK_GLES2_Renderer_create(overlay);
        if (!IJK_GLES2_Renderer_isValid(_renderer))
            return NO;

        if (!IJK_GLES2_Renderer_use(_renderer))
            return NO;

        IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, _backingWidth, _backingHeight);
    }

    return YES;
}

3 小节

    最后,对iOS端FFmpeg软解和硬解做一个小节:

视频显示相同点异同点
FFmpeg

1.都是通过创建EAGLContext上下文将RenderBuffer与CAEAGLLayer绑定,与OpenGL ES共享RenderBuffer,再render;

2.显示主逻辑是统一的;

1.像素数据源不同,FFmpeg软解的输出保存在AVFrame的uint8_t* data[AV_NUM_DATA_POINTERS],而videotoolbox的输出保存在AVFrame的opaque中,具体是CVPixelBuffer;

​​​​​​​

2.在给OpenGL ES喂像素数据时不同,FFmpeg解码后直接输出像素数据,因此在func_uploadTexture喂数据时直接喂raw data即可,而videotoolbox则是CVPixelBuffer;

videotoolbox

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/536245.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Socks5代理IP如何获取?如何使用?

当我们在互联网上浏览网页、下载文件或者进行在线活动时&#xff0c;隐私和安全问题常常被提及。在这样的环境下&#xff0c;一个有效的解决方案是使用Sock5IP。本教程将向您介绍Sock5IP的使用方法&#xff0c;帮助您保护个人隐私并提升网络安全。 一、什么是Sock5IP&#xff1…

Elasticsearch部署安装

环境准备 Anolis OS 8 Firewall关闭状态&#xff0c;端口自行处理 Elasticsearch&#xff1a;7.16.1&#xff08;该版本需要jdk11&#xff09; JDK&#xff1a;11.0.19 JDK # 解压 tar -zxvf jdk-11.0.19_linux-x64_bin.tar.gz# 编辑/etc/profile vim /etc/profile# 加入如下…

Linux内核之Binder驱动红黑树:rb_root用法实例(四十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

SpringBoot - Logback 打印第三方 Jar 日志解决方案

问题描述 最近碰到一个很苦恼的问题&#xff0c;就是第三方的 Jar 在自己项目里日志可以正常输出&#xff0c;但是一旦被引用到其他项目里&#xff0c;就日志死活打不出来…… 解决方案 这是原来的配置 - logback.xml <?xml version"1.0" encoding"UTF-8…

【JavaScript】DOM编程-什么是事件

今天几号 实现效果&#xff1a; 在这个示例中我们的事件三要素都是什么呢&#xff1f; &#xff08;1&#xff09;事件源&#xff0c;事件被触发的对象 谁&#xff1a;按钮 &#xff08;2&#xff09;事件类型&#xff0c;如何触发&#xff0c;什么事件&#xff0c;比如鼠标…

SCI一区 | Matlab实现INFO-TCN-BiGRU-Attention向量加权算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测

SCI一区 | Matlab实现INFO-TCN-BiGRU-Attention向量加权算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现INFO-TCN-BiGRU-Attention向量加权算法优化时间卷积双向门控循环单元注意力机制多变量时间序列预测预测效果基本介绍模型描述程…

详解小度Wi-Fi内部芯片及电路原理图分析

小度随身WiFi是一款便携式USB路由器&#xff0c;它实现了用户跨终端联网&#xff0c;随身携带&#xff0c;可以在室内实现免费WiFi覆盖。外形美观&#xff0c;小巧便携。 这一款小度WiFi采用的主芯片是MT7601UN&#xff0c;一款高度集成的Wi-Fi单芯片&#xff0c;支持150 Mbp…

Java工具类:批量发送邮件(带附件)

​ 不好用请移至评论区揍我 原创代码&#xff0c;请勿转载&#xff0c;谢谢&#xff01; 一、介绍 用于给用户发送特定的邮件内容&#xff0c;支持附件、批量发送邮箱账号必须要开启 SMTP 服务&#xff08;具体见下文教程&#xff09;本文邮箱设置示例以”网易邮箱“为例&…

PyTorch-Lightning:trining_step的自动优化

文章目录 PyTorch-Lightning&#xff1a;trining_step的自动优化总结&#xff1a; class _ AutomaticOptimization()def rundef _make_closuredef _training_stepclass ClosureResult():def from_training_step_output class Closure PyTorch-Lightning&#xff1a;trining_ste…

嵌入式单片机入职第二天-EEPROM与IIC

上午&#xff1a; 1.安装Jlink驱动&#xff0c;死活没反应&#xff0c;因为昨天才装完系统&#xff0c;领导让我装电脑主板驱动 领导方法进惠普官网通过查询电脑型号&#xff0c;里面几十个驱动搞得我眼花&#xff0c;领导告诉我进官网就去开会了&#xff0c;可能因为是外网&…

Win11 WSL2 install Ubuntu20.04 and Seismic Unix

Win11系统&#xff0c;先启用或关闭Windows功能&#xff0c;勾选“适用于Linux的Windows子系统”和“虚拟机平台”两项 设置wsl默认版本为wsl2&#xff0c;并更新 wsl --list --verbose # 查看安装版本及内容 wsl --set-default-version 2 # 设置wsl默认版本为wsl2 # 已安装…

nvm安装详细教程(安装nvm、node、npm、cnpm、yarn及环境变量配置)

一、安装nvm 1. 下载nvm 点击 网盘下载 进行下载 2、双击下载好的 nvm-1.1.12-setup.zip 文件 3.双击 nvm-setup.exe 开始安装 4. 选择我接受&#xff0c;然后点击next 5.选择nvm安装路径&#xff0c;路径名称不要有空格&#xff0c;然后点击next 6.node.js安装路径&#…

智慧矿山视频智能监控与安全监管方案

一、行业背景 随着全球能源需求的日益增长&#xff0c;矿业行业作为国民经济的重要支柱&#xff0c;其发展日益受到广泛关注。然而&#xff0c;传统矿山管理模式的局限性逐渐显现&#xff0c;如生产安全、人员监管、风险预警等方面的问题日益突出。因此&#xff0c;智慧矿山智…

【算法练习】30:快速排序学习笔记

一、快速排序的算法思想 原理&#xff1a;快速排序基于分治策略。它的基本思想是选择一个元素作为“基准”&#xff0c;将待排序序列划分为两个子序列&#xff0c;使得左边的子序列中的所有元素都小于基准&#xff0c;右边的子序列中的所有元素都大于基准。这个划分操作被称为分…

【Python函数和类3/6】函数的返回值

目录 知识回顾 目标 函数的返回值 Tips 练习 ​编辑return的其它特性 任意类型的返回值 返回多个值 return的位置 小结 局部变量 局部变量的作用域 全局变量 全局变量的作用域 同名变量 pi的作用域 总结 知识回顾 在上篇博客中&#xff0c;我们学习给函数设置参…

集群开发学习(一)(安装GO和MySQL,K8S基础概念)

完成gin小任务 参考文档&#xff1a; https://www.kancloud.cn/jiajunxi/ginweb100/1801414 https://github.com/hanjialeOK/going 最终代码地址&#xff1a;https://github.com/qinliangql/gin_mini_test.git 学习 1.安装go wget https://dl.google.com/go/go1.20.2.linu…

玩机进阶教程------手机定制机 定制系统 解除系统安装软件限制的一些步骤解析

定制机 在于各工作室与商家合作定制rom中有一些定制机。限制用户私自安装第三方软件。或者限制解锁 。无法如正常机登陆账号等等。定制机一般用于固定行业或者一些部门。专机专用。例如很多巴枪扫描机型等等。或者一些小牌机型。对于没有官方包的机型首先要导出各个分区来制作…

【OpenVINO™】使用 OpenVINO™ C# API 部署 YOLOv9 目标检测和实例分割模型(上篇)

YOLOv9模型是YOLO系列实时目标检测算法中的最新版本&#xff0c;代表着该系列在准确性、速度和效率方面的又一次重大飞跃。它通过引入先进的深度学习技术和创新的架构设计&#xff0c;如通用ELAN&#xff08;GELAN&#xff09;和可编程梯度信息&#xff08;PGI&#xff09;&…

复合数据类型

在C语言中&#xff0c;复合数据类型是指那些可以包含多个简单数据类型的数据类型。以下是一些常见的C语言复合数据类型以及相关的例子&#xff1a; 1. 数组&#xff08;Arrays&#xff09;&#xff1a; 数组是一种可以存储多个相同类型数据的数据结构。例如&#xff1a; #in…

从像素游戏到 3A 大作的游戏引擎/框架

Bevy —— Rust 构建的游戏引擎 Bevy 是一款由 Rust 语言构建且简单明了的数据驱动的游戏引擎&#xff0c;并将永远保持开源且免费。 Mach —— Zig 游戏引擎和图形工具包 Mach 是一个 Zig 游戏引擎和图形工具包&#xff0c;用于构建高性能、真正跨平台、健壮且模块化的游戏&…