Android5.0錄屏
Android L新增了MediaProjection錄屏的api,搗鼓了大半天,照著github上的demo擼了一遍程式碼,梳理梳理。
錄屏實現依賴MediaProjectionManager
通過
mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE)
拿到是例項
然後建立intent,並startActivityForResult
Intent intent = mediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(intent, 123);
在onActvitiyResult()中拿到MediaProjection例項
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == 123) {
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
if (mediaProjection != null ) {
mRecorder = new ScreenRecorder(mWidth, mHeight, mediaProjection, mDensty);
}
new Thread() {
@Override
public void run() {
mRecorder.startRecorder();
}
}.start();
btnRecorder.setText("停止錄屏" );
moveTaskToBack(true);
}
}
錄屏很消耗資源,另起一個執行緒操作
前面的這些步驟呢,主要目的就是一個,開啟錄屏。
然後,通過MediaProjection例項建立一個虛擬螢幕,剩下的工作主要就是通過MediaCodec,MediaMuxer
將virtualDisplay的進行編碼輸出到MP4檔案中。
思路還是比較簡短的,但是呢,我也是踩了不少坑,之前完全不熟悉相關的API只能一遍一遍照著demo擼。
歸納一下實現步驟
1.拿到MediaProjectionManager服務 getSystemService();
2.建立intent,並啟動 mediaprojectionmanager.createCpatureIntent();
3.初始化編碼器 MediaCodec.createEncoderByType(), 並建立一個suface
mEncoder.createInputSuface()
啟動編碼器,mEncoder.start();
4.使用suface建立虛擬螢幕 mediaprojection.createVirtualDisplay()
5.建立一個混合器,MediaMuxer;
6.編碼並輸出。
具體程式碼
1.拿到MediaProjectionManager服務 getSystemService();
mediaProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
2.建立intent,並啟動 mediaprojectionmanager.createCpatureIntent();
Intent intent = mediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(intent, 123);
拿到mediaprojection物件
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == 123) {
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
if (mediaProjection != null ) {
mRecorder = new ScreenRecorder(mWidth, mHeight, mediaProjection, mDensty);
}
new Thread() {
@Override
public void run() {
mRecorder.startRecorder();
}
}.start();
btnRecorder.setText("停止錄屏");
moveTaskToBack(true);
}
}
ScreenRecorder構造方法主要是進行了傳值工作
public ScreenRecorder(int mWidth, int mHeight, MediaProjection mediaProjection, int mDensty) {
this.mWidth = mWidth;
this.mHeight = mHeight;
this.mediaProjection = mediaProjection;
this.mDensty = mDensty;
File file = new File(savePath);
if (!file.exists()) {
file.mkdirs();
}
}
在onActivityResult中 另起一個執行緒呼叫了startRecorder()方法
看程式碼
public void startRecorder() {
prepareRecorder();
startRecording();
}
在prepareRecorder()中初始化編碼器,並設定一些引數
設定了bit率,影響清晰度的4000000-6000000b比較合適
framerate 幀率 就是常說的fps
其他的設定還是看google文件吧,我也不是特別清楚,還在學習中
private void prepareRecorder() {
mBufferInfo = new MediaCodec.BufferInfo(); //元資料,描述bytebuffer的資料,尺寸,偏移
//建立格式化物件 MIMI_TYPE 傳入的 video/avc 是H264編碼格式
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
int frameRate = 45;
format.setInteger(MediaFormat.KEY_BIT_RATE, 3000000);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
format.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate);
format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate);
/*MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);*/
try {
mEncorder = MediaCodec.createEncoderByType(MIME_TYPE);
mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mInputSurface = mEncorder.createInputSurface();
mEncorder.start();
} catch (IOException e) {
e.printStackTrace();
releaseEncorders();
}
}
將format傳給mEncorder,然後呼叫start,編碼器就開始工作了,會自動將資料寫入到MediaCodec的緩衝區內,我們拿出緩衝區內編碼好的資料,使用MediaMuxer進行混合輸出就ok了,看到有大神將,聲音和視訊的混合之後一起輸出,就實現了錄屏和錄音同步。這個後面可以嘗試下的
接著看
private void startRecording() {
File saveFile = new File(savePath, System.currentTimeMillis() + ".mp4");
try {
mMuxer = new MediaMuxer(saveFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
Log.e("TAG", "mwidth " + mWidth + " heigh " + mHeight + " mdensty " + mDensty + " Flag "
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC + " surface " + String.valueOf(mInputSurface == null));
mediaProjection.createVirtualDisplay("SCREENRECORDER", mWidth, mHeight, mDensty, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
mInputSurface, null, null);
drainEncoder();
} catch (Exception e) {
e.printStackTrace();
}finally {
releaseEncorders();
}
}
MediaMuxer需要一個檔案來儲存輸出的視訊,並傳入一個輸出格式
MediaMuxer輸出格式目前只支援MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
MUXER_OUTPUT_WEBM兩種。
建立虛擬螢幕之後
就開始編碼輸出了,
private void drainEncoder() {
while (!mQuit.get()) {
Log.e("TAG", "drain.....");
int bufferIndex = mEncorder.dequeueOutputBuffer(mBufferInfo, 0);
if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat());
if (!mMuxerStarted && mTrackIndex >= 0) {
mMuxer.start();
mMuxerStarted = true;
}
}
if (bufferIndex >= 0) {
Log.e("TAG", "drain...write..");
ByteBuffer bufferData = mEncorder.getOutputBuffer(bufferIndex);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
mBufferInfo.size = 0;
}
if (mBufferInfo.size != 0) {
if (mMuxerStarted) {
bufferData.position(mBufferInfo.offset);
bufferData.limit(mBufferInfo.offset + mBufferInfo.size);
mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo);
}
}
mEncorder.releaseOutputBuffer(bufferIndex, false);
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
}
}
這段程式碼呢先拿到輸出buffer的index
index的含義:
MediaCodec.INFO_TRY_AGAIN_LATER 超時
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED 格式改變
我們在MediaCodec.INFO_OUTPUT_FORMAT_CHANGED 這個的時候會開啟muxer
if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat());
if (!mMuxerStarted && mTrackIndex >= 0) {
mMuxer.start();
mMuxerStarted = true;
}
}
也比較容易理解
之前是muxer並沒有呼叫start(),所以這個時候是沒有輸出的,混合也是不能起任何作用的
MediaCodec.BUFFER_FLAG_END_OF_STREAM 輸出結尾
MediaCodec.BUFFER_FLAG_CODEC_CONFIG 開始編碼 我們需要忽略
index大於0,寫資料
if (mBufferInfo.size != 0) {
if (mMuxerStarted) {
bufferData.position(mBufferInfo.offset);
bufferData.limit(mBufferInfo.offset + mBufferInfo.size);
mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo);
}
}
不要忘了釋放資源,這個是真消耗資源
我用的三星s6 9200,開始沒有處理資源,手機卡死好幾次,電量也耗得。。。。
程式碼就不貼了
好了,就到這裡