Android錄屏 MediaRecorder介紹

Android錄屏 MediaRecorder介紹

Android錄屏的三種方案

1、adb shell命令screenrecord
2、MediaRecorder, MediaProjection
3、MediaCodec和MediaMuxer, MediaProjection , 

一、screenrecord命令

screenrecord是一個shell命令,支持Android4.4(API level 19)以上,
錄製的視頻格式爲mp4 ,存放到手機sd卡里,默認錄製時間爲180s

adb shell screenrecord --size 1280x720 --bit-rate 6000000 --time-limit 30 /sdcard/demo.mp4
 --size 指定視頻分辨率,根據手機情況決定
 --bit-rate 指定視頻比特率,默認爲4M,該值越小,保存的視頻文件越小;
 --time-limit 指定錄製時長,若設定大於180,命令不會被執行;

並不是所以手機都執行screenrecord命令,部分手機不識別。
我在幾款華爲手機就沒執行成功報錯:
/system/bin/sh: screenrecord: inaccessible or not found

二、 MediaRecorder

MediaProjection是Android5.0後纔開放的屏幕採集接口,通過系統級服務MediaProjectionManager進行管理。

這裏先整體說一下屏幕錄製的流程,不然看起來費勁。

1、通過startActivityForResult(Intent intent)判斷是否錄屏授權的Activity
其中intent對象就需要MediaProjectionManager.createScreenCaptureIntent();獲取

2、在onActivityResult回調方法中做具體錄屏工作
比如:創建MediaRecorder,設置MP4文件路徑
創建VirtualDisplay,設置屏幕相關參數
如果不在onActivityResult回調中執行會有問題。

3、開始錄屏
MediaRecorder.start()

4、停止錄屏
MediaRecorder.reset();
MediaRecorder.release();


錄屏過程用到錄音權限和數據讀寫權限。

三、MediaCodec和MediaMuxer

MediaCodec提供對音視頻壓縮編碼和解碼功能,MediaMuxer可以將音視頻混合生成多媒體文件,生成MP4文件。

這個錄屏的方式和MediaRecorder是類似的,只是流程第二部有點不同,這裏不做介紹。

詳情可以參考:https://www.jianshu.com/p/8b313692ac85

四、MediaRecorder項目示例的主要代碼

這裏只做了錄製和停止錄製,沒有做相關適配,比如橫豎屏切換後尺寸變化。

簡單效果:

在這裏插入圖片描述

生成的MP4文件會在sdcard目錄下,並且以錄屏時間爲文件名。

1、MainActivity

package com.liwenzhi.screen;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;


public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    String TAG = "MainActivity";
    MediaProjectionManager mProjectionManager;
    MediaRecordService mediaRecord;
    int displayWidth = 1280;
    int displayHeight = 1920;
    Button btn_start_record;
    Button btn_stop_record;
    TextView tv_time;
    int REQUEST_CODE_PERMISSIONS = 99;
    int REQUEST_CODE_SCREEN = 100;

    //錄音權限和數據讀寫權限
    String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
    boolean mPassPermissions = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
        initEvent();
    }

    private void initView() {
        btn_start_record = findViewById(R.id.btn_start_record);
        btn_stop_record = findViewById(R.id.btn_stop_record);
        tv_time = findViewById(R.id.tv_time);
    }

    private void initData() {
        //權限申請
        //逐個判斷你要的權限是否已經通過
        judgePermissions();
        if (!mPassPermissions) {
            ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_PERMISSIONS);
        }
    }

    private void initEvent() {
        btn_start_record.setOnClickListener(this);
        btn_stop_record.setOnClickListener(this);
    }

    // 申請權限後的回調
    // 1、錄音和讀寫
    // 2、錄屏startActivityForResult後的回調
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //錄音和讀寫權限
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (resultCode != Activity.RESULT_OK) {
                mPassPermissions = false;
            }
        } else

            // 錄屏權限
            if (requestCode == REQUEST_CODE_SCREEN) {
                if (resultCode == Activity.RESULT_OK) {
                    try {
                        // mediaProjection 如果不在權限申請中回調,獲取到的對象爲空
                        MediaProjection mediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
                        if (mediaProjection == null) {
                            Log.e(TAG, "media projection is null");
                            return;
                        }
                        File file = new File("/sdcard/" + MyTimeUtils.getTimeString("yyyy-MM-dd_HH:mm:ss") + ".mp4");  //錄屏生成文件
                        if (!file.exists()) {
                            file.createNewFile();
                        }
                        mediaRecord = new MediaRecordService(displayWidth, displayHeight, 6000000, 1,
                                mediaProjection, file.getAbsolutePath());
                        mediaRecord.start();
                        second = 0;
                        mHandler.sendEmptyMessageDelayed(10, 1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    Toast.makeText(MainActivity.this, "錄屏失敗", Toast.LENGTH_LONG).show();
                }
            }

    }


    @Override
    public void onBackPressed() {
        //super.onBackPressed();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_start_record:
                startRecord();
                break;
            case R.id.btn_stop_record:
                stopRecord();
                break;
        }
    }

    // 開始錄屏
    private void startRecord() {
        judgePermissions();
        if (!mPassPermissions) {
            Toast.makeText(this, "基礎權限沒通過!", Toast.LENGTH_LONG).show();
            return;
        }
        mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        if (mProjectionManager != null) {
            Intent intent = mProjectionManager.createScreenCaptureIntent();
            PackageManager packageManager = getPackageManager();
            if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
                //存在錄屏授權的Activity
                startActivityForResult(intent, REQUEST_CODE_SCREEN);
            } else {
                Toast.makeText(this, "沒有錄屏權限!", Toast.LENGTH_LONG).show();
            }
        }
    }

    // 停止錄屏
    private void stopRecord() {
        mediaRecord.release();
        mHandler.removeMessages(10);
    }

    int second;
    // 計算時間
    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 10:
                    second++;
                    tv_time.setText(second + "");
                    mHandler.sendEmptyMessageDelayed(10, 1000);
                    break;
            }
        }
    };

    //判斷每個權限是否通過
    private void judgePermissions() {
        boolean permission = true;
        for (int i = 0; i < permissions.length; i++) {
            if (ContextCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
                // 未授予的權限
                permission = false;
            }
        }
        mPassPermissions = permission;
    }

}


2、MediaRecordService

package com.liwenzhi.screen;

import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.util.Log;

public class MediaRecordService extends Thread {

    private static final String TAG = "MediaRecordService";

    private int mWidth;
    private int mHeight;
    private int mBitRate;
    private int mDpi;
    private String mDstPath;
    private MediaRecorder mMediaRecorder;
    private MediaProjection mMediaProjection;
    private static final int FRAME_RATE = 60; // 60 fps

    private VirtualDisplay mVirtualDisplay;

    public MediaRecordService(int width, int height, int bitrate, int dpi, MediaProjection mp, String dstPath) {
        mWidth = width;
        mHeight = height;
        mBitRate = bitrate;
        mDpi = dpi;
        mMediaProjection = mp;
        mDstPath = dstPath;
    }

    @Override
    public void run() {
        try {
            initMediaRecorder();

            //在mediarecorder.prepare()方法後調用
            mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display", mWidth, mHeight, mDpi,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mMediaRecorder.getSurface(), null, null);
            Log.i(TAG, "created virtual display: " + mVirtualDisplay);
            mMediaRecorder.start();
            Log.i(TAG, "mediarecorder start");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化MediaRecorder
     *
     * @return
     */
    public void initMediaRecorder() {
        mMediaRecorder = new MediaRecorder();
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mMediaRecorder.setOutputFile(mDstPath);
        mMediaRecorder.setVideoSize(mWidth, mHeight);
        mMediaRecorder.setVideoFrameRate(FRAME_RATE);
        mMediaRecorder.setVideoEncodingBitRate(mBitRate);
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

        try {
            mMediaRecorder.prepare();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.i(TAG, "media recorder" + mBitRate + "kps");
    }

    public void release() {
        if (mVirtualDisplay != null) {
            mVirtualDisplay.release();
            mVirtualDisplay = null;
        }
        if (mMediaRecorder != null) {
            mMediaRecorder.setOnErrorListener(null);
            mMediaProjection.stop();
            mMediaRecorder.reset();
            mMediaRecorder.release();
        }
        if (mMediaProjection != null) {
            mMediaProjection.stop();
            mMediaProjection = null;
        }
        Log.i(TAG, "release");
    }
}


3、MyTimeUtils

package com.liwenzhi.screen;


import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * a Utils for time
 * <p>
 * identify the following letter: :"yyyy-MM-dd DD HH:mm:ss SSS"
 * * method1: long getTimeLong()
 * * method2: int getTimeInt(String filter)
 * * method3: String getTimeString()
 * * method4: String getTimeString(long time)
 * * method5: String getTimeString(long time, String filter)
 * * method6: String getTimeString( String filter)
 */

public class MyTimeUtils {


    public static long getTimeLong() {
        return System.currentTimeMillis();
    }
    
    public static int getTimeInt(String filter) {
        SimpleDateFormat format = new SimpleDateFormat(filter);
        String time = format.format(new Date());
        return Integer.parseInt(time);
    }


    public static final String getTimeString() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(new Date(getTimeLong()));
    }

    public static final String getTimeString(long time) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(new Date(time));
    }

    public static final String getTimeString(long time, String filter) {
        SimpleDateFormat format = new SimpleDateFormat(filter);
        return format.format(new Date(time));
    }

    public static final String getTimeString(String filter) {
        SimpleDateFormat format = new SimpleDateFormat(filter);
        return format.format(new Date(getTimeLong()));
    }

    public static Long getTimeLong(String filter, String date) {
        try {
            SimpleDateFormat format = new SimpleDateFormat(filter);
            Date dateTime = format.parse(date);
            return dateTime.getTime();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return 0L;
    }


}

測試apk和項目源碼下載:
https://download.csdn.net/download/wenzhi20102321/12262262

這個項目只是簡單錄屏,如果要做得好,
最好是正在通知欄/懸浮框裏面控制停止,
並且可以按退出鍵回到主界面,
要用到服務來控制錄屏屏幕的開始和停止。

共勉:時間總會過去,多做一下有意義的事情。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章