使用MediaProjection和MediaRecorder实现屏幕录制

一、实现效果

这个Demo主要是实现Android手机屏幕录制的功能,可以实现视频、音频的录制,可以选取录制视频的效果,是否开启音频录制。截图如下:

点击START按钮开始屏幕录制,这里还可以选择标清或高清视频,是否开启音频录制等;点击STOP按钮结束录制。


二、代码分析
整个Demo比较简单,只有两个类:一个是应用程序入口MainActivity,一个是具体实现录制功能的ScreenRecordService。

在MainActivity中,点击START按钮,系统向用户请求屏幕录制的相关权限,这里获取权限其实是调用 mediaProjectionManager.createScreenCaptureIntent()获得一个intent,通过 startActivityForResult(intent) 请求权限。在onActivityResult() 中响应用户动作,获得允许则开始屏幕录制。代码如下,新建MainActivity继承Activity,向其中加入以下代码:

[java]  view plain  copy
  1. public class MainActivity extends Activity {  
  2.   
  3.     private static final String TAG = "MainActivity";  
  4.       
  5.     private TextView mTextView;  
  6.       
  7.     private static final String RECORD_STATUS = "record_status";  
  8.     private static final int REQUEST_CODE = 1000;  
  9.       
  10.     private int mScreenWidth;  
  11.     private int mScreenHeight;  
  12.     private int mScreenDensity;  
  13.       
  14.     /** 是否已经开启视频录制 */  
  15.     private boolean isStarted = false;  
  16.     /** 是否为标清视频 */  
  17.     private boolean isVideoSd = true;  
  18.     /** 是否开启音频录制 */  
  19.     private boolean isAudio = true;  
  20.       
  21.     @Override  
  22.     protected void onCreate(Bundle savedInstanceState) {  
  23.         // TODO Auto-generated method stub  
  24.         super.onCreate(savedInstanceState);  
  25.         setContentView(R.layout.activity_main);  
  26.           
  27.         Log.i(TAG, "onCreate");  
  28.         if(savedInstanceState != null) {  
  29.             isStarted = savedInstanceState.getBoolean(RECORD_STATUS);  
  30.         }  
  31.         getView() ;  
  32.         getScreenBaseInfo();  
  33.     }  
  34.       
  35.     private void getView() {  
  36.         mTextView = (TextView) findViewById(R.id.button_control);  
  37.         if(isStarted) {  
  38.             statusIsStarted();  
  39.         } else {  
  40.             statusIsStoped();  
  41.         }  
  42.         mTextView.setOnClickListener(new OnClickListener() {  
  43.               
  44.             @Override  
  45.             public void onClick(View v) {  
  46.                 // TODO Auto-generated method stub  
  47.                 if(isStarted) {  
  48.                     stopScreenRecording();  
  49.                     statusIsStoped();  
  50.                     Log.i(TAG, "Stoped screen recording");  
  51.                 } else {  
  52.                     startScreenRecording();  
  53.                 }  
  54.             }  
  55.         });  
  56.           
  57.         RadioGroup radioGroup = (RadioGroup) findViewById(R.id.radio_group);  
  58.         radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {  
  59.               
  60.             @Override  
  61.             public void onCheckedChanged(RadioGroup group, int checkedId) {  
  62.                 // TODO Auto-generated method stub  
  63.                 switch (checkedId) {  
  64.                 case R.id.sd_button:  
  65.                     isVideoSd = true;  
  66.                     break;  
  67.                 case R.id.hd_button:  
  68.                     isVideoSd = false;  
  69.                     break;  
  70.   
  71.                 default:  
  72.                     break;  
  73.                 }  
  74.             }  
  75.         });  
  76.           
  77.         CheckBox audioBox = (CheckBox) findViewById(R.id.audio_check_box);  
  78.         audioBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {  
  79.               
  80.             @Override  
  81.             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {  
  82.                 // TODO Auto-generated method stub  
  83.                 isAudio = isChecked;  
  84.             }  
  85.         });  
  86.     }  
  87.       
  88.     /** 
  89.      * 开启屏幕录制时的UI状态 
  90.      */  
  91.     private void statusIsStarted() {  
  92.         mTextView.setText(R.string.stop_recording);  
  93.         mTextView.setBackgroundDrawable(getResources().getDrawable(R.drawable.selector_red_bg));  
  94.     }  
  95.       
  96.     /** 
  97.      * 结束屏幕录制后的UI状态 
  98.      */  
  99.     private void statusIsStoped() {  
  100.         mTextView.setText(R.string.start_recording);  
  101.         mTextView.setBackgroundDrawable(getResources().getDrawable(R.drawable.selector_green_bg));  
  102.     }  
  103.       
  104.     /** 
  105.      * 获取屏幕相关数据 
  106.      */  
  107.     private void getScreenBaseInfo() {  
  108.         DisplayMetrics metrics = new DisplayMetrics();  
  109.         getWindowManager().getDefaultDisplay().getMetrics(metrics);  
  110.         mScreenWidth = metrics.widthPixels;  
  111.         mScreenHeight = metrics.heightPixels;  
  112.         mScreenDensity = metrics.densityDpi;  
  113.     }  
  114.       
  115.     @Override  
  116.     protected void onSaveInstanceState(Bundle outState) {  
  117.         // TODO Auto-generated method stub  
  118.         super.onSaveInstanceState(outState);  
  119.         outState.putBoolean(RECORD_STATUS, isStarted);  
  120.     }  
  121.       
  122.     /** 
  123.      * 获取屏幕录制的权限 
  124.      */  
  125.     private void startScreenRecording() {  
  126.         // TODO Auto-generated method stub  
  127.         MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);  
  128.         Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();  
  129.         startActivityForResult(permissionIntent, REQUEST_CODE);  
  130.     }  
  131.       
  132.     @Override  
  133.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
  134.         // TODO Auto-generated method stub  
  135.         super.onActivityResult(requestCode, resultCode, data);  
  136.         if(requestCode == REQUEST_CODE) {  
  137.             if(resultCode == RESULT_OK) {  
  138.                 // 获得权限,启动Service开始录制  
  139.                 Intent service = new Intent(this, ScreenRecordService.class);  
  140.                 service.putExtra("code", resultCode);  
  141.                 service.putExtra("data", data);  
  142.                 service.putExtra("audio", isAudio);  
  143.                 service.putExtra("width", mScreenWidth);  
  144.                 service.putExtra("height", mScreenHeight);  
  145.                 service.putExtra("density", mScreenDensity);  
  146.                 service.putExtra("quality", isVideoSd);  
  147.                 startService(service);  
  148.                 // 已经开始屏幕录制,修改UI状态  
  149.                 isStarted = !isStarted;  
  150.                 statusIsStarted();  
  151.                 simulateHome(); // this.finish();  // 可以直接关闭Activity  
  152.                 Log.i(TAG, "Started screen recording");  
  153.             } else {  
  154.                 Toast.makeText(this, R.string.user_cancelled, Toast.LENGTH_LONG).show();  
  155.                 Log.i(TAG, "User cancelled");  
  156.             }  
  157.         }  
  158.     }  
  159.       
  160.     /** 
  161.      * 关闭屏幕录制,即停止录制Service 
  162.      */  
  163.     private void stopScreenRecording() {  
  164.         // TODO Auto-generated method stub  
  165.         Intent service = new Intent(this, ScreenRecordService.class);  
  166.         stopService(service);  
  167.         isStarted = !isStarted;  
  168.     }  
  169.       
  170.     /** 
  171.      * 模拟HOME键返回桌面的功能 
  172.      */  
  173.     private void simulateHome() {  
  174.         Intent intent = new Intent(Intent.ACTION_MAIN);  
  175.         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  176.         intent.addCategory(Intent.CATEGORY_HOME);  
  177.         this.startActivity(intent);  
  178.     }  
  179.       
  180.     @Override  
  181.     public boolean onKeyDown(int keyCode, KeyEvent event) {  
  182.         // 在这里将BACK键模拟了HOME键的返回桌面功能(并无必要)  
  183.         if(keyCode == KeyEvent.KEYCODE_BACK) {  
  184.             simulateHome();  
  185.             return true;  
  186.         }  
  187.         return super.onKeyDown(keyCode, event);  
  188.     }  
  189.       
  190. }  

在ScreenRecordService中,第一步需要获得MediaProjectionManager的实例,通过它才可以获得MediaProjection的实例。然后在createMediaRecorder()方法中创建MediaRecorder的实例,并且完成对它的配置。录制的视频的质量就是在这里配置完成的,主要是setVideoEncodingBitRate(), setVideoEncodingBitRate()这两个方法控制。在配置完成后一定要记得mediaRecorder.prepare(); 并且这行代码必须在创建VirtualDisplay的实例之前调用,否则不能正常获取到 Surface的实例。新建类ScreenRecordService继承Service,在其中加入以下代码:

[java]  view plain  copy
  1. public class ScreenRecordService extends Service {  
  2.   
  3.     private static final String TAG = "ScreenRecordingService";  
  4.       
  5.     private int mScreenWidth;  
  6.     private int mScreenHeight;  
  7.     private int mScreenDensity;  
  8.     private int mResultCode;  
  9.     private Intent mResultData;  
  10.     /** 是否为标清视频 */  
  11.     private boolean isVideoSd;  
  12.     /** 是否开启音频录制 */  
  13.     private boolean isAudio;  
  14.       
  15.     private MediaProjection mMediaProjection;  
  16.     private MediaRecorder mMediaRecorder;  
  17.     private VirtualDisplay mVirtualDisplay;  
  18.       
  19.     @Override  
  20.     public void onCreate() {  
  21.         // TODO Auto-generated method stub  
  22.         super.onCreate();  
  23.         Log.i(TAG, "Service onCreate() is called");  
  24.     }  
  25.       
  26.     @Override  
  27.     public int onStartCommand(Intent intent, int flags, int startId) {  
  28.         // TODO Auto-generated method stub  
  29.         Log.i(TAG, "Service onStartCommand() is called");  
  30.           
  31.         mResultCode = intent.getIntExtra("code", -1);  
  32.         mResultData = intent.getParcelableExtra("data");  
  33.         mScreenWidth = intent.getIntExtra("width"720);  
  34.         mScreenHeight = intent.getIntExtra("height"1280);  
  35.         mScreenDensity = intent.getIntExtra("density"1);  
  36.         isVideoSd = intent.getBooleanExtra("quality"true);  
  37.         isAudio = intent.getBooleanExtra("audio"true);  
  38.           
  39.         mMediaProjection =  createMediaProjection();  
  40.         mMediaRecorder = createMediaRecorder();  
  41.         mVirtualDisplay = createVirtualDisplay(); // 必须在mediaRecorder.prepare() 之后调用,否则报错"fail to get surface"  
  42.         mMediaRecorder.start();  
  43.           
  44.         return Service.START_NOT_STICKY;  
  45.     }  
  46.       
  47.     private MediaProjection createMediaProjection() {  
  48.         Log.i(TAG, "Create MediaProjection");  
  49.         return ((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE)).getMediaProjection(mResultCode, mResultData);  
  50.     }  
  51.       
  52.     private MediaRecorder createMediaRecorder() {  
  53.         SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");  
  54.         Date curDate = new Date(System.currentTimeMillis());  
  55.         String curTime = formatter.format(curDate).replace(" """);  
  56.         String videoQuality = "HD";  
  57.         if(isVideoSd) videoQuality = "SD";  
  58.           
  59.         Log.i(TAG, "Create MediaRecorder");  
  60.         MediaRecorder mediaRecorder = new MediaRecorder();  
  61.         if(isAudio) mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);   
  62.         mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);   
  63.         mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);   
  64.         mediaRecorder.setOutputFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + "/" + videoQuality + curTime + ".mp4");  
  65.         mediaRecorder.setVideoSize(mScreenWidth, mScreenHeight);  //after setVideoSource(), setOutFormat()  
  66.         mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);  //after setOutputFormat()  
  67.         if(isAudio) mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);  //after setOutputFormat()  
  68.         int bitRate;  
  69.         if(isVideoSd) {  
  70.             mediaRecorder.setVideoEncodingBitRate(mScreenWidth * mScreenHeight);   
  71.             mediaRecorder.setVideoFrameRate(30);   
  72.             bitRate = mScreenWidth * mScreenHeight / 1000;  
  73.         } else {  
  74.             mediaRecorder.setVideoEncodingBitRate(5 * mScreenWidth * mScreenHeight);   
  75.             mediaRecorder.setVideoFrameRate(60); //after setVideoSource(), setOutFormat()  
  76.             bitRate = 5 * mScreenWidth * mScreenHeight / 1000;  
  77.         }  
  78.         try {  
  79.             mediaRecorder.prepare();  
  80.         } catch (IllegalStateException | IOException e) {  
  81.             // TODO Auto-generated catch block  
  82.             e.printStackTrace();  
  83.         }  
  84.         Log.i(TAG, "Audio: " + isAudio + ", SD video: " + isVideoSd + ", BitRate: " + bitRate + "kbps");  
  85.           
  86.         return mediaRecorder;  
  87.     }  
  88.       
  89.     private VirtualDisplay createVirtualDisplay() {  
  90.         Log.i(TAG, "Create VirtualDisplay");  
  91.         return mMediaProjection.createVirtualDisplay(TAG, mScreenWidth, mScreenHeight, mScreenDensity,   
  92.                 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mMediaRecorder.getSurface(), nullnull);  
  93.     }  
  94.       
  95.     @Override  
  96.     public void onDestroy() {  
  97.         // TODO Auto-generated method stub  
  98.         super.onDestroy();  
  99.         Log.i(TAG, "Service onDestroy");  
  100.         if(mVirtualDisplay != null) {  
  101.             mVirtualDisplay.release();  
  102.             mVirtualDisplay = null;  
  103.         }  
  104.         if(mMediaRecorder != null) {  
  105.             mMediaRecorder.setOnErrorListener(null);  
  106.             mMediaProjection.stop();  
  107.             mMediaRecorder.reset();  
  108.         }  
  109.         if(mMediaProjection != null) {  
  110.             mMediaProjection.stop();  
  111.             mMediaProjection = null;  
  112.         }  
  113.     }  
  114.       
  115.     @Override  
  116.     public IBinder onBind(Intent intent) {  
  117.         // TODO Auto-generated method stub  
  118.         return null;  
  119.     }  
  120.   
  121. }  

由上基本就可以实现屏幕录制的功能,但是MediaProjection是在API 21中加入的,所以只能在21以上的手机上使用。在低Android版本的手机上也可以在不root的情况下实现截屏、屏幕录制等功能,但是那都只有应用程序本身占用的屏幕范围,不包括状态栏。最后记得在manifest.xml中加入以下权限:

[html]  view plain  copy
  1. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
  2.     <uses-permission android:name="android.permission.RECORD_AUDIO" />  
如果你的项目中并不需要录制音频,则 "android.permission.RECORD_AUDIO" 这句可以不要。这里录制的音频只能是来自麦克风的声音,并不能直接录制手机发出的声音,比如电话录音等。

三、总结及补充

屏幕录制的步骤大概可以总结为:1)通过MediaProjectionManager取得向用户申请权限的intent,在onActivityResult()完成对用户动作的响应;2)用户允许后开始录制,可以直接写在一个Activity里,但是像这样另外写在Service里更为妥当,录制的代码也可以单独抽出来写成一个ScreenRecorder的类;3)获取MediaProjection的实例,获取及配置MediaRecorder的实例,并记得MediaRecorder需要prepare();4)获取VirtualDisplay的实例,它也是MediaProjection, MediaRecorder完成交互的地方,录制的屏幕内容其实就是mediaRecorder.getSurface() 获得的 surface 上的内容。

如果不使用MediaProjection + MediaRecorder组合,也可以使用MediaProjection + MediaCodec + MediaMuxer组合实现相同的功能。其中各个类的作用简要总结如下:

MediaMuxer:将音频和视频进行混合生成多媒体文件,输出mp4格式;
MediaCodec:将音视频进行压缩编码,并可以对Surface内容进行编码,实现屏幕录像功能;
MediaExtrator:将音视频分路,和MediaCodec正好反过程;
MediaFormat:用于描述多媒体数据的格式;
MediaRecoder:用于录像并压缩编码,相较于MediaCodec更适合屏幕录像;
MediaPlayer:用于播放压缩编码后的音视频文件;
AudioRecord:用于录制PCM数据;
AudioTrack:用于播放PCM数据;             PCM即原始音频采样数据


源码下载请点这里
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值