移动端滤镜开发(三)OpenGL实现预览播放效果

215 篇文章 4 订阅

https://www.jianshu.com/p/f432bfe8ce15

 

写在前面的话

<p>
上一篇文章简单介绍了OpenGl的使用,并实现了OpenGl显示图片的效果,但是滤镜的效果不仅仅只用在图片上面,一般来说现在视频和拍照取景也是会有滤镜的需求的,所以这一篇,就是介绍用OpenGL实现预览效果

摄像预览其实就是Android的Camera开发,对于Android的Camera开发,一般有两种方式,一种是借助Intent和MediaStroe调用系统Camera App程序来实现拍照和摄像功能,另外一种是根据Camera API自写Camera程序,自然第一种不能作为我们的滤镜开发,所以我们采用第二种方式。

视频播放则是使用了主要用到MediaPlayer。

两者的实现方式基本一致

Camera预览开发

<p>
工欲善其事必先利其器,首先我们了解一下Camera API

一.Camera API

<p>

Camera的初始化需要使用静态方法通过API calledCamera.open提供并初始化相机对象

Camera mCamera =  Camera.open(); 

简单看下Camera类提供的方法

  • getCameraInfo(int cameraId, Camera.CameraInfo cameraInfo) 它返回一个特定摄像机信息

  • getNumberOfCameras() 它返回限定的可用的设备上的照相机的整数

  • lock()它被用来锁定相机,所以没有其他应用程序可以访问它

  • release() 它被用来释放在镜头锁定,所以其他应用程序可以访问它

  • open(int cameraId) 它是用来打开特定相机时,支持多个摄像机

  • enableShutterSound(boolean enabled) 它被用来使能/禁止图像俘获的默认快门声音

  • startPreview() 开始预览

  • startFaceDetection() 此功能启动人脸检测相机

  • stopFaceDetection() 它是用来阻止其通过上述功能启用的脸部检测

  • startSmoothZoom(int value) 这需要一个整数值,并调整摄像机的焦距非常顺畅的值

  • stopSmoothZoom() 它是用来阻止摄像机的变焦

  • stopPreview() 它是用来阻止相机的预览给用户

  • takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback
    raw, Camera.PictureCallback jpeg)
    它被用来使能/禁止图像拍摄的默认快门声音

实现预览效果呢其实有好几种方法,主要都是与SurfaceView,GLSurfaceView,SurfaceTexture,TextureView这几个有关,那这里就简单介绍下这几个区别

SurfaceView, GLSurfaceView, SurfaceTexture, TextureView的区别

由于这里的东西其实牵扯很多,就不做太详细的介绍,有兴趣的可以自行百度,我就做简单的区分介绍

关键字:View
SurfaceView
GLSurfaceView
TextureView
这三个的后缀都是View,所以这三个都是用来显示的

SurfaceView是Android1.0(API level1)时期就存在的,虽然是继承于View,但是他包含一个Surface模块(简单地说Surface对应了一块屏幕缓冲区,每个window对应一个Surface,任何View都是画在Surface上的,传统的view共享一块屏幕缓冲区,所有的绘制必须在UI线程中进行),所以SurfaceView与普通View的区别就在于他的渲染在单独的线程的,这对于一些游戏、视频等性能相关的应用非常有益,因为它不会影响主线程对事件的响应。同时由于这个特性它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中,一些View中的特性也无法使用。

GLSurfaceView从Android 1.5(API level 3)开始加入,它的加入是为了解决SurfaceView渲染线程要单独写导致的统一性不好的状态,在SurfaceView的基础上,它加入了EGL的管理,并自带了渲染线程。另外它定义了用户需要实现的Render接口,提供了用Strategy pattern更改具体Render行为的灵活性。作为GLSurfaceView的Client,只需要将实现了渲染函数的Renderer的实现类设置给GLSurfaceView即可。概括一句话就是 使用了模板的 SurfaceView。

TextureView在4.0(API level 14)中引入。TextureView重载了draw()方法,其中主要把SurfaceTexture中收到的图像数据作为纹理更新到对应的HardwareLayer中。所以TextureView必须在硬件加速的窗口中。因为TextureView不包含Surface,所以其实就是一个普通的View,可以和其它普通View一样进行移动,旋转,缩放,动画等变化。

关键字:Texture
SurfaceTexture

SurfaceTexture从Android 3.0(API level 11)加入。和SurfaceView不同的是,它对图像流的处理并不直接显示,而是转为GL外部纹理,因此可用于图像流数据的二次处理(如Camera滤镜,桌面特效等)。

接下来就来实现预览效果,分别从简单到复杂

二.SurfaceView实现预览效果

<p>
首先我们来创建一个相机预览加载View,该类继承SurfaceView.Callback接口类,并且需要实现里面的接口方法以便监听SurfaceView控件的创建以及销毁事件的回调,在回调方法中关联相机预览显示。

如下

  public class CameraView extends SurfaceView implements SurfaceHolder.Callback{

    private SurfaceHolder holder;
    private Camera mCamera;

    public CameraView(Context context) {
        this(context,null);
    }

    public CameraView(Context context, AttributeSet attrs) {
        super(context, attrs);
        holder = getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            mCamera = getCameraInstance();
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        }catch (IOException e){

        }
    }

    public static Camera getCameraInstance(){
        Camera c = null;
        try {
            c = Camera.open(); // attempt to get a Camera instance
        }
        catch (Exception e){
            // Camera is not available (in use or does not exist)
        }
        return c; // returns null if camera is unavailable
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }

    public void  releaseCamera(){
        mCamera.release();
    }
}

接下来就是将这个View放到布局文件中,当onPause时候调用View的releaseCamera方法即可

运行如下

图1 Camera预览图

虽然是显示出来了,但是显示的界面确实不正常的,这是为什么呢?

手机Camera的图像数据都是来自于摄像头硬件的图像传感器(Image Sensor),这个Sensor被固定到手机之后是有一个默认的取景方向的,这个方向如下图所示,坐标原点位于手机横放时的左上角:

图2 Camera的图像数据方向

所以呢我们需要使用camera的setDisplayOrientation方法来调整方向,在初始化的时候mCamera.setDisplayOrientation(90);即可

运行如下:

图3 Camera预览图

三.TextureView实现预览效果

<p>

同样我们创建一个相机预览加载View,该类继承TextureView与TextureView.SurfaceTextureListener接口类,在onSurfaceTextureAvailable方法内,初始化Camera类,并设置Camera的setPreviewTexture方法为onSurfaceTextureAvailable方法参数中的SurfaceTexture即可

代码如下:

public class CameraTextureView extends TextureView implements TextureView.SurfaceTextureListener{

    Context mContext;
    private Camera mCamera;


    public CameraTextureView(Context context){
        super(context);
        mContext = context;
        this.setSurfaceTextureListener(this);
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

        try {
            mCamera = Camera.open();
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewTexture(surface);
            mCamera.startPreview();

        } catch (IOException t) {
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        mCamera.stopPreview();
        mCamera.release();
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }
}

接下来就是将这个View放到布局文件中

运行如下

图4 Camera预览图

四.GLSurfaceView实现预览效果

<p>

GLSurfaceView和前面两个预览View不同的是,他需要拿到数据自己进行渲染预览,大致流程如下:

GLSurfaceView->setRender->onSurfaceCreated回调方法中构造一个SurfaceTexture对象,然后设置到Camera预览中->SurfaceTexture中的回调方法onFrameAvailable来得知一帧的数据准备好了->requestRender通知Render来绘制数据->在Render的回调方法onDrawFrame中调用SurfaceTexture的updateTexImage方法获取一帧数据,然后开始使用GL来进行绘制预览。

OK接下来就是用代码来实现这个流程了

其实这里就是将之前的OpenGl绘制图片与上面的Camera相关的应用结合到一起就可以实现相关的功能

在OpenGl绘制图片基础上我们进行相关修改

1.添加Camera相关

Camera相关其实和前面一样我们这里要给Camera对象一个SurfaceTexture,这个SurfaceTexture我们需要自己创建,在onSurfaceCreated中出创建这个对象,并设置当Camera有新数据的回调函数,如下

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    ...
    surfaceTexture = new SurfaceTexture(0);
    surfaceTexture.setOnFrameAvailableListener(onFrameAvailableListener);
}

private SurfaceTexture.OnFrameAvailableListener onFrameAvailableListener = new SurfaceTexture.OnFrameAvailableListener() {

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        requestRender();
    }
};

这个回调函数作用是当有新的Camera数据时候去让OpenGl的onDrawFrame调用,进行重新绘制

接下来就是在onSurfaceChanged去打开Camera,如下

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {

  openCamera();

}

private Camera mCamera;

private void openCamera(){
    try {
        mCamera = getCameraInstance();
        mCamera.setPreviewTexture(surfaceTexture);
        mCamera.startPreview();
    }catch (IOException e){

    }
}

public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
}

到这里关于Camera相关的设置就已经完毕了,接下来就是获取数据去显示了

2.片段着色器程序修改

当Camera有新的数据则会通过OnFrameAvailableListener通知到我们,然后我们会通过requestRender()来触发OpenGl的重绘

我们通过surfaceTexture.updateTexImage()来获取新的Camera预览数据

这个方法的官网解释如下:

Update the texture image to the most recent frame from the image stream. This may only be called while the OpenGL ES context that owns the texture is current on the calling thread. It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target

大意是,从图像流中更新纹理图像到最近的帧中。这个函数仅仅当拥有这个纹理的Opengl ES上下文当前正处在绘制线程时被调用。它将隐式的绑定到这个扩展的GL_TEXTURE_EXTERNAL_OES 目标纹理

所以我们需要更改我们的片段着色器程序如下

#extension GL_OES_EGL_image_external : require

precision mediump float;
varying vec2 v_texCoord;
uniform samplerExternalOES s_texture;

void main() 
  gl_FragColor = texture2D( s_texture, v_texCoord )
}

并且在onDrawFrame()方法内部调用surfaceTexture.updateTexImage(),即可以达到预览效果

@Override
public void onDrawFrame(GL10 gl) {
  ...
  if (surfaceTexture == null)
      return;
  surfaceTexture.updateTexImage();
  ...
}       

运行如下

图5 camera预览图

可以看到这里视频是不对的,通过设置setDisplayOrientation也没有效果,我们只能通过去修改顶点着色器位置与片段着色器位置来进行修正了

修改如下

private static final float[] VERTEX = {
    -1.0f, 1.0f, 0.0f,
    -1.0f, -1.0f, 0.0f,
    1.0f, -1.0f, 0.0f,
    1.0f, 1.0f, 0.0f,
};  

private static final float[] UV_TEX_VERTEX = {  
    0.0f, 1.0f,
    1.0f, 1.0f,
    1.0f, 0.0f,
    0.0f, 0.0f,
};

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 4);

接下来运行,如下

图6 正确的camera预览图

到这里GLSurfaceView实现预览效果就完成啦

视频播放开发

<p>
前面实现了相机预览效果,这里则是来实现视频播放效果,其实SurfaceView这些设计之初就是为了解决视频播放,游戏等问题,所以我们可以来看一下之前相机预览的方法是否适用于这里。如果可以适用的话,那么就简单很多了。

我们看上面的三种方式,主要是用了Camera的方法mCamera.setPreviewTexture(surface)与mCamera.setPreviewDisplay(holder),所以只要视频播放也有相同的方法就可以了。

视频播放主要用到MediaPlayer,值得高兴的是MediaPlayer中确实是有上述的两种方法,那么这样我们就可以用上一篇文章相同的方式来实现播放效果了

一.SurfaceView实现视频播放效果

<p>

这里其实和之前的流程一样,只是将之前的相机预览相关改成视频播放,这里就直接将代码贴上来了,不做过多的介绍

public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback{

    private SurfaceHolder holder;

    private MediaPlayer mediaPlayer;

    public final String videoPath = Environment.getExternalStorageDirectory().getPath()+"/one.mp4";

    public CameraSurfaceView(Context context) {
        this(context, null);
    }

    public CameraSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        holder = getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (mediaPlayer == null) {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mp.start();
                }
            });
            mediaPlayer.setDisplay(holder);
            try {
                mediaPlayer.setDataSource(videoPath);
                mediaPlayer.prepareAsync();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            mediaPlayer.start();
        }
    }


    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }

}

运行如下

图1 SurfaceView 实现视频播放

二.TextureView实现视频播放效果

<p>

同上,代码如下

public class CameraTextureView extends TextureView implements TextureView.SurfaceTextureListener{

    Context mContext;

    private MediaPlayer mediaPlayer;

    public final String videoPath = Environment.getExternalStorageDirectory().getPath()+"/one.mp4";


    public CameraTextureView(Context context){
        super(context);
        mContext = context;
        this.setSurfaceTextureListener(this);
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

        if (mediaPlayer == null) {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mp.start();
                }
            });
            Surface surfaces = new Surface(surface);
            mediaPlayer.setSurface(surfaces);
            surfaces.release();
            try {
                mediaPlayer.setDataSource(videoPath);
                mediaPlayer.prepareAsync();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            mediaPlayer.start();
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {

        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }
}

运行如下

图2 TextureView实现视频播放

三.GLSurfaceView实现视频播放效果

<p>

这里和之前基本一样只需要将OpenCamera部分的代码改成打开MediaPlayer就好了,这里我就贴上这一部分的代码,如下

private MediaPlayer mediaPlayer;

public final String videoPath = Environment.getExternalStorageDirectory().getPath()+"/one.mp4";


private void openMediaPlayer(){
    if (mediaPlayer == null) {
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mp.start();
            }
        });
        Surface surface = new Surface(surfaceTexture);
        mediaPlayer.setSurface(surface);
        surface.release();
        try {
            mediaPlayer.setDataSource(videoPath);
            mediaPlayer.prepareAsync();
        } catch (IOException e) {
            e.printStackTrace();
        }
    } else {
        mediaPlayer.start();
    }
}

接下来运行如下

图2 GLSurfaceView实现视频播放

写在后面的话

<p>

这里基本把所有的实现预览相关的方式都讲解过了,但是基于要对Camera预览的与视频播放数据要进行处理,所以我们采用最后一种GLSurfaceView方式来作为滤镜开发的主要方式,到这里基本就完成了所以的准备工作,包括图片,相机预览,视频播放这三者都通过OpenGL方式实现了,接下来就可以开始我们的滤镜开发了



作者:前世小书童
链接:https://www.jianshu.com/p/f432bfe8ce15
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要开发基于OpenGL ES的实时滤镜相机,您需要以下开发环境: 1. 一台计算机:您需要一台配有OpenGL ES 2.0或更高版本的计算机,以便能够编写和测试您的代码。 2. 编辑器:您需要一个能够编写OpenGL ES代码的编辑器。一些常用的编辑器包括Visual Studio Code、Sublime Text和Atom。 3. OpenGL ES SDK:您需要安装适用于您的目标平台的OpenGL ES SDK。例如,如果您的目标平台是Android设备,则需要安装Android NDK。 4. 相机API:您需要使用相机API来访问设备的摄像头。对于Android设备,您可以使用Camera2 API。 5. 滤镜库:您需要选择并集成一个滤镜库,以便能够添加各种滤镜效果。一些常用的滤镜库包括GPUImage和OpenCV。 6. 设备测试:最后,您需要在目标设备上测试您的应用程序。您可以使用Android模拟器进行测试,但最好在实际设备上进行测试,以确保您的应用程序能够正常工作并获得所需的性能。 ### 回答2: 开发基于OpenGL ES的实时滤镜相机需要具备以下开发环境: 1. 操作系统:开发可以在 Windows、Mac OS X 或 Linux 上进行,这些操作系统都支持OpenGL ES开发。 2. 集成开发环境(IDE):可以选择使用常见的IDE,如Android Studio、Visual Studio Code、Eclipse等。这些IDE都支持OpenGL ES开发,并且提供了强大的代码编辑和调试功能。 3. SDK:如果是开发Android应用,需要下载并安装Android SDK。Android SDK包含了Android平台的开发工具和资源,提供了OpenGL ES库和相关文档。 4. OpenGL ES库:OpenGL ES库是实现OpenGL ES的软件包,提供了OpenGL ES的函数接口和硬件加速功能。开发者需要下载并集成OpenGL ES库到项目中。 5. 设备和模拟器:为了测试应用程序,可以使用Android设备或者Android模拟器。如果要在真机上进行测试,需要确保设备支持OpenGL ES。 6. 学习资源:了解OpenGL ES的基本原理和开发技巧是非常重要的。可以通过阅读官方文档、教程、书籍或者参加培训来学习OpenGL ES。 7. 文件导入:将想要应用的滤镜效果的着色器代码以及资源文件导入项目中。 8. 编写代码:根据项目需求,使用OpenGL ES提供的函数接口编写相机和滤镜效果的相关代码。通过OpenGL ES的API可以设置相机预览、处理图像数据,应用滤镜效果实现实时预览。 9. 测试和调试:在开发过程中,需要通过测试和调试来确保应用程序的正常运行。可以利用IDE提供的工具进行应用调试,检查是否出现错误和异常情况。 10. 发布和部署:完成开发后,可以将应用程序打包并发布到相应的应用商店。根据目标平台的要求,将应用程序部署到Android设备或者发布到其他平台。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值