Android錄屏的三種解決方案
本文總結(jié)三種用于安卓錄屏的解決方案:
adb shell命令screenrecord
MediaRecorder, MediaProjection
MediaProjection , MediaCodec和MediaMuxer
screenrecord命令
screenrecord是一個(gè)shell命令,支持Android4.4(API level 19)以上,錄制的視頻格式為mp4 ,存放到手機(jī)sd卡里,默認(rèn)錄制時(shí)間為180s
adb shell screenrecord --size 1280*720 --bit-rate 6000000 --time-limit 30 /sdcard/demo.mp4
--size 指定視頻分辨率;
--bit-rate 指定視頻比特率,默認(rèn)為4M,該值越小,保存的視頻文件越??;
--time-limit 指定錄制時(shí)長(zhǎng),若設(shè)定大于180,命令不會(huì)被執(zhí)行;
MediaRecorder
MediaProjection是Android5.0后才開放的屏幕采集接口,通過系統(tǒng)級(jí)服務(wù)MediaProjectionManager進(jìn)行管理。
錄屏過程可以分成兩個(gè)部分,即通過MediaProjectionManage申請(qǐng)錄屏權(quán)限,用戶允許后開始錄制屏幕;然后通過MediaRecorder對(duì)音視頻數(shù)據(jù)進(jìn)行處理。
獲取MediaProjectionManager實(shí)例
MediaProjectionManager mProjectionManager = (MediaProjectionManager) getSystemService("media_projection");
申請(qǐng)權(quán)限
Intent captureIntent = mProjectionManager.createScreenCaptureIntent();
startActivityForResult(captureIntent, LOCAL_REQUEST_CODE);
createScreenCaptureIntent()這個(gè)方法會(huì)返回一個(gè)intent,你可以通過startActivityForResult方法來傳遞這個(gè)intent,為了能開始屏幕捕捉,activity會(huì)提示用戶是否允許屏幕捕捉(為了防止開發(fā)者做一個(gè)木馬,來捕獲用戶私人信息),你可以通過getMediaProjection來獲取屏幕捕捉的結(jié)果。
在onActivityResult中獲取結(jié)果
@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();
}
創(chuàng)建MediaRecorder進(jìn)程
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()方法后調(diào)用
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提供對(duì)音視頻壓縮編碼和解碼功能,MediaMuxer可以將音視頻混合生成多媒體文件,生成MP4文件。
與MediaRecorder類似,都需要先通過MediaProjectionManager獲取錄屏權(quán)限,在回調(diào)中進(jìn)行屏幕數(shù)據(jù)處理。
這里創(chuàng)建另一個(gè)進(jìn)程:
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, int height, int bitrate, int dpi, MediaProjection mp, 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", mWidth, mHeight, mDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mSurface, null, 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) {
// 后續(xù)輸出格式變化
resetOutputFormat();
} else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
// 請(qǐng)求超時(shí)
// 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);
}
}
}
/**
* 硬解碼獲取實(shí)時(shí)幀數(shù)據(jù)并寫入mp4文件
*
* @param index
*/
private void encodeToVideoTrack(int index) {
// 獲取到的實(shí)時(shí)幀視頻數(shù)據(jù)
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, mWidth, 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, null, null, 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;
}
}
}
該進(jìn)程只實(shí)現(xiàn)了視頻錄制,調(diào)用該進(jìn)程只需修改主進(jìn)程中的onActivityResult方法。
總結(jié)
MediaProjection似乎只有在屏幕發(fā)生變化時(shí)才傳輸,因此錄屏推流的畫面顯得不夠流暢
到此這篇關(guān)于Android錄屏的三種方案的文章就介紹到這了,更多相關(guān)Android錄屏的三種方案內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- android MediaRecorder實(shí)現(xiàn)錄屏?xí)r帶錄音功能
- 淺析Android錄屏 MediaRecorder
- android實(shí)現(xiàn)錄屏小功能
- android實(shí)現(xiàn)錄屏功能
- android設(shè)置adb自帶screenrecord錄屏命令
- android桌面懸浮窗顯示錄屏?xí)r間控制效果
- Android錄屏功能的實(shí)現(xiàn)
- Android5.0以上版本錄屏實(shí)現(xiàn)代碼(完整代碼)
- android視頻截屏&手機(jī)錄屏實(shí)現(xiàn)代碼
- Android開發(fā)實(shí)現(xiàn)錄屏小功能
相關(guān)文章
Android中Volley框架進(jìn)行請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)的使用
這篇文章主要介紹了Android中Volley框架進(jìn)行請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)的使用,本文給大家介紹的非常詳細(xì)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10
Android實(shí)現(xiàn)簡(jiǎn)單圖庫輔助器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)單圖庫輔助器的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
使用ViewPager實(shí)現(xiàn)android軟件使用向?qū)Чδ軐?shí)現(xiàn)步驟
現(xiàn)在的大部分android軟件,都是使用說明,就是第一次使用該軟件時(shí),會(huì)出現(xiàn)向?qū)?,可以左右滑?dòng),然后就進(jìn)入應(yīng)用的主界面了,下面我們就實(shí)現(xiàn)這個(gè)功能2013-11-11
Android模擬實(shí)現(xiàn)網(wǎng)易新聞客戶端
這篇文章主要為大家詳細(xì)介紹了Android模擬實(shí)現(xiàn)網(wǎng)易新聞客戶端,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
Android WebView無法彈出軟鍵盤的原因及解決辦法
這篇文章主要介紹了Android WebView無法彈出軟鍵盤的原因及解決辦法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06
Android開發(fā)之DialogFragment用法實(shí)例總結(jié)
這篇文章主要介紹了Android開發(fā)之DialogFragment用法,結(jié)合實(shí)例形式總結(jié)分析了Android使用DialogFragment代替Dialog功能的相關(guān)使用技巧與注意事項(xiàng),需要的朋友可以參考下2017-11-11
Android 布局控件之LinearLayout詳細(xì)介紹
Android 布局控件之LinearLayout詳細(xì)介紹,需要的朋友可以參考一下2013-05-05
Android UI控件之ProgressBar進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Android UI控件之ProgressBar進(jìn)度條的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android Tablayout 自定義Tab布局的使用案例
這篇文章主要介紹了Android Tablayout 自定義Tab布局的使用案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Android Fragment動(dòng)態(tài)創(chuàng)建詳解及示例代碼
這篇文章主要介紹了Android Fragment動(dòng)態(tài)創(chuàng)建詳解的相關(guān)資料,并附實(shí)例代碼及實(shí)現(xiàn)效果圖,需要的朋友可以參考下2016-11-11

