掌握Android图像显示原理(中)

前言

在上一篇文章《Android图形渲染原理(上)》中,详细的讲解了图像消费者,我们已经了解了Android中的图像元数据是如何被SurfaceFlinger,HWComposer或者OpenGL ES消费的,那么,图像元数据又是怎么生成的呢?这一篇文章就来详细介绍Android中的图像生产者——SKIA,OPenGL ES,Vulkan,他们是Android中最重要的三支画笔。
在这里插入图片描述

图像生产者

OpenGL ES

什么是OpenGL呢?OpenGL是一套图像编程接口,对于开发者来说,其实就是一套C语言编写的API接口,通过调用这些函数,便可以调用显卡来进行计算机的图形开发。虽然OpenGL是一套API接口,但它并没有具体的实现这些接口,接口的实现是由显卡的驱动程序来完成的。在前一篇文章中介绍过,显卡驱动是其他模块和显卡沟通的入口,开发者通过调用OpenGL的图像编程接口发出渲染命令,这些渲染命令被称为DrawCall,显卡驱动会将渲染命令翻译能GPU能理解的数据,然后通知GPU读取数据进行操作。OpenGL ES又是什么呢?它是为了更好的适应嵌入式等硬件较差的设备,推出的OpenGL的剪裁版,基本和OpenGL是一致的。Android从4.0开始默认开启硬件加速,也就是默认使用OpenGL ES来进行图形的生成和渲染工作。

我们接着来看看如何使用OpenGL ES。

如何使用OpenGL ES?

想要在Android上使用OpenGL ES,我们要先了解EGL。OpenGL虽然是跨平台的,但是在各个平台上也不能直接使用,因为每个平台的窗口都是不一样的,而EGL就是适配Android本地窗口系统和OpenGL ES桥接层。

OpenGL ES 定义了平台无关的 GL 绘图指令,EGL则定义了控制 displays,contexts 以及 surfaces 的统一的平台接口
在这里插入图片描述

那么如何使用EGL和OpenGL ES生成图形呢?其实比较简单,主要有这三步

  1. EGL初始化Display,Context和Surface
  2. OpenGL ES调用绘制指令
  3. EGL提交绘制后的buffer

我们详细来看一下每一步的流程

1,EGL进行初始化:主要初始化Display,Context 和Surface三个元素就可以了。

  • Display(EGLDisplay) 是对实际显示设备的抽象
//创建于本地窗口系统的连接
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
//初始化display
eglInitialize(display, NULL, NULL);
  • Context (EGLContext) 存储 OpenGL ES绘图的一些状态信息
/* create an EGL rendering context */
context = eglCreateContext(display, config, EGL_NO_CONTEXT, NULL);
  • Surface(EGLSurface)是对用来存储图像的内存区域
//设置Surface配置
eglChooseConfig(display, attribute_list, &config, 1, &num_config);
//创建本地窗口
native_window = createNativeWindow();
//创建surface
surface = eglCreateWindowSurface(display, config, native_window, NULL);
  • 初始化完成后,需要绑定上下文
//绑定上下文
eglMakeCurrent(display, surface, surface, context);

2,OpenGL ES调用绘制指令:主要通过使用 OpenGL ES API ——gl_*(),接口进行绘制图形

//绘制点
glBegin(GL_POINTS);
    glVertex3f(0.7f,-0.5f0.0f); //入参为三维坐标
    glVertex3f(0.6f,-0.7f0.0f);
    glVertex3f(0.6f,-0.8f0.0f);
glEnd();
//绘制线
glBegin(GL_LINE_STRIP);
    glVertex3f(1.0f1.0f0.0f);
    glVertex3f(0.5f0.5f0.0f);
    glVertex3f(0.7f0.5f0.0f);
glEnd();
//……

3,EGL提交绘制后的buffer:通过eglSwapBuffer()进行双缓冲buffer的切换

EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);

swapBuffer切换缓冲区buffer后,显卡就会对Buffer中的图像进行渲染处理。此时,我们的图像就能显示出来了。

我们看一个完整的使用流程Demo

#include <stdlib.h>
#include <unistd.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
typedef ... NativeWindowType;
extern NativeWindowType createNativeWindow(void);
static EGLint const attribute_list[] = {
   
        EGL_RED_SIZE, 1,
        EGL_GREEN_SIZE, 1,
        EGL_BLUE_SIZE, 1,
        EGL_NONE
};
int main(int argc, char ** argv)
{
   
        EGLDisplay display;
        EGLConfig config;
        EGLContext context;
        EGLSurface surface;
        NativeWindowType native_window;
        EGLint num_config;

        /* get an EGL display connection */
        display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

        /* initialize the EGL display connection */
        eglInitialize(display, NULL, NULL);

        /* get an appropriate EGL frame buffer configuration */
        eglChooseConfig(display, attribute_list, &config, 1, &num_config);

        /* create an EGL rendering context */
        context = eglCreateContext(display, config, EGL_NO_CONTEXT, NULL);

        /* create a native window */
        native_window = createNativeWindow();

        /* create an EGL window surface */
        surface = eglCreateWindowSurface(display, config, native_window, NULL);

        /* connect the context to the surface */
        eglMakeCurrent(display, surface, surface, context);

        /* clear the color buffer */
        glClearColor(1.0, 1.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();

        eglSwapBuffers(display, surface);

        sleep(10);
        return EXIT_SUCCESS;
}

介绍完EGL和OpenGL的使用方式了,我们可以开始看Android是如何通过它进行界面的绘制的,这里会列举两个场景:开机动画,硬件加速来详细的讲解OpenGL ES作为图像生产者,是如何生产,即如何绘制图像的。

OpenGL ES播放开机动画

当Android系统启动时,会启动Init进程,Init进程会启动Zygote,ServerManager,SurfaceFlinger等服务。随着SurfaceFlinger的启动,我们的开机动画也会开始启动。先看看SurfaceFlinger的初始化函数。

//文件-->/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {
   
    ...
    mStartBootAnimThread = new StartBootAnimThread();
    if (mStartBootAnimThread->Start() != NO_ERROR) {
   
        ALOGE("Run StartBootAnimThread failed!");
    }
}

//文件-->/frameworks/native/services/surfaceflinger/StartBootAnimThread.cpp
status_t StartBootAnimThread::Start() {
   
    return run("SurfaceFlinger::StartBootAnimThread", PRIORITY_NORMAL);
}

bool StartBootAnimThread::threadLoop() {
   
    property_set("service.bootanim.exit", "0");
    property_set("ctl.start", "bootanim");
    // Exit immediately
    return false;
}

从上面的代码可以看到,SurfaceFlinger的init函数中会启动BootAnimThread线程,BootAnimThread线程会通过property_set来发送通知,它是一种Socket方式的IPC通信机制,对Android IPC通信感兴趣的可以看看我的这篇文章《深入理解Android进程间通信机制》,这里就不过多讲解了。init进程会接收到bootanim的通知,然后启动我们的动画线程BootAnimation。

了解了前面的流程,我们开始看BootAnimation这个类,Android的开机动画的逻辑都在这个类中。我们先看看构造函数和onFirsetRef函数,这是这个类创建时最先执行的两个函数:

//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
        mTimeFormat12Hour(false), mTimeCheckThread(NULL) {
   
    //创建SurfaceComposerClient
    mSession = new SurfaceComposerClient();
    //……
}

void BootAnimation::onFirstRef() {
   
    status_t err = mSession->linkToComposerDeath(this);
    if (err == NO_ERROR) {
   
        run("BootAnimation", PRIORITY_DISPLAY);
    }
}

构造函数中创建了SurfaceComposerClient,SurfaceComposerClient是SurfaceFlinger的客户端代理,我们可以通过它来和SurfaceFlinger建立通信。构造函数执行完后就会执行onFirsetRef()函数,这个函数会启动BootAnimation线程

接着看BootAnimation线程的初始化函数readyToRun。

//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
status_t BootAnimation::readyToRun() {
   
    mAssets.addDefaultAssets();

    sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
            ISurfaceComposer::eDisplayIdMain));
    DisplayInfo dinfo;
    //获取屏幕信息
    status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
    if (status)
        return -1;

    // 通知SurfaceFlinger创建Surface,创建成功会返回一个SurfaceControl代理
    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
            dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);

    SurfaceComposerClient::openGlobalTransaction();
    //设置这个layer在SurfaceFlinger中的层级顺序
    control->setLayer(0x40000000);

    //获取surface
    sp<Surface> s = control->getSurface();

    // 以下是EGL的初始化流程
    const EGLint attribs[] = {
   
            EGL_RED_SIZE,   8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE,  8,
            EGL_DEPTH_SIZE, 0,
            EGL_NONE
    };
    EGLint w, h;
    EGLint numConfigs;
    EGLConfig config;
    EGLSurface surface;
    EGLContext context;

    //步骤1:获取Display
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    //步骤2:初始化EGL
    eglInitialize(display, 0, 0);
    //步骤3:选择参数
    eglChooseConfig(display, attribs, &config, 1, &numConfigs);
    //步骤4:传入SurfaceFlinger生成的surface,并以此构造EGLSurface
    surface = eglCreateWindowSurface(display, config, s.get(), NULL);
    //步骤5:构造egl上下文
    context = eglCreateContext(display, config, NULL, NULL);
    //步骤6:绑定EGL上下文
    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
        return NO_INIT;
    //……
}

通过readyToRun函数可以看到,里面主要做了两件事情:初始化Surface,初始化EGL,EGL的初始化流程和上面OpenGL ES使用中讲的流程是一样的,这里就不详细讲了,主要简单介绍一下Surface初始化的流程,详细的流程会在下一篇文章图像缓冲区中讲,它的步骤如下:

  • 创建SurfaceComponentClient
  • 通过SurfaceComponentClient通知SurfaceFlinger创建Surface,并返回SurfaceControl
  • 有了SurfaceControl之后,我们就可以设置这块Surface的层级等属性,并能获取到这块Surface。
  • 获取到Surface后,将Surface绑定到EGL中去

Surface也创建好了,EGL也创建好了,此时我们就可以通过OpenGL来生成图像——也就是开机动画了,我们接着看看线程的执行方法threadLoop函数中是如何播放的动画的。

//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
bool BootAnimation::threadLoop()
{
   
    bool r;
    if (mZipFileName.isEmpty()) {
   
        r = android();   //Android默认动画
    } else {
   
        r = movie();     //自定义动画
    }
	//动画播放完后的释放工作
    eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(mDisplay, mContext);
    eglDestroySurface(mDisplay, mSurface);
    mFlingerSurface.clear();
    mFlingerSurfaceControl.clear();
    eglTerminate(mDisplay);
    eglReleaseThread();
    IPCThreadState::self()->stopProcess();
    return r;
}

函数中会判断是否有自定义的开机动画文件,如果没有就播放默认的动画,有就播放自定义的动画,播放完成后就是释放和清除的操作。默认动画和自定义动画的播放方式其实差不多,我们以自定义动画为例,看看具体的实现流程。

//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
bool BootAnimation::movie()
{
   
    //根据文件路径加载动画文件
    Animation* animation = loadAnimation(mZipFileName);
    if (animation == NULL)
        return false;

    //……

    
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    // 调用OpenGL清理屏幕
    glShadeModel(GL_FLAT);
    glDisable(GL_DITHER);
    glDisable(GL_SCISSOR_TEST);
    glDisable(GL_BLEND);

    glBindTexture(GL_TEXTURE_2D, 0);
    glEnable(GL_TEXTURE_2D);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    //……

    //播放动画
    playAnimation(*animation);

    //……
    
    //释放动画
    releaseAnimation(animation);

    return false;
}

movie函数主要做的事情如下

  1. 通过文件路径加载动画
  2. 调用OpenGL做清屏操作
  3. 调用playAnimation函数播放动画。
  4. 停止播放动画后通过releaseAnimation释放资源

我们接着看playAnimation函数

//文件-->/frameworks/base/cmds/bootanimation/BootAnimation.cpp
bool BootAnimation::playAnimation(const Animation& animation)
{
   
    const size_t pcount = animation.parts.size();
    nsecs_t frameDuration = s2ns(1) / animation.fps;
    const int animationX = (mWidth - animation.width) / 2;
    const int animationY = (mHeight - animation.height) / 2;

    //遍历动画片段
    for (size_t i=0 ; i<pcount ; i++) {
   
        const Animation::Part& part(animation.parts[i]);
        const size_t fcount = part.frames.size();
        glBindTexture(GL_TEXTURE_2D, 0);

        // Handle animation package
        if (part.animation != NULL) {
   
            playAnimation(*part.animation);
            if (exitPending())
                break;
            continue; //to next part
        }
		
        //循环动画片段
        for (int r=0 ; !part.count || r<part.count ; r++) {
   
            // Exit any non playuntil complete parts immediately
            if(exitPending() && !part.playUntilComplete)
                break;

            
            //启动音频线程,播放音频文件
            if (r == 0 && part.audioData && playSoundsAllowed()) {
                  
                if (mInitAudioThread != nullptr) {
   
                    mInitAudioThread->join();
                }
                audioplay::playClip(part.audioData, part.audioLength);
            }

            glClearColor(
                    part.backgroundColor[0],
                    part.backgroundColor[1],
                    part.backgroundColor[2],
                    1.0f);
			//按照frameDuration频率,循环绘制开机动画图片纹理
            for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
   
                const Animation::Frame& frame(part.frames[j]);
                nsecs_t lastFrame = systemTime();

                if (r > 0) {
   
                    glBindTexture(GL_TEXTURE_2D, frame.tid);
                } else {
   
                    if (part.count != 1) {
   
                        //生成纹理
                        glGenTextures(1, &frame.tid);
                        //绑定纹理
                        glBindTexture(GL_TEXTURE_2D, frame.tid);
                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                    }
                    int w, h;
                    initTexture(frame.map, &w, &h);
                }

                const int xc = animationX + frame.trimX;
                const int yc = animationY + frame.trimY;
                Region clearReg(Rect(mWidth, mHeight));
                clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
                if (!clearReg.isEmpty()) {
   
                    Region::const_iterator head(clearReg.begin());
                    Region::const_iterator tail(clearReg.end());
                    glEnable(GL_SCISSOR_TEST);
                    while (head != tail) {
   
                        const Rect& r2(*head++);
                        glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
                        glClear(GL_COLOR_BUFFER_BIT);
                    }
                    glDisable(GL_SCISSOR_TEST);
                }
                // 绘制纹理
                glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight),
                              0, frame.trimWidth, frame.trimHeight);
                if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
   
                    drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
                }

                eglSwapBuffers(mDisplay, mSurface);

                nsecs_t now = systemTime();
                nsecs_t delay = frameDuration - (now - lastFrame);
                //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
                lastFrame = now;

                if (delay > 0) {
   
                    struct timespec spec;
                    spec.tv_sec  = (now + delay) / 1000000000;
                    spec.tv_nsec = (now + delay) % 1000000000;
                    int err;
                    do {
   
                        err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);
                    } while (err<0 && errno == EINTR);
                }

                checkExit();
            }
			//休眠
            usleep(part.pause * ns2us(frameDuration));

            // 动画退出条件判断
            if(exitPending() && !part.count)
                break;
        }

    }

    // 释放纹理
    for (const Animation::Part& part : animation.parts) {
   
        if (part.count != 1) {
   
            const size_t fcount = part.frames.size();
            for (size_t j = 0; j < fcount; j++) {
   
                const Animation::Frame& frame(part.frames[j]);
                glDeleteTextures(1, &frame.tid);
            }
        }
    }

    // 关闭和视频音频
    audioplay::setPlaying(false);
    audioplay::destroy();

    return true;
}

从上面的源码可以看到,playAnimation函数播放动画的原理,其实就是按照一定的频率,循环调用glDrawTexiOES函数,绘制图片纹理,同时调用音频播放模块播放音频。

通过OpenGL ES播放动画的案例就讲完了,我们也了解了通过OpenGL来播放视频的一种方式,我们接着看第二个案例,Activity界面如何通过OpenGL来进行硬件加速,也就是硬件绘制绘制的。

OpenGL ES进行硬件加速

我们知道,Activity界面的显示需要经历Measure测量,Layout布局,和Draw绘制三个过程,而Draw绘制流程又分为软件绘制和硬件绘制,硬件绘制便是通过OpenGL ES进行的。我们直接看看硬件绘制流程里,OpenGL ES是如何来进行绘制的,它的入口在ViewRootImpl的performDraw函数中。

//文件-->/frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
   
    //……
    draw(fullRedrawNeeded);
    //……
}

private void draw(boolean fullRedrawNeeded) {
   
    Surface surface = mSurface;
    if (!surface.isValid()) {
   
        return
  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Android开发使用OpenCV进行图像处理是一项常见的任务。OpenCV是一个开源的计算机视觉库,它提供了许多强大的图像处理和计算机视觉算法。 要找到OpenCV的文手册,可以在互联网上搜索OpenCV的官方网站,他们提供了许多文档和教程,其包括了文的版本。在OpenCV的官方网站上,你可以找到适用于Android开发的文手册,其包含了关于如何使用OpenCV库的详细说明和示例代码。 另外,你还可以在国内的一些技术论坛或博客上寻找文的OpenCV教程。这些资源通常由熟悉OpenCV和Android的开发者撰写,并提供了一些实际项目的案例和指导。 此外,文社区还有一些开发者编写的OpenCV相关书籍或教程。这些资源通常是基于他们对OpenCV的经验和研究编写的,提供了更深入、系统的学习资料。你可以在互联网上搜索这些书籍或教程,并根据自己的需求进行选择。 总之,在Android开发使用OpenCV可以帮助开发者实现许多图像处理和计算机视觉的功能。通过寻找适合自己的文教程和手册,可以更好地理解和应用OpenCV的功能和技术。 ### 回答2: Android开发OpenCV文手册是一本专门针对在Android平台上使用OpenCV进行图像处理和计算机视觉开发的指南。OpenCV是一个广泛使用的开源计算机视觉库,它提供了一系列丰富的功能和工具,用于处理图像和视频数据。 这本手册首先介绍了Android开发环境的搭建,包括Java开发环境的安装、Android Studio的配置和OpenCV库的导入。然后,手册详细说明了OpenCV在Android开发的基本用法,包括图像的读取和显示图像处理的常用算法、图像特征提取和匹配、目标检测和跟踪等。 手册的内容丰富全面,结构清晰,每个主题都有详细的代码示例和实践案例。同时,手册也提供了一些实用的技巧和注意事项,帮助开发者更好地理解和使用OpenCV库。此外,手册还介绍了一些常见的问题和解决方案,以帮助开发者解决在实际开发可能遇到的困难。 对于想要学习Android图像处理和计算机视觉的开发者来说,这本文手册是一个宝贵的参考资料。它不仅提供了对OpenCV库的详细介绍,还将其与Android开发环境结合起来,使开发者能够更加方便地进行图像处理和计算机视觉的开发工作。 总之,Android开发OpenCV文手册是一本很有价值的指南,它能够帮助开发者快速入门和掌握Android平台上使用OpenCV进行图像处理和计算机视觉开发的技巧和方法。 ### 回答3: OpenCV(开源计算机视觉库)是一个功能强大且广泛使用的开源库,用于处理图像和视频的计算机视觉任务。它提供了许多基本的图像处理和计算机视觉算法,可以在Android开发进行视觉处理。 对于想要开发Android应用程序并集成OpenCV的人来说,Opencv文手册将是一个非常有用的资源。Opencv文手册提供了关于OpenCV库各种函数和方法的详细说明,以及它们如何在Android平台上使用的指导。 Opencv文手册提供了适用于Android的特定指南,例如如何在项目配置OpenCV库,如何导入OpenCV模块,以及如何使用OpenCV函数和方法进行图像处理。手册还提供了使用OpenCV进行基本图像处理的示例代码,例如图像的灰度化、旋转、裁剪等。 此外,Opencv文手册还涵盖了计算机视觉算法的基本原理和应用。这些算法包括图像特征提取、目标检测和跟踪、人脸识别等。手册提供了这些算法的详细说明和示例代码,使开发人员能够理解和应用这些强大的视觉算法。 总之,Opencv文手册对于想要在Android开发集成OpenCV并进行图像处理和计算机视觉任务的开发人员来说,将是一个非常有用的资源。它提供了完整的文档和示例代码,帮助开发人员学习和使用OpenCV库。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值