Android5.0以上版本录屏实现代码(完整代码)
我录屏的方式是分别录制音频和视频,最后合并成mp4格式,比较麻烦,因为网上完整的教程比较少,所以我打算写一个完整版的,照着我的代码写完之后,至少是能够实现功能的,而不是简单的介绍下用法。
1既然是录制视频,我们应该有一个按钮控制开始和结束。
2在录制之前,需要先判断一下Android系统的版本是否大于5.0,并且动态申请一下权限(读写,录音,照相机),这一步可以在点开始按钮的时候执行
if(ContextCompat.checkSelfPermission(context,Manifest.permission.WRITE_EXTERNAL_STORAGE) !=PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this,newString[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},102); } if(ContextCompat.checkSelfPermission(context,Manifest.permission.RECORD_AUDIO) !=PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this,newString[]{Manifest.permission.RECORD_AUDIO},103); } if(ContextCompat.checkSelfPermission(context,Manifest.permission.CAMERA) !=PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(this,newString[]{Manifest.permission.RECORD_AUDIO},104); } Intentintent=null; if(android.os.Build.VERSION.SDK_INT>=android.os.Build.VERSION_CODES.LOLLIPOP){ intent=mediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(intent,101);//正常情况是要执行到这里的,作用是申请捕捉屏幕 }else{ ShowUtil.showToast(context,"Android版本太低,无法使用该功能"); }
3定义MediaProjection和MediaProjectionManager等一些其他必要的变量
booleanisrun=false;//用来标记录屏的状态privateMediaProjectionManagermediaProjectionManager; privateMediaProjectionmediaProjection;//录制视频的工具privateintwidth,height,dpi;//屏幕宽高和dpi,后面会用到 privateScreenRecorderscreenRecorder;//这个是自己写的录视频的工具类,下文会放完整的代码 Threadthread;//录视频要放在线程里去执行
在onCreat里写好实例化
mediaProjectionManager=(MediaProjectionManager)context.getSystemService(MEDIA_PROJECTION_SERVICE); WindowManagermanager=this.getWindowManager(); DisplayMetricsoutMetrics=newDisplayMetrics(); manager.getDefaultDisplay().getMetrics(outMetrics); width=outMetrics.widthPixels; height=outMetrics.heightPixels; dpi=outMetrics.densityDpi;
4我们在onActivityResult回调方法中,来处理返回的事件
@Override protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){ if(requestCode==102){ Toast.makeText(context,"缺少读写权限",Toast.LENGTH_SHORT).show(); return; } if(requestCode==103){ Toast.makeText(context,"缺少录音权限",Toast.LENGTH_SHORT).show(); return; } if(requestCode==104){ Toast.makeText(context,"缺少相机权限",Toast.LENGTH_SHORT).show(); return; } if(requestCode!=101){ Log.e("HandDrawActivity","errorrequestCode="+requestCode); } if(resultCode!=RESULT_OK){ Toast.makeText(context,"捕捉屏幕被禁止",Toast.LENGTH_SHORT).show(); return; } mediaProjection=mediaProjectionManager.getMediaProjection(resultCode,data); if(mediaProjection!=null){ screenRecorder=newScreenRecorder(width,height,mediaProjection,dpi); } thread=newThread(){ @Override publicvoidrun(){ screenRecorder.startRecorder();//跟ScreenRecorder有关的下文再说,总之这句话的意思就是开始录屏的意思 } }; thread.start(); binding.startPlayer.setText("停止");//开始和停止我用的同一个按钮,所以开始录屏之后把按钮文字改一下 isrun=true;//录屏状态改成真 }
5先放上ScreenRecorder代码,只想要结果的朋友呢,直接把类粘贴走,把报错的地方改一改(在我自己的项目里可是不报错的),就实现了录制屏幕的功能了,还想看看的,可以往下看看
importandroid.hardware.display.DisplayManager; importandroid.media.MediaCodec; importandroid.media.MediaCodecInfo; importandroid.media.MediaFormat; importandroid.media.MediaMuxer; importandroid.media.MediaRecorder; importandroid.media.projection.MediaProjection; importandroid.os.Build; importandroid.os.Environment; importandroid.text.TextUtils; importandroid.util.Log; importandroid.view.Surface; importcom.coremedia.iso.boxes.Container; importcom.googlecode.mp4parser.authoring.Movie; importcom.googlecode.mp4parser.authoring.Track; importcom.googlecode.mp4parser.authoring.builder.DefaultMp4Builder; importcom.googlecode.mp4parser.authoring.container.mp4.MovieCreator; importcom.googlecode.mp4parser.authoring.tracks.AppendTrack; importjava.io.File; importjava.io.FileNotFoundException; importjava.io.IOException; importjava.io.RandomAccessFile; importjava.nio.ByteBuffer; importjava.nio.channels.FileChannel; importjava.util.ArrayList; importjava.util.LinkedList; importjava.util.List; publicclassScreenRecorder{ privateintmWidth,mHeight,mDensty; privateMediaProjectionmediaProjection; privateMediaCodec.BufferInfomBufferInfo; privateMediaCodecmEncorder; privateSurfacemInputSurface; privateMediaMuxermMuxer; privatebooleanisQuit=false; privatebooleanmMuxerStarted=false; privateintmTrackIndex; privateStringpath=Environment.getExternalStorageDirectory().getAbsolutePath()+"/cache"; privateMediaRecordermediaRecorder; publicScreenRecorder(intmWidth,intmHeight,MediaProjectionmediaProjection,intmDensty){ this.mWidth=mWidth; this.mHeight=mHeight; this.mediaProjection=mediaProjection; this.mDensty=mDensty; Filefile=newFile(path); if(!file.exists()){ file.mkdirs(); } } publicvoidstartRecorder(){ prepareRecorder(); startLuYin(); startRecording(); } publicvoidstop(){ isQuit=true; releaseEncorders(1); ListfilePath=newArrayList<>(); filePath.add(path+"/APlanyinpin.amr"); filePath.add(path+"/APlanshipin.mp4"); joinVideo(filePath,path); } publicvoiddestory(){ releaseEncorders(0); } privatevoidstartLuYin(){ Filefile=newFile(path,"APlanyinpin.amr"); mediaRecorder=newMediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); mediaRecorder.setOutputFile(file.getAbsolutePath()); try{ mediaRecorder.prepare(); mediaRecorder.start(); Log.e("HandDrawActivity","已经开始录音"); }catch(IOExceptione){ e.printStackTrace(); } } privatevoidprepareRecorder(){ mBufferInfo=newMediaCodec.BufferInfo();//元数据,描述bytebuffer的数据,尺寸,偏移 //创建格式化对象MIMI_TYPE传入的video/avc是H264编码格式 MediaFormatformat=MediaFormat.createVideoFormat("video/avc",mWidth,mHeight); intframeRate=45; format.setInteger(MediaFormat.KEY_BIT_RATE,3000000); format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); format.setInteger(MediaFormat.KEY_FRAME_RATE,frameRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT,1); format.setInteger(MediaFormat.KEY_CAPTURE_RATE,frameRate); format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER,1000000/frameRate); try{ mEncorder=MediaCodec.createEncoderByType("video/avc"); mEncorder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); mInputSurface=mEncorder.createInputSurface(); mEncorder.start(); }catch(IOExceptione){ e.printStackTrace(); releaseEncorders(0); } } privatevoidstartRecording(){ FilesaveFile=newFile(path,"APlanshipin.mp4"); try{ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){ mMuxer=newMediaMuxer(saveFile.getAbsolutePath(),MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); mediaProjection.createVirtualDisplay("SCREENRECORDER",mWidth,mHeight,mDensty,DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mInputSurface,null,null); drainEncoder(); } }catch(Exceptione){ e.printStackTrace(); } } privatevoiddrainEncoder(){ while(!isQuit){ Log.e("TAG","drain....."); intbufferIndex=mEncorder.dequeueOutputBuffer(mBufferInfo,0); if(bufferIndex==MediaCodec.INFO_TRY_AGAIN_LATER){ try{ Thread.sleep(10); }catch(InterruptedExceptione){ e.printStackTrace(); } } if(bufferIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ mTrackIndex=mMuxer.addTrack(mEncorder.getOutputFormat()); if(!mMuxerStarted&&mTrackIndex>=0){ mMuxer.start(); mMuxerStarted=true; Log.e("HandDrawActivity","已经开始录屏"); } } if(bufferIndex>=0){ Log.e("TAG","drain...write.."); ByteBufferbufferData=mEncorder.getOutputBuffer(bufferIndex); if((mBufferInfo.flags&MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!=0){ mBufferInfo.size=0; } if(mBufferInfo.size!=0){ if(mMuxerStarted){ bufferData.position(mBufferInfo.offset); bufferData.limit(mBufferInfo.offset+mBufferInfo.size); mMuxer.writeSampleData(mTrackIndex,bufferData,mBufferInfo); } } mEncorder.releaseOutputBuffer(bufferIndex,false); if((mBufferInfo.flags&MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){ break; } } } Log.e("HandDrawActivity","已经结束录屏"); } privatevoidreleaseEncorders(inti){ if(mediaProjection!=null){ mediaProjection.stop(); } mBufferInfo=null; if(mEncorder!=null){ mEncorder.stop(); } mInputSurface=null; if(mMuxer!=null&&i==1){ mMuxer.stop(); } if(mediaRecorder!=null){ mediaRecorder.stop(); mediaRecorder.reset(); mediaRecorder.release(); } } privatebooleanjoinVideo(List filePaths,StringresultPath){ Log.e("HandDrawActivity","准备合成中"); booleanresult=false; if(filePaths==null||filePaths.size()<=0||TextUtils.isEmpty(resultPath)){ thrownewIllegalArgumentException(); } if(filePaths.size()==1){//只有一个视频片段,不需要合并 returntrue; } try{ Movie[]inMovies=newMovie[filePaths.size()]; for(inti=0;i videoTracks=newLinkedList<>(); List
6从startRecorder方法说起
publicvoidstartRecorder(){ prepareRecorder();//录视频前的准备 startLuYin();//直接录音频(不用准备) startRecording();//录视频 }
录音的方法
privatevoidstartLuYin(){ Filefile=newFile(path,"APlanyinpin.amr"); mediaRecorder=newMediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//声音来源,麦克 mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);//音频格式,默认,其实就是上面定义好的amr了,除此之外还有mp4 mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//编码格式,问题是我不知道编码格式对什么有影响,是音质高低还是文件大小还是解析快慢,等我有时间去专门研究一下 mediaRecorder.setOutputFile(file.getAbsolutePath()); try{ mediaRecorder.prepare(); mediaRecorder.start(); Log.e("HandDrawActivity","已经开始录音"); }catch(IOExceptione){ e.printStackTrace(); } }
//录视频前的准备工作 privatevoidprepareRecorder(){ mBufferInfo=newMediaCodec.BufferInfo();//元数据,描述bytebuffer的数据,尺寸,偏移 //创建格式化对象MIMI_TYPE传入的video/avc是H264编码格式 MediaFormatformat=MediaFormat.createVideoFormat("video/avc",mWidth,mHeight); intframeRate=45; format.setInteger(MediaFormat.KEY_BIT_RATE,3000000); format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); format.setInteger(MediaFormat.KEY_FRAME_RATE,frameRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT,1); format.setInteger(MediaFormat.KEY_CAPTURE_RATE,frameRate); format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER,1000000/frameRate);//编码器的设置,具体是设置的啥我也不太清楚,但是网上查一查都是这么写的!!! try{ mEncorder=MediaCodec.createEncoderByType("video/avc"); mEncorder.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE); mInputSurface=mEncorder.createInputSurface(); mEncorder.start();//让编码器先跑起来 }catch(IOExceptione){ e.printStackTrace(); releaseEncorders(0); } }
这里也是准备工作
privatevoidstartRecording(){ FilesaveFile=newFile(path,"APlanshipin.mp4"); try{ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){ mMuxer=newMediaMuxer(saveFile.getAbsolutePath(),MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);//百度一下MediaMuxer,讲的很详细的 mediaProjection.createVirtualDisplay("SCREENRECORDER",mWidth,mHeight,mDensty,DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mInputSurface,null,null); drainEncoder(); } }catch(Exceptione){ e.printStackTrace(); } }
这个就是开始写视频文件了
privatevoiddrainEncoder(){ while(!isQuit){ Log.e("TAG","drain....."); intbufferIndex=mEncorder.dequeueOutputBuffer(mBufferInfo,0); if(bufferIndex==MediaCodec.INFO_TRY_AGAIN_LATER){ try{ Thread.sleep(10); }catch(InterruptedExceptione){ e.printStackTrace(); } } if(bufferIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){ mTrackIndex=mMuxer.addTrack(mEncorder.getOutputFormat()); if(!mMuxerStarted&&mTrackIndex>=0){ mMuxer.start(); mMuxerStarted=true; Log.e("HandDrawActivity","已经开始录屏"); } } if(bufferIndex>=0){ Log.e("TAG","drain...write.."); ByteBufferbufferData=mEncorder.getOutputBuffer(bufferIndex); if((mBufferInfo.flags&MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!=0){ mBufferInfo.size=0; } if(mBufferInfo.size!=0){ if(mMuxerStarted){ bufferData.position(mBufferInfo.offset); bufferData.limit(mBufferInfo.offset+mBufferInfo.size); mMuxer.writeSampleData(mTrackIndex,bufferData,mBufferInfo); } } mEncorder.releaseOutputBuffer(bufferIndex,false); if((mBufferInfo.flags&MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){ break; } } } Log.e("HandDrawActivity","已经结束录屏"); }
这个就是把录好的音频和视频合并成mp4的方法了,也是点击停止录屏的时候用到的
privatebooleanjoinVideo(ListfilePaths,StringresultPath){ Log.e("HandDrawActivity","准备合成中"); booleanresult=false; if(filePaths==null||filePaths.size()<=0||TextUtils.isEmpty(resultPath)){ thrownewIllegalArgumentException(); } if(filePaths.size()==1){//只有一个视频片段,不需要合并 returntrue; } try{ Movie[]inMovies=newMovie[filePaths.size()]; for(inti=0;i videoTracks=newLinkedList<>(); List
这个就是结束的时候了,该清空的清空,该注销的注销,i是用来判断录没录的,有可能刚进入这个页面都没录过,直接就返回到别的页面了,那就有可能空指针异常,因为有些变量都没初始化,所以用i判断一下,也可以自己写别的方法判端
privatevoidreleaseEncorders(inti){ if(mediaProjection!=null){ mediaProjection.stop(); } mBufferInfo=null; if(mEncorder!=null){ mEncorder.stop(); } mInputSurface=null; if(mMuxer!=null&&i==1){ mMuxer.stop(); } if(mediaRecorder!=null){ mediaRecorder.stop(); mediaRecorder.reset(); mediaRecorder.release(); } }
7部分代码也是我从网上扒的,但是网上的代码就没怎么见过比较完整的版本的,我上面写的都是经过我自己测试绝对没问题的而且代码也没什么遗漏的,要是发现有遗漏的代码我后续再补上。