1. 程式人生 > 程式設計 >Android錄屏的三種解決方案

Android錄屏的三種解決方案

本文總結三種用於安卓錄屏的解決方案:

adb shell命令screenrecord
MediaRecorder, MediaProjection
MediaProjection,MediaCodec和MediaMuxer

screenrecord命令

screenrecord是一個shell命令,支援Android4.4(API level 19)以上,錄製的視訊格式為mp4,存放到手機sd卡里,預設錄製時間為180s

adb shell screenrecord --size 1280*720 --bit-rate 6000000 --time-limit 30 /sdcard/demo.mp4

--size 指定視訊解析度;

--bit-rate 指定視訊位元率,預設為4M,該值越小,儲存的視訊檔案越小;

--time-limit 指定錄製時長,若設定大於180,命令不會被執行;

MediaRecorder

MediaProjection是Android5.0後才開放的螢幕採集介面,通過系統級服務MediaProjectionManager進行管理。

錄屏過程可以分成兩個部分,即通過MediaProjectionManage申請錄屏許可權,使用者允許後開始錄製螢幕;然後通過MediaRecorder對音視訊資料進行處理。

獲取MediaProjectionManager例項

MediaProjectionManager mProjectionManager = (MediaProjectionManager) getSystemService("media_projection");

申請許可權

Intent captureIntent = mProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent,LOCAL_REQUEST_CODE);

createScreenCaptureIntent()這個方法會返回一個intent,你可以通過startActivityForResult方法來傳遞這個intent,為了能開始螢幕捕捉,activity會提示使用者是否允許螢幕捕捉(為了防止開發者做一個木馬,來捕獲使用者私人資訊),你可以通過getMediaProjection來獲取螢幕捕捉的結果。

在onActivityResult中獲取結果

@Override
  public void onActivityResult(int requestCode,int resultCode,Intent data) {
    MediaProjection mediaProjection = mProjectionManager.getMediaProjection(resultCode,data);
    if (mediaProjection == null) {
    Log.e(TAG,"media projection is null");
    return;
  }
    File file = new File("xx.mp4"); //錄屏生成檔案
    mediaRecord = new MediaRecordService(displayWidth,displayHeight,6000000,1,mediaProjection,file.getAbsolutePath());
    mediaRecord.start();
}

建立MediaRecorder程序

package com.unionpay.service;
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");
  }
}

MediaCodec與MediaMuxer

MediaCodec提供對音視訊壓縮編碼和解碼功能,MediaMuxer可以將音視訊混合生成多媒體檔案,生成MP4檔案。
與MediaRecorder類似,都需要先通過MediaProjectionManager獲取錄屏許可權,在回撥中進行螢幕資料處理。

這裡建立另一個程序:

package com.unionpay.service;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.projection.MediaProjection;
import android.util.Log;
import android.view.Surface;
public class ScreenRecordService extends Thread{
    private static final String TAG = "ScreenRecordService";
  private int mWidth;
  private int mHeight;
  private int mBitRate;
  private int mDpi;
  private String mDstPath;
  private MediaProjection mMediaProjection;
  // parameters for the encoder
  private static final String MIME_TYPE = "video/avc"; // H.264 Advanced
                 // Video Coding
  private static final int FRAME_RATE = 30; // 30 fps
  private static final int IFRAME_INTERVAL = 10; // 10 seconds between
                // I-frames
  private static final int TIMEOUT_US = 10000;
  private MediaCodec mEncoder;
  private Surface mSurface;
  private MediaMuxer mMuxer;
  private boolean mMuxerStarted = false;
  private int mVideoTrackIndex = -1;
  private AtomicBoolean mQuit = new AtomicBoolean(false);
  private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
  private VirtualDisplay mVirtualDisplay;
  public ScreenRecordService(int width,String dstPath) {
    super(TAG);
    mWidth = width;
    mHeight = height;
    mBitRate = bitrate;
    mDpi = dpi;
    mMediaProjection = mp;
    mDstPath = dstPath;
  }
  /**
   * stop task
   */
  public final void quit() {
    mQuit.set(true);
  }
  @Override
  public void run() {
    try {
    try {
      prepareEncoder();
      mMuxer = new MediaMuxer(mDstPath,MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display",mSurface,null);
    Log.d(TAG,"created virtual display: " + mVirtualDisplay);
    recordVirtualDisplay();
    } finally {
    release();
    }
  }
  private void recordVirtualDisplay() {
    while (!mQuit.get()) {
    int index = mEncoder.dequeueOutputBuffer(mBufferInfo,TIMEOUT_US);
//   Log.i(TAG,"dequeue output buffer index=" + index);
    if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
      // 後續輸出格式變化
      resetOutputFormat();
    } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
      // 請求超時
//     Log.d(TAG,"retrieving buffers time out!");
      try {
      // wait 10ms
      Thread.sleep(10);
      } catch (InterruptedException e) {
      }
    } else if (index >= 0) {
      // 有效輸出
      if (!mMuxerStarted) {
      throw new IllegalStateException("MediaMuxer dose not call addTrack(format) ");
      }
      encodeToVideoTrack(index);
      mEncoder.releaseOutputBuffer(index,false);
    }
    }
  }
  /**
   * 硬解碼獲取實時幀資料並寫入mp4檔案
   * 
   * @param index
   */
  private void encodeToVideoTrack(int index) {
    // 獲取到的實時幀視訊資料
    ByteBuffer encodedData = mEncoder.getOutputBuffer(index);
    if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
    // The codec config data was pulled out and fed to the muxer
    // when we got
    // the INFO_OUTPUT_FORMAT_CHANGED status.
    // Ignore it.
    Log.d(TAG,"ignoring BUFFER_FLAG_CODEC_CONFIG");
    mBufferInfo.size = 0;
    }
    if (mBufferInfo.size == 0) {
    Log.d(TAG,"info.size == 0,drop it.");
    encodedData = null;
    } else {
//   Log.d(TAG,"got buffer,info: size=" + mBufferInfo.size + ",presentationTimeUs="
//     + mBufferInfo.presentationTimeUs + ",offset=" + mBufferInfo.offset);
    }
    if (encodedData != null) {
    mMuxer.writeSampleData(mVideoTrackIndex,encodedData,mBufferInfo);
    }
  }
  private void resetOutputFormat() {
    // should happen before receiving buffers,and should only happen
    // once
    if (mMuxerStarted) {
    throw new IllegalStateException("output format already changed!");
    }
    MediaFormat newFormat = mEncoder.getOutputFormat();
    mVideoTrackIndex = mMuxer.addTrack(newFormat);
    mMuxer.start();
    mMuxerStarted = true;
    Log.i(TAG,"started media muxer,videoIndex=" + mVideoTrackIndex);
  }
  private void prepareEncoder() throws IOException {
    MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE,mHeight);
    format.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
    format.setInteger(MediaFormat.KEY_BIT_RATE,mBitRate);
    format.setInteger(MediaFormat.KEY_FRAME_RATE,FRAME_RATE);
    format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,IFRAME_INTERVAL);
    Log.d(TAG,"created video format: " + format);
    mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
    mEncoder.configure(format,MediaCodec.CONFIGURE_FLAG_ENCODE);
    mSurface = mEncoder.createInputSurface();
    Log.d(TAG,"created input surface: " + mSurface);
    mEncoder.start();
  }
  private void release() {
    if (mEncoder != null) {
    mEncoder.stop();
    mEncoder.release();
    mEncoder = null;
    }
    if (mVirtualDisplay != null) {
    mVirtualDisplay.release();
    }
    if (mMediaProjection != null) {
    mMediaProjection.stop();
    }
    if (mMuxer != null) {
    mMuxer.stop();
    mMuxer.release();
    mMuxer = null;
    }
  }
}

該程序只實現了視訊錄製,呼叫該程序只需修改主程序中的onActivityResult方法。

總結

MediaProjection似乎只有在螢幕發生變化時才傳輸,因此錄屏推流的畫面顯得不夠流暢

到此這篇關於Android錄屏的三種方案的文章就介紹到這了,更多相關Android錄屏的三種方案內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!