安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

Android音视频之openGL特效

6月10日 终离去投稿
  今天浅谈:openGLopenSLES
  代码有点长一丶openGL特效(1)1。MediaCodec
  MediaCodec是Android4。1。2(API16)提供的一套编解码API。它的使用非常简单,它存在一个输入缓冲区与一个输出缓冲区,在编码时我们将数据塞入输入缓冲区,然后从输出缓冲区取出编码完成后的数据就可以了。
  除了直接操作输入缓冲区之外,还有另一种方式来告知MediaCodec需要编码的数据,那就是:
  使用此接口创建一个Surface,然后我们在这个Surface中作画,MediaCodec就能够自动的编码Surface中的画作,我们只需要从输出缓冲区取出编码完成之后的数据即可。
  此前,我们使用OpenGL进行绘画显示在屏幕上,然而想要复制屏幕图像到cpu内存中却不是一件非常轻松的事情。所以我们可以直接将OpenGL显示到屏幕中的图像,同时绘制到MediaCodeccreateInputSurface当中去。
  PBO(PixelBufferObject,像素缓冲对象)通过直接的内存访问(DirectMemoryAccess,DMA)高速的复制屏幕图像像素数据到CPU内存,但这里我们直接使用createInputSurface更简单
  录制我们在另外一个线程中进行(录制现场),所以录制的EGL环境和显示的EGL环境(GLSurfaceView,显示线程)是两个独立的工作环境,他们又能够共享上下文资源:显示线程中使用的texture等,需要能够在录制线程中操作(通过录制线程中使用OpenGL绘制到MediaCodec的Surface)。
  在这个线程中我们需要自己来:1、配置录制使用的EGL环境(参照GLSurfaceView是怎么配置的)
  2、完成将显示的图像绘制到MediaCodec的Surface中
  3、编码(H。264)与复用(封装mp4)的工作
  【更多音视频学习资料,点击下方链接免费领取,先码住不迷路】
  音视频开发基础知识和资料包
  2。极快、极慢模式视频录制2。1。创摄像头预览
  NDK51OpenGL:FBO
  定义一个DouyinView继承GLSurfaceView,并setRenderer(douyinRenderer);DouyinRenderer负责渲染DouyinRenderer中创建画布,设置效果CameraFilter写入fbo(帧缓存),ScreenFilter负责往屏幕上渲染
  AbstractFilterpublicabstractclassAbstractFilter{
  protectedFloatBuffermGLVertexB
  protectedFloatBuffermGLTextureB
  顶点着色
  protectedintmVertexShaderId;
  片段着色
  protectedintmFragmentShaderId;
  protectedintmGLProgramId;
  顶点着色器
  attributevec4
  赋值给glPosition(顶点)
  protectedintvP
  varyingvec2textureC
  protectedintvC
  uniformmat4vM
  protectedintvM
  片元着色器
  Samlpe2D扩展samplerExternalOES
  protectedintvT
  protectedintmOutputW
  protectedintmOutputH
  publicAbstractFilter(Contextcontext,intvertexShaderId,intfragmentShaderId){
  this。mVertexShaderIdvertexShaderId;
  this。mFragmentShaderIdfragmentShaderId;
  4个点x,y42float4字节所以424
  mGLVertexBufferByteBuffer。allocateDirect(424)
  。order(ByteOrder。nativeOrder())
  。asFloatBuffer();
  mGLVertexBuffer。clear();
  float〔〕VERTEX{
  1。0f,1。0f,
  1。0f,1。0f,
  1。0f,1。0f,
  1。0f,1。0f
  };
  mGLVertexBuffer。put(VERTEX);
  mGLTextureBufferByteBuffer。allocateDirect(424)
  。order(ByteOrder。nativeOrder())
  。asFloatBuffer();
  mGLTextureBuffer。clear();
  float〔〕TEXTURE{
  0。0f,1。0f,
  1。0f,1。0f,
  0。0f,0。0f,
  1。0f,0。0f
  };
  mGLTextureBuffer。put(TEXTURE);
  initilize(context);
  initCoordinate();
  }
  protectedvoidinitilize(Contextcontext){
  StringvertexSharderOpenGLUtils。readRawTextFile(context,mVertexShaderId);
  StringframentShaderOpenGLUtils。readRawTextFile(context,mFragmentShaderId);
  mGLProgramIdOpenGLUtils。loadProgram(vertexSharder,framentShader);
  获得着色器中的attribute变量position的索引值
  vPositionGLES20。glGetAttribLocation(mGLProgramId,vPosition);
  vCoordGLES20。glGetAttribLocation(mGLProgramId,vCoord);
  vMatrixGLES20。glGetUniformLocation(mGLProgramId,vMatrix);
  获得Uniform变量的索引值
  vTextureGLES20。glGetUniformLocation(mGLProgramId,vTexture);
  }
  publicvoidonReady(intwidth,intheight){
  mOutputW
  mOutputH
  }
  publicvoidrelease(){
  GLES20。glDeleteProgram(mGLProgramId);
  }
  publicintonDrawFrame(inttextureId){
  设置显示窗口
  GLES20。glViewport(0,0,mOutputWidth,mOutputHeight);
  使用着色器
  GLES20。glUseProgram(mGLProgramId);
  传递坐标
  mGLVertexBuffer。position(0);
  GLES20。glVertexAttribPointer(vPosition,2,GLES20。GLFLOAT,false,0,mGLVertexBuffer);
  GLES20。glEnableVertexAttribArray(vPosition);
  mGLTextureBuffer。position(0);
  GLES20。glVertexAttribPointer(vCoord,2,GLES20。GLFLOAT,false,0,mGLTextureBuffer);
  GLES20。glEnableVertexAttribArray(vCoord);
  GLES20。glActiveTexture(GLES20。GLTEXTURE0);
  GLES20。glBindTexture(GLES20。GLTEXTURE2D,textureId);
  GLES20。glUniform1i(vTexture,0);
  GLES20。glDrawArrays(GLES20。GLTRIANGLESTRIP,0,4);
  returntextureId;
  }
  修改坐标
  protectedvoidinitCoordinate(){
  }
  }
  ScreenFilter
  负责往屏幕上渲染
  publicclassScreenFilterextendsAbstractFilter{
  publicScreenFilter(Contextcontext){
  super(context,R。raw。basevertex,R。raw。basefrag);
  }
  }
  CameraFilterpublicclassCameraFilterextendsAbstractFilter{
  privateint〔〕mFrameB
  privateint〔〕mFrameBufferT
  privatefloat〔〕
  publicCameraFilter(Contextcontext){
  super(context,R。raw。cameravertex2,R。raw。camerafrag2);
  }
  Override
  protectedvoidinitCoordinate(){
  mGLTextureBuffer。clear();
  摄像头是颠倒的
  float〔〕TEXTURE{
  0。0f,0。0f,
  1。0f,0。0f,
  0。0f,1。0f,
  1。0f,1。0f
  };
  调整好了镜像
  float〔〕TEXTURE{
  1。0f,0。0f,
  0。0f,0。0f,
  1。0f,1。0f,
  0。0f,1。0f,
  };
  修复旋转逆时针旋转90度
  float〔〕TEXTURE{
  0。0f,0。0f,
  0。0f,1。0f,
  1。0f,0。0f,
  1。0f,1。0f
  };
  mGLTextureBuffer。put(TEXTURE);
  }
  Override
  publicvoidrelease(){
  super。release();
  destroyFrameBuffers();
  }
  publicvoiddestroyFrameBuffers(){
  删除fbo的纹理
  if(mFrameBufferTextures!null){
  GLES20。glDeleteTextures(1,mFrameBufferTextures,0);
  mFrameBufferT
  }
  删除fbo
  if(mFrameBuffers!null){
  GLES20。glDeleteFramebuffers(1,mFrameBuffers,0);
  mFrameB
  }
  }
  Override
  publicvoidonReady(intwidth,intheight){
  super。onReady(width,height);
  if(mFrameBuffers!null){
  destroyFrameBuffers();
  }
  fbo的创建(缓存)
  1、创建fbo(离屏屏幕)
  mFrameBuffersnewint〔1〕;
  1、创建几个fbo2、保存fboid的数据3、从这个数组的第几个开始保存
  GLES20。glGenFramebuffers(mFrameBuffers。length,mFrameBuffers,0);
  2、创建属于fbo的纹理
  mFrameBufferTexturesnewint〔1〕;用来记录纹理id
  创建纹理
  OpenGLUtils。glGenTextures(mFrameBufferTextures);
  让fbo与纹理发生关系
  创建一个2d的图像
  目标2d纹理等级格式宽、高格式数据类型(byte)像素数据
  GLES20。glBindTexture(GLES20。GLTEXTURE2D,mFrameBufferTextures〔0〕);GLES20。glTexImage2D(GLES20。GLTEXTURE2D,0,GLES20。GLRGBA,mOutputWidth,mOutputHeight,0,GLES20。GLRGBA,GLES20。GLUNSIGNEDBYTE,null);
  让fbo与纹理绑定起来,后续的操作就是在操作fbo与这个纹理上了
  GLES20。glBindFramebuffer(GLES20。GLFRAMEBUFFER,mFrameBuffers〔0〕);GLES20。glFramebufferTexture2D(GLES20。GLFRAMEBUFFER,GLES20。GLCOLORATTACHMENT0,GLES20。GLTEXTURE2D,mFrameBufferTextures〔0〕,0);
  解绑
  GLES20。glBindTexture(GLES20。GLTEXTURE2D,0);
  GLES20。glBindFramebuffer(GLES20。GLFRAMEBUFFER,0);
  }
  Override
  publicintonDrawFrame(inttextureId){
  设置显示窗口
  GLES20。glViewport(0,0,mOutputWidth,mOutputHeight);
  不调用的话就是默认的操作glsurfaceview中的纹理了。显示到屏幕上了
  这里我们还只是把它画到fbo中(缓存)
  GLES20。glBindFramebuffer(GLES20。GLFRAMEBUFFER,mFrameBuffers〔0〕);
  使用着色器
  GLES20。glUseProgram(mGLProgramId);
  传递坐标
  mGLVertexBuffer。position(0);
  GLES20。glVertexAttribPointer(vPosition,2,GLES20。GLFLOAT,false,0,mGLVertexBuffer);
  GLES20。glEnableVertexAttribArray(vPosition);
  mGLTextureBuffer。position(0);
  GLES20。glVertexAttribPointer(vCoord,2,GLES20。GLFLOAT,false,0,mGLTextureBuffer);
  GLES20。glEnableVertexAttribArray(vCoord);
  变换矩阵
  GLES20。glUniformMatrix4fv(vMatrix,1,false,matrix,0);GLES20。glActiveTexture(GLES20。GLTEXTURE0);
  因为这一层是摄像头后的第一层,所以需要使用扩展的GLTEXTUREEXTERNALOES
  GLES20。glBindTexture(GLES11Ext。GLTEXTUREEXTERNALOES,textureId);
  GLES20。glUniform1i(vTexture,0);
  GLES20。glDrawArrays(GLES20。GLTRIANGLESTRIP,0,4);
  GLES20。glBindTexture(GLES11Ext。GLTEXTUREEXTERNALOES,0);
  GLES20。glBindFramebuffer(GLES20。GLFRAMEBUFFER,0);
  返回fbo的纹理id
  returnmFrameBufferTextures〔0〕;
  }
  publicvoidsetMatrix(float〔〕matrix){
  this。
  }
  }
  【更多音视频学习资料,点击下方链接免费领取,先码住不迷路】
  C程序员必看,抓住音视频开发的大浪潮!冲击年薪60万
  2。渲染时定义一个录制类MediaRecorderpublicclassDouyinRendererimplementsGLSurfaceView。Renderer,
  SurfaceTexture。OnFrameAvailableListener{
  privateScreenFiltermScreenF
  privateDouyinViewmV
  privateCameraHelpermCameraH
  privateSurfaceTexturemSurfaceT
  privatefloat〔〕mtxnewfloat〔16〕;
  privateint〔〕mT
  privateCameraFiltermCameraF
  privateMediaRecordermMediaR
  publicDouyinRenderer(DouyinViewdouyinView){
  mViewdouyinV
  }
  Override
  publicvoidonSurfaceCreated(GL10gl,EGLConfigconfig){
  初始化的操作
  mCameraHelpernewCameraHelper(Camera。CameraInfo。CAMERAFACINGBACK);
  准备好摄像头绘制的画布
  通过opengl创建一个纹理id
  mTexturesnewint〔1〕;
  这里可以不配置(当然配置了也可以)
  GLES20。glGenTextures(mTextures。length,mTextures,0);
  mSurfaceTexturenewSurfaceTexture(mTextures〔0〕);
  mSurfaceTexture。setOnFrameAvailableListener(this);
  注意:必须在gl线程操作opengl
  mCameraFilternewCameraFilter(mView。getContext());
  mScreenFilternewScreenFilter(mView。getContext());
  渲染线程的EGL上下文
  EGLContexteglContextEGL14。eglGetCurrentContext();
  mMediaRecordernewMediaRecorder(mView。getContext(),sdcarda。mp4,CameraHelper。HEIGHT,CameraHelper。WIDTH,eglContext);
  }
  画布发生了改变
  paramgl
  paramwidth
  paramheight
  Override
  publicvoidonSurfaceChanged(GL10gl,intwidth,intheight){
  开启预览
  mCameraHelper。startPreview(mSurfaceTexture);
  mCameraFilter。onReady(width,height);
  mScreenFilter。onReady(width,height);
  }
  开始画画吧
  paramgl
  Override
  publicvoidonDrawFrame(GL10gl){
  配置屏幕
  清理屏幕:告诉opengl需要把屏幕清理成什么颜色
  GLES20。glClearColor(0,0,0,0);
  执行上一个:glClearColor配置的屏幕颜色
  GLES20。glClear(GLES20。GLCOLORBUFFERBIT);
  把摄像头的数据先输出来
  更新纹理,然后我们才能够使用opengl从
  SurfaceTexure当中获得数据进行渲染
  mSurfaceTexture。updateTexImage();
  surfaceTexture比较特殊,在opengl当中使用的是特殊的采样器samplerExternalOES(不是sampler2D)
  获得变换矩阵
  mSurfaceTexture。getTransformMatrix(mtx);
  mCameraFilter。setMatrix(mtx);
  责任链
  intidmCameraFilter。onDrawFrame(mTextures〔0〕);
  加效果滤镜
  id效果1。onDrawFrame(id);
  id效果2。onDrawFrame(id);
  。。。。
  加完之后再显示到屏幕中去
  mScreenFilter。onDrawFrame(id);
  进行录制
  mMediaRecorder。encodeFrame(id,mSurfaceTexture。getTimestamp());
  }
  publicvoidonSurfaceDestroyed(){
  mCameraHelper。stopPreview();
  }
  publicvoidstartRecord(floatspeed){
  try{
  mMediaRecorder。start(speed);
  }catch(IOExceptione){
  e。printStackTrace();
  }
  }
  publicvoidstopRecord(){
  mMediaRecorder。stop();
  }
  surfaceTexture有一个有效的新数据的时候回调
  paramsurfaceTexture
  Override
  publicvoidonFrameAvailable(SurfaceTexturesurfaceTexture){
  mView。requestRender();
  }
  }3。录制类MediaRecorder
  MediaRecorder
  录制类
  publicclassMediaRecorder{
  privatefinalContextmC
  privatefinalStringmP
  privatefinalintmW
  privatefinalintmH
  privatefinalEGLContextmEglC
  privateMediaCodecmMediaC
  privateSurfacemInputS
  privateMediaMuxermMediaM
  privateHandlermH
  privateEGLBasemEglB
  privatebooleanisS
  
  privatefloatmS
  paramcontext上下文
  parampath保存视频的地址
  paramwidth视频宽
  paramheight视频高
  还可以让人家传递帧率fps、码率等参数
  publicMediaRecorder(Contextcontext,Stringpath,intwidth,intheight,EGLContexteglContext){
  mContextcontext。getApplicationContext();
  mP
  mW
  mH
  mEglContexteglC
  }
  开始录制视频
  paramspeed
  publicvoidstart(floatspeed)throwsIOException{
  mS
  配置MediaCodec编码器
  视频格式
  类型(avc高级编码h264)编码出的宽、高
  MediaFormatmediaFormatMediaFormat。createVideoFormat(MediaFormat。MIMETYPEVIDEOAVC,mWidth,mHeight);
  参数配置
  1500kbs码率
  mediaFormat。setInteger(MediaFormat。KEYBITRATE,1500000);
  帧率
  mediaFormat。setInteger(MediaFormat。KEYFRAMERATE,20);
  关键帧间隔
  mediaFormat。setInteger(MediaFormat。KEYIFRAMEINTERVAL,20);
  颜色格式(RGBYUV)
  从surface当中回去
  mediaFormat。setInteger(MediaFormat。KEYCOLORFORMAT,MediaCodecInfo。CodecCapabilities。COLORFormatSurface);
  编码器
  mMediaCodecMediaCodec。createEncoderByType(MediaFormat。MIMETYPEVIDEOAVC);
  将参数配置给编码器
  mMediaCodec。configure(mediaFormat,null,null,MediaCodec。CONFIGUREFLAGENCODE);
  交给虚拟屏幕通过opengl将预览的纹理绘制到这一个虚拟屏幕中
  这样MediaCodec就会自动编码inputSurface中的图像
  mInputSurfacemMediaCodec。createInputSurface();
  H。264
  播放:
  MP4解复用(解封装)解码绘制
  封装器复用器
  一个mp4的封装器将h。264通过它写出到文件就可以了
  mMediaMuxernewMediaMuxer(mPath,MediaMuxer。OutputFormat。MUXEROUTPUTMPEG4);
  配置EGL环境
  Handler:线程通信
  Handler:子线程通知主线程
  Looper。loop();
  HandlerThreadhandlerThreadnew
  HandlerThread(VideoCodec);
  handlerThread。start();
  LooperlooperhandlerThread。getLooper();
  用于其他线程通知子线程
  mHandlernewHandler(looper);
  子线程:EGL的绑定线程,对我们自己创建的EGL环境的opengl操作都在这个线程当中执行
  mHandler。post(newRunnable(){
  Override
  publicvoidrun(){
  创建我们的EGL环境(虚拟设备、EGL上下文等)
  mEglBasenewEGLBase(mContext,mWidth,mHeight,mInputSurface,mEglContext);
  启动编码器
  mMediaCodec。start();
  isS
  }
  });
  }
  传递纹理进来
  相当于调用一次就有一个新的图像需要编码
  publicvoidencodeFrame(finalinttextureId,finallongtimestamp){
  if(!isStart){
  
  }
  mHandler。post(newRunnable(){
  Override
  publicvoidrun(){
  把图像画到虚拟屏幕
  mEglBase。draw(textureId,timestamp);
  从编码器的输出缓冲区获取编码后的数据就ok了
  getCodec(false);
  }
  });
  }
  获取编码后的数据
  paramendOfStream标记是否结束录制
  privatevoidgetCodec(booleanendOfStream){
  不录了,给mediacodec一个标记
  if(endOfStream){
  mMediaCodec。signalEndOfInputStream();
  }
  输出缓冲区
  MediaCodec。BufferInfobufferInfonewMediaCodec。BufferInfo();
  希望将已经编码完的数据都获取到然后写出到mp4文件
  while(true){
  等待10ms
  intstatusmMediaCodec。dequeueOutputBuffer(bufferInfo,10000);
  让我们重试1、需要更多数据2、可能还没编码为完(需要更多时间)
  if(statusMediaCodec。INFOTRYAGAINLATER){
  如果是停止我继续循环
  继续循环就表示不会接收到新的等待编码的图像
  相当于保证mediacodec中所有的待编码的数据都编码完成了,不断地重试取出编码器中的编码好的数据
  标记不是停止,我们退出,下一轮接收到更多数据再来取输出编码后的数据
  if(!endOfStream){
  不写这个会卡太久了,没有必要你还是在继续录制的,还能调用这个方法的!
  
  }
  否则继续
  }elseif(statusMediaCodec。INFOOUTPUTFORMATCHANGED){
  开始编码就会调用一次
  MediaFormatoutputFormatmMediaCodec。getOutputFormat();
  配置封装器
  增加一路指定格式的媒体流视频
  indexmMediaMuxer。addTrack(outputFormat);
  mMediaMuxer。start();
  }elseif(statusMediaCodec。INFOOUTPUTBUFFERSCHANGED){
  忽略
  }else{
  成功取出一个有效的输出
  ByteBufferoutputBuffermMediaCodec。getOutputBuffer(status);
  如果获取的ByteBuffer是配置信息,不需要写出到mp4
  if((bufferInfo。flagsMediaCodec。BUFFERFLAGCODECCONFIG)!0){
  bufferInfo。size0;
  }
  if(bufferInfo。size!0){
  bufferInfo。presentationTimeUs(long)(bufferInfo。presentationTimeUsmSpeed);
  写到mp4
  根据偏移定位
  outputBuffer。position(bufferInfo。offset);
  ByteBuffer可读写总长度
  outputBuffer。limit(bufferInfo。offsetbufferInfo。size);
  写出
  mMediaMuxer。writeSampleData(index,outputBuffer,bufferInfo);
  }
  输出缓冲区我们就使用完了,可以回收了,让mediacodec继续使用
  mMediaCodec。releaseOutputBuffer(status,false);
  结束
  if((bufferInfo。flagsMediaCodec。BUFFERFLAGENDOFSTREAM)!0){
  
  }
  }
  }
  }
  publicvoidstop(){
  isS
  mHandler。post(newRunnable(){
  Override
  publicvoidrun(){
  getCodec(true);
  mMediaCodec。stop();
  mMediaCodec。release();
  mMediaC
  mMediaMuxer。stop();
  mMediaMuxer。release();
  mMediaM
  mEglBase。release();
  mEglB
  mInputS
  mHandler。getLooper()。quitSafely();
  mH
  }
  });
  }
  EGLBase
  EGL配置与录制的opengl操作工具类
  publicclassEGLBase{
  privateScreenFiltermScreenF
  privateEGLSurfacemEglS
  privateEGLDisplaymEglD
  privateEGLConfigmEglC
  privateEGLContextmEglC
  paramcontext
  paramwidth
  paramheight
  paramsurfaceMediaCodec创建的surface我们需要将其贴到我们的虚拟屏幕上去
  parameglContextGLThread的EGL上下文
  publicEGLBase(Contextcontext,intwidth,intheight,Surfacesurface,EGLContexteglContext){
  配置EGL环境
  createEGL(eglContext);
  把Surface贴到mEglDisplay,发生关系
  int〔〕attriblist{
  EGL14。EGLNONE
  };
  绘制线程中的图像就是往这个mEglSurface上面去画
  mEglSurfaceEGL14。eglCreateWindowSurface(mEglDisplay,mEglConfig,surface,attriblist,0);
  绑定当前线程的显示设备及上下文,之后操作opengl,就是在这个虚拟显示上操作
  if(!EGL14。eglMakeCurrent(mEglDisplay,mEglSurface,mEglSurface,mEglContext)){
  thrownewRuntimeException(eglMakeCurrent失败!);
  }
  像虚拟屏幕画
  mScreenFilternewScreenFilter(context);
  mScreenFilter。onReady(width,height);
  }
  privatevoidcreateEGL(EGLContexteglContext){
  创建虚拟显示器
  mEglDisplayEGL14。eglGetDisplay(EGL14。EGLDEFAULTDISPLAY);
  if(mEglDisplayEGL14。EGLNODISPLAY){
  thrownewRuntimeException(eglGetDisplayfailed);
  }
  初始化显示器
  int〔〕versionnewint〔2〕;
  12。1020203
  major:主版本记录在version〔0〕
  minor:子版本记录在version〔1〕
  if(!EGL14。eglInitialize(mEglDisplay,version,0,version,1)){
  thrownewRuntimeException(eglInitializefailed);
  }
  egl根据我们配置的属性选择一个配置
  int〔〕attriblist{
  EGL14。EGLREDSIZE,8,缓冲区中红分量位数
  EGL14。EGLGREENSIZE,8,
  EGL14。EGLBLUESIZE,8,
  EGL14。EGLALPHASIZE,8,
  EGL14。EGLRENDERABLETYPE,
  EGL14。EGLOPENGLES2BIT,egl版本2
  EGL14。EGLNONE
  };
  EGLConfig〔〕configsnewEGLConfig〔1〕;
  int〔〕numconfignewint〔1〕;
  attriblist:属性列表属性列表的第几个开始
  configs:获取的配置(输出参数)
  numconfig:长度和configs一样就行了
  if(!EGL14。eglChooseConfig(mEglDisplay,attriblist,0,configs,0,configs。length,numconfig,0)){
  thrownewIllegalArgumentException(eglChooseConfig2failed);
  }
  mEglConfigconfigs〔0〕;
  int〔〕ctxattriblist{
  EGL14。EGLCONTEXTCLIENTVERSION,2,egl版本2
  EGL14。EGLNONE
  };
  创建EGL上下文
  3sharecontext:共享上下文传绘制线程(GLThread)中的EGL上下文达到共享资源的目的发生关系
  mEglContextEGL14。eglCreateContext(mEglDisplay,mEglConfig,eglContext,ctxattriblist,0);
  创建失败
  if(mEglContextEGL14。EGLNOCONTEXT){
  thrownewRuntimeException(EGLContextError。);
  }
  }
  paramtextureId纹理id代表一个图片
  paramtimestamp时间戳
  publicvoiddraw(inttextureId,longtimestamp){
  绑定当前线程的显示设备及上下文,之后操作opengl,就是在这个虚拟显示上操作
  if(!EGL14。eglMakeCurrent(mEglDisplay,mEglSurface,mEglSurface,mEglContext)){
  thrownewRuntimeException(eglMakeCurrent失败!);
  }
  画画画到虚拟屏幕上
  mScreenFilter。onDrawFrame(textureId);
  刷新eglsurface的时间戳
  EGLExt。eglPresentationTimeANDROID(mEglDisplay,mEglSurface,timestamp);
  交换数据
  EGL的工作模式是双缓存模式,内部有两个framebuffer(fb)
  当EGL将一个fb显示屏幕上,另一个就在后台等待opengl进行交换
  EGL14。eglSwapBuffers(mEglDisplay,mEglSurface);
  }
  回收
  publicvoidrelease(){
  EGL14。eglDestroySurface(mEglDisplay,mEglSurface);
  EGL14。eglMakeCurrent(mEglDisplay,EGL14。EGLNOSURFACE,EGL14。EGLNOSURFACE,EGL14。EGLNOCONTEXT);
  EGL14。eglDestroyContext(mEglDisplay,mEglContext);
  EGL14。eglReleaseThread();
  EGL14。eglTerminate(mEglDisplay);
  }
  }
投诉 评论

中国3大廉价酒,普通人瞧不上,老酒友却当成宝,你都喝过吗白酒,是公认的国民饮品,饮品界的常青树。自酒这个饮品诞生以来,不乏传承千年的名酒,比如汾酒和杜康,被无数文人墨客的歌颂,几乎成了美酒的代名词。而近些年,除了这些老牌……Android音视频之openGL特效今天浅谈:openGLopenSLES代码有点长一丶openGL特效(1)1。MediaCodecMediaCodec是Android4。1。2(API16)提供的……中国移动三季报大丰收,日均赚3。61亿元,结果评论区成吐槽大近日,中国移动对外发布了三季报,数据显示,公司前三季度的营收达到了7235亿元,同比上涨了11。5。归母净利润高达985亿元,同比增长13。3。前三季度一共有273天,平均每天……美国加息致中东多国货币贬值通胀高企受美联储连续大幅加息等多重因素影响,近期中东地区多国货币持续大幅贬值,通胀率不断飙升,对国民经济和民众生活造成严重冲击。土耳其里拉资料图图源:新华社20日,土耳其里……标致新款代步小车,外观很前卫时尚,不用烧油,续航400km在新能源的大潮下,连标致这样的品牌也开始造新能源车型了。近日,标致汽车发布了新款e208的官图以及部分配置信息。从目前公布的新款车型官图来看,外观部分的造型相比老款没有明……曾经惊艳到你的女明星礼服,你还记得几套呢,再回首依然很美哦今天和大家一起回首过往,几位女明星们曾经穿礼服惊艳到众人的照片吧!第一张是赵丽颖2019年底参加星光大赏颁奖典礼时穿的礼服中其中一套,因为场后复出瘦的很快,星星锁骨上了热搜,再……极限反杀日本9名超一流棋手,11连胜成就三连冠,聂圣当年有多如果让日本人赢个8:0,那颜面可就丢大了!中国围棋队员未战就暗生恐战心理,唯有聂卫平力主应战。1984年7月的一天,时任《新体育》杂志总编郝克强,收到日本《围棋俱乐部》杂……篮网官方宣布杜兰特正式归队训练满脸笑容冲小弟喊话表情抢镜北京时间9月20日,篮网巨星凯文杜兰特正式归队,篮网官方晒出杜兰特回归的训练照,并且配文写道:新的一周,同样的磨砺。点击输入图片描述(最多30字)点击输入图片描述(……5G丹拿智能音乐座舱升级包汉EV旗舰加推2大无忧购车政策1大无忧用车保障首付15起,5年超长贷非营运车辆三电终身保修置换补贴4000元(首任车主)5大专属贵宾服务5大智联在线……神舟嫦娥天问这是宇宙级别的中国式浪漫4月24日是中国航天日从神舟北斗到嫦娥玉兔再到天宫天问中国人民把对浩瀚星空和未知宇宙的无尽憧憬寄托在这些美好的名字中这些名字背后的故事……支付宝史诗级更新,网友吐槽不如不更要说现在的互联网APP最大的特点是什么,黑马第一时间就想到了一个词:孤岛。这是因为各大互联网巨头旗下的相关APP,除了自己旗下的产品之外基本都不怎么互通,一个二个都在试图……iPhone电量百分比消失5年后回归!网友调侃把取消的功能加中国经济周刊经济网讯8月9日凌晨,苹果推送的iOS16Beta5加入了电池百分比选项,用户可以在右上角的电池图标内方便地查看当前剩余的电量。此外,苹果在iOS16Beta4后加……
华为5G新机正式回归?官宣12月23日,正式发布今日油价调整消息9月28日92号95号汽油价格,国内油价或下女人是水做的,经典方一碗汤,清心柔肝滋肾水,帮你对抗初老隔夜水隔夜茶隔夜菜隔夜肉,哪个不可以吃?医生告诉你真相河北队总经理俱乐部曾一年开销超20亿,为了股改奔走2年无曙光万亿龙头卖爆!比亚迪单月销量突破13万辆,产业链指数2个月涨魔兽世界怀旧服wlk金属矿石草药名单潜力股是雪莲花和泰坦神铁云南俊发物业违法逃汇被罚120万元安徽人游安徽循着夏日的蝉鸣走进一片绿野仙踪今天是我3字开头的最后一个生日了上手一下会自己拍照的佳能PowerShotPick从1G跟到6G,中国通讯何时能够翻身?院士需解决卡脖子技术这天,我回家晚了语文作文三十岁单身美女总裁的感言爱心100文艺服务队文化下乡组委会挂牌仪式在京举办如何复原被磁化的显示器几月份去毕棚沟旅游好多少月份去毕棚沟旅游好50倍变焦骁龙870,降至1589元女职工产前检查算工作时间美术教学计划爽肤水含酒精好不好不含酒精的爽肤水有哪些向往的生活5张艺兴彭昱畅大抢购,绿源液冷动力助力一骑绝尘渴望作文心路难找放下执念

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利