Android视频编辑器(一)通过OpenGL预览、录制视频以及断点续录等
前言
方案选择
视频预览
GLSurfaceView的作用
@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {}@Overridepublic void onDrawFrame(GL10 gl) {}
mCamera = Camera.open(cameraId);if (mCamera != null){/**选择当前设备允许的预览尺寸*/Camera.Parameters param = mCamera.getParameters();preSize = getPropPreviewSize(param.getSupportedPreviewSizes(), mConfig.rate,mConfig.minPreviewWidth);picSize = getPropPictureSize(param.getSupportedPictureSizes(),mConfig.rate,mConfig.minPictureWidth);param.setPictureSize(picSize.width, picSize.height);param.setPreviewSize(preSize.width,preSize.height);mCamera.setParameters(param);}private Camera.Size getPropPictureSize(List<Camera.Size> list, float th, int minWidth){Collections.sort(list, sizeComparator);int i = 0;for(Camera.Size s:list){if((s.height >= minWidth) && equalRate(s, th)){break;}i++;}if(i == list.size()){i = 0;}return list.get(i);}private Camera.Size getPropPreviewSize(List<Camera.Size> list, float th, int minWidth){Collections.sort(list, sizeComparator);int i = 0;for(Camera.Size s:list){if((s.height >= minWidth) && equalRate(s, th)){break;}i++;}if(i == list.size()){i = 0;}return list.get(i);}private static boolean equalRate(Camera.Size s, float rate){float r = (float)(s.width)/(float)(s.height);if(Math.abs(r - rate) <= 0.03) {return true;}else{return false;}}private Comparator<Camera.Size> sizeComparator=new Comparator<Camera.Size>(){public int compare(Camera.Size lhs, Camera.Size rhs) {if(lhs.height == rhs.height){return 0;}else if(lhs.height > rhs.height){return 1;}else{return -1;}}};
这个代码还是相当简单,这里就不过多介绍了,网上也有很多不同的但是类似功能的适配方法,大家可以多了解下,相互对照。
第二个就是,摄像头取数据的坐标系和屏幕显示的坐标系不太相同,简单的说就是,不管是前置还是后置摄像头,我们都需要对摄像头取的数据进行一些坐标系旋转操作,才能正常的显示到屏幕上,不然的话就会出现画面扭曲的情况。因为我们是采用的OpengGL进行视频录制的,所以我们会有一系列的AFilter来进行shader的加载和画面的渲染工作,所以我们将摄像头数据的旋转也放到这个里面来做。这部分后面再说,CameraController类主要就是Camera的一个包装类,还会包括一些视频尺寸控制等代码,具体的请下载完整demo,进行查看。
public static int uLoadShader(int shaderType,String source){int shader= GLES20.glCreateShader(shaderType);if(0!=shader){GLES20.glShaderSource(shader,source);GLES20.glCompileShader(shader);int[] compiled=new int[1];GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,compiled,0);if(compiled[0]==0){glError(1,"Could not compile shader:"+shaderType);glError(1,"GLES20 Error:"+ GLES20.glGetShaderInfoLog(shader));GLES20.glDeleteShader(shader);shader=0;}}return shader;}
Buffer的初始化
/*** Buffer初始化*/protected void initBuffer(){ByteBuffer a= ByteBuffer.allocateDirect(32);a.order(ByteOrder.nativeOrder());mVerBuffer=a.asFloatBuffer();mVerBuffer.put(pos);mVerBuffer.position(0);ByteBuffer b= ByteBuffer.allocateDirect(32);b.order(ByteOrder.nativeOrder());mTexBuffer=b.asFloatBuffer();mTexBuffer.put(coord);mTexBuffer.position(0);}
绑定默认的纹理
/*** 绑定默认纹理*/protected void onBindTexture(){GLES20.glActiveTexture(GLES20.GL_TEXTURE0+textureType);GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,getTextureId());GLES20.glUniform1i(mHTexture,textureType);}
每次绘制前需要清理画布
/*** 清理画布*/protected void onClear(){GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);}
这些代码在不同的filter中其实都是公用的,所以我们通过一个抽象类,来进行管理。
public void setFlag(int flag) {super.setFlag(flag);float[] coord;if(getFlag()==1){ //前置摄像头 顺时针旋转90,并上下颠倒coord=new float[]{1.0f, 1.0f,0.0f, 1.0f,1.0f, 0.0f,0.0f, 0.0f,};}else{ //后置摄像头 顺时针旋转90度coord=new float[]{0.0f, 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f, 0.0f,};}mTexBuffer.clear();mTexBuffer.put(coord);mTexBuffer.position(0);}
摄像头和AFilter我们都已经准备好了,下一步,就是我们需要把Camera取的数据显示在GLSurfaceView上面了,也就是需要将AFilter、CameraController和GLSurfaceView联系起来。然后,因为我们后续会涉及到很多不同AFilter的管理,所以我们创建一个CameraDraw类,来管理AFilter。让其实现GLSurfaceView.Renderer接口,便于管理。
public class CameraDrawer implements GLSurfaceView.Renderer
然后,在类的构造函数中,进行AFilter的初始化
public CameraDrawer(Resources resources){//初始化一个滤镜 也可以叫控制器showFilter = new ShowFilter(resources); }
在onSurfaceCreated中,进行SurfaceTextured的创建,并且和AFilter进行绑定
@Overridepublic void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {textureID = createTextureID();mSurfaceTextrue = new SurfaceTexture(textureID);showFilter.create();showFilter.setTextureId(textureID); }
在onSurfaceChanged中,进行一些参数的更改和纹理的重新绑定
@Overridepublic void onSurfaceChanged(GL10 gl10, int i, int i1) {width = i;height = i1;/**创建一个帧染缓冲区对象*/GLES20.glGenFramebuffers(1,fFrame,0);/**根据纹理数量 返回的纹理索引*/GLES20.glGenTextures(1, fTexture, 0);/**将生产的纹理名称和对应纹理进行绑定*/GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fTexture[0]);/**根据指定的参数 生产一个2D的纹理 调用该函数前 必须调用glBindTexture以指定要操作的纹理*/GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, mPreviewWidth, mPreviewHeight,0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);useTexParameter();GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);}
在onDrawFrame中进行图像的绘制工作。
@Overridepublic void onDrawFrame(GL10 gl10) {/**更新界面中的数据*/mSurfaceTextrue.updateTexImage();/**绘制显示的filter*/GLES20.glViewport(0,0,width,height);showFilter.draw();}
CameraDraw目前所做的主要工作就是这样,然后我们将CameraController、CameraDraw和自定义的CameraView控件进行绑定,就可以实现摄像头数据预览了。
private void init() {/**初始化OpenGL的相关信息*/setEGLContextClientVersion(2);//设置版本setRenderer(this);//设置RenderersetRenderMode(RENDERMODE_WHEN_DIRTY);//主动调用渲染setPreserveEGLContextOnPause(true);//保存Context当pause时setCameraDistance(100);//相机距离/**初始化Camera的绘制类*/mCameraDrawer = new CameraDrawer(getResources());/**初始化相机的管理类*/mCamera = new CameraController();}
然后,分别在三个生命周期的函数中调用CameraController和CameraDrawer的相关方法,以及打开摄像头
@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {mCameraDrawer.onSurfaceCreated(gl,config);if (!isSetParm){open(0);stickerInit();}mCameraDrawer.setPreviewSize(dataWidth,dataHeight);}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {mCameraDrawer.onSurfaceChanged(gl,width,height);}@Overridepublic void onDrawFrame(GL10 gl) {if (isSetParm){mCameraDrawer.onDrawFrame(gl);}}
然后在onFrameAvailable函数中,调用即可
@Overridepublic void onFrameAvailable(SurfaceTexture surfaceTexture) {this.requestRender();}
我们的视频预览的主体流程就是这样,然后我们可以直接在布局中使用CameraView类即可。
视频录制和断点录制
drawFilter = new ShowFilter(resources);
这里需要注意一下,为了显示在屏幕上是正常的,我们进行了旋转的操作。所以,我们在录制的AFilter里面需要加上矩阵翻转的控制。
OM= MatrixUtils.getOriginalMatrix();MatrixUtils.flip(OM,false,true);//矩阵上下翻转drawFilter.setMatrix(OM);
然后同样分别进行drawFilter的create,在onDrawFrame里面讲textureId进行绑定以及绘制。还有就是添加录制控制的相关代码
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fFrame[0]);GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,GLES20.GL_TEXTURE_2D, fTexture[0], 0);GLES20.glViewport(0,0,mPreviewWidth,mPreviewHeight);drawFilter.setTextureId(fTexture[0]);drawFilter.draw();//解绑GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0);if (recordingEnabled){/**说明是录制状态*/switch (recordingStatus){case RECORDING_OFF:videoEncoder = new TextureMovieEncoder();videoEncoder.setPreviewSize(mPreviewWidth,mPreviewHeight);videoEncoder.startRecording(new TextureMovieEncoder.EncoderConfig(savePath, mPreviewWidth, mPreviewHeight,3500000, EGL14.eglGetCurrentContext(),null));recordingStatus = RECORDING_ON;break;case RECORDING_ON:case RECORDING_PAUSED:break;case RECORDING_RESUMED:videoEncoder.updateSharedContext(EGL14.eglGetCurrentContext());videoEncoder.resumeRecording();recordingStatus = RECORDING_ON;break;case RECORDING_RESUME:videoEncoder.resumeRecording();recordingStatus=RECORDING_ON;break;case RECORDING_PAUSE:videoEncoder.pauseRecording();recordingStatus=RECORDING_PAUSED;break;default:throw new RuntimeException("unknown recording status "+recordingStatus);}}else {switch (recordingStatus) {case RECORDING_ON:case RECORDING_RESUMED:case RECORDING_PAUSE:case RECORDING_RESUME:case RECORDING_PAUSED:videoEncoder.stopRecording();recordingStatus = RECORDING_OFF;break;case RECORDING_OFF:break;default:throw new RuntimeException("unknown recording status " + recordingStatus);}}if (videoEncoder != null && recordingEnabled && recordingStatus == RECORDING_ON){videoEncoder.setTextureId(fTexture[0]);videoEncoder.frameAvailable(mSurfaceTextrue);}
上面主要逻辑是,在数据返回的视频判断当前的录制状态,如果是正在录制,就将SurfaceTexture给到VideoEncoder进行数据的编码,如果没有录制,就跳过该帧,这样就可以实现断点续录,即录制 —>暂停录制—>继续录制,而且这样录制出来的是一个整体的视频文件。
这里就不贴出TexureMovieEncoder和VideoEncoderCore类的详细代码了。
Camera的手动对焦
Camera.Parameters parameters = mCamera.getParameters();boolean supportFocus=true;boolean supportMetering=true;//不支持设置自定义聚焦,则使用自动聚焦,返回if (parameters.getMaxNumFocusAreas() <= 0) {supportFocus=false;}if (parameters.getMaxNumMeteringAreas() <= 0){supportMetering=false;}List<Camera.Area> areas = new ArrayList<Camera.Area>();List<Camera.Area> areas1 = new ArrayList<Camera.Area>();//再次进行转换point.x= (int) (((float)point.x)/ MyApplication.screenWidth*2000-1000);point.y= (int) (((float)point.y)/MyApplication.screenHeight*2000-1000);int left = point.x - 300;int top = point.y - 300;int right = point.x + 300;int bottom = point.y + 300;left = left < -1000 ? -1000 : left;top = top < -1000 ? -1000 : top;right = right > 1000 ? 1000 : right;bottom = bottom > 1000 ? 1000 : bottom;areas.add(new Camera.Area(new Rect(left, top, right, bottom), 100));areas1.add(new Camera.Area(new Rect(left, top, right, bottom), 100));if(supportFocus){parameters.setFocusAreas(areas);}if(supportMetering){parameters.setMeteringAreas(areas1);}try {mCamera.setParameters(parameters);// 部分手机 会出Exception(红米)mCamera.autoFocus(callback);} catch (Exception e) {e.printStackTrace();}
主要涉及到了一下坐标变换,因为大部分的手机的前置摄像头不支持对焦功能,所以我们不进行前置摄像头的对焦。
结语
Android视频编辑器(一)通过OpenGL预览、录制视频以及断点续录等相关推荐
- Android视频编辑器(二)预览、录制视频加上水印和美白磨皮效果
前言 这是视频编辑器系列的第二篇文章,在上篇文章中,我们讲解了利用OpenGl和SurfaceView进行视频预览,MediaCodec和MeidaMuxer进行视频录制和断点续录.而这篇主 ...
- Android 音视频开发(三) -- Camera2 实现预览、拍照功能
音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...
- Android 音视频开发(二) -- Camera1 实现预览、拍照功能
音视频 系列文章 Android 音视频开发(一) – 使用AudioRecord 录制PCM(录音):AudioTrack播放音频 Android 音视频开发(二) – Camera1 实现预览.拍 ...
- android 与后台实时视频,Android实时监控项目第四篇:后台线程发送预览帧视频数据...
还记得上篇提到的setPreviewCallback(Camera.PreviewCallback cb)函数吗?我们在开始预览帧视频之前,调用的它,这里要注意其内部的Camera.PreviewCa ...
- Android Camera2 教程 · 第三章 · 预览
Android Camera2 教程 · 第三章 · 预览 DarylGo关注 Android Camera 上一章<Camera2 开启相机>我们学习了如何开启和关闭相机,接下来我们来学 ...
- Android OpenGL添加水印并录制视频--抖音视频录制原理
Android OpenGL添加水印并录制视频–抖音视频录制原理 简单的视频录制,我们可以使用MediaRecorder,具体示例可以参考Gitee: Camera2VideoJava 本文将介绍采集 ...
- 谷歌发布 Android 8.1 首个开发者预览版,优化内存效率
今晨,谷歌推出了 Android 8.1 首个开发者预览版,此次升级涵盖了针对多个功能的提升优化,其中包含对 Android Go (设备运行内存小于等于 1 GB)和加速设备上对机器学习的全新神经网 ...
- Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理:底层SurfaceView+上层绘制ImageView)
[后注:]下载代码的注意,我的手机是4.3寸的屏,华为U9200.如果不能运行的请修改参数.看前文的第四条.Y的,省的说我传的代码不能用 最近一直在审视以前做过的东西,关于android摄像头预览, ...
- Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整(原理:底层SurfaceView+上层绘制ImageView)...
Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理:底层SurfaceView+上层绘制ImageView) 分类: Android开发 Androi ...
最新文章
- 3、kubernetes应用快速入门190625
- java虚拟机内存告警_Java虚拟机总结
- K8S部署Kuboard V3
- uniapp防抖操作
- 51单片机并行I/O口工作原理
- 机器学习笔记II: 决策树
- 使用 PDB 避免 Kubernetes 集群中断
- 关于人行acs对账不及时_记工记账新方法,不用本子不用笔,一个手机全搞定
- ImageUtils.java:图片处理工具类[裁剪/图片水印/文字水印/缩放补白/Base64加密解密]
- (并查集)~APTX4869(fzu 2233)
- 线性代数笔记【矩阵与线性方程组】
- 贴个图,讲下技巧如何进入9008模式:记一次救砖小米note3-9008刷小米note3-小米note3miui10降级miui9
- opensuse12.2 KDE 使用环境配置
- java get set写法_java get set方法的使用
- 马士兵网络安全大师班薪选课程
- Websocket和PHP Socket编程
- 2016弱校联盟十一专场10.2部分题解
- XYOJ1259: 找零钱(除法 余数)
- 调用U9系统里的新增杂收服务服务
- Stacked Queries(堆叠注入)