IJKPLAYER源码分析-iOS端显示

news2024/11/28 2:27:52

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.coloradmin.cn/o/1587142.html

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

相关文章

maven package的时候@Value

运行时候没问题 ,但是在maven package的时候Value报错 代码如上 ,说红框内的minio.endpoint找不到,我对了一遍配置是能找到的 解决办法,再pom.xml加入如下代码,确保把你的配置文件路径引入进去 <resources> <resource><directory>src/main/resources</…

设计模式-组合模式(Composite Pattern)

1. 概念 组合模式是一种结构型设计模式&#xff0c;它允许将对象组合成树状的层次结构&#xff0c;用来表示“整体-部分”的关系。 2. 原理结构图 原理图 抽象角色&#xff08;Component&#xff09;&#xff1a;这是组合模式的核心&#xff0c;它定义了树叶和树枝构件的公…

多模态 ——LLaVA 集成先进图像理解与自然语言交互GPT-4的大模型

概述 提出了一种大型模型 LLaVA&#xff0c;它使用 GPT-4 生成多模态语言图像指令跟随数据&#xff0c;并利用该数据将视觉和语言理解融为一体。初步实验表明&#xff0c;LLaVA 展示了出色的多模态聊天能力&#xff0c;在合成多模态指令上的表现优于 GPT-4。 在科学质量保证中…

Web程序设计-实验01 HTML与CSS基础

【实验主题】 影视详情页设计 【实验任务】 1、浏览并分析多个影视详情页面&#xff08;详见参考资源&#xff0c;建议自行搜索更多影视网站&#xff09;的主要元素构成和版面设计&#xff0c;借鉴并构思预期效果。 2、新建 index.html文件&#xff0c;合理运用HTML标记编写…

基于GSP工具箱的NILM算法matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于GSP工具箱的NILM算法matlab仿真。GSP是图形信号处理的缩写&#xff0c;GSP非常适合对未知数据进行分类&#xff0c;尤其是当训练数据非常短时。GSPBox的基本理论是谱图论和…

SEDEX验厂是什么?

SEDEX验厂是一种审核流程&#xff0c;其主要目的是评估工厂在劳工标准、环境管理、健康与安全以及管理体系等方面的合规性。以下是关于SEDEX验厂的一些关键信息&#xff1a; SEDEX验厂审核标准主要包括以下几个方面&#xff1a; 劳工标准和劳动法规&#xff1a;工厂必须遵守当…

Redis中的集群(七)

集群 ASK错误 ASKING命令 ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识&#xff0c;以下是该命令的伪代码实现: def ASKING(): # 打开标识 client.flags | REDIS_ASKING# 向客户端返回OK回复 reply("OK")在一般情况下&#xff0c;如果客户…

谷歌官方力作——CodeGemma代码语言模型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

开发日志2024-04-11

开发日志2024/04/11 1、会员/普通用户预约完成后&#xff0c;技师对应的积分添加预约完成的项目价格添加到统计表的业绩字段中&#xff0c;同时对应的服务次数字段1 实现代码&#xff1a; 前端 shHandler(){this.$confirm(确定操作?, "提示", {confirmButtonText…

基于模型预测算法的含储能微网双层能量管理模型

基于模型预测算法的含储能微网双层能量管理模型 文章目录 基于模型预测算法的含储能微网双层能量管理模型一、项目介绍二、源程序下载 一、项目介绍 代码主要做的是一个微网双层优化调度模型&#xff0c;微网聚合单元包括风电、光伏、储能以及超级电容器&#xff0c;在微网的运…

ELFK (Filebeat+ELK)日志分析系统

一. 相关介绍 Filebeat&#xff1a;轻量级的开源日志文件数据搜集器。通常在需要采集数据的客户端安装 Filebeat&#xff0c;并指定目录与日志格式&#xff0c;Filebeat 就能快速收集数据&#xff0c;并发送给 logstash 进或是直接发给 Elasticsearch 存储&#xff0c;性能上相…

Maven、redis、javaJDK环境配置及安装

一、Maven下载配置 Maven下载地址 下载完成完成配置环境变量 新建系统变量 MAVEN_HOME 地址 设置MAVEN… mvn -v 检测成功 二 、redis安装 下载地址 在安装目录cmd输入redis-server --version检测版本号 三、JAVA配置 设置JAVA… 测试

【Vue3语法单文件——自用】

1. Vue3基础语法 <script setup> import { ref,computed } from vue// 定义响应式的变量 const count ref(0) const author ref({name: John Doe,books: [Vue 2 - Advanced Guide,Vue 3 - Basic Guide,Vue 4 - The Mystery] }) //定义props const props defineProps(…

贪心算法|56.合并区间

力扣题目链接 class Solution { public:vector<vector<int>> merge(vector<vector<int>>& intervals) {vector<vector<int>> result;if (intervals.size() 0) return result; // 区间集合为空直接返回// 排序的参数使用了lambda表达…

Mongodb前后端整合篇

一、前端篇 1.1mongoose介绍 Mongoose 是一个对象文档模型库&#xff0c;官网 http://www.mongoosejs.net/ 方便使用代码操作 mongodb 数据库pnpm i mongoose5.13.15 1.2初步使用 import mongoose from mongoose; //设置 strictQuery 为 true mongoose.set(strictQuery, true…

ubuntu或类Debian获取某些包的离线版本-包括依赖(还有一些意想不到的用途,哈哈)

前言 偶尔能碰到很特殊的情况。网址白名单&#xff0c;纯内网&#xff0c;超多依赖及一些很难描述的场景。 比如一些少见的发行版缺少某些包。这时候可以找一台类似的系统环境来下载离线包及 其依赖包&#xff0c;然后转移到内网进行安装。如果是网址白名单&#xff0c;或者纯内…

为什么需要网络切片?

网络切片是电信领域的一个突破性概念&#xff0c;它允许将物理网络基础设施划分为多个虚拟网络&#xff0c;称为切片。每个切片作为一个独立的网络运行&#xff0c;拥有自己的专用资源和定制的特性&#xff0c;满足不同应用、行业或用户的特定需求。 将网络切片视为在共享物理…

计算机网络——交换机和路由器

目录 前言 引言 交换机是用来做什么的&#xff1f; 与路由器有什么区别&#xff1f; 网关 子网掩码 网关、路由 前言 本博客是博主用于复习计算机网络的博客&#xff0c;如果疏忽出现错误&#xff0c;还望各位指正。 这篇博客是在B站掌芝士zzs这个UP主的视频的总结&am…

【论文阅读】Digging Into Self-Supervised Monocular Depth Estimation

论文&#xff1a;https://arxiv.org/pdf/1806.01260.pdf 代码&#xff1a;https://github.com/nianticlabs/monodepth2 Q: 这篇论文试图解决什么问题&#xff1f; A: 这篇论文试图解决的问题是如何提高仅使用单目图像进行深度估计的性能。具体来说&#xff0c;它关注的是如何…

[大模型]Qwen1.5-7B-Chat-GPTQ-Int4 部署环境

Qwen1.5-7B-Chat-GPTQ-Int4 部署环境 说明 Qwen1.5-72b 版本有BF16、INT8、INT4三个版本&#xff0c;三个版本性能接近。由于BF16版本需要144GB的显存&#xff0c;让普通用户忘却止步&#xff0c;而INT4版本只需要48GB即可推理&#xff0c;给普通用户本地化部署创造了机会。&…