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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

KKVIEW远程远程访问家里电脑

远程访问家里电脑&#xff1a;简易指南与价值所在 在数字化时代&#xff0c;电脑已成为我们日常生活和工作中不可或缺的工具。有时&#xff0c;我们可能在外出时急需访问家中电脑里的某个文件或应用&#xff0c;这时&#xff0c;远程访问家里电脑就显得尤为重要。本文将简要介…

微服务-7 Docker

一、镜像、容器、仓库 容器是镜像的实例&#xff0c;仓库中存储着镜像。 二、镜像的操作 三、容器的操作 创建容器停止容器&#xff0c;查看后发现没有了(docker ps 默认只展示没有停止的) docker ps -a (可以展示运行中和停止的镜像)删除容器&#xff1a;(docker rm 不能删除…

突破界限 千视将在 NAB 2024 展会上展示领先的 AV over IP 技术

突破界限&#xff01;千视将在 NAB 2024 展会上展示领先的 AV over IP技术 作为AV over IP领域的先驱者&#xff0c;Kiloview将于2024年4月14日至17日在NAB展会&#xff08;展台号&#xff1a;SU6029&#xff09;隆重登场&#xff0c;展示我们领先业界的AV over IP产品、解决方…

【问题】解决1130-Host‘ ‘is not allowed to connect to this MySQL 本地无法连接服务器的数据库

【问题】解决1130-Host‘ ‘is not allowed to connect to this MySQL 本地无法连接服务器的数据库 原因: 默认mysql只允许 localhost 本地访问数据库, 解决方法 将 localhost 改为 % 所有 第一步 回车 输入密码 mysql -u root -p 第二步 切换数据库 use mysql 第三步 更新所…

Day96:云上攻防-云原生篇Docker安全系统内核版本漏洞CDK自动利用容器逃逸

目录 云原生-Docker安全-容器逃逸&系统内核漏洞 云原生-Docker安全-容器逃逸&docker版本漏洞 CVE-2019-5736 runC容器逃逸(需要管理员配合触发) CVE-2020-15257 containerd逃逸(启动容器时有前提参数) 云原生-Docker安全-容器逃逸&CDK自动化 知识点&#xff1…

Ubuntu下配置Android NDK环境

Android-NDK的下载 下载Android-NDK wget -c http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin 执行bin文件&#xff08;即解压&#xff09; ./android-ndk-r10c-linux-x86_64.bin Android-NDK的配置 要想使用Android-NDK&#xff0c;还需要进行环境变量…

GitHub repository - Code - Issues - Pull Requests - Wiki

GitHub repository - Code - Issues - Pull Requests - Wiki 1. Code2. Issues3. Pull Requests4. WikiReferences 1. Code 显示该仓库中的文件列表。仓库名下方是该仓库的简单说明和 URL. 2. Issues 用于 BUG 报告、功能添加、方向性讨论等&#xff0c;将这些以 Issue 形式进…

less+rem适配+媒体查询布局(主流)

rem适配布局 一.rem基础二.媒体查询1.概念2.语法&#xff08;1&#xff09;.mediatype查询类型&#xff08;2&#xff09;.关键字&#xff08;3&#xff09;.媒体特性&#xff08;4&#xff09;.应用 3.媒体查询rem实现元素动态大小变化4.引入资源&#xff08;针对不同媒体查询…

JSONP是跨域资源共享的古老技术吗

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

前端和后端解决跨域问题的方法

目前很多java web开发都是采用前后端分离框架进行开发&#xff0c;相比于单体项目容易产生跨域问题。 一、跨域问题CORS 1.什么是跨域问题&#xff1f; 后端接收到请求并返回结果了&#xff0c;浏览器把这个响应拦截了。 2.跨域问题是怎么产生的&#xff1f; 浏览器基于同源…

spring boot 集成rocketMq + 基本使用

1. RocketMq基本概念 1. NameServer 每个NameServer结点之间是相互独立&#xff0c;彼此没有任何信息交互 启动NameServer。NameServer启动后监听端口&#xff0c;等待Broker、Producer、Consumer连接&#xff0c; 相当于一个路由控制中心。主要是用来保存topic路由信息&#…

ETL中如何运用好MQ消息集成

一、ETL的主要作用 ETL&#xff08;Extract, Transform, Load&#xff09;是数据仓库中的关键环节&#xff0c;其主要作用是将数据从源系统中抽取出来&#xff0c;经过转换和清洗后加载到数据仓库中。具体而言&#xff1a; Extract&#xff08;抽取&#xff09;&#xff1a;从…

Ubuntu 安装Java、Git、maven、Jenkins等持续集成环境

Ubuntu 持续集成 安装OpenJdk 查看所有可安装的 JDK 版本 apt list OpenJDK\*使用 apt 安装 JDK&#xff08;以 11为例&#xff09;,最好是用11&#xff0c;java8对应的jenkins会有兼容问题。 sudo apt install openjdk-11-jdk openjdk-11-jre安装成功后&#xff0c;可以使用以…

WS2812B彩灯

目录 1、介绍 2、参数 3、引脚功能 4、应用电路 5、Code 1、介绍 WS2812是一种智能控制LED灯源&#xff0c;集成了控制电路和RGB芯片在一个5050封装组件中。它的主要特点和技术规格如下&#xff1a; 集成设计&#xff1a;WS2812将控制电路和RGB芯片集成在同一个封装中&…

软考高级架构师:数据库模式概念和例题

一、AI 讲解 数据库模式分为三个层次&#xff1a;外模式、概念模式和内模式。这三个层次分别对应不同的抽象级别&#xff0c;帮助数据库管理员和用户以不同的视角理解数据库结构。 外模式&#xff08;用户级&#xff09;&#xff1a;是数据库用户的视图。每个用户可以通过外模…

HarmonyOS NEXT应用开发—在Native侧实现进度通知功能

介绍 本示例通过模拟下载场景介绍如何将Native的进度信息实时同步到ArkTS侧。 效果图预览 使用说明 点击“Start Download“按钮后&#xff0c;Native侧启动子线程模拟下载任务Native侧启动子线程模拟下载&#xff0c;并通过Arkts的回调函数将进度信息实时传递到Arkts侧 实…

神经网络背后的数学原理

原文地址&#xff1a;The Math Behind Neural Networks 2024 年 3 月 29 日 深入研究现代人工智能的支柱——神经网络&#xff0c;了解其数学原理&#xff0c;从头开始实现它&#xff0c;并探索其应用。 神经网络是人工智能 &#xff08;AI&#xff09; 的核心&#xff0c;为…

智能网络新纪元:机器学习赋能未来计算机网络高速发展

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…

软件无线电系列——多率信号处理之抽取

本节目录 一、等效基带谱 二、抽取概念 三、低通信号的整数倍抽取 四、数字带通信号的抽取 1、整带抽取 2、带通信号的正交复抽取 3、带通信号的正交实抽取本节内容 一、等效基带谱 对于任何采样频率为fs的实采样信号&#xff0c;无论是Nyquist采样还是带通采样&#xff0c;采…

企业培训系统私有化解决方案:PlayEdu

PlayEdu&#xff1a;打造私有化的企业智慧教育平台&#xff0c;赋能全员高效成长&#xff01;- 精选真开源&#xff0c;释放新价值。 概览 随着企业不断发展及市场竞争加剧&#xff0c;内部培训的重要性日益凸显。然而&#xff0c;在实施过程中&#xff0c;如何确保培训内容与…