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